使用wire重构商品微服务

news2024/11/26 10:34:25

一.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.coloradmin.cn/o/1296501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 Kubernetes 为 CI/CD 流水线打造高效可靠的临时环境

介绍 在不断发展的科技世界中&#xff0c;快速构建高质量的软件至关重要。在真实环境中测试应用程序是及早发现和修复错误的关键。但是&#xff0c;在真实环境中设置 CI/CD 流水线进行测试可能既棘手又昂贵。 Kubernetes 是一个流行的容器编排平台&#xff0c;提供临时环境解决…

Github入门教程之高效搜索和查看需要的项目

对咱们新入门的小白来说&#xff0c;前两天手把手注册 Github 账号的任务已经完成&#xff0c;接下来&#xff0c;学习如何高效搜索和查看自己感兴趣的内容。 下面是之前教程传送门 超详细GitHub注册和登录教程-CSDN博客 一. 搜索 可以在页面左上角「Search or jump to ...」…

IDEA 社区版 add GitLab Account

问题 IntelliJ IDEA Community Edition 2023.3&#xff08;社区版&#xff09;在使用GitLab连接时&#xff0c;使用个人访问令牌出现报错&#xff0c;代码&#xff1a; GraphQL error:[No such type ProjectMember,so it cant be a fraggment condition,Field id doesnt exis…

iview Table实现跨页勾选记忆功能以及利用ES6的Map数据结构实现根据id进行对象数组的去重

因为iview Table组件的勾选是选中当前页的所有数据,当我们切到别的页面时,会发送请求给后端,这个时候就会刷新我们之前页码已经选中的数据。现在有个需求就是,在我们选择不同页码的数据勾选中之后,实现跨页勾选记忆功能,就是说已经打钩了的数据,不管切到哪一页它都是打钩…

Python django-xadmin:构建强大的 Django 后台管理系统

概要 Django作为一款强大的Web框架&#xff0c;其后台管理系统提供了便捷的数据管理和操作方式。而 django-xadmin 则为Django的后台管理系统提供了更加强大的功能和灵活的定制选项。在本文中&#xff0c;我们将深入研究如何使用django-xadmin&#xff0c;并通过详细的示例代码…

智能优化算法应用:基于鱼鹰算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鱼鹰算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鱼鹰算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鱼鹰算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

【c语言指针详解】指针的基本概念和用法

目录 一、指针的基本概念和用法 二、指针运算 2.1 指针的自增和自减运算 2.2 指针的自增和自减运算 三、数组和指针 四、指针和函数 4.1 在函数中使用指针作为参数和返回值 4.1.1 使用指针作为函数参数 4.1.2 使用指针作为函数返回值 4.2 指针参数的传值和传引用特性 4.2.1 指针…

Windows 系统,TortoiseSVN 无法修改 Log 信息解决方法

使用SVN提交版本信息时&#xff0c;注释内容写的不全。通过右键TortoiseSVN的Show log看到提交的的注释&#xff0c;右键看到Edit log message的选项&#xff0c;然而提交后却给出错误提示&#xff1a; Repository has not been enabled to accept revision propchanges; ask …

【模型量化】神经网络量化基础及代码学习总结

1 量化的介绍 量化是减少神经网络计算时间和能耗的最有效的方法之一。在神经网络量化中&#xff0c;权重和激活张量存储在比训练时通常使用的16-bit或32-bit更低的比特精度。当从32-bit降低到8-bit&#xff0c;存储张量的内存开销减少了4倍&#xff0c;矩阵乘法的计算成本则二…

Mint Blockchain,一个聚焦在 NFT 领域的 L2 网络

Mint 是什么&#xff1f; Mint 是一个聚焦在 NFT 领域的创新型 L2 网络。Mint Blockchain 致力于促进 NFT 资产协议标准的创新和现实商业场景中 NFT 资产的大规模采用。 不管是过去 3 年在以太坊网络涌现的 NFT&#xff0c;还是当下在比特币网络活跃的“铭文” NFT&#xff0c…

HarmonyOS开发(九):数据管理

1、概述 1.1、功能简介 数据管理为开发者提供数据存储、数据管理能力。 它分为两个部分&#xff1a; 数据存储&#xff1a;提供通用数据持久化能力&#xff0c;根据数据特点&#xff0c;分为用户首选项、键值型数据库和关系型数据库。数据管理&#xff1a;提供高效的数据管…

gitlab注册无中国区电话验证问题

众所周知gitlab对中国区不友好&#xff0c;无法直接注册&#xff0c;页面无法选择86的手机号进行验证码发送。 Google上众多的方案是修改dom&#xff0c;而且时间大约是21年以前。 修改dom&#xff0c;对于现在的VUE、React框架来说是没有用的&#xff0c;所以不用尝试。 直接看…

springboot3远程调用

RPC 两个服务器之间的调用 远程请求 内部服务之间的调用 可以通过 cloud 注册中心 openfeign等 外部服务的调用 http请求 外部协议 api:远程接口 sdk&#xff1a;本地调用 调用阿里云的天气请求

Navicat 技术指引 | 适用于 GaussDB 分布式的日志查询与配置设置

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

Python---random库

目录 基本随机数函数(): rand.seed() random() 扩展随机数函数(): random库包含两类函数&#xff1a;基本随机数函数&#xff0c;扩展随机数函数 基本随机数函数:seed(),random() 扩展随机数函数&#xff1a;randint,getrandbits(),uniform(),randrange(),choice(),shuff…

分布式和微服务区别

1.分布式 微服务和分布式的区别 1.将一个大的系统划分为多个业务模块&#xff0c;业务模块分别部署到不同的机器上&#xff0c;各个业务模块之间通过接口进行数据交互。区别分布式的方式是根据不同机器不同业务。 2.分布式是否属于微服务&#xff1f; 答案是肯定的。微服务的意…

微信小程序引入Vant Weapp修改样式不起作用,使用外部样式类进行覆盖

一、引入Vant Weapp后样式问题 在项目中使用第三方组件修改css样式时,总是出现各种各样问题,修改的css样式不起作用,没有效果,效果不符合预期等。 栗子(引入一个搜索框组件)实现效果: 左侧有一个搜索文字背景为蓝色,接着跟一个搜索框 wxml <view class"container&q…

cache 2.单机并发缓存

0.对原教程的一些见解 个人认为原教程中两点知识的引入不够友好。 首先是只读数据结构 ByteView 的引入使用是有点迷茫的&#xff0c;可能不能很好理解为什么需要ByteView。 第二是主体结构 Group的引入也疑惑。其实要是熟悉groupcache&#xff0c;那对结构Group的使用是清晰…

修改pip源

修改pip源 永久修改 PS C:\Users\Dell> pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/Writing to C:\Users\Dell\AppData\Roaming\pip\pip.ini临时修改 pip install -i(即--index-url简写) http://mirrors.aliyun.com/pypi/simple/ selenium…

图像叠加中文字体

目录 1) 前言2) freetype下载3) Demo3.1) 下载3.2) 编译3.3) 运行3.4) 结果3.5) 更详细的使用见目录中说明 4) 积少成多 1) 前言 最近在做图片、视频叠加文字&#xff0c;要求支持中文&#xff0c;基本原理是将图片或视频解码后叠加文字&#xff0c;之后做图片或视频编码即可。…