golang 库之「依赖注入」

文章目录

    • 1. 写在最前面
    • 2. 依赖注入
      • 2.1 使用场景
      • 2.2 框架对比
    • 3. fx 框架使用场景示例
      • 3.1 示例
      • 3.2 golang 原生的库
      • 3.3 fx 库
      • 3.4 对比
        • 3.4.1 如上两种实现方式对比
        • 3.4.2 关于过度设计
        • 3.4.3 感悟
    • 4. 碎碎念
    • 5. 参考资料

1. 写在最前面

同事在技术分享的时候用了 golang 的 fx 框架,突然想起之前有一次帮别人 review 代码的时候也看到过这个框架。只是大概知道是「依赖注入」的框架,并没有深入分析理解过,包括这个框架的优势、劣势以及适合的场景一概不清楚。知识这个东西,摆在那里就是人家的,学到了才是自己的。所以顺便整理一波,方便后面自己要用的时候,可以很快的上手。

注: 有再一再二,没有再三再四。第一次和第二次看到的时候可以说不理解,但是都第三次和第四次了,这可说不过去了。

2. 依赖注入

2.1 使用场景

维基百科官方话术:「依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端)。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态一部分。传递服务给客户端,而非允许客户端来建立或寻找服务,是本设计模式的基本要求。」

注:维基百科的这个概念我读了三遍能理解,但是很难深入的分辨出什么场景适合用依赖注入。

这个概念让我陷入了沉思,然后我又不死心的,问了问 chatgpt

chatgpt 的回答如下:

在这里插入图片描述

总结:chatgpt 的回答整体上比维基百科理解起来要更简单一点。(ps:遇到不懂的概念可以试试 chatgpt

2.2 框架对比

以下是几个常用的 golang 依赖注入框架:

  • google 的 wire : 一个用于管理依赖注入的代码生成工具,它提供了一种简单的方式来定义依赖关系,并生成相应的代码。wire 可以检查和解析依赖关系、生成可重用、高效的代码。官方文档,GitHub - google/wire: Compile-time Dependency Injection for Go

  • uber 的 fx:一个基于 wire 构建的更高级的依赖注入框架,它提供了更多的特性和功能,如生命周期的管理、插件系统、服务注册等。fx 旨在提供一种更易于使用和维护的依赖注入模式。官方文档:GitHub - uber-go/fx: A dependency injection based application framework for Go.

注:两者的差别,在于 wire 是使用 Code Gen 的方式,而 fx 则是使用的 reflection.

3. fx 框架使用场景示例

在使用框架之前,一定要先想清楚,业务的复杂程度真的到了必需框架不可的程度了吗?

个人观点:

  • 引入库会一定程度上减少了业务开发的复杂性,但是也会导致理解代码的成本变高(ps: 通用的常见库除外,比如日志库

3.1 示例

实现一个 http server ,支持如下两个 POST 方法

  • /echo :将请求的内容,直接作为响应的 body

  • /hello:请求的内容拼接 hello 字符,将其作为响应的 body

形如:

在这里插入图片描述

3.2 golang 原生的库

代码:

package mainimport ("fmt""io""log""net/http"
)var handleMap = map[string]func(w http.ResponseWriter, r *http.Request){"/echo":  handleEcho,"/hello": handleHello,
}func main() {http.HandleFunc("/", handleRequest)log.Fatal(http.ListenAndServe(":8080", nil))
}func handleRequest(w http.ResponseWriter, r *http.Request) {f := handleMap[r.URL.Path]if f != nil {f(w, r)return}http.NotFound(w, r)
}func handleEcho(w http.ResponseWriter, r *http.Request) {body, err := io.ReadAll(r.Body)if err != nil {http.Error(w, "Error reading request body", http.StatusInternalServerError)return}fmt.Fprintf(w, "%s", body)
}func handleHello(w http.ResponseWriter, r *http.Request) {body, err := io.ReadAll(r.Body)if err != nil {http.Error(w, "Error reading request body", http.StatusInternalServerError)return}msg := fmt.Sprintf("%s hello", body)fmt.Fprint(w, msg)
}

3.3 fx 库

代码:

package mainimport ("context""fmt""io""net""net/http""go.uber.org/fx""go.uber.org/zap"
)// 定义的接口
type Route interface {http.HandlerPattern() string
}func AsRoute(f any) any {return fx.Annotate(f,fx.As(new(Route)),fx.ResultTags(`group:"routes"`),)
}// 实现 Route 接口的数据结构, echo 接口
type EchoHandler struct {log *zap.Logger
}func NewEchoHandler(log *zap.Logger) *EchoHandler {return &EchoHandler{log: log,}
}
func (*EchoHandler) Pattern() string {return "/echo"
}func (h *EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {if _, err := io.Copy(w, r.Body); err != nil {h.log.Warn("Failed to handle request", zap.Error(err))}
}// 实现 Route 接口的数据结构, hello 接口
type HelloHandler struct {log *zap.Logger
}func NewHelloHandler(log *zap.Logger) *HelloHandler {return &HelloHandler{log: log}
}func (*HelloHandler) Pattern() string {return "/hello"
}func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {body, err := io.ReadAll(r.Body)if err != nil {h.log.Error("Failed to read request", zap.Error(err))http.Error(w, "Internal server error", http.StatusInternalServerError)return}if _, err := fmt.Fprintf(w, "Hello, %s\n", body); err != nil {h.log.Error("Failed to write response", zap.Error(err))http.Error(w, "Internal server error", http.StatusInternalServerError)return}
}func NewServeMux(route []Route) *http.ServeMux {mux := http.NewServeMux()for _, r := range route {mux.Handle(r.Pattern(), r)}return mux
}func NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux, log *zap.Logger) *http.Server {srv := &http.Server{Addr: ":8081", Handler: mux}lc.Append(fx.Hook{OnStart: func(ctx context.Context) error {ln, err := net.Listen("tcp", srv.Addr)if err != nil {return err}log.Info("Starting HTTP Server", zap.String("addr", srv.Addr))go srv.Serve(ln)return nil},OnStop: func(ctx context.Context) error {return srv.Shutdown(ctx)},})return srv
}func UseHttpServer(*http.Server) {}func main() {fx.New(fx.Provide(NewHTTPServer,fx.Annotate(NewServeMux,fx.ParamTags(`group:"routes"`),),AsRoute(NewEchoHandler),AsRoute(NewHelloHandler),zap.NewExample,),fx.Invoke(UseHttpServer),).Run()}

3.4 对比

fx 库的官方介绍如下:

  • Eliminate globals: Fx helps you remove global state from your application. No more init() or global variables. Use Fx-managed singletons.
  • Code reuse: Fx lets teams within your organization build loosely-coupled and well-integrated shareable components.
  • Battle tested: Fx is the backbone of nearly all Go services at Uber.

总结:

  • 消除了 init() 和 全局变量的使用

  • 解耦程度更好以及更方便的共享组件

  • 在 Uber 内部成熟度很高,几乎所有的 go 服务的底层支柱。

但是:

  • 使用了 init() 和 全局变量 真的有什么坏处吗?

  • 你的程序真的需要那么高的解耦程度吗?

  • 「backbone」代表 100% 可靠吗?

最近开发心得,代码凡是依赖别人的,交付时间完全依赖别人排期。但是代码全部控制在自己手里,那里方便修复改哪里!

「你的修复方式,不会影响到客户。但是你为了追求完美的代码,延误了交付时间,一定会招来客户投诉」(ps:不要问我怎么知道的,成长血泪史

3.4.1 如上两种实现方式对比
golang 原生库fx 库
开发复杂度
解耦程度
运维成本

注:代码首先是需要人理解和维护的,其次都是其次。如果理解的成本变高,那相应的维护成本可想而知。

    以上等级划分:均为高中低三档按照笔者的比较:golang 原始库完胜 fx 库

注意:不能大而全的总结为 fx 库不好,而是总结为在代码的规模较少、又要保证交付节奏时,不引入复杂或者自己不熟悉的库,不失为一种很好的选择。

注:凡是自己控制的,都是可靠的,凡是有依赖的,皆需要多问问「真的是这样吗?」

3.4.2 关于过度设计

上面的对比让笔者脑子中产生了一个之前看过的关键词「过度设计」。

维基百科给出的定义:

「过度设计」指的是一种过于复杂的方式设计产品或提供问题的解决方案的行为,而在这种情况下,可以证明存在一种更简单的解决方案,其效率和效果与原设计相同。

注:参考 Paweł Głogowski 的这个定义,可以总计为「解决你所有没有的问题的代码和设计」

原因: 我们试图预测未来,对未知的问题做准备。(ps:你认为的未来的问题,可能根本没有出现的机会,所以减少焦虑,不要过度,因为未来的事情担心和设计

解决方法

  • 让工程师成为真正的产品工程师

  • 正确定义问题来减少模糊性

  • 多问:这对解决当前用户的问题有什么帮助?要是现在不解决会怎么?

3.4.3 感悟
  • 开发者应该视自己的方案来选择技术实践,框架提供的是「选择」,而不是限制开发者的自由。

  • 框架的目的是协助工程师,如果不知道需要什么协助,用框架也帮不上忙,说不定还会束手束脚。

4. 碎碎念

上海的天气真的是瞬息万变,昨天还可以穿小裙子,今天就要穿呢子大衣,惹不起!

  • 时间扑面而来,我们终将释怀。健康的活着,平静的过着,开心的笑着,适当的忙着,就很好。

  • 第一是做让自己开心的事情,第二是「绝对不做让自己不开心的事情」。

  • 就算失败,我也想知道,自己倒在距离终点多远的地方。

5. 参考资料

  • 用 fx 来替 Go 依赖注入吧

  • golang 依赖注入 wire 和 dig 体验对比

  • Golang 依赖注入经典解决方案uber/fx理论解析)

  • 依赖注入是什么?如何使用它?

  • 过度设计会扼杀你的产品_文化 & 方法_Simón Muñoz_InfoQ精选文章

  • Overengineering in software development | Solidstudio

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

套接字的多种可选项

套接字可选项和I/O缓冲大小 套接字的多种可选项 套接字可选项分为 IPPROTO_IP、IPPROTO_TCP、SOL_SOCKET 三层,各层的含义为: IPPROTO_IP:IP 协议相关事项; IPPROTO_TCP:TCP 协议相关事项; SOL_SOCKET&am…

苍穹外卖-day09

用户端历史订单模块 1. 查询历史订单 1.1 需求分析和设计 产品原型: 业务规则 分页查询历史订单可以根据订单状态查询展示订单数据时,需要展示的数据包括:下单时间、订单状态、订单金额、订单明细(商品名称、图片)…

如何设置静态代理IP切换电脑上网地址使用?

在当今的网络时代,代理IP已成为一种常见的网络访问方式。通过使用代理IP,我们可以隐藏自己的真实IP地址,从而保护自己的隐私和安全。但是,有时候我们需要切换代理IP来满足不同的上网需求。本文将介绍如何设置静态代理IP切换电脑上…

优秀智慧园区案例 - 珠海华发智慧园区,万字长文解析先进智慧园区建设方案经验

一、项目背景 珠海华发产业园运营管理有限公司(简称“产业园公司”)是2016年起连续五年跻身“中国企业500强”、国务院国企改革“双百企业”的珠海华发集团旗下的实体产业发展载体运营平台,依托“四园一基地”:中以国际产业园、信…

Java自学第11课:电商项目(4)重新建立项目

经过前几节的学习,我们已经找到之前碰到的问题的原因了。那么下面接着做项目学习。 1 新建dynamic web project 建立时把web.xml也生成下,省的右面再添加。 会询问是否改为java ee环境?no就行,其实改过来也是可以的。这个不重要。…

基于springboot实现福聚苑社区团购平台系统项目【项目源码】

基于springboot实现福聚苑社区团购平台系统演示 Javar技术 Java是一种网络脚本语言,广泛运用于web应用开发,可以用来添加网页的格式动态效果,该语言不用进行预编译就直接运行,可以直接嵌入HTML语言中,写成js语言&…

【KVM-4】硬件虚拟化技术(详)

前言 大家好,我是秋意零。 经过前面章节的介绍,已经知道KVM虚拟化必须依赖于硬件辅助的虚拟化技术,本节就来介绍一下硬件虚拟化技术。 👿 简介 🏠 个人主页: 秋意零🔥 账号:全平…

从替代走向引领,永洪科技迈向全球化

对于数据分析领域而言,这是一个最好的时代。 《全球数字经济白皮书(2023年)》介绍,2016年-2022年,中国数字经济年均复合增长率为14.2%,数字经济发展增速和规模兼具。随着数字基础实施持续夯实、数字应用不…

前端面试系列之工程化篇

如果对前端八股文感兴趣,可以留意公重号:码农补给站,总有你要的干货。 前端工程化 Webpack 概念 本质上,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个…

MyBatis 反射工具箱:带你领略不一样的反射设计思路

反射是 Java 世界中非常强大、非常灵活的一种机制。在面向对象的 Java 语言中,我们只能按照 public、private 等关键字的规范去访问一个 Java 对象的属性和方法,但反射机制可以让我们在运行时拿到任何 Java 对象的属性或方法。 有人说反射打破了类的封装…

企业微信开发教程一:添加企微应用流程图解以及常见问题图文说明

最近在前辈的基础上新添加了一个企微应用,过程中遇到了一些卡点,这里一一通过图片标注与注释的方式记录一下,希望能给后来人提供一些清晰明了的帮助,话不多说,大家直接看图吧。 (文中包括一些本项目独有的配…

matlab背景部分最小化算法人脸检测

1、内容简介 略 18-可以交流、咨询、答疑 matlab背景部分最小化算法人脸检测 2、内容说明 matlab人脸检测 matlab人脸检测,背景部分最小化算法 3、仿真分析 略. 4、参考论文 略 链接:https://pan.baidu.com/s/1yQ1yDfk-_Qnq7tGpa23L7g 提取码&…

双H桥直流马达步进电机驱动芯片SS8833E

由工采网代理的率能SS8833E是一款适用于有刷直流或双极步进电机的集成电机驱动芯片;采用eTSSOP16封装;该器件集成了两个PNMOS H桥和电流调节电路;电机输出电流可以由外部脉宽调制器(PWM)或内部PWM电流控制器控制。 工…

家庭安全计划 挑战赛| 溺水预防

溺水预防 从了解到行动 家庭安全计划 | 少年急救官 地震避险逃生该怎么做? 起火了该如何应对? 哪些行为容易导致溺水? 家庭风险隐患有哪些? 家庭逃生演练四步骤你会吗? 国际救助儿童会(英国&#xff…

虚拟化服务器+华为防火墙+kiwi_syslog访问留痕

一、适用场景 1、大中型企业需要对接入用户的访问进行记录时,以前用3CDaemon时,只能用于小型网络当中,记录的数据量太大时,本例采用破解版的kiwi_syslog。 2、当网监、公安查到有非法访问时,可提供基于五元组的外网访…

kubernetes--Pod进阶

目录 一、资源限制: 1. 资源限制的两种规范: 2. Pod 和 容器 的资源请求和限制: 3. CPU 资源单位: 4. 内存资源单位 : 5. 资源限制示例: 二、健康检查:探针(Probe) 1. 探…

Git Gui的使用及ssh协议-IEDA使用git

目录 一.Git Gui的使用 二.ssh协议 2.1 什么是ssh key 2.2 配置用户名和邮箱(如果已经配置,就跳过) 2.3 生成(或删除)秘钥 ​编辑 2.4 远程仓库绑定公钥 三.IEDA使用git 3.1 idea配置Git 3.2 项目上传Git 3.3 演示 一.Git Gu…

python打包部署脚本

linux可使用expect来实现自动交互,windows想要写出同样的功能脚本,只能使用python或者安装ActiveTcl 1、安装python Microsoft Store搜索python直接安装,默认会直接添加到环境变量https://www.python.org/官网下载,点击安装时会提…

lua 时间差功能概略

简介 在进行程序设计过程中,经常需要对某些函数、某些程序片断从开始运行到运行结束所耗费的时间进行一些量化。这种量化实际上就是计算时间差。 获取函数耗时情景如下: function time_used() --开始计时-- do something at here. --结束计时--时间差&…

tomcat下载与使用教程

1. tomcat下载 官网:https://tomcat.apache.org/ 镜像地址:https://mirrors.huaweicloud.com/apache/tomcat/ 1、选择一个版本下载,官网下载速度缓慢,推荐镜像 2、对压缩包进行解压,无需进行安装,解压放…