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

news2025/5/25 23:00:07

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


相关项目

https://github.com/takashabe/go-ddd-sample
基本的CURD,代码简洁,适合快速上手 DDD

https://github.com/eyazici90/go-ddd
适合理解第一个项目后,有一定基础

https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example
比较复杂,适合进阶


简要介绍

 

这里借用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

// 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

// 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层

// 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  拿到数据返回给前端

// 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层分别定义查询实例和命令实例,把涉及到查询的操作都绑定到查询实例,把涉及到命令的操作都绑定到命令示例,可以读写分离,写操作可以写主库,读操作读从库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2336491.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

天线静电防护:NRESDTLC5V0D8B

一. 物联网天线的使用环境 1.1 联网天线广泛应用于智能家居领域,比如智能门锁、智能摄像头等设备中,通过天线实现设备与家庭网络的连接,用户可以远程控制和监控家居设备。以智能摄像头为例,它通过天线将拍摄的画面实时传输到用户…

【Linux 并发与竞争】

【Linux 并发与竞争】 Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,多个任务甚至中断都能访问的资源叫做共享资源,就和共享单车一样。在驱动开发中要注意对共享资源的保护,也就是要处理对共享…

实用类题目

1. 密码强度检测 题目描述:生活中,为保证账户安全,密码需要有一定强度。编写一个方法,接收一个字符串作为密码,判断其是否符合以下强度要求:长度至少为 8 位,包含至少一个大写字母、一个小写字…

STM32F103C8T6-基于FreeRTOS系统实现步进电机控制

引言 上一篇文章讲述了如何使用蓝牙连接stm32进行数据收发控制步进电机,这篇在之前的基础上通过移植操作系统(FreeRTOS或者其他的也可以,原理操作都类似)实现步进电机控制。 上篇博客指路:STM32蓝牙连接Android实现云…

macOS安装java

一、下载 官网Java Downloads | Oracle 安装载java8,下载对应的JDK Java Downloads | Oracle 二、双击安装 安装 完成 三、查看安装位置 打开终端窗口,执行命令: /usr/libexec/java_home -V /Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Content…

zkmall模块商城:B2C 场景下 Vue3 前端性能优化的广度探索与实践

ZKmall作为面向B2C场景的模块化电商平台,其前端性能优化在Vue3框架下的实践融合了架构设计、渲染机制与业务特性,形成了一套多维度的优化体系。以下从技术实现与业务适配两个维度展开分析: 一、Vue3响应式系统深度适配 ​Proxy驱动的精准更新…

【Netty篇】Future Promise 详解

目录 一、 Netty Future 与 Promise —— 异步世界的“信使”与“传话筒”🚀1、 理解 Netty Future2、 理解 Netty Promise 二、 代码案例解读💻例1:同步处理任务成功👍例2:异步处理任务成功📲例3&#xff…

视频分析设备平台EasyCVR安防视频管理系统,打造电石生产智能视频监控新体系

一、背景介绍 电石生产中的出炉工序是整个生产流程中最为繁重且危险的环节。在开堵炉眼的过程中,电石极易发生飞溅现象,尤其在进行吹氧操作时,人员灼伤的风险极高。鉴于此,该工序正逐步由传统的人工操作模式向智能化方向转变。然…

从 PyTorch 到 ONNX:深度学习模型导出全解析

在模型训练完毕后,我们通常希望将其部署到推理平台中,比如 TensorRT、ONNX Runtime 或移动端框架。而 ONNX(Open Neural Network Exchange)正是 PyTorch 与这些平台之间的桥梁。 本文将以一个图像去噪模型 SimpleDenoiser 为例&a…

Android 应用添加Tile到SystemUI QuickSettings

安卓源码里有谷歌给的关于 Tile 的说明。 frameworks/base/packages/SystemUI/docs/qs-tiles.md SystemUI QuickSettings 简称QS,指的是 下拉菜单里的区域。区域里的一个选项就是一个 Tile 。 下图是 frameworks/base/packages/SystemUI/docs/ 里的附图示例&#…

【MySQL】前缀索引、索引下推、访问方法,自适应哈希索引

最左前缀原则 对于INDEX(name, age)来说最左前缀可以是联合索引的最左N个字段, 也可以是字符串索引的最左M个字符。 SELECT * FROM t WHERE name LIKE 张%其效果和单独创建一个INDEX(name)的效果是一样的若通过调整索引字段的顺序, 可以少维护一个索引树, 那么这个顺序就是需要…

Android Studio开发知识:从基础到进阶

引言 Android开发作为移动应用开发的主流方向之一,曾吸引了无数开发者投身其中。然而,随着市场饱和和技术迭代,当前的Android开发就业形势并不乐观,竞争日益激烈。尽管如此,掌握扎实的开发技能仍然是脱颖而出的关键。本…

ocr-身份证正反面识别

在阿里云官网,申请一个token [阿里官方]身份证OCR文字识别_API专区_云市场-阿里云 (aliyun.com) 观察一下post请求body部分json字符串,我们根据这个创建一个java对象 先默认是人像面 public class IdentityBody {public String image;class configure…

单节锂电池4.2V升压5V都有哪些国产芯片推荐?国产SL4011高效,高性价比

针对单节锂电池(4.2V)升压至5V应用中 SL4011升压芯片 的核心优势解析,结合其技术参数与典型应用场景进行详细说明: 1. 宽输入电压与高兼容性 输入范围:2.7V-12V,完美覆盖单节锂电池全周期电压(3…

机器学习 | 神经网络介绍 | 概念向

文章目录 📚从生物神经元到人工神经元📚神经网络初识🐇激活函数——让神经元“动起来”🐇权重与偏置——调整信息的重要性🐇训练神经网络——学习的过程🐇过拟合与正则化——避免“死记硬背” &#x1f440…

视频孪生重构施工逻辑:智慧工地的数字化升级

当"智慧工地"概念在2017年首次写入《建筑业发展"十三五"规划》时,行业普遍将其等同于摄像头与传感器的简单叠加。十年数字浪潮冲刷下,智慧工地的内涵已发生本质跃迁:从工具层面的信息化改造,进化为基于视频数…

六根觉性:穿透表象的清净觉知之光

在喧嚣的禅堂里,老禅师轻叩茶盏,清脆的声响划破沉寂。这声"叮"不仅震动耳膜,更叩击着修行者的心性——这正是佛教揭示的六根觉性在世间万相中的妙用。当我们凝视《楞严经》中二十五圆通法门,六根觉性犹如六道澄明之光&a…

spring:注解@Component、@Controller、@Service、@Reponsitory

背景 spring框架的一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean是如何加载到容器的,可以使用spring注解方式或者spring XML配置方式。 spring注解方式直接对项目中的类进行注解,减少了配置文件内容,更加便于…

Halcon应用:九点标定-手眼标定

提示:若没有查找的算子,可以评论区留言,会尽快更新 Halcon应用:九点标定-手眼标定 前言一、Halcon应用?二、应用实战1、图形理解[eye-to-hand]:1.1、开始应用2 图形理解[eye-in-hand] 前言 本篇博文主要用…

【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(一)

自动引用计数 前言alloc/retain/release/dealloc实现苹果的实现 autoreleaseautorelease实现苹果的实现 总结 前言 此前,写过一遍对自动引用计数的简单学习,因此掠过其中相同的部分:引用计数初步学习 alloc/retain/release/dealloc实现 由于…