写点什么

Golang 中的整洁架构

用户头像
baiyutang
关注
发布于: 3 小时前
Golang 中的整洁架构

译者:baiyutang

原文:https://eminetto.medium.com/clean-architecture-using-golang-b63587aa5e3f

什么是整洁架构


以《架构整洁之道》出名的作者罗伯特 “Bob 大叔” 马丁,提出了一种架构,包含几个非常重要的点,诸如:可测试性、架构独立性、数据库和接口。

整洁架构的约束条件:

  • 架构独立性:框架不依赖于未来加载的软件库。

  • 可测试性:业务规则可以被测试,并且不依赖 UI、数据库、Web 服务器或其他外部要素。

  • UI 独立:UI 可以轻易变化,但不应改变其他系统。一个 Web UI 可以被控制台 UI 代替,但不改变业务规则。

  • 数据库独立:可以将 OracleSQL Server,替换为 MongoBigTableCouchDB 或其他。业务规则不应该被数据库限制。

  • 外部代理商独立:事实上,业务规则根本不了解外部世界。

更多在 >> https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

那么,根据这些限制条件,每层都应该独立和可测试。

按照 Bob 大叔的架构,我们可以把代码分为 4 层:

  • Entities

  • Use Cases

  • Controller

  • Framework & Driver

Golang 中的整洁架构

文件目录

让我们用 user 包,看一个简单的例子

ls -ln pkg/user-rw-r — r — 1 501 20 5078 Feb 16 09:58 entity.go-rw-r — r — 1 501 20 3747 Feb 16 10:03 mongodb.go-rw-r — r — 1 501 20 509  Feb 16 09:59 repository.go-rw-r — r — 1 501 20 2403 Feb 16 10:30 service.go
复制代码

Entities

文件 entity.go 中,是我们的实体:

//User datatype User struct {	ID                 entity.ID    `json:"id" bson:"_id,omitempty"`	Picture            string       `json:"picture" bson:"picture,omitempty"`	Email              string       `json:"email" bson:"email"`	Password           string       `json:"password" bson:"password,omitempty"`	Type               Type         `json:"type" bson:"type"`	Company            []*Company   `json:"company" bson:"company,omitempty"`	CreatedAt          time.Time    `json:"created_at" bson:"created_at"`	ValidatedAt        time.Time    `json:"validated_at" bson:"validated_at,omitempty"`}
复制代码

Repositories

文件 repository.go 中,是我们定义的仓储层的接口,它是实体被保存的地方。在本例中,仓储层是 “Bob 大叔” 架构中的 Framework & Driver,内容如下:

package user
import "github.com/thecodenation/stamp/pkg/entity"
type Reader interface { Find(id entity.ID) (*User, error) FindByEmail(email string) (*User, error) FindByChangePasswordHash(hash string) (*User, error) FindByValidationHash(hash string) (*User, error) FindAll() ([]*User, error)}
type Writer interface { Update(user *User) error Store(user *User) (entity.ID, error) AddCompany(id entity.ID, company *Company) error AddInvite(userID entity.ID, companyID entity.ID) error}
//Repository repository interfacetype Repository interface { Reader Writer }
复制代码

接口可以被任何一种存储层实现,像 MongoDB, MySQL 等等。在本案例中我们用 MongoDB 来实现,见 mongodb.go

package user
import ( "errors" "os" "github.com/juju/mgosession" "github.com/thecodenation/stamp/pkg/entity" mgo "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson")
type repo struct { pool *mgosession.Pool}
//NewMongoRepository create new repositoryfunc NewMongoRepository(p *mgosession.Pool) Repository { return &repo{ pool: p, }}
func (r *repo) Find(id entity.ID) (*User, error) { result := User{} session := r.pool.Session(nil) coll := session.DB(os.Getenv("MONGODB_DATABASE")).C("user") err := coll.Find(bson.M{"_id": id}).One(&result) if err != nil { return nil, err } return &result, nil}
func (r *repo) FindByEmail(email string) (*User, error) {}
func (r *repo) FindByChangePasswordHash(hash string) (*User, error) {}
func (r *repo) FindAll() ([]*User, error) {}
func (r *repo) Update(user *User) error {}
func (r *repo) Store(user *User) (entity.ID, error) {}
func (r *repo) AddCompany(id entity.ID, company *Company) error {}
func (r *repo) AddInvite(userID entity.ID, companyID entity.ID) error {}
func (r *repo) FindByValidationHash(hash string) (*User, error) {}
复制代码

Services

文件 service.go 代表“Bob 大叔”定义的用例层。这个文件中有 Service 的接口和实现,Service 的接口如下:

//Service service interfacetype Service interface {	Reader	Writer	Register(user *User) (entity.ID, error)	ForgotPassword(user *User) error	ChangePassword(user *User, password string) error	Validate(user *User) error	Auth(user *User, password string) error	IsValid(user *User) bool	GetRepo() Repository}
复制代码

Controller

最后一层,在我们架构中的 Controller 是我们 Api 实现的内容:

cd api ; tree.|____handler| |____company.go| |____user.go| |____address.go| |____skill.go| |____invite.go| |____position.go|____rice-box.go|____main.go
复制代码

下面的代码来自 api/main.go,我们能看到如何使用 Service

session, err := mgo.Dial(os.Getenv("MONGODB_HOST"))if err != nil {	elog.Error(err)}mPool := mgosession.NewPool(nil, session, 1)queueService, err := queue.NewAWSService()if err != nil {		elog.Error(err)}userRepo := user.NewMongoRepository(mPool)userService := user.NewService(userRepo, queueService)
复制代码

Tests

现在我们很容易在我们的包内创建测试用户,如:

package user
import ( "testing" "time"
"github.com/thecodenation/stamp/pkg/entity" "github.com/thecodenation/stamp/pkg/queue")
func TestIsValidUser(t *testing.T) { u := User{ ID: entity.NewID(), FirstName: "Bill", LastName: "Gates", } userRepo := NewInmemRepository() queueService, _ := queue.NewInmemService() userService := NewService(userRepo, queueService)
if userService.IsValid(&u) == true { t.Errorf("got %v want %v", true, false) }
u.ValidatedAt = time.Now() if userService.IsValid(&u) == false { t.Errorf("got %v want %v", false, true) }}
复制代码

总结

运用整洁架构,我们可以把数据库从 MongoDB 改为 Neo4j,而不会破坏应用程序的其余部分。我们可以在不降低质量和速度的前提下,发展我们的软件。

发布于: 3 小时前阅读数: 12
用户头像

baiyutang

关注

广州 2017.12.13 加入

Microservices | Golang | Cloud Nitive | “Smart work,Not hard”

评论

发布
暂无评论
美化你的 Golang 项目