基于FX构建大型Golang应用

Uber开源的FX可以帮助Go应用解耦依赖,实现更好的代码复用。原文: How to build large Golang applications using FX

构建复杂的Go应用程序可能会引入很多耦合
构建复杂的Go应用程序可能会引入很多耦合

Golang是一种流行编程语言,功能强大,但人们还是会发现在处理依赖关系的同时组织大型代码库很复杂。

Go开发人员有时必须将依赖项的引用传递给其他人,从而造成重用代码很困难,并造成技术(如数据库引擎或HTTP服务器)与业务代码紧密耦合。

FX是由Uber创建的依赖注入工具,可以帮助我们避免不正确的模式,比如包中的init函数和传递依赖的全局变量,从而有助于重用代码。

本文将通过创建一个示例web应用,使用FX处理文本片段,以避免Golang代码中的紧耦合。

代码结构

首先定义代码结构:

lib/
  config/
  db/
  http/

config.yml
main.go

utils/

该架构将应用程序的不同参与者分成自己的Go包,这样如果需要替换DB技术就会很有帮助。

每个包定义向其他包公开的接口及其实现。

main.go文件将是依赖项的主要注入点,并将运行应用程序。

最后,utils包将包含将在应用程序中重用的所有不依赖于依赖项的代码片段。

首先,编写一个基本的main.go文件:

package main

import "go.uber.org/fx"

func main() {
 app := fx.New()
 app.Run()
}

声明FX应用程序并运行。接下来我们将看到如何在这个应用程序中注入更多特性。

模块架构

为了给应用程序添加功能,我们将使用FX模块,通过它在代码库中创建边界,使代码更具可重用性。

我们从配置模块开始,包含以下文件:

  • config.go定义向应用程序公开的数据结构。
  • fx.go将模块发布,设置需要的一切,并在启动时加载配置。
  • load.go是接口的实现。
// lib/config/config.go
package config

type httpConfig struct {
 ListenAddress string
}

type dbConfig struct {
 URL string
}

type Config struct {
 HTTP httpConfig
 DB   dbConfig
}

第一个文件定义了配置对象的结构。

// lib/config/load.go
package config

import (
 "fmt"
 "github.com/spf13/viper"
)

func getViper() *viper.Viper {
 v := viper.New()
 v.AddConfigPath(".")
 v.SetConfigFile("config.yml")
 return v
}

func NewConfig() (*Config, error) {
 fmt.Println("Loading configuration")
 v := getViper()
 err := v.ReadInConfig()
 if err != nil {
  return nil, err
 }
 var config Config
 err = v.Unmarshal(&config)
 return &config, err
}

load.go文件使用Viper框架从YML文件加载配置。我还添加了示例打印语句,以便稍后解释。

// lib/config/fx.go
package config

import "go.uber.org/fx"

var Module = fx.Module("config", fx.Provide(NewConfig))

这里通过使用fx.Module发布FX模块,这个函数接受两种类型的参数:

  • 第一个参数是用于日志记录的模块的名称。
  • 其余参数是希望向应用程序公开的依赖项。

这里我们只使用fx.Provide导出Config对象,这个函数告诉FX使用NewConfig函数来加载配置。

值得注意的是,如果Viper加载配置失败,NewConfig也会返回错误。如果错误不是nil, FX将显示错误并退出。

第二个要点是,该模块不导出Viper,而只导出配置实例,从而允许我们轻松的用任何其他配置框架替换Viper。

加载模块

现在,要加载我们的模块,只需要将它传递给main.go中的fx.New函数。

// main.go
package main

import (
 "fx-example/lib/config"
 "go.uber.org/fx"
)

func main() {
 app := fx.New(
  config.Module,
 )
 app.Run()
}

当我们运行这段代码时,可以在日志中看到:

[Fx] PROVIDE    *config.Config <= fx-example/lib/config.NewConfig() from module "config"
...
[Fx] RUNNING

FX告诉我们成功检测到fx-example/lib/config.NewConfig()提供了我们的配置,但是没有在控制台中看到"Loading configuration"。因为FX只在需要时调用提供程序,我们没使用刚才构建的配置,所以FX不会加载。

我们可以暂时在fx.New中添加一行,看看是否一切正常。

func main() {
 app := fx.New(
  config.Module,
  fx.Invoke(func(cfg *config.Config) {}),
 )
 app.Run()
}

我们添加了对fix.Invoke的调用,注册在应用程序一开始就调用的函数,这将是程序的入口,稍后将启动我们的HTTP服务器。

DB模块

接下来我们使用GORM(Golang ORM)编写DB模块。

package db

import (
 "github.com/izanagi1995/fx-example/lib/config"
 "gorm.io/driver/sqlite"
 "gorm.io/gorm"
)

type Database interface {
 GetTextByID(id int) (string, error)
 StoreText(text string) (uint, error)
}

type textModel struct {
 gorm.Model
 Text string
}

type GormDatabase struct {
 db *gorm.DB
}

func (g *GormDatabase) GetTextByID(id int) (string, error) {
 var text textModel
 err := g.db.First(&text, id).Error
 if err != nil {
  return "", err
 }
 return text.Text, nil
}

func (g *GormDatabase) StoreText(text string) (uint, error) {
 model := textModel{Text: text}
 err := g.db.Create(&model).Error
 if err != nil {
  return 0, err
 }
 return model.ID, nil
}

func NewDatabase(config *config.Config) (*GormDatabase, error) {
 db, err := gorm.Open(sqlite.Open(config.DB.URL), &gorm.Config{})
 if err != nil {
  return nil, err
 }
 err = db.AutoMigrate(&textModel{})
 if err != nil {
  return nil, err
 }
 return &GormDatabase{db: db}, nil
}

在这个文件中,首先声明一个接口,该接口允许存储文本并通过ID检索文本。然后用GORM实现该接口。

NewDatabase函数中,我们将配置作为参数,FX会在注册模块时自动注入。

// lib/db/fx.go
package db

import "go.uber.org/fx"

var Module = fx.Module("db",
 fx.Provide(
  fx.Annotate(
   NewDatabase,
   fx.As(new(Database)),
  ),
 ),
)

与配置模块一样,我们提供了NewDatabase函数。但这一次需要添加一个annotation。

这个annotation告诉FX不应该将NewDatabase函数的结果公开为*GormDatabase,而应该公开为Database接口。这再次允许我们将使用与实现解耦,因此可以稍后替换Gorm,而不必更改其他地方的代码。

不要忘记在main.go中注册db.Module

// main.go
package main

import (
 "fx-example/lib/config"
 "fx-example/lib/db"
 "go.uber.org/fx"
)

func main() {
 app := fx.New(
  config.Module,
  db.Module,
 )
 app.Run()
}

现在我们有了一种无需考虑底层实现就可以存储文本的方法。

HTTP模块

以同样的方式构建HTTP模块。

// lib/http/server.go
package http

import (
 "fmt"
 "github.com/izanagi1995/fx-example/lib/db"
 "io/ioutil"
 stdhttp "net/http"
 "strconv"
 "strings"
)

type Server struct {
 database db.Database
}

func (s *Server) ServeHTTP(writer stdhttp.ResponseWriter, request *stdhttp.Request) {
 if request.Method == "POST" {
  bodyBytes, err := ioutil.ReadAll(request.Body)
  if err != nil {
   writer.WriteHeader(400)
   _, _ = writer.Write([]byte("error while reading the body"))
   return
  }
  id, err := s.database.StoreText(string(bodyBytes))
  if err != nil {
   writer.WriteHeader(500)
   _, _ = writer.Write([]byte("error while storing the text"))
   return
  }
  writer.WriteHeader(200)
  writer.Write([]byte(strconv.Itoa(int(id))))
 } else {
  pathSplit := strings.Split(request.URL.Path, "/")
  id, err := strconv.Atoi(pathSplit[1])
  if err != nil {
   writer.WriteHeader(400)
   fmt.Println(err)
   _, _ = writer.Write([]byte("error while reading ID from URL"))
   return
  }
  text, err := s.database.GetTextByID(id)
  if err != nil {
   writer.WriteHeader(400)
   fmt.Println(err)
   _, _ = writer.Write([]byte("error while reading text from database"))
   return
  }
  _, _ = writer.Write([]byte(text))
 }
}

func NewServer(db db.Database) *Server {
 return &Server{database: db}
}

HTTP处理程序检查请求是POST还是GET请求。如果是POST请求,将正文存储为文本,并将ID作为响应发送。如果是GET请求,则从查询路径中获取ID对应的文本。

// lib/http/fx.go
package http

import (
 "go.uber.org/fx"
 "net/http"
)

var Module = fx.Module("http", fx.Provide(
 fx.Annotate(
  NewServer,
  fx.As(new(http.Handler)),
 ),
))

最后,将服务器公开为http.Handler,这样就可以用更高级的工具(如Gin或Gorilla Mux)替换刚才构建的简单HTTP服务器。

现在,我们可以将模块导入到main函数中,并编写一个Invoke调用来启动服务器。

// main.go
package main

import (
 "fx-example/lib/config"
 "fx-example/lib/db"
 "fx-example/lib/http"
 "go.uber.org/fx"
 stdhttp "net/http"
)

func main() {
 app := fx.New(
  config.Module,
  db.Module,
  http.Module,
  fx.Invoke(func(cfg *config.Config, handler stdhttp.Handler) error {
   go stdhttp.ListenAndServe(cfg.HTTP.ListenAddress, handler)
   return nil
  }),
 )
 app.Run()
}

瞧!我们有一个简单的HTTP服务器连接到一个SQLite数据库,所有都基于FX。

总结一下,FX可以帮助我们解耦代码,使其更易于重用,并且减少对正在进行的实现的依赖,还有助于更好的理解整体体系架构,而无需梳理复杂的调用和引用链。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

本文由 mdnice 多平台发布

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

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

相关文章

sql注入第一关

判断注入点的类型 通常 Sql 注入漏洞分为 2 种类型&#xff1a; 数字型字符型 数字型测试 在参数后面加上单引号,比如: http://xxx/abc.php?id1 如果页面返回错误&#xff0c;则存在 Sql 注入。 原因是无论字符型还是整型都会因为单引号个数不匹配而报错。 如果未报错&…

Go语言中的HTTP代理处理机制

在当今的互联网世界&#xff0c;HTTP代理是一种常见的网络通信方式&#xff0c;用于保护用户的隐私、突破网络限制或提高网络访问速度。在Go语言中&#xff0c;代理处理机制的实现可以为开发者提供强大的网络通信能力。本文将深入探讨Go语言中的HTTP代理处理机制。 首先&#…

每日一道面试题:Java中序列化与反序列化

写在开头 哈喽大家好&#xff0c;在高铁上码字的感觉是真不爽啊&#xff0c;小桌板又拥挤&#xff0c;旁边的小朋友也比较的吵闹&#xff0c;影响思绪&#xff0c;但这丝毫不影响咱学习的劲头&#xff01;哈哈哈&#xff0c;在这喧哗的车厢中&#xff0c;思考着这样的一个问题…

PrimeFaces修改默认加载动画

Background 默认加载动画不够醒目&#xff0c;我们可以在网上下载个好看的gif图&#xff0c;然后修改默认设置&#xff0c;具体步骤如下参考官方地址&#xff1a;https://www.primefaces.org/showcase/ui/ajax/status.xhtml 实现效果如下 xhtml源码 <p:ajaxStatus onstar…

【人工智能】八数码问题的A*搜索算法实现

一、实验要求 熟悉和掌握启发式搜索的定义、估价函数和算法过程&#xff0c;并利用A*算法求解八数码问题&#xff0c;理解求解流程和搜索顺序 二、实验原理 定义h*(n)为状态n到目的状态的最优路径的代价&#xff0c;则当A搜索算法的启发函数h(n)小于等于h* (n)&#xff0c;即满…

使用毫米波雷达传感器的功能安全兼容系统设计指南2(TI文档)

2.3 步骤3&#xff1a;平台选择 平台选择是设计生命周期中最关键的步骤之一。一旦从第二步完成了一个成熟的系统框图&#xff0c;重要的任务就是根据性能需求选择系统模块/子系统。TI广泛的毫米波雷达传感器产品组合可以帮助实现许多性能要求&#xff0c;如远程或中程、角度分辨…

GoogLeNet模型详解

模型介绍 GoogLeNet是谷歌工程师设计的深度神经网络结构&#xff0c;于2014年在ImageNet比赛中取得了冠军。它的设计特点在于既有深度&#xff0c;又在横向上拥有“宽度”&#xff0c;并采用了一种名为Inception的核心子网络结构。这个网络名字中的“GoogLeNet”是对LeNet的致…

Layui + Echarts 5.0

Layui 怎么整合最新版本的 Echarts 5.0&#xff0c;Echarts 4 升级到 5后&#xff0c;有了很大改变&#xff0c;新的配置项4是无法兼容的&#xff0c;所以想要使用新的功能&#xff0c;都需要升级&#xff01; 新建一个echarts.js文件 layui.define(function (exports) {// 这…

2023年算法OOA-CNN-BiLSTM-ATTENTION回归预测(matlab)

OOA-CNN-BiLSTM-Attention鲸鱼算法优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 鱼鹰优化算法&#xff08;Osprey optimization algorithm&#xff0c;OOA&#xff09;由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出&#xff0c;其模拟鱼鹰的捕…

Nodejs前端学习Day5

苦其心志&#xff0c;劳其筋骨 文章目录 前言一、处理路径问题二、path路径模块总结 前言 继续fs 一、处理路径问题 在使用fs模块操作文件时&#xff0c;如果提供的操作路径是以./或…/开头的相对路径时&#xff0c;很容易出现路径动态拼接错误的问题 原因&#xff1a;代码在…

USB-C显示器:未来显示技术的革新者

随着科技的不断发展&#xff0c;显示技术也在不断进步&#xff0c;而USB-C显示器作为最新的显示技术&#xff0c;正在引领着显示行业的发展潮流。USB-C显示器具有许多优点&#xff0c;如高速传输、便捷连接、节能环保等&#xff0c;使其成为未来显示技术的革新者。 一、USB-C显…

【leetcode】01背包总结

01 背包 关键点 容器容量固定每件物品只有两种状态&#xff1a;不选、选 1 件求最大价值 代码 int N, W; // N件物品&#xff0c;容量为W int w[N], v[N]; // w为大小&#xff0c;v为容量/* 数组定义 */ int[][] dp new int[N][W 1]; // 注意是W 1, 因为重量会取到W dp[…

向日葵企业“云策略”升级 支持Android 被控策略设置

此前&#xff0c;贝锐向日葵推出了适配PC企业客户端的云策略功能&#xff0c;这一功能支持管理平台统一修改设备设置&#xff0c;上万设备实时下发实时生效&#xff0c;很好的解决了当远程控制方案部署后&#xff0c;想要灵活调整配置需要逐台手工操作的痛点&#xff0c;大幅提…

小型洗衣机哪个牌子好用又耐用?最好用的迷你洗衣机推荐

最近这两年在洗衣机中火出圈的内衣洗衣机&#xff0c;它不仅可以清洁我们较难清洗的衣物&#xff0c;自带除菌功能&#xff0c;可以让衣物上的细菌&#xff0c;还能在清洗的过程中呵护我们衣物的面料&#xff0c;虽然说它是内衣洗衣机&#xff0c;它的功能不止可以清洗内衣&…

精通Python第16篇—深入解析Pyecharts极坐标系参数与实战

文章目录 Pyecharts绘制多种炫酷极坐标系参数说明与方向的技术博客1. 导入必要的库2. 极坐标系基础3. 定制化极坐标系4. 方向性的极坐标系5. 极坐标系的动画效果6. 自定义极坐标轴标签7. 添加极坐标系的背景图8. 极坐标系的雷达图总结 Pyecharts绘制多种炫酷极坐标系参数说明与…

JVM系列——对象管理

JVM对象分布 对象头 第一类是用于存储对象自身的运行时数据&#xff0c;如哈希码&#xff08;HashCode&#xff09;、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等 另外一部分是类型指针&#xff0c;即对象指向它的类型元数据的指针&#xff0c;Java 虚…

DeepSORT算法实现车辆和行人跟踪计数和是否道路违规检测(代码+教程)

DeepSORT算法是一种用于目标跟踪的算法&#xff0c;它可以对车辆和行人进行跟踪计数&#xff0c;并且可以检测是否存在道路违规行为。该算法采用深度学习技术来提取特征&#xff0c;并使用卡尔曼滤波器来估计物体的速度和位置。 DeepSORT算法通过首先使用目标检测算法来识别出…

1 月 27日算法练习-贪心

文章目录 扫地机器人分糖果最小战斗力差距谈判纪念品分组 扫地机器人 思路&#xff1a; 最优机器人清理方法&#xff1a;机器人清理方法先扫左边&#xff0c;有时间再扫右边。最短时间&#xff1a;通过枚举&#xff0c;从 1 开始&#xff0c;清理面积会越大直到全部面积的清理…

【Sql Server】新手一分钟看懂在已有表基础上增加字段和说明

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

计算机网络——IP协议

前言 网络层的主要负责地址分配和路由选择,ip负责在网络中进行数据包的路由和传输。 IPv4报文组成&#xff08;了解&#xff09; IPv4首部&#xff1a;IPv4首部包含了用于路由和传输数据的控制信息&#xff0c;其长度为20个字节&#xff08;固定长度&#xff09;。 版本&#…