通过GO后端项目实践理解DDD架构

最近在工作过程中重构的项目要求使用DDD架构,在网上查询资料发现教程五花八门,并且大部分内容都是长篇的概念讲解,晦涩难懂,笔者看了一些github上入门的使用DDD的GO项目,并结合自己开发中的经验,谈谈自己对DDD的理解。

相关项目

github.com/takashabe/g...

基本的CURD,代码简洁,适合快速上手 DDD

github.com/eyazici90/g...

适合理解第一个项目后,有一定基础

github.com/ThreeDotsLa...

比较复杂,适合进阶

简要介绍

​编辑

这里借用github老哥的一张图来说明,那些长篇大论就不叙述了,因为咱们主要是在代码中应用嘛,直接讲解在项目中要怎么写。

DDD主要分为几层,interface,application,domain,infrastructure

在DDD中的调用顺序是 interface -> application -> domain

可以类比MVC controller -> service -> model

可以发现DDD比MVC多了一个infrastructure层,这里用某张表的CURD来举例,这里的某张表在domain层中被称为某个仓储( Repository ),其实就是这张表的CURD操作,只不过你在domain定义的是接口,接口中方法的技术实现在infrastructure定义。

也就是说你的业务规则在domain定义,业务逻辑中与其他系统(db,文件系统,网络,第三方库等)的交互写在infrastructure层。

举个简单的例子,对于用户表(用户仓储),在domain层定义接口
internal/domain/user/user.go

scss 复制代码
// UserRepository 表示用户仓库接口
// 由基础设施层实现
type UserRepository interface {
	Get(ctx context.Context, id int) (*domain.User, error) // 根据ID获取用户
	GetAll(ctx context.Context) ([]*domain.User, error)    // 获取所有用户
	Save(ctx context.Context, user *domain.User) error     // 保存用户
}

要实现接口中定义的方法,就需要涉及到数据库(这里是Mysql)的CURD操作, Mysql不属于本系统,所以方法的实现放在infrastructure

internal/infrastructure/user/user.go

go 复制代码
// userRepository 实现 UserRepository 接口
type userRepository struct {
	conn *sql.DB // 数据库连接
}

// NewUserRepository 返回初始化的 UserRepository 实现
func NewUserRepository(conn *sql.DB) repository.UserRepository {
	return &userRepository{conn: conn}
}

// Get returns domain.User
// Get 根据ID返回用户对象
func (r *userRepository) Get(ctx context.Context, id int) (*domain.User, error) {
	row, err := r.queryRow(ctx, "select id, name from users where id=?", id)
	if err != nil {
		return nil, err
	}
	u := &domain.User{}
	err = row.Scan(&u.ID, &u.Name)
	if err != nil {
		return nil, err
	}
	return u, nil
}

// GetAll returns list of domain.User
// GetAll 返回所有用户列表
func (r *userRepository) GetAll(ctx context.Context) ([]*domain.User, error) {
	rows, err := r.query(ctx, "select id, name from users")
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	us := make([]*domain.User, 0)
	for rows.Next() {
		u := &domain.User{}
		err = rows.Scan(&u.ID, &u.Name)
		if err != nil {
			return nil, err
		}
		us = append(us, u)
	}
	return us, nil
}

// Save saves domain.User to storage
// Save 将用户对象保存到存储
func (r *userRepository) Save(ctx context.Context, u *domain.User) error {
	stmt, err := r.conn.Prepare("insert into users (name) values (?)")
	if err != nil {
		return err
	}
	defer stmt.Close()

	_, err = stmt.ExecContext(ctx, u.Name)
	return err
}

完成domain接口的定义以及实现后,相当于MVC的model层实现了,那么只需要在上层调用domain的接口就行了

DDD中的interface,application和MVC的controller和service层一样的,interface用于接收http请求,把参数传递到下层,application用于整合业务逻辑,把http接口需要的数据返回

internal/application/user/user.go 整合数据返回给interface层

go 复制代码
// UserInteractor 嵌入了domain的用户CURD接口
type UserInteractor struct {
	Repository repository.UserRepository 
}

// GetUser returns user
// GetUser 返回指定ID的用户
func (i UserInteractor) GetUser(ctx context.Context, id int) (*domain.User, error) {
	return i.Repository.Get(ctx, id)
}

internal/interface/user/user.go 拿到数据返回给前端

go 复制代码
// Handler 用户处理器
type Handler struct {
	UserInteractor *application.UserInteractor
}

// getUser 处理获取单个用户的请求
func (h Handler) getUser(w http.ResponseWriter, r *http.Request, id int) {
	ctx := r.Context()

	user, err := h.UserInteractor.GetUser(ctx, id)
	if err != nil {
		Error(w, http.StatusNotFound, err, "failed to get user") // 获取用户失败
		return
	}
	JSON(w, http.StatusOK, user)
}

一个简单的DDD架构就这样实现了

有几点经验

  • domain层的一个领域负责的是相关的业务,也就是对 /domain/xxx 这个目录,只要与这个领域相关的表都可以定义在该目录下
  • domain除了定义接口,相关的业务逻辑实现也要放在这里,比如对领域中的某些参数的校验,即在该领域定义的结构体绑定一些校验方法等,但是与第三方交互的具体技术实现都放在infrastructure
  • 对于想采用CQRS的项目,一般是在application层分别定义查询实例和命令实例,把涉及到查询的操作都绑定到查询实例,把涉及到命令的操作都绑定到命令示例,可以读写分离,写操作可以写主库,读操作读从库

相关推荐
zopple8 小时前
常见的 Spring 项目目录结构
java·后端·spring
cjy0001119 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
小江的记录本10 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
sheji341610 小时前
【开题答辩全过程】以 基于springboot的校园失物招领系统为例,包含答辩的问题和答案
java·spring boot·后端
程序员cxuan11 小时前
人麻了,谁把我 ssh 干没了
人工智能·后端·程序员
wuyikeer12 小时前
Spring Framework 中文官方文档
java·后端·spring
Victor35612 小时前
MongoDB(61)如何避免大文档带来的性能问题?
后端
Victor35612 小时前
MongoDB(62)如何避免锁定问题?
后端
wuyikeer13 小时前
Spring BOOT 启动参数
java·spring boot·后端
子木HAPPY阳VIP13 小时前
Ubuntu 22.04 VMware 设置固定IP配置
人工智能·后端·目标检测·机器学习·目标跟踪