golang 库之「依赖注入」

news2024/11/23 21:49:53

文章目录

    • 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 main

import (
    "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 main

import (
    "context"
    "fmt"
    "io"
    "net"
    "net/http"

    "go.uber.org/fx"
    "go.uber.org/zap"
)

// 定义的接口
type Route interface {
    http.Handler

    Pattern() 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.coloradmin.cn/o/1194168.html

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

相关文章

chatglm3-6b记录问答对

# 打开文件,第二个参数是打开文件的模式,a代表追加,也就是说,打开这个文件之后直接定位到文件的末尾 file open(chatlog.txt, "a") # 写入数据 file.write(ask:prompt_text\n) file.write(response:response\n) # 关闭文件 fil…

基于SSM的水果网上商城设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…

Ubuntu 安装常见问题

1. 安装oh my zsh 搜狗输入法不能用 vim /etc/environmentexport XIM_PROGRAMfcitx export XIMfcitx export GTK_IM_MODULEfcitx export QT_IM_MODULEfcitx export XMODIFIERS“imfcitx” export LANG“zh_CN.UTF-8”配置完后重启,稍等一会,右上角会有个…

35 字段类型不匹配 影响 使用索引?

前言 这是一个经常能够看到的问题, 又或者 经常在面试中碰到 如果 索引字段类型 不匹配, 然后 不会使用索引 这里 我们来看一下 具体的情况 测试表结构如下 CREATE TABLE tz_test (id int(11) unsigned NOT NULL AUTO_INCREMENT,field1 varchar(128) DEFAULT NULL,PRIMA…

接口自动化测试之Fiddler使用教程

一、Fiddler 简介 Fiddler工具介绍 Fiddler是一个通过代理的方式来进行抓包工具,运行时会在本地建立一个代理服务,默认地址:127.0.0.1:8888。Fiddler开启之后,配置本机代理,再打开IE浏览器,IE的PROXY会自…

C语言与C++的区别和联系

C语言和C到底是什么关系? 首先C和C语言本来就是两种不同的编程语言,但C确实是对C语言的扩充和延伸,并且对C语言提供后向兼容的能力。对于有些人说的“C完全就包含了C语言”的说法也并没有错。 C一开始被本贾尼斯特劳斯特卢普(Bja…

身份证读取器手持机 二代证核验手持终端 身份证核查手持机

身份证手持机外观比较小巧,方便携带,支持条码识别、人脸识别、NFC卡刷卡、内置二代证加密模块,可离线采集和识别二代身份证,进行身份识别。信息读取更便捷、安全高效。采用IP65高防护等级,1.5M防摔,可以适应…

SmartBear正式收购Stoplight,并计划在核心API设计、文档和门户产品中集成其功能

不久前,软件开发和可视化工具提供商SmartBear正式宣布收购全球领先的API设计公司Stoplight。这一收购是为了打造业内最全面的API开发平台,为寻求现代化API实践的开发团队提供更好的透明度、自动化与生产力。将Stoplight在API方面的优势(包括治…

Linux驱动应用层与内核层之间的数据传递

摘要 本文将深入探讨在Linux驱动中,应用层与内核层之间数据传递的机制和优化策略。我们将详细解析这一过程中的各个步骤,包括数据从应用层到内核层的传输,以及从内核层返回应用层的过程。此外,我们将提出并评估一系列可能的优化策…

Docker+K8s基础(重要知识点总结)

目录 一、Docker的核心1,Docker引擎2,Docker基础命令3,单个容器运行多个服务进程4,多个容器运行多个服务进程5,备份在容器中运行的数据库6,在宿主机和容器之间共享数据7,在容器之间共享数据8&am…

已解决:TypeError: ‘NoneType‘ object is not callable 问题

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页: 🐅🐾猫头虎的博客🎐《面试题大全专栏》 🦕 文章图文并茂&#x1f996…

GC5958低压三相无刷直流驱动芯片,无感,正弦,低压,PWM调速可替代APX9358/茂达

GC5958提供了无传感器的三相无刷直流电机的速度控制的所有电路。正弦波驱动器的方法将是更好的低噪声。控制器功能包括启动电路、反电动势换向控制。脉宽调制) 速度控制。锁定保护和热关断电路GC5958既适合游戏机器,也适用于需要无声驱动的CPU冷却器。它以DFN3x3-10…

一篇文章教会你写一个贪吃蛇小游戏(纯C语言)

一篇文章教会你写一个贪吃蛇小游戏 1、游戏展示2、游戏功能3、Win32 API3.1 控制台程序3.2 控制台屏幕上的坐标COORD3.3 GetStdHandle函数3.4 GetConsoleCursorInfo函数3.4.1 CONSOLE_CURSOR_INFO结构体 3.5 SetConsoleCursorInfo函数3.6 SetConsoleCursorPosition函数3.7 GetA…

自动私信引流软件的运行分享,与开发需要到的技术分析

先来看实操成果,↑↑需要的同学可看我名字↖↖↖↖↖,或评论888无偿分享 一、引言 随着移动互联网的普及和人们对便捷生活的追求,引流APP已成为越来越多企业的必备工具。本文将为大家详细介绍开发引流APP需要用到的技术和流程,帮…

CSS3渐变颜色

CSS3 渐变可以让你在两个或多个指定的颜色之间显示平稳的过渡。 CSS3渐变有两种类型:线性渐变(Linear Gradients)和径向渐变(Radial Gradients)。 线性渐变(Linear Gradients): 线性…

阻塞队列+定时器+常见的锁策略

1)阻塞队列:是一个线程安全的队列,是可以保证线程安全的 1.1)如果当前队列为空,尝试出队列,进入阻塞状态,一直阻塞到队列里面的元素不为空 1.2)如果当前队列满了,尝试入队列,也会产生阻塞,一直阻…

Ionic 组件 ion-item-divider ion-item-group ion-item-sliding ion-label ion-note

1 ion-item-divider Item dividers是块元素&#xff0c;可用于分隔列表中的items 。它们类似于列表标题&#xff0c;但它们不应该只放在列表的顶部&#xff0c;而应该放在items之间。 <ion-list><ion-item-group><ion-item-divider><ion-label> Secti…

javascript 操作mysql数据库

目录 一&#xff1a;Javascript访问MYSQL 二&#xff1a;JavaScript中操作Mysql数据库实例 一&#xff1a;Javascript访问MYSQL 1、下载MYSQL的ODBC连接 2、在JS中建立ODBC连接如下&#xff1a; var con new ActiveXObject("ADODB.Connection"); con.Connection…

Linux软件包(源码包和二进制包)

Linux下的软件包众多&#xff0c;且几乎都是经 GPL 授权、免费开源&#xff08;无偿公开源代码&#xff09;的。这意味着如果你具备修改软件源代码的能力&#xff0c;只要你愿意&#xff0c;可以随意修改。 GPL&#xff0c;全称 General Public License&#xff0c;中文名称“通…

jbase代码生成器(成型篇)

上一篇说到通用码表可以解决百分之八十的基础维护功能&#xff0c;剩下的百分二十的需要级联维护的界面可以用代码生成器生成代码&#xff0c;基于生成的代码拷贝再组装界面&#xff0c;来解决这百分之二十的工作量里的百分之八十工作量。 首先实现代码生成器 Class Jbase.Ma…