此篇是我对于领域驱动和整洁架构的思考和实践,有很多不足之处,请大家多评论指教。
调用关系:infra层 <-> adapter层 -> useCase层 -> domain层
- infra层的web调用adapter层的controller
- controller实现useCase层调用infra层的db,log,cache...
总结:有点违反单向依赖,之后可以在adapter层增加网关或门面来对接infra层,使之被同层的controller调用,实现进一步的单向依赖。这里可能需要初始化的时候进行依赖注入。如果有更好的想法,可以留言探讨。
github库
请求处理流程
请求
GET /user/1
infra 基础设施层(包含 web,UI,Device,DB,...)
// web:接受请求,并处理数据
// gin example
r.GET('/user/:id', GetUserByID)
func GetUserByID(c *gin.Context) {
id := c.Param("id") // 提取id
controller := adapter.UserController{C: c}
controller.GetUserByID(id)
}
// db:实现 domain repo 接口
// mysql example
type MysqlUserRepo struct {}
func (repo MysqlUserRepo) GetByID(id string) (*domain.User, error) {
var user domain.User
if result := global.DB.Where("id = ?", id).First(&user); result.Error != nil {
return nil, result.Error
}
return &user, nil
}
adapter 调度层
// controller(控制器, 调用 UseCase)
type UserController struct {
C *gin.Context
}
func (controller *UserController) GetUserByID(id string) {
presenter := userPresenter{c: controller.C}
uc := usecase.UserUseCase{Repo: db.MysqlUserRepo{}, Output: &presenter}
uc.GetUserByID(id)
}
// presenter(演示者, 实现 Use Case Output Port)
type userPresenter struct {
c *gin.Context}
func (presenter *userPresenter) Standard(user *domain.User, err error) {
// todo: 处理 err
presenter.c.JSON(http.StatusOK, gin.H{"id": user.ID})
}
useCase 用例层
// 用户用例(实现 Use Case input Port,调用 Use Case Output Port)
// 之所以用useCase命名而不用interactor,是因为Gland提示拼写错误,看着烦
type UserUseCase struct {
Repo domain.UserRepo Output UserOutput}
// Use Case Output Port
type UserOutput interface {
Standard(u *domainUser, err error)
}
func (u *UserUseCase) GetUserByID(id string) {
user, err := u.Repo.GetByID(id) u.Output.Standard(user, err)
}
domain 领域层
// 实体或者聚合根
type User struct {
ID string
}
// 存储库
type UserRepo interface {
GetByID(id string) (*User, error)
}