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

最近在工作过程中重构的项目要求使用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.mzph.cn/pingmian/77013.shtml

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

相关文章

Ubuntu系统连网问题

0. Preface 给一台新电脑装上Ubuntu系统后,接好网线,发现上不了网,右上角是有网络连接的图标的,也能获取到ip地址,就是没办法连网,ping www.google.com也没反应。 其实应该是网络设置有点问题,…

C/C++---头文件保护机制

在 C 和 C 编程里,头文件保护机制是一种防止头文件被重复包含的技术,它主要借助 #ifndef、#define 和 #endif 这些预处理指令来达成,也可以使用 #pragma once 这一编译器特定指令。下面详细阐述这一机制: 1. 头文件重复包含的问题…

蓝桥杯 8. 分巧克力

分巧克力 原题目链接 问题描述 儿童节那天有 K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。 小明一共有 N 块巧克力,其中第 i 块是 Hᵢ Wᵢ 的长方形。为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。 要求…

从 SQL2API 到 Text2API:开启数据应用开发的新征程

在技术革新浪潮的席卷下,数据应用开发领域正经历着深刻变革。曾经,构建数据 API 需要开发者具备扎实的数据库知识和编程技能,手动编写复杂的 SQL 查询与 API 代码,这一过程不仅耗时费力,还将众多非技术人员阻挡在数据应…

继承:(开始C++的进阶)

我们今天来学习C的进阶: 面向对象三大特性:封装,继承,多态。 封装我们在前面已经学了,我们细细理解,我们的类的封装,迭代器的封装(vector的迭代器可以是他的原生指针,li…

冒泡排序、插入排序、快速排序、堆排序、希尔排序、归并排序

目录 冒泡排序插入排序快速排序(未优化版本)快速排序(优化版本)堆排序希尔排序归并排序各排序时间消耗对比 冒泡排序 冒泡排序核心逻辑就是对数组从第一个位置开始进行遍历,如果发现该元素比下一个元素大,则交换位置,如果不大,就…

JavaScript:表单及正则表达式验证

今天我要介绍的是在JavaScript中关于表单验证内容的知识点介绍&#xff1a; 关于表单验证&#xff0c;我接下来则直接将内容以及效果显示出来并作注解&#xff0c;这样可以清晰看见这个表达验证的妙用&#xff1a; <form id"ff" action"https://www.baidu.…

天元证券|调仓曝光!首批科技基金一季报出炉

4月15日&#xff0c;中欧基金、永赢基金、长城基金等公募基金公司旗下部分权益类基金产品一季报出炉。 券商中国记者梳理发现&#xff0c;永赢信息产业智选混合主要聚焦信息技术领域布局&#xff0c;前十大重仓股中9只股票属于信息技术行业&#xff0c;合计占基金资产净值比例达…

SpringAI版本更新:向量数据库不可用的解决方案!

Spring AI 前两天&#xff08;4.10 日&#xff09;更新了 1.0.0-M7 版本后&#xff0c;原来的 SimpleVectorStore 内存级别的向量数据库就不能用了&#xff0c;Spring AI 将其全部源码删除了。 此时我们就需要一种成本更低的解决方案来解决这个问题&#xff0c;如何解决呢&…

Sklearn入门之datasets的基本用法

、 Sklearn全称:Scipy-toolkit Learn是 一个基于scipy实现的的开源机器学习库。它提供了大量的算法和工具&#xff0c;用于数据挖掘和数据分析&#xff0c;包括分类、回归、聚类等多种任务。本文我将带你了解并入门Sklearn下的datasets在机器学习中的基本用法。 获取方式 pi…

优化 Dockerfile 性能之实践(Practice of Optimizing Dockerfile Performance)

优化 Dockerfile 性能之实践 构建 Docker 镜像时&#xff0c;Dockerfile 的性能会显著影响构建过程的效率。经过优化的 Dockerfile 可以缩短构建时间、最小化镜像大小并提高整体容器性能。在本文中&#xff0c;我们将探讨优化 Dockerfile 性能的最佳实践。 尽量减少层数 影响…

出现 ERR_CERT_COMMON_NAME_INVALID | 301 302 重定向的解决方法

目录 前言1. 问题所示2. 原理分析3. 解决方法前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 爬虫神器,无代码爬取,就来:bright.cn 1. 问题所示 执行代码时,出现如下提示: GET https://xxxx/admin-api/system

C语言 —— 指尖跃迁 刻印永恒 - 文件操作

目录 1. 什么是文件 1.1 程序文件 1.2 数据文件 1.3 文件名 2. 二进制文件和文本文件 3. 文件的打开与关闭 3.1 流和标准流 3.2 文件指针 3.3 文件的打开与关闭 fopen fclose 4. 文件的顺序读写 4.1 fgetc和fputc fgetc fputc 4.2 fgets和fputs fgets fputs…

用css给div列表加个序号

用 CSS 的 counter 相关属性来为列表添加序号。以下是具体的代码&#xff0c;我将以 HTML 文件的形式提供&#xff0c;并且会运行展示效果&#xff1a; .as-div {// counter-reset: my-counter; /* 计数器名称是my-counter */// counter-reset: small-apple; /* 计数器名称是s…

Rust : 关于*const () 与type erase

*const () 可以替代泛型&#xff0c;更加灵活。 一、 代码 //use std::mem::transmute; trait Work {fn process(&self); } struct Foo(String);impl Work for Foo {fn process(&self) {println!("process work from Foo : {}", self.0);} } struct Bar(S…

【专题刷题】双指针(二)

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

吉尔吉斯斯坦工商会代表团赴齐河德瑞新能源汽车考察

德州齐河&#xff0c;2025年4月15日电 时中美贸易突变之际&#xff0c;乘国家一带一路之风。 展中国新能源之宏图&#xff0c;塑国贸体系之新方向。 今日上午&#xff0c;吉尔吉斯斯坦共和国工商会代表团一行三人受邀抵达济南&#xff0c;开启对德瑞新能源科技有限公司&…

【记录condapack打包环境到超算上顺利运行】

以安装CLRNet为例子 本地Linux系统上的操作步骤。 由于官方的安装包的步骤&#xff0c;执行condapack的时候会报错&#xff0c;所以使用以下步骤进行安装包。 安装其他 Python 依赖包 pip install -r requirements.txt✅ 二、构建并打包项目&#xff08;核心步骤&#xff…

Windows OpenUtau-v0.1.529-开源歌曲合成软件[提供MIDI编辑、歌词调整、音色修改 等功能,音乐创作者的必备工具]

Windows OpenUtau 链接&#xff1a;https://pan.xunlei.com/s/VONy_Refvo6_813Ig--nu5_rA1?pwdejzc# 引擎&#xff08;Resampler&#xff09;和拼接器&#xff08;Wavtool&#xff09;是UTAU协议中音频处理的两大组件。前端编辑器通过调用引擎和拼接器&#xff0c;对音频进行…

虚拟卡可以解决订阅 ChatGPT 时无法付款的问题

在全球掀起 AI 热潮的今天&#xff0c;因为工作的需要有些朋友要用ChatGPT&#xff0c;它也成为了不少人日常学习、工作、创作和编程的得力助手。然而&#xff0c;不少用户在尝试订阅 ChatGPT Plus&#xff08;付费版&#xff09;时&#xff0c;却遇到了一个令人头疼的问题——…