当前位置: 首页 > news >正文

Go语言基于 DDD(Domain Driven Design)领域驱动设计架构实现备忘录 todolist

写在前面

基于 DDD(Domain Driven Design) 领域驱动设计 架构实现todolist,在此之前也查阅了很多资料,似乎每个人都有自己所理解的DDD。

这里我也只说我自己理解的 DDD,如何和你所理解的有出入,那一定是你对,我理解是错的。

在这里插入图片描述

功能

代码都在github上:https://github.com/CocaineCong/todolist-ddd

非常简单的功能,主要是介绍DDD的整体架构,可以把 docs 下的 json 文件导入postman中,进行请求即可。

  • 用户模块:注册、登陆
  • 备忘录模块:创建、更新、列表、删除、详情
  • 其他模块:日志打印、jwt鉴权、cors跨域、docker启动环境

架构

MVC

在讲DDD之间,我们要先简单回顾一下传统的MVC架构。相比于DDD,MVC的架构被更多人所熟知。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 领域层: 纯业务逻辑,定义各种dao,entity充血模型。
  • 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 接口层

这一层的作用主要和上游做交互:

  1. 接受上游传送的参数,并进行校验。
  2. 请求数据参数转发到application应用层。
  3. 数据参数转发给上游。
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,加解密服务等等。

在这里插入图片描述

⚠️ 有几个很重要的概念:

  1. 充血模型:entity 需要增加对应的业务逻辑方法,而不只是单纯的数据载体。
  2. 依赖倒置:可能从MVC的角度,我们看起来是 domain 依赖 infrastructure,因为domain需要infra的数据源来做业务操作。但其实domain只是定义了interface,真正实现这些interface是从infra中实现,而我们一开始启动的时候就会将所有依赖都注入到domain中,所以是一个依赖倒置的关系。
  3. 注入依赖:我个人不太倾向这种注入依赖的方式。虽然说技术实现和语言无关,但对于go而言,我个人比较倾向函数式编程,而不是面向对象编程。
http://www.dtcms.com/a/392521.html

相关文章:

  • Go基础:Go变量、常量及运算符详解
  • c++如何开发游戏
  • 3D体素(Voxel)算法原理内容综述
  • 家庭劳务机器人进化史:从单一功能到全能管家的四阶跃迁
  • 【工具推荐及使用】——基于pyecharts的Pythpn可视化
  • Transformer实战(19)——微调Transformer语言模型进行词元分类
  • ModelView【QT】
  • ES6 promise-try-catch-模块化开发
  • webrtc弱网-ProbeController类源码分析与算法原理
  • Pycharm远程同步Jetson Orin Super
  • 深入解析Tomcat类加载器:为何及如何打破Java双亲委派模型
  • 基于BP神经网络的PID控制器matlab参数整定和性能仿真
  • RabbitMQ死信队列与幂等性处理的性能优化实践指南
  • 基于python全国热门景点旅游管理系统的设计与实现
  • 鸿蒙Next ArkTS卡片生命周期:深入理解与管理实践
  • 荣耀手机(安卓)快速传数据换机到iPhone17 Pro
  • Linux的线程池
  • [bitcoin白皮书_1] 时间戳服务器 | 简化支付验证
  • OAuth 认证在电商 API 中的实现与安全
  • Linux 是什么?初学者速查表
  • openharmony之AV_CodeC音视频编解码模块驱动实现原理详解(三)
  • Llamaindex-Llama_indexRAG进阶_Embedding_model与ChromaDB-文档切分与重排序
  • 如何使用WordToCard自动拆分文章制作小红书卡片
  • RTX 4090重塑数字内容创作:4K视频剪辑与3D渲染的效率革命
  • Spring AI开发指导-MCP
  • C++/操作系统
  • 动手学深度学习(pytorch版):第八章节—循环神经网络(4)循环神经网络
  • Jenkins与Arbess,CICD工具一文全面对比分析
  • 矩阵、线性代数
  • react常用的hooks