Go语言的简洁架构.docx

上传人:b****1 文档编号:11099492 上传时间:2023-05-29 格式:DOCX 页数:21 大小:22.60KB
下载 相关 举报
Go语言的简洁架构.docx_第1页
第1页 / 共21页
Go语言的简洁架构.docx_第2页
第2页 / 共21页
Go语言的简洁架构.docx_第3页
第3页 / 共21页
Go语言的简洁架构.docx_第4页
第4页 / 共21页
Go语言的简洁架构.docx_第5页
第5页 / 共21页
Go语言的简洁架构.docx_第6页
第6页 / 共21页
Go语言的简洁架构.docx_第7页
第7页 / 共21页
Go语言的简洁架构.docx_第8页
第8页 / 共21页
Go语言的简洁架构.docx_第9页
第9页 / 共21页
Go语言的简洁架构.docx_第10页
第10页 / 共21页
Go语言的简洁架构.docx_第11页
第11页 / 共21页
Go语言的简洁架构.docx_第12页
第12页 / 共21页
Go语言的简洁架构.docx_第13页
第13页 / 共21页
Go语言的简洁架构.docx_第14页
第14页 / 共21页
Go语言的简洁架构.docx_第15页
第15页 / 共21页
Go语言的简洁架构.docx_第16页
第16页 / 共21页
Go语言的简洁架构.docx_第17页
第17页 / 共21页
Go语言的简洁架构.docx_第18页
第18页 / 共21页
Go语言的简洁架构.docx_第19页
第19页 / 共21页
Go语言的简洁架构.docx_第20页
第20页 / 共21页
亲,该文档总共21页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

Go语言的简洁架构.docx

《Go语言的简洁架构.docx》由会员分享,可在线阅读,更多相关《Go语言的简洁架构.docx(21页珍藏版)》请在冰点文库上搜索。

Go语言的简洁架构.docx

Go语言的简洁架构

Go语言的简洁架构

我想告诉你的是什么

目前简洁架构已是众所周知。

然而,我们可能无法很好的知道具体实现的细节。

所以我尝试使用gRPC编写一个具有简洁架构意识的例子。

hatajoe/8am

在Github上创建了一个账户用于开发hatajoe/8am。

详见:

1

这个小型项目表示用户注册的例子,请随意回复任何内容。

项目结构

8am是基于简介架构的,这个项目的结构如下:

%tree

.

├──Makefile

├──README.md

├──app

│├──domain

││├──model

││├──repository

││└──service

│├──interface

││├──persistence

││└──rpc

│├──registry

│└──usecase

├──cmd

│└──8am

│└──main.go

└──vendor

├──vendorpackages

|...

顶层目录包含3个分支:

app:

应用包的根目录

cmd:

main包目录

vender:

几个依赖包目录

简洁架构有几个概念层如下:

简洁架构有4层,蓝色层、绿色层、红色层和黄色层,顺序如上所示,除了蓝色代表了app目录外,这些层分别代表了:

接口:

绿色层

用例:

红色层

域:

黄色层

关于简洁架构最重要的事就是编写访问层之间的接口。

实体——黄色层

IMO,实体层在结构层次中更像是domain层。

所以我命名这个层为app/domain是为了防止与DDD实体混淆。

app/domain有三个包:

模型:

包含聚合、实体和值对象

仓库:

聚合的仓库接口

服务:

包含依赖于各种模型的应用服务

我解释下对于每个包的执行细节:

模型

模型的用户聚合如下所示:

这里不是实际的聚合,前提是各种实体和值对象将在未来添加。

packagemodel

typeUserstruct{

idstring

emailstring

}

funcNewUser(id,emailstring)*User{

return&User{

id:

id,

email:

email,

}

}

func(u*User)GetID()string{

returnu.id

}

func(u*User)GetEmail()string{

returnu.email

}

聚合是事务为了保持他们业务规则的一致性的边界。

因此每个聚合会存在一个对应的仓库。

仓库

在仓库层,仓库只是一个接口,是因为仓库无需知道持久化实现的细节。

但持久化也是仓库层的重要本质。

用户聚合仓库的实现是:

packagerepository

import"

typeUserRepositoryinterface{

FindAll()([]*model.User,error)

FindByEmail(emailstring)(*model.User,error)

Save(*model.User)error

}

FindAll获取系统保留的所有用户,持久化保存到系统中。

我再说一遍,这一层不应该知道对象在何处保存或序列化。

服务

服务层用于收集业务逻辑,这些业务逻辑不包含在模型中。

例如,应用不允许注册存在的邮件地址。

如果模型具有此验证,我们会感觉到如下的一些错误:

func(u*User)Duplicated(emailstring)bool{

//Finduserbyemailfrompersistence

layer...

}

Duplicated函数与User模型不相关。

为了解决这个,我们可以像下面这样添加服务层:

typeUserServicestruct{

reporepository.UserRepository

}

func(s*UserService)Duplicated(emailstring)error{

user,err:

=s.repo.FindByEmail(email)

ifuser!

=nil{

returnfmt.Errorf("%salreadyexists",

email)

}

iferr!

=nil{

returnerr

}

returnnil

}

实体通过其它层包含业务逻辑和接口。

业务逻辑应该被包含在模型和服务层中,而不应该依赖其他层。

如果我们需要访问任何其他层,我们应该使用仓库接口。

通过这样的反向依赖,可以使包独立,获得更好的测试和维护。

用例-红色层

用例是应用的一次操作单元。

在8am中,用户列表和用户注册均被定义为用例。

这些用例被如下的接口所代表:

typeUserUsecaseinterface{

ListUser()([]*User,error)

RegisterUser(emailstring)error

}

为什么是接口?

这是因为用例被使用于接口层——绿色层。

如果要在层之间进行访问,我们应该始终定义接口来实现。

UserUsecase的实现很简单,如下:

typeuserUsecasestruct{

reporepository.UserRepository

service*service.UserService

}

funcNewUserUsecase(reporepository.UserRepository,

service*service.UserService)*userUsecase{

return&userUsecase{

repo:

repo,

service:

service,

}

}

func(u*userUsecase)ListUser()([]*User,error){

users,err:

=u.repo.FindAll()

iferr!

=nil{

returnnil,err

}

returntoUser(users),nil

}

func(u*userUsecase)RegisterUser(emailstring)error

{

uid,err:

=uuid.NewRandom()

iferr!

=nil{

returnerr

}

iferr:

=u.service.Duplicated(email);err!

=nil

{

returnerr

}

user:

=model.NewUser(uid.String(),email)

iferr:

=u.repo.Save(user);err!

=nil{

returnerr

}

returnnil

}

userUsercase依赖于两个包,接口repository.UserRepository和结构体*service.UserService。

当使用用例的用户,初始化用例时必须引用这两个包。

这些独立性通常通过DI容器解决,这将写在后续的条目中。

ListUser用例获取所有注册的用户,如果用户没有被相同的email地址注册时,用RegisterUser将此用户注册到系统中。

一个要点,User不是model.User。

model.User可能有很多种业务知识,但是其他层无法知道这些。

所以我为用例的用户定义DAO来概括这些知识。

typeUserstruct{

IDstring

Emailstring

}

functoUser(users[]*model.User)[]*User{

res:

=make([]*User,len(users))

fori,user:

=rangeusers{

res[i]=&User{

ID:

user.GetID(),

Email:

user.GetEmail(),

}

}

returnres

}

所以,为什么你认为服务用作具体的实现而不是使用接口?

这是因为服务不依赖于其他层。

相反的,仓库在各层间访问,依赖于服务的细节不被其他层所知道而实现的,所以仓库被定义为接口。

我认为这在架构中是最重要的事情。

接口——绿色层

这一层体现的是具体的对象,如API端点处理程序、RDB的仓库或其他边界的接口。

在这种情况下,我添加了2个具体的对象,内存存储访问器和gRPC服务。

内存存储访问器

我添加了具体的用户仓库作为内存存储访问器。

typeuserRepositorystruct{

mu*sync.Mutex

usersmap[string]*User

}

funcNewUserRepository()*userRepository{

return&userRepository{

mu:

&sync.Mutex{},

users:

map[string]*User{},

}

}

func(r*userRepository)FindAll()([]*model.User,

error){

r.mu.Lock()

deferr.mu.Unlock()

users:

=make([]*model.User,len(r.users))

i:

=0

for_,user:

=ranger.users{

users[i]=model.NewUser(user.ID,user.Email)

i++

}

returnusers,nil

}

func(r*userRepository)FindByEmail(emailstring)

(*model.User,error){

r.mu.Lock()

deferr.mu.Unlock()

for_,user:

=ranger.users{

ifuser.Email==email{

returnmodel.NewUser(user.ID,user.Email),

nil

}

}

returnnil,nil

}

func(r*userRepository)Save(user*model.User)error

{

r.mu.Lock()

deferr.mu.Unlock()

r.users[user.GetID()]=&User{

ID:

user.GetID(),

Email:

user.GetEmail(),

}

returnnil

}

这是仓库的具体实现。

如果需要在RDB或其他中保存用户,则需要其他的实现方式。

但即使在这种情况下,我们不需要改变模型层。

模型层依赖于独立的仓库接口,而不关心实现细节。

这真惊人。

User被定义为仅在此包适用。

这也是为了解决拆分层之间的关系。

typeUserstruct{

IDstring

Emailstring

}

gRPC服务

我认为gRPC服务也包含在接口层内。

gRPC被定义在如下的app/interface/rpc目录中:

user_service.go是包装gRPC的端点处理程序:

typeuserServicestruct{

userUsecaseusecase.UserUsecase

}

funcNewUserService(userUsecaseusecase.UserUsecase)

*userService{

return&userService{

userUsecase:

userUsecase,

}

}

func(s*userService)ListUser(ctxcontext.Context,in

*protocol.ListUserRequestType)

(*protocol.ListUserResponseType,error){

users,err:

=s.userUsecase.ListUser()

iferr!

=nil{

returnnil,err

}

res:

=&protocol.ListUserResponseType{

Users:

toUser(users),

}

returnres,nil

}

func(s*userService)RegisterUser(ctx

context.Context,in*protocol.RegisterUserRequestType)

(*protocol.RegisterUserResponseType,error){

iferr:

=

s.userUsecase.RegisterUser(in.GetEmail());err!

=nil

{

return&protocol.RegisterUserResponseType{},

err

}

return&protocol.RegisterUserResponseType{},nil

}

functoUser(users[]*usecase.User)[]*protocol.User{

res:

=make([]*protocol.User,len(users))

fori,user:

=rangeusers{

res[i]=&protocol.User{

Id:

user.ID,

Email:

user.Email,

}

}

returnres

}

userService仅依赖于用例接口。

如果你想从其他层中(如CUI)使用用例,你可以实现你想要的接口。

v1.go用于解决使用DI容器的对象依赖关系:

funcApply(server*grpc.Server,ctn

*registry.Container){

protocol.RegisterUserServiceServer(server,

NewUserService(ctn.Resolve("user-usecase").

(usecase.UserUsecase)))

}

v1.go应用包被从*registry.Container到gRPC服务中检索。

最后,让我们看一下DI容器的实现。

注册表

注册表就是DI容器,用于解决对象间的依赖。

我曾使用

sarulabs/di

go语言(golang)中的依赖注入容器,在Github上创建一个账户用于开发sarulabs/di,详见:

1

typeContainerstruct{

ctndi.Container

}

funcNewContainer()(*Container,error){

builder,err:

=di.NewBuilder()

iferr!

=nil{

returnnil,err

}

iferr:

=builder.Add([]di.Def{

{

Name:

"user-usecase",

Build:

buildUserUsecase,

},

}...);err!

=nil{

returnnil,err

}

return&Container{

ctn:

builder.Build(),

},nil

}

func(c*Container)Resolve(namestring)interface{}{

returnc.ctn.Get(name)

}

func(c*Container)Clean()error{

returnc.ctn.Clean()

}

funcbuildUserUsecase(ctndi.Container)(interface{},

error){

repo:

=memory.NewUserRepository()

service:

=service.NewUserService(repo)

returnusecase.NewUserUsecase(repo,service),nil

}

上面的例子,我通过使用buildUserUsecase函数将字符串user-usecase与具体的用例实现相结合。

所以我们可以在一个注册表中替代任何用例的具体实现。

谢谢您阅读本文,如果您有任何建议和改善,欢迎反馈。

容器时代志愿者招募

如果你对技术懵懵懂懂,想要入门却不知从何下手;

如果你求知若渴,想要学习更多技术、思想;

如果你对于技术有着一种狂热的喜爱并且热爱开源,以其为信仰。

志愿者计划JOINUS

容器时代志愿编辑

志愿内容

公众号运营——比如晨读文章推荐、周推荐等;(特别欢迎在校大学生)

翻译——容器生态圈相关教程、文章、资讯等的翻译;

∙发表于:

 2018-11-06

∙原文链接:

∙腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

∙如有侵权,请联系yunjia_community@删除。

 

使用Golang构建整洁架构

什么是整洁架构?

在《CleanArchitecture:

ACraftsman’sGuidetoSoftwareStructureandDesign》一书中,著名作家Robert“UncleBob”Martin提出了一种具有一些重要特性的体系结构,如框架、数据库和接口的可测试性和独立性。

整洁架构的约束条件是:

∙独立的框架。

该体系结构并不依赖于某些带有特性的软件库的存在。

这允许您使用这些框架作为工具,而不是将您的系统束缚在有限的约束中。

∙可测试的。

业务规则可以在没有UI、数据库、Web服务器或任何其他外部元素的情况下进行测试。

∙独立的UI。

UI可以很容易地更改,而不会改变系统的其他部分。

例如,可以用控制台UI替换WebUI,而不需要更改业务规则。

∙独立的数据库。

您可以将Oracle或SQLServer替换为Mongo、BigTable、CouchDB或其他数据库。

您的业务规则不绑定到数据库。

∙独立的任意外部代理。

事实上,你的业务规则根本就不用了解外部的构成。

了解更多请查看:

 

因此,基于这些约束,每一层都必须是独立的和可测试的。

从UncleBob的架构中,我们可以将代码分成4层:

∙实体:

封装企业范围的业务规则。

Go中的实体是一组数据结构和函数。

∙用例:

这个层中的软件包含应用程序特定的业务规则。

它封装并实现了系统的所有用例。

∙控制器:

该层中的软件是一组适配器,它将数据从最方便的用例和实体转换为最方便的外部代理,例如数据库或Web。

∙框架和驱动程序:

这个层通常由框架和工具(如数据库、Web框架等)组成。

使用Golang构建整洁架构

让我们以user包为例:

ls-lnpkg/user

-rw-r — r — 1501205078Feb1609:

58entity.go

-rw-r — r — 1501203747Feb1610:

03mongodb.go

-rw-r — r — 150120509Feb1609:

59repository.go

-rw-r — r — 1501202403Feb1610:

30service.go

在entity.go文件中,我们有自己的实体:

//UserdatatypeUserstruct{

IDentity.ID`json:

"id"bson:

"_id,omitempty"`

Picturestring`json:

"picture"bson:

"picture,omitempty"`

Emailstring`json:

"email"bson:

"email"`

Passwordstring`json:

"password"bson:

"password,omitempty"`

TypeType`json:

"type"bson:

"type"`

Company[]*Company`json:

"company"bson:

"company,omitempty"`

CreatedAttime.Time`json:

"created_at"bson:

"created_at"`

ValidatedAttime.Time`json:

"validated_at"bson:

"validated_at,omitempty"`}

在repository.go文件中我们定义存储库的接口,用于保存存储实体。

在这种情况下,存储库意味着UncleBob架构中的框架和驱动层。

它的内容是:

packageuser

import"

//Repositoryrepositoryinterface

typeRepositoryinterface{

Find(identity.ID)(*User,error)

FindByEmail(emailstring)(*User,error)

FindByChangePasswordHash(hashstring)(*User,error)

FindByValidationHash(hashstring)(*User,error)

FindAll()([]*User,error)

Update(user*User)error

Store(user*User)(entity.ID,error)

AddCompany(identity.ID,company*Company)error

AddInvite(userIDentity.ID,companyIDentity.ID)error}

该接口可以在任何类型的存储层中实现,如MongoDB、MySQL等。

在我们的例子中,我们使用MongoDB来实现,就像在mongodb.go中看到的那样:

packageuser

import(

"errors"

"os"

"

"g

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2