使用wire重构商品微服务

一.wire简介

Wire 是一个轻巧的Golang依赖注入工具。它由Go Cloud团队开发,通过自动生成代码的方式在编译期完成依赖注入。

依赖注入是保持软件 “低耦合、易维护” 的重要设计准则之一。

此准则被广泛应用在各种开发平台之中,有很多与之相关的优秀工具。

其中最著名的当属 Spring,Spring IOC 作为框架的核心功能对Spring的发展到今天统治地位起了决定性作用。

依赖注入很重要,所以Golang社区中早已有人开发了相关工具, 比如来自Uber 的 dig 、来自Facebook 的 inject 。他们都通过反射机制实现了运行时依赖注入。

二.快速使用

2.1安装

安装很简单,运行 go get github.com/google/wire/cmd/wire 之后, wire 命令行工具 将被安装到 $GOPATH/bin 。只要确保 $GOPATH/bin 在 $PATH 中, wire 命令就可以在任何目录调用了。安装成功后运行如下命令
在这里插入图片描述

2.2快速入门

设计一个程序,其中 Event依赖Greeter,Greeter依赖Message

package main

import (
	"fmt"
	"github.com/pkg/errors"
	"time"
)

type Message string

func NewMessage(phrase string) Message {
	return Message(phrase)
}

type Greeter struct {
	Message Message
}

func NewGreeter(m Message) Greeter {
	return Greeter{Message: m}
}

func (g Greeter) Greet() Message {
	return g.Message
}

type Event struct {
	Greeter Greeter // <- adding a Greeter field
}

func NewEvent(g Greeter) (Event, error) {
	if time.Now().Unix()%2 == 0 {
		return Event{}, errors.New("could not create event: event greeter is grumpy")
	}
	return Event{Greeter: g}, nil
}

func (e Event) Start() {
	msg := e.Greeter.Greet()
	fmt.Println(msg)
}

如果运行Event需要逐个构建依赖,代码如下

func main() {
    message := NewMessage("lisus2000")
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}

在此之前先介绍两个概念

Provider
Provider 你可以把它理解成工厂函数,这个函数的入参是依赖的属性,返回值为新一个新的类型实例

如下所示都是 provider 函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。

func NewMessage() Message {
 return Message("Hi there!")
}

func NewGreeter(m Message) Greeter {
 return Greeter{Message: m}
}

Injector

我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系

如下代码,我们把Event、Greeter、Message 的工厂函数(provider)一股脑塞入wire.Build()中,代表着构建 Event依赖Greeter、Message。我们不必关心Greeter、Message之间的依赖关系,wire会帮我们处理

func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}

编写wire.go文件

//go:build wireinject
// +build wireinject

package main

import "github.com/google/wire"

var wireSet = wire.NewSet(wire.Struct(new(Greeter), "Message"), NewMessage)

func InitializeEvent(phrase string) (Event, error) {
   //我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系
   //如下代码,我们把Event、Greeter、Message 的工厂函数(provider)一股脑塞入wire.Build()中,
   //代表着构建 Event依赖Greeter、Message。我们不必关心Greeter、Message之间的依赖关系,wire会帮我们处理

   panic(wire.Build(NewEvent, wireSet))
   //return Event{}, nil
}

注:使用wire生成代码时,要在代码上加以下

可以在wire.go第一行加入 //+build wireinject (与//go:build wireinject等效)注释,确保了这个文件在我们正常编译的时候不会被引用

在带有wire.go目录下运行wire命令,就会生成wire_gen.go文件,如下图所示

在这里插入图片描述

在这里插入图片描述

完整的main.go文件如下

package main

import (
   "fmt"
   "github.com/pkg/errors"
   "time"
)

type Message string

func NewMessage(phrase string) Message {
   return Message(phrase)
}

type Greeter struct {
   Message Message
}

func NewGreeter(m Message) Greeter {
   return Greeter{Message: m}
}

func (g Greeter) Greet() Message {
   return g.Message
}

type Event struct {
   Greeter Greeter // <- adding a Greeter field
}

func NewEvent(g Greeter) (Event, error) {
   if time.Now().Unix()%2 == 0 {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
   }
   return Event{Greeter: g}, nil
}

func (e Event) Start() {
   msg := e.Greeter.Greet()
   fmt.Println(msg)
}

func main() {
   event, _ := InitializeEvent("hello")
   event.Start()
}

wire的进阶,详见https://blog.csdn.net/weixin_50071922/article/details/133278161

三.基于wire构建商品微服务

3.1编写商品微服务提供者
func NewGoodsAppWire(logOpts *log.Options, registrar registry.Registrar, serverOpts *options.ServerOptions,
   rpcServer *rpcserver.Server) (*gapp.App, error) {
   //初始化log
   log.Init(logOpts)
   defer log.Flush()

   return gapp.New(
      gapp.WithName(serverOpts.Name),
      gapp.WithRPCServer(rpcServer),
      gapp.WithRegistrar(registrar),
   ), nil
}
func NewRegistrar(registry *options.RegistryOptions) registry.Registrar {
   c := api.DefaultConfig()
   c.Address = registry.Address
   c.Scheme = registry.Scheme
   client, err := api.NewClient(c)
   if err != nil {
      panic(err)
   }
   return consul.New(client, consul.WithHealthCheck(true))
}
func NewGoodsRPCServerWire(telemetry *options.TelemetryOptions,
   serverOpts *options.ServerOptions, gserver gpb.GoodsServer) (*rpcserver.Server, error) {
   // 初始化 open-telemetry 的 exporter
   trace.InitAgent(trace.Options{
      Name:     telemetry.Name,
      Endpoint: telemetry.Endpoint,
      Sampler:  telemetry.Sampler,
      Batcher:  telemetry.Batcher,
   })
   // 这里会根据 endpoint 为单元注册 trace 服务的实例
   rpcAddr := fmt.Sprintf("%s:%d", serverOpts.Host, serverOpts.Port)
   var opts []rpcserver.ServerOption
   opts = append(opts, rpcserver.WithAddress(rpcAddr))
   if serverOpts.EnableLimit {
      opts = append(opts, rpcserver.WithUnaryInterceptor(grpc.NewUnaryServerInterceptor()))
   }
   grpcServer := rpcserver.NewServer(opts...)
   gpb.RegisterGoodsServer(grpcServer.Server, gserver)
   return grpcServer, nil
}
func NewGoodsServerWire(srv v1.ServiceFactory) proto.GoodsServer {
   return &goodsServer{
      srv: srv,
   }
}
func NewGoodsServiceWire(data v1.DataFactory, dataSearch v12.SearchFactory) ServiceFactory {
   return &service{
      data:       data,
      dataSearch: dataSearch,
   }
}
func GetDBFactoryOr(mysqlOpts *options.MySQLOptions) (v1.DataFactory, error) {
   if mysqlOpts == nil && dbFactory == nil {
      return nil, errors.WithCode(code.ErrConnectDB, "failed to get mysql store factory")

   }
   var err error
   once.Do(func() {
      dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
         mysqlOpts.Username,
         mysqlOpts.Password,
         mysqlOpts.Host,
         mysqlOpts.Port,
         mysqlOpts.Database)
      //希望大家自己可以去封装logger
      newLogger := logger.New(
         log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
         logger.Config{
            SlowThreshold:             time.Second,                         // 慢 SQL 阈值
            LogLevel:                  logger.LogLevel(mysqlOpts.LogLevel), // 日志级别
            IgnoreRecordNotFoundError: true,                                // 忽略ErrRecordNotFound(记录未找到)错误
            Colorful:                  false,                               // 禁用彩色打印
         },
      )
      db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
         Logger: newLogger,
      })
      if err != nil {
         return
      }
      sqlDB, _ := db.DB()
      dbFactory = &mysqlFactory{
         db: db,
      }
      //允许连接多少个mysql
      sqlDB.SetMaxOpenConns(mysqlOpts.MaxOpenConnections)
      //允许最大的空闲的连接数
      sqlDB.SetMaxIdleConns(mysqlOpts.MaxIdleConnections)
      //重用连接的最大时长
      sqlDB.SetConnMaxLifetime(mysqlOpts.MaxConnectionLifetime)

   })
   if dbFactory == nil || err != nil {
      return nil, errors.WithCode(code.ErrConnectDB, "failed to get mysql store factory")
   }
   return dbFactory, nil
}
func GetSearchFactoryOr(opts *options.EsOptions) (v12.SearchFactory, error) {
   if opts == nil && searchFactory == nil {
      return nil, errors.New("failed to get es client")
   }

   once.Do(func() {
      esOpt := db.EsOptions{
         Host: opts.Host,
         Port: opts.Port,
      }
      esClient, err := db.NewEsClient(&esOpt)
      if err != nil {
         return
      }
      searchFactory = &dataSearch{
         esClient: esClient,
      }

   })
   if searchFactory == nil {
      return nil, errors.New("failed to get es client")
   }
   return searchFactory, nil
}
3.2编写wire.go文件
//go:build wireinject
// +build wireinject

package srv

import (
   "github.com/google/wire"
   v1 "mxshop/app/goods/srv/internal/controller/v1"
   "mxshop/app/goods/srv/internal/data/v1/data_search/v1/es"
   "mxshop/app/goods/srv/internal/data/v1/db"
   v12 "mxshop/app/goods/srv/internal/service/v1"
   "mxshop/app/pkg/options"
   gapp "mxshop/gmicro/app"
   "mxshop/pkg/log"
)

func initApp(*log.Options, *options.ServerOptions, *options.RegistryOptions,
   *options.TelemetryOptions, *options.MySQLOptions, *options.EsOptions) (*gapp.App, error) {
   wire.Build(NewGoodsAppWire,
      NewRegistrar,
      NewGoodsRPCServerWire,
      v1.NewGoodsServerWire,
      v12.NewGoodsServiceWire,
      db.GetDBFactoryOr,
      es.GetSearchFactoryOr,
   )

   return &gapp.App{}, nil
}

3.3生成wire_gen.go文件

在这里插入图片描述

生成后的文件如下:

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package srv

import (
   v1_2 "mxshop/app/goods/srv/internal/controller/v1"
   "mxshop/app/goods/srv/internal/data/v1/data_search/v1/es"
   "mxshop/app/goods/srv/internal/data/v1/db"
   "mxshop/app/goods/srv/internal/service/v1"
   "mxshop/app/pkg/options"
   "mxshop/gmicro/app"
   "mxshop/pkg/log"
)

// Injectors from wire.go:

func initApp(logOptions *log.Options, serverOptions *options.ServerOptions, registryOptions *options.RegistryOptions, telemetryOptions *options.TelemetryOptions, mySQLOptions *options.MySQLOptions, esOptions *options.EsOptions) (*app.App, error) {
   registrar := NewRegistrar(registryOptions)
   dataFactory, err := db.GetDBFactoryOr(mySQLOptions)
   if err != nil {
      return nil, err
   }
   searchFactory, err := es.GetSearchFactoryOr(esOptions)
   if err != nil {
      return nil, err
   }
   serviceFactory := v1.NewGoodsServiceWire(dataFactory, searchFactory)
   goodsServer := v1_2.NewGoodsServerWire(serviceFactory)
   server, err := NewGoodsRPCServerWire(telemetryOptions, serverOptions, goodsServer)
   if err != nil {
      return nil, err
   }
   appApp, err := NewGoodsAppWire(logOptions, registrar, serverOptions, server)
   if err != nil {
      return nil, err
   }
   return appApp, nil
}

在如下方法替换接口

在这里插入图片描述

运行main.go文件,如下显示,正常启动

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/230613.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis主从架构中从节点的master_link_status:down

项目场景&#xff1a; 在搭建Redis的主从架构时&#xff0c;查看Redis的从节点状态时发现其连接的主节点的状态为down&#xff0c;并且查看主节点的状态时发现连接的从节点数量为0。 问题描述 原因分析&#xff1a; 可能在主节点中配置了密码&#xff0c;即requirepass。 解决…

angular状态管理方案(ngrx)

完全基于redux的ngrx方案&#xff0c;我们看看在angular中如何实现。通过一个简单的计数器例子梳理下整个流程 一 安装 &#xff1a;npm i ngrx/store 这里特别要注意一点&#xff1a;安装 ngrx/store的时候会出现和angular版本不一致的问题 所以检查一下angular/core的版本…

vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)

目录 一、什么是Vue路由导航守卫&#xff1f; 二、全局守卫 1、beforeEach 下面是一个beforeEach的示例代码&#xff1a; 2、beforeResolve 下面是一个beforeResolve的示例代码&#xff1a; 3、afterEach 下面是一个afterEach的示例代码&#xff1a; 三、路由独享守卫…

Endnote在word中加入参考文献及自定义参考文献格式方式

第一部分&#xff1a;在word中增加引用步骤 1、先下载对应文献的endnote引用格式&#xff0c;如在谷歌学术中的下载格式如下&#xff1a; 2、在endnote中打开存储env的格式库&#xff0c;导入对应下载的文件格式&#xff1a;file>import>file>choose,import对应文件&a…

C.小苯的排列构造

C-小苯的排列构造_北京信息科技大学第十五届程序设计竞赛&#xff08;同步赛&#xff09; (nowcoder.com) 凑2很容易想出来&#xff0c;但是2 4 1 3 这个内核不好想&#xff0c;算是一种尝试和经验吧 #include<bits/stdc.h> using namespace std;int n;int main() {cin&g…

【Linux】如何清空某个文件的内容

cat /dev/null > file1 清空某个文件的内容使用cat /dev/null > file1&#xff0c;它将 /dev/null 的内容&#xff08;空内容&#xff09;重定向到 file1。 如下所示&#xff0c;file1文件里的内容被清空。 错误写法 错误写法是&#xff1a;cat file1 > /dev/null&…

近期Chrome浏览器 不知哪个版本升级后原先http强制跳转到https,导致服务端302强制跳转到http也没反应

关于Chrome更新http强制跳转到https解决方法 近期Chrome浏览器 不知哪个版本升级后原先http强制跳转到https&#xff0c;导致服务端302强制跳转到http也没反应一、F12检查加载的Response Headers中有没有Non-Authoritative-Reason二、找了资料后得到解决方案&#xff1a;三、找…

Navicat 技术指引 | 适用于 GaussDB 分布式的备份/还原功能

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

常见的中间件--消息队列中间件测试点

最近刷题&#xff0c;看到了有问中间件的题目&#xff0c;于是整理了一些中间件的知识&#xff0c;大多是在小破站上的笔记&#xff0c;仅供大家参考~ 主要分为七个部分来分享&#xff1a; 一、常见的中间件 二、什么是队列&#xff1f; 三、常见消息队列MQ的比较 四、队列…

力扣1445 连续字符

目录 ​编辑 题目 示例 示例1 示例2 提示 详细解读 题目 给你一个字符串 s &#xff0c;字符串的「能量」定义为&#xff1a;只包含一种字符的最长非空子字符串的长度。 请你返回字符串 s 的 能量。 解题思路 这个问题的解法相对比较简单&#xff0c;可以通过遍历字…

SIT3232E高静电防护,单电源供电,双通道,RS232 收发器

SIT3232E 是一款 3.0V~5.5V 供电、双通道、低功耗、高静电防护 ESD 保护&#xff0c;完全满足 TIA/EIA-232 标准要求的 RS-232 收发器。 SIT3232E 包括两个驱动器和两个接收器&#xff0c;具有增强形 ESD 保护功能&#xff0c;达到 15kV 以上 HBM ESD 、 8kV …

vmware ubuntu22 安装vmtools并设置共享文件夹

我是你爹&#xff0c;再不会就紫砂。 权限不够或没读写权限自己改下就行。 1. 主机下新建文件夹&#xff0c;并如下图设置成共享 2. 把上面文件夹路径添加到共享文件夹里面 3. 开启ubuntu&#xff0c;在登陆界面显示之前我们会看到下图的重新安装vmware tools由灰变黑&#x…

【C语言】网络字节序和主机字节序

网络字节序和主机字节序是计算机中字节的两种排序方式&#xff0c;它们主要用于解决不同计算机之间数据通信的问题。 一、网络字节序 也被称为大端字节序&#xff0c;是一种标准的字节序。在网络通信中&#xff0c;如果两台主机的字节序不同&#xff0c;可能会导致数据解释的二…

EasyExcel之文件导出最佳实践

文件导出 官方文档&#xff1a;写Excel | Easy Excel (alibaba.com) 引言 当使用 EasyExcel 进行 Excel 文件导出时&#xff0c;我最近在工作中遇到了一个需求。因此&#xff0c;我决定写这篇文章来分享我的经验和解决方案。如果你对这个话题感兴趣&#xff0c;那么我希望这篇…

队列排序:给定序列a,每次操作将a[1]移动到 从右往左第一个严格小于a[1]的元素的下一个位置,求能否使序列有序,若可以,求最少操作次数

题目 思路&#xff1a; 赛时代码&#xff08;先求右起最长有序区间长度&#xff0c;再求左边最小值是否小于等于右边有序区间左端点的数&#xff09; #include<bits/stdc.h> using namespace std; #define int long long const int maxn 1e6 5; int a[maxn]; int n; …

DOS命令

1.cd.. 返回主目录 2.cd 目录 切换到当前目录 3.dir 查看目录的所有文件夹 4.cls 清除dos窗口所有内容 5.键盘的向上箭头 查看上面输入的命令 6.exit退出dos窗口

webSRc实现浏览器播放rtsp【海康】

先上代码 <template><div>video的配置自己写<video id"video" autoplay width"900" height"900"></video></div> </template><script> export default {name: index1,data() {return {webRtcServer: …

LeetCode Hot100 78.子集

题目&#xff1a; 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 方法&#xff1a;灵神 选 or 不选 class Solution {private final List&…

天津大数据培训机构品牌 数据分析师的发展方向

大数据专业还是有一定难度的&#xff0c;毕竟大数据开发技术所包含的编程技术知识是比较杂且多的如果是计算机专业的学生或者自身有一定基础的人学&#xff0c;相对来说会比较容易&#xff0c;但对于零基础小伙伴学习来说&#xff0c;想要学习大数据&#xff0c;难度还是很高的…

Struts 框架(架构师考试复习资料)

Struts 是一个基于 SUN J2EE平台的 MVC 框架&#xff0c;主要是采用 Servlet 和 JSP 技术来实现的。在 Struts 框架中&#xff0c;模型由实现业务逻辑的 JavaBean 或 EJB 构件构成&#xff0c;控制器由ActionServlet和 Action 来实现&#xff0c;视图由一组 JSP 文件构成&#…
最新文章