go程序制作成容器_清晰架构的 Go 微服务:程序容器

d92c257e34c2f3118a828d39db690c33.png

作者 | 倚天码农

来源 | CSDN博客,责编 | 夕颜

出品 | CSDN(ID:CSDNnews)

清晰架构(Clean Architecture)的一个理念是隔离程序的框架,使框架不会接管你的应用程序,而是由你决定何时何地使用它们。在本程序中,我特意不在开始时使用任何框架,因此我可以更好地控制程序结构。只有在整个程序结构布局完成之后,我才会考虑用某些库替换本程序的某些组件。这样,引入的框架或第三方库的影响就会被正确的依赖关系所隔离。目前,除了logger,数据库,gRPC和Protobuf(这是无法避免的)之外,我只使用了两个第三方库ozzo-validation¹和YAML²,而其他所有库都是Go的标准库。

你可以使用本程序作为构建应用程序的基础。你可能会问,那么本框架岂不是要接管整个应用程序吗?是的。但事实是,无论是你自建框架还是引进第三方框架,你都需要一个基本框架作为构建应用程序的基础。该基础需要具有正确的依赖性和可靠的设计,然后你可以决定是否引入其他库。你当然可以自己建立一个框架,但你最终可能会花费大量的时间和精力来完善它。你也可以使用本程序作为起点,而不是构建自己的项目,从而为你节省时间和精力。

程序容器是项目中最复杂的部分,是将应用程序的不同部分粘合在一起的关键组件。本程序的其他部分是直截了当且易于理解的,但这一部分不是。好消息是,一旦你理解了这一部分,那么整个程序就都在掌控之中。

ff92d4f8367e9affb6904bfb8a6f2607.png

容器包(“container” package)的组成部分

容器包由五部分组成:

1. “容器”(“container”)包:它负责创建具体类型并将它们注入其他文件。顶级包中只有一个文件“container.go”,它定义容器的接口。

cea93a0cb09cfdd38987034a5f7aff7b.png

2. “servicecontainer”子包:容器接口的实现。只有一个文件“serviceContainer.go”,这是“容器”包的关键。以下是代码。它的起点是“InitApp”,它从文件中读取配置数据并设置日志记录器(logger)。

type ServiceContainer struct { FactoryMap map[string]interface{} AppConfig *config.AppConfig}func (sc *ServiceContainer) InitApp(filename string) error { var err error config, err := loadConfig(filename) if err != nil { return errors.Wrap(err, "loadConfig") } sc.AppConfig = config err = loadLogger(config.Log) if err != nil { return errors.Wrap(err, "loadLogger") } return nil}// loads the loggerfunc loadLogger(lc config.LogConfig) error { loggerType := lc.Code err := logFactory.GetLogFactoryBuilder(loggerType).Build(&lc) if err != nil { return errors.Wrap(err, "") } return nil}// loads the application configurationsfunc loadConfig(filename string) (*config.AppConfig, error) { ac, err := config.ReadConfig(filename) if err != nil { return nil, errors.Wrap(err, "read container") } return ac, nil}

3. “configs”子包:负责从YAML文件加载程序配置,并将它们保存到“appConfig”结构中以供容器使用。

aaa6c906ce709023771aa28365ed3f61.png

4. “logger”子包:它里面只有一个文件“logger.go”,它提供了日志记录器接口和一个“Log”变量来访问日志记录器。因为每个文件都需要依赖记录,所以它需要一个独立的包来避免循环依赖。

1925b478583a131e6942237f04cb8338.png

5. 最后一部分是不同类型的工厂(factory)。

它的内部分层与应用层分层相匹配。对于“usecase”和“dataservice”层,有“usecasefactory”和“dataservicefactory”。另一个工厂是“datastorefactory”,它负责创建底层数据处理链接。因为数据提供者可以是gRPC或除数据库之外的其他类型的服务,所以它被称为“datastorefactry”而不是“databasefactory”。日志记录组件(logger)也有自己的工厂。

7d81f6d2636d6abc4eee484ab91a6174.png

用例工厂(Use Case Factory)

对于每个用例,例如“registration”,接口在“usecase”包中定义,但具体类型在“usecase”包下的“registration”子包中定义。此外,容器包中有一个对应的工厂负责创建具体的用例实例。对于“注册(registration)”用例,它是“registrationFactory.go”。用例与用例工厂之间的关系是一对一的。用例工厂负责创建此用例的具体类型(concrete type)并调用其他工厂来创建具体类型所需的成员(member in a struct)。最低级别的具体类型是sql.DBs和gRPC连接,它们需要被传递给持久层,这样才能访问数据库中的数据。

如果Go支持泛型,你可以创建一个通用工厂来构建不同类型的实例。现在,我必须为每一层创建一个工厂。另一个选择是使用反射(refection),但它有不少问题,因此我没有采用。

“Registration” 用例工厂(Use Case Factory):

每次调用工厂时,它都会构建一个新类型。以下是“注册(Registration)”用例创建具体类型的代码。它是工厂方法模式(factory method pattern)的典型实现。如果你想了解有关如何在Go中实现工厂方法模式的更多信息,请参阅此处³.

// Build creates concrete type for RegistrationUseCaseInterfacefunc (rf *RegistrationFactory) Build(c container.Container, appConfig *config.AppConfig, key string) (UseCaseInterface, error) {uc := appConfig.UseCase.Registrationudi, err := buildUserData(c, &uc.UserDataConfig)if err != nil {return nil, errors.Wrap(err, "")}tdi, err := buildTxData(c, &uc.TxDataConfig)if err != nil {return nil, errors.Wrap(err, "")}ruc := registration.RegistrationUseCase{UserDataInterface: udi, TxDataInterface: tdi}return &ruc, nil}func buildUserData(c container.Container, dc *config.DataConfig) (dataservice.UserDataInterface, error) {dsi, err := dataservicefactory.GetDataServiceFb(dc.Code).Build(c, dc)if err != nil {return nil, errors.Wrap(err, "")}udi := dsi.(dataservice.UserDataInterface)return udi, nil}

数据存储工厂(Data store factory):

“注册(Registration)”用例需要通过数据存储工厂创建的数据库链接来访问数据库。所有代码都在“datastorefactory”子包中。我详细解释了数据存储工厂如何工作,请看这篇文章依赖注入(Dependency Injection)。

数据存储工厂的当前实现支持两个数据库和一个微服务,MySql和CouchDB,以及gRPC缓存服务; 每个实现都有自己的工厂文件。如果引入了新数据库,你只需添加一个新的工厂文件,并在以下代码中的“dsFbMap”中添加一个条目。

// To map "database code" to "database interface builder"// Concreate builder is in corresponding factory file. For example, "sqlFactory" is in "sqlFactory".govar dsFbMap = map[string]dsFbInterface{config.SQLDB: &sqlFactory{},config.COUCHDB: &couchdbFactory{},config.CACHE_GRPC: &cacheGrpcFactory{},}// DataStoreInterface serve as a marker to indicate the return type for Build methodtype DataStoreInterface interface{}// The builder interface for factory method pattern// Every factory needs to implement Build methodtype dsFbInterface interface {Build(container.Container, *config.DataStoreConfig) (DataStoreInterface, error)}//GetDataStoreFb is accessors for factoryBuilderMapfunc GetDataStoreFb(key string) dsFbInterface {return dsFbMap[key]}

以下是MySql数据库工厂的代码,它实现了上面的代码中定义的“dsFbInterface”。它创建了MySql数据库链接。

容器内部有一个注册表(registry),用作数据存储工厂创建的链接(如DB或gRPC连接)的缓存,它们在整个应用程序创建一次。无论何时需要它们,需首先从注册表中检索它,如果没有找到,则创建一个新的并将其放入注册表中。以下是“Build”代码。

// sqlFactory is receiver for Build methodtype sqlFactory struct{}// implement Build method for SQL databasefunc (sf *sqlFactory) Build(c container.Container, dsc *config.DataStoreConfig) (DataStoreInterface, error) {key := dsc.Code//if it is already in container, returnif value, found := c.Get(key); found {sdb := value.(*sql.DB)sdt := databasehandler.SqlDBTx{DB: sdb}logger.Log.Debug("found db in container for key:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值