哪里建设品牌网站,全新wordpress主题,锦州公司做网站,正版seo搜索引擎使用传统的分层架构#xff0c;我们的所有依赖项都指向一个方向#xff0c;上面的每一层都依赖于下面的层。传输层将依赖于交互器#xff0c;交互器将依赖于持久层。 在六边形架构中#xff0c;所有依赖项都指向内部——我们的核心业务逻辑对传输层或数据源一无所知。尽管如… 使用传统的分层架构我们的所有依赖项都指向一个方向上面的每一层都依赖于下面的层。传输层将依赖于交互器交互器将依赖于持久层。 在六边形架构中所有依赖项都指向内部——我们的核心业务逻辑对传输层或数据源一无所知。尽管如此传输层知道如何使用交互器数据源知道如何符合存储库接口。 概述
最近在想着写一个个人项目但是在项目的结构上却犯了难此时翻到了一个视频采用Hexagonal architecture六边形架构也被称为Ports and Adapters大致就是下面图片的结构 一共分为三层
Domain 里面放的是处理的基本逻辑可以理解为大纲它决定着Application和Framework的选择和实现
Application:它协调使用我们的Domain代码, 通过位于两者之间的方式调整从framework到domain的请求
Framework 为外部组件提供交互方式驱动通常放在左边被驱动放在右边
我们需要注意的是
高层模块不应该依赖于低层模块。两者都应该依赖于抽象。抽象不应该依赖于细节。细节应该依赖于抽象。在驱动侧适配器依赖于端口由应用程序服务实现因此适配器不知道谁会响应其调用它只知道有哪些方法是保证可用的因此它依赖于抽象。在被驱动侧应用程序服务依赖于端口而适配器则实现端口的接口有效地反转了依赖关系因为“低级”适配器即数据库存储库被迫实现应用程序核心中定义的抽象这是“高级”的。
所以我们的项目目录会像这样 例子
这里我们也能看出六边形架构的另外一个称呼Ports and Adapters的原因适配器实现端口通常为接口以达到代码解耦的作用下面将以上面的目录进行具体的例子讲解
完整代码link
本项目很简单就是实现一个简单加减乘除的运算和数据库保存那么我们秉持着核心domain层统领一切适配器实现端口的原则我们先定义 ./ports/core.go:
package portstype ArithmeticPort interface {Addition(a int32, b int32) (int32, error)Subtraction(a int32, b int32) (int32, error)Multiplication(a int32, b int32) (int32, error)Division(a int32, b int32) (int32, error)
}有了接口我们就得配以适配器 ./adapters/core/arithmetic/arithmetic.go
type Adapter struct {
}func NewAdapter() *Adapter {return Adapter{}
}func (Arith Adapter) Addition(a int32, b int32) (int32, error) {return a b, nil
}func (Arith Adapter) Subtraction(a int32, b int32) (int32, error) {return a - b, nil
}func (Arith Adapter) Multiplication(a int32, b int32) (int32, error) {return a * b, nil
}func (Arith Adapter) Division(a int32, b int32) (int32, error) {return a / b, nil
}这便是我们的核心逻辑当项目慢慢变大时核心层逻辑也会越来越多。
接下来就到了应用层当我们实现了运算那么便需要拿到结果注意此时还用不到sql所以我们把目的写进 ./ports/app.go:
type APIPort interface {GetAddition(a int32, b int32) (int32, error)GetSubtraction(a int32, b int32) (int32, error)GetMultiplication(a int32, b int32) (int32, error)GetDivision(a int32, b int32) (int32, error)
}之后适配器实现
type Adapter struct {// depedencies injectionarith ports.ArithmeticPortdb ports.DBPort
}func NewAdapter(db ports.DBPort, arith ports.ArithmeticPort) *Adapter {return Adapter{db: db,arith: arith,}
}func (api Adapter) GetAddition(a int32, b int32) (int32, error) {answer, err : api.arith.Addition(a, b)err api.db.AddToHistory(answer, addition)if err ! nil {return 0, err}return answer, nil
}func (api Adapter) GetSubtraction(a int32, b int32) (int32, error) {answer, err : api.arith.Subtraction(a, b)err api.db.AddToHistory(answer, subtraction)if err ! nil {return 0, err}return answer, nil
}func (api Adapter) GetMultiplication(a int32, b int32) (int32, error) {return api.arith.Multiplication(a, b)
}func (api Adapter) GetDivision(a int32, b int32) (int32, error) {return api.arith.Division(a, b)
}然后就到了用依赖的时候了也就是framework本文就讲讲mysql的CRUD:
// internal/ports/framework_right.go
package portstype DBPort interface {CloseDBConnection()AddToHistory(answer int32, operation string) error
}然后实现
//internal/adapters/framework/right/db/db.go
package dbtype Adapter struct {db *sql.DB
}func NewAdapter(driverName, dataSourceName string) (*Adapter, error) {// connect to dbdb, err : sql.Open(driverName, dataSourceName)...
}func (da Adapter) AddToHistory(answer int32, operation string) error {stmt, err : da.db.Prepare(INSERT INTO history (data, answer, opration) VALUES (?,?,?))...
}之后我们编写测试文件进行测试通常情况一个适配器配一个测试文件
基本都创建好之后如何连接呢
我们在cmd文件中创建一个main.go连接所有端口和适配器代码的地方将依赖项注入到需要的层中例如将数据库注入到framework层
这样实现了代码的解耦例如我们想换一个数据库只需要更换数据库名和数据源名其余不需要修改同时我们的业务逻辑也不需要了解特定的数据源限制
总结
所以总结一下优点
能够封装数据源实现细节长期稳定性和可扩展性因为只需少许更改所以在微服务部署失败时很容易回滚也可以直接通过配置文件决定数据源
但是他也并不是silver bullet我们应该多多检测层之间的漏洞预防逻辑泄露等问题
参考文章学习视频
Ready for changes with Hexagonal Architecture
Hexagonal Architecture, there are always two sides to every story
How To Structure Your Go App - Full Course [ Hex Arch Tests ]