• 依赖注入的正确打开方式 bilibili/kratos × google/wire


    在这里插入图片描述

    一、前言

    依赖注入相信大家都不陌生也不是什么新鲜的概念了,笔者初次深切体会依赖注入这种设计模式是在16年笔者在学习phalapi框架时使用的DI()函数,简单来说就是将所有的资源初始化集中在一起,通过统一的容器对外提供,而不是通过全局变量或到处New的方式。最近在学习kratos又看到一个团队的历史项目都使用wier,一个是正面教材一个是反面教材也有一些自己的思考,希望能够分享出来和大家交流交流。

    资料如下:

    二、依赖注入解决什么问题?

    其实笔者开始写Goalng包括团队内部自己研发的小框架go-core主要使用的是资源容器的方式,框架帮助你将资源统一的容器管理起来(也是一种间接的全局变量的方式,只是不能直接进行修改),但对于业务分层如果是微服务的话足够简单就直接 packagename.funcname 进行调用,如果是稍微复杂一些的项目用一个空的status的全局变量来提供func的隔离。

    但这样的设计会存在一下几个问题:

    • 依赖不清晰:
      • 虽然限定了调用层级,但无法直观了解当前模块依赖那些下层模块
    • 资源获取僵化不灵活:
      • 资源获取在最底层虽然可以也实现多态,但影响范围只能是全局,无法控制作用域
    • 避免犯错:
      • 无论是容器资源管理还是全局status变量都存在被误操作的可能性

    三、一个糟糕的依赖注入的设计

    如果你使用成熟框架无论是go-zero或kratos都应该按照他们的最佳实践来执行,如果是框架层没有约束还是要更具自己的实际情况来进行选择,有限选择最简单的方式顺应研发人员的直觉,设计模式如果用不对那将是场灾难,就和最近接触到了一个项目虽然使用了wier作为依赖注入,但是用的一塌糊涂:

    它存在的问题:

    • 貌似依赖注入了又貌似没有
      • 一个大对象实例化了保管了所有资源
      • 获取资源还是在使用New的方式
      • 应为需要New所以下次实例化需要的资源也变成了当前这一层的依赖,需要一层一层将资源传递下去
      • 所有入口资源都依赖了一个全局的大资源

    一个设计模式如果没有用对,虽然解决了上面资源共享的问题,但是又带来了一堆奇怪的问题,真的是得不偿失。所以架构也好业务也好都是逐步演进过来的,不要将设计模式当做万能药,第一性原则还是保持简单和高内聚低耦合,遇到文件引入对应的方案然后需要做好落地的最佳实践。

    四、怎样是一个好的依赖注入?

    当笔者接触到上面的项目之后就非常的迷茫,在思考怎么样才是一个好的依赖注入设计模式的落地呢?当接触到 kratos 之后给了我一些启发:

    一个好的依赖注入需要具备哪些特性

    • 编码过程中所有的依赖都只能从自身对象实例中获取,不允许New或使用全局变量
    • 依赖注入过程要贯穿全流程,从grpc服务或http服务初始化开始而不是从业务才开始使用

    bilibili/kratos × google/wire 标准样例:

    // 定义依赖关系,从头到尾
    func wireApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) {
    	panic(wire.Build(
    		server.ProviderSet,
    		data.ProviderSet,
    		biz.ProviderSet,
    		service.ProviderSet,
    		newApp))
    }
    
    
    // 初始化方法也要参与依赖注入
    func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server) *kratos.App {
    	return kratos.New(
    		kratos.ID(id),
    		kratos.Name(Name),
    		kratos.Version(Version),
    		kratos.Metadata(map[string]string{}),
    		kratos.Logger(logger),
    		kratos.Server(
    			gs,
    			hs,
    		),
    	)
    }
    
    
    // 程序启动直接从wier生成的方法中获取
    	app, cleanup, err := wireApp(bc.Server, bc.Data, logger)
    	if err != nil {
    		panic(err)
    	}
    	defer cleanup()
    
    	// start and wait for stop signal
    	if err := app.Run(); err != nil {
    		panic(err)
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    依赖的资源都是局部的,并且只关注自己所依赖的范围,依赖的依赖怎么注入不应该被关注

    // GreeterService is a greeter service.
    type GreeterService struct {
    	v1.UnimplementedGreeterServer
    
    	uc *biz.GreeterUsecase
    }
    
    // NewGreeterService new a greeter service.
    func NewGreeterService(uc *biz.GreeterUsecase) *GreeterService {
    	return &GreeterService{uc: uc}
    }
    
    ...
    // GreeterUsecase is a Greeter usecase.
    type GreeterUsecase struct {
    	repo GreeterRepo
    	log  *log.Helper
    }
    
    // NewGreeterUsecase new a Greeter usecase.
    func NewGreeterUsecase(repo GreeterRepo, logger log.Logger) *GreeterUsecase {
    	return &GreeterUsecase{repo: repo, log: log.NewHelper(logger)}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    HiveSQL分位数函数percentile()使用详解+实例代码
    小米12s 12sU 12sP 12x 12pro天玑版等小米机型通用解锁bl 刷写root全部步骤教程
    Maven工程打jar包的N种方式
    论文总结:3D Talking Face With Personalized Pose Dynamics
    Ubuntu 安装和卸载mysql
    分布式事务解决方案
    SSM框架Demo: 简朴博客系统
    大神都在用的Word文档技巧,你们快来学
    Pytorch与tensorboard观察Loss变化
    代码随想录算法训练营第五十六天|1143.最长公共子序列、1035.不相交的线、53. 最大子序和
  • 原文地址:https://blog.csdn.net/u011142688/article/details/126208671