石家庄品牌网站建设,怎么在手机上建网站,前端网站做中 英文怎么说,门户网站收录写在前面
基于 DDD(Domain Driven Design) 领域驱动设计 架构实现todolist#xff0c;在此之前也查阅了很多资料#xff0c;似乎每个人都有自己所理解的DDD。
这里我也只说我自己理解的 DDD#xff0c;如何和你所理解的有出入#xff0c;那一定是你对#xff0c;我理解是错…写在前面
基于 DDD(Domain Driven Design) 领域驱动设计 架构实现todolist在此之前也查阅了很多资料似乎每个人都有自己所理解的DDD。
这里我也只说我自己理解的 DDD如何和你所理解的有出入那一定是你对我理解是错的。功能
代码都在github上https://github.com/CocaineCong/todolist-ddd
非常简单的功能主要是介绍DDD的整体架构可以把 docs 下的 json 文件导入postman中进行请求即可。
用户模块注册、登陆备忘录模块创建、更新、列表、删除、详情其他模块日志打印、jwt鉴权、cors跨域、docker启动环境
架构
MVC
在讲DDD之间我们要先简单回顾一下传统的MVC架构。相比于DDDMVC的架构被更多人所熟知。MVC分别代表着
View视图层处理请求和响应Controller控制所有的业务逻辑Model具体的数据交互也就是dao层
我平时写Go语言多一点我项目的架构图一般如下并不是传统意义上的MVC但也是MVC的思想Controller层只做三件事
处理上游请求的参数做参数校验之类的过滤掉一些无用的请求转发请求参数到service获取service的响应转发给上游
Service复杂的业务逻辑操作Model就是真正的与数据打交道的一层。持久层
DDD
todolist的整体目录结构图如下
./todolist-ddd
├── application // 应用层: 做domain编排
│ ├── task // task 应用层模块
│ └── user // user 应用层模块
├── cmd // 启动入口
├── conf // 配置文件
├── consts // 常量定义
├── docs // 接口文档
├── domain // 领域层:
│ ├── task // task 领域层模块
│ │ ├── entity // task 实体定义及充血对象
│ │ ├── repository // task 实体的数据持久化接口
│ │ └── service // task 具体业务逻辑
│ └── user // user 领域层模块
│ ├── entity // user 实体定义及充血对象
│ ├── repository // user 实体的数据持久化接口
│ └── service // user 具体业务逻辑
├── infrastructure // 基础架构层: 提供数据来源和基础服务能力
│ ├── auth // 鉴权认证服务
│ ├── common // 公共服务
│ │ ├── context // context 上下游管理
│ │ └── log // log 服务
│ ├── encrypt // 加密 服务
│ └── persistence // 持久层
│ ├── dbs // db数据连接
│ ├── task // task 的dao层 访问task数据库
│ └── user // user 的dao层 访问user数据库
├── interfaces // 接口层: 对接不同的端进行适配转化
│ ├── adapter // 适配器
│ │ └── initialize // Web 路由初始化
│ ├── controller // controller 层
│ ├── midddleware // 中间件
│ └── types // 类型
└── logs // 日志文件存储整个DDD的大体分成了四层
interface 接口层: 对接不同的端进行适配转化成对应的函数输入到项目中。application 应用层: 做domain层的业务编排。domain 领域层: 纯业务逻辑定义各种daoentity充血模型。infrastructure 基础架构层: 提供数据来源和基础服务能力相当于真正进行操作的dao层。
domain 领域层
这一层是做具体的业务逻辑。我们先重点说一下这一层怎么划分领域我们不过多赘述我们只说领域层里面应该是怎么样的。
首先领域层与领域层之间是不能互相调用的domain自己是独立的一层。其次domain和infra两者的关系看上去是domain依赖infra因为domain需要infra的数据来源。但其实domain是不依赖infra的而是infra依赖domain这就是依赖倒置。比如会在user domain层中定义业务所需要的interface包括创建user查询用户等等对应项目路径domain/user/repository/user.go
type UserBase interface {CreateUser(ctx context.Context, user *entity.User) (*entity.User, error)GetUserByName(ctx context.Context, username string) (*entity.User, error)GetUserByID(ctx context.Context, id uint) (*entity.User, error)
}在 user domain 中就可以直接使用这些定义好的业务interface比如对于项目的路径domain/user/service/user.go
func (u *UserDomainImpl) GetUserDetail(ctx context.Context, id uint) (*entity.User, error) {return u.repo.GetUserByID(ctx, id)
}这时候你可能会有疑惑这不就是虚空interface吗真正实现落库的在哪不就是infra吗
是的真正实现的查询的其实是在infra里面对应项目路径infrastructure/persistence/user/repo.go
func (r *RepositoryImpl) GetUserByID(ctx context.Context, id uint) (*entity.User, error) {var u *Usererr : r.db.WithContext(ctx).Model(User{}).Where(id ?, id).Find(u).Errorif err ! nil {return nil, err}if u.ID 0 {return nil, errors.New(user not found)}return PO2Entity(u), nil
}所以这就引入了一个概念叫注入依赖。我们的domain是这样进行创建的domain/user/service/user.go
type UserDomainImpl struct {repo repository.Userencrypt repository.PwdEncrypt
}
func NewUserDomainImpl(repo repository.User, encrypt repository.PwdEncrypt) UserDomain {return UserDomainImpl{repo: repo, encrypt: encrypt}
}而依赖是在一开始创建的时候便已经注入进去了项目的infrastructure/container/init.go
func LoadingDomain() {repos : persistence.NewRepositories(dbs.DB)jwtService : auth.NewJWTTokenService()pwdEncryptService : encrypt.NewPwdEncryptService()// user domainuserDomain : userSrv.NewUserDomainImpl(repos.User, pwdEncryptService)userApp.GetServiceImpl(userDomain, jwtService)...
}所以我们可以看到Domain层只专注于业务逻辑并且是依赖抽象接口而不是具体实现
interface 接口层
这一层的作用主要和上游做交互
接受上游传送的参数并进行校验。请求数据参数转发到application应用层。数据参数转发给上游。
func UserLoginHandler() gin.HandlerFunc {return func(ctx *gin.Context) {var req types.UserReqerr : ctx.ShouldBind(req)if err ! nil {log.LogrusObj.Infoln(err)ctx.JSON(http.StatusOK, types.RespError(err, bind req))return}entity : types.UserReq2Entity(req)resp, err : user.ServiceImplIns.Login(ctx, entity)if err ! nil {ctx.JSON(http.StatusOK, types.RespError(err, login failed))return}ctx.JSON(http.StatusOK, types.RespSuccessWithData(resp))}
}application 应用层
这一层的作用是对各种domain层的函数方法进行编排。
func (s *ServiceImpl) Login(ctx context.Context, entity *entity.User) (any, error) {user, err : s.ud.FindUserByName(ctx, entity.Username)if err ! nil {return nil, err}// 检查密码err s.ud.CheckUserPwd(ctx, user, entity.Password)if err ! nil {return nil, errors.New(invalid password)}// 生成tokentoken, err : s.tokenService.GenerateToken(ctx, user.ID, user.Username)if err ! nil {return nil, err}return LoginResponse(user, token), nil
}infrastructure 基础架构层
这一层是各种外部数据来源服务或者功能服务比如db、redis、rpc或者各种jwt加解密服务等等。⚠️ 有几个很重要的概念
充血模型entity 需要增加对应的业务逻辑方法而不只是单纯的数据载体。依赖倒置可能从MVC的角度我们看起来是 domain 依赖 infrastructure因为domain需要infra的数据源来做业务操作。但其实domain只是定义了interface真正实现这些interface是从infra中实现而我们一开始启动的时候就会将所有依赖都注入到domain中所以是一个依赖倒置的关系。注入依赖我个人不太倾向这种注入依赖的方式。虽然说技术实现和语言无关但对于go而言我个人比较倾向函数式编程而不是面向对象编程。