learn.lianglianglee.com/专栏/DDD实战课/13 代码模型(上):如何使用DDD设计微服务代码模型?.md.html
2022-08-14 03:40:33 +08:00

301 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>13 代码模型如何使用DDD设计微服务代码模型.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/DDD实战课/00 开篇词 学好了DDD你能做什么.md.html">00 开篇词 学好了DDD你能做什么</a>
</li>
<li>
<a href="/专栏/DDD实战课/01 领域驱动设计微服务设计为什么要选择DDD.md.html">01 领域驱动设计微服务设计为什么要选择DDD</a>
</li>
<li>
<a href="/专栏/DDD实战课/02 领域、子域、核心域、通用域和支撑域:傻傻分不清?.md.html">02 领域、子域、核心域、通用域和支撑域:傻傻分不清?</a>
</li>
<li>
<a href="/专栏/DDD实战课/03 限界上下文:定义领域边界的利器">03 限界上下文:定义领域边界的利器</a>
</li>
<li>
<a href="/专栏/DDD实战课/04 实体和值对象:从领域模型的基础单元看系统设计.md.html">04 实体和值对象:从领域模型的基础单元看系统设计</a>
</li>
<li>
<a href="/专栏/DDD实战课/05 聚合和聚合根:怎样设计聚合?.md.html">05 聚合和聚合根:怎样设计聚合?</a>
</li>
<li>
<a href="/专栏/DDD实战课/06 领域事件:解耦微服务的关键.md.html">06 领域事件:解耦微服务的关键</a>
</li>
<li>
<a href="/专栏/DDD实战课/07 DDD分层架构有效降低层与层之间的依赖.md.html">07 DDD分层架构有效降低层与层之间的依赖</a>
</li>
<li>
<a href="/专栏/DDD实战课/08 微服务架构模型:几种常见模型的对比和分析.md.html">08 微服务架构模型:几种常见模型的对比和分析</a>
</li>
<li>
<a href="/专栏/DDD实战课/09 中台:数字转型后到底应该共享什么?.md.html">09 中台:数字转型后到底应该共享什么?</a>
</li>
<li>
<a href="/专栏/DDD实战课/10 DDD、中台和微服务它们是如何协作的.md.html">10 DDD、中台和微服务它们是如何协作的</a>
</li>
<li>
<a href="/专栏/DDD实战课/11 DDD实践如何用DDD重构中台业务模型.md.html">11 DDD实践如何用DDD重构中台业务模型</a>
</li>
<li>
<a href="/专栏/DDD实战课/12 领域建模:如何用事件风暴构建领域模型?.md.html">12 领域建模:如何用事件风暴构建领域模型?</a>
</li>
<li>
<a class="current-tab" href="/专栏/DDD实战课/13 代码模型如何使用DDD设计微服务代码模型.md.html">13 代码模型如何使用DDD设计微服务代码模型</a>
</li>
<li>
<a href="/专栏/DDD实战课/14 代码模型(下):如何保证领域模型与代码模型的一致性?.md.html">14 代码模型(下):如何保证领域模型与代码模型的一致性?</a>
</li>
<li>
<a href="/专栏/DDD实战课/15 边界:微服务的各种边界在架构演进中的作用?.md.html">15 边界:微服务的各种边界在架构演进中的作用?</a>
</li>
<li>
<a href="/专栏/DDD实战课/16 视图:如何实现服务和数据在微服务各层的协作?.md.html">16 视图:如何实现服务和数据在微服务各层的协作?</a>
</li>
<li>
<a href="/专栏/DDD实战课/17 从后端到前端:微服务后,前端如何设计?.md.html">17 从后端到前端:微服务后,前端如何设计?</a>
</li>
<li>
<a href="/专栏/DDD实战课/18 知识点串讲基于DDD的微服务设计实例.md.html">18 知识点串讲基于DDD的微服务设计实例</a>
</li>
<li>
<a href="/专栏/DDD实战课/19 总结(一):微服务设计和拆分要坚持哪些原则?.md.html">19 总结(一):微服务设计和拆分要坚持哪些原则?</a>
</li>
<li>
<a href="/专栏/DDD实战课/20 总结分布式架构关键设计10问.md.html">20 总结分布式架构关键设计10问</a>
</li>
<li>
<a href="/专栏/DDD实战课/答疑有关3个典型问题的讲解.md.html">答疑有关3个典型问题的讲解</a>
</li>
<li>
<a href="/专栏/DDD实战课/结束语 所谓高手,就是跨过坑和大海.md.html">结束语 所谓高手,就是跨过坑和大海</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>13 代码模型如何使用DDD设计微服务代码模型</h1>
<p>你好,我是欧创新。</p>
<p>上一讲我们完成了领域模型的设计,接下来我们就要开始微服务的设计和落地了。那微服务落地时首先要确定的就是微服务的代码结构,也就是我今天要讲的微服务代码模型。</p>
<p>只有建立了标准的微服务代码模型和代码规范后,我们才可以将领域对象所对应的代码对象放在合适的软件包的目录结构中。标准的代码模型可以让项目团队成员更好地理解代码,根据代码规范实现团队协作;还可以让微服务各层的逻辑互不干扰、分工协作、各据其位、各司其职,避免不必要的代码混淆。另外,标准的代码模型还可以让你在微服务架构演进时,轻松完成代码重构。</p>
<p>那在 DDD 里,微服务的代码结构长什么样子呢?我们又是依据什么来建立微服务代码模型?这就是我们今天重点要解决的两个问题。</p>
<h2>DDD 分层架构与微服务代码模型</h2>
<p>我们参考 DDD 分层架构模型来设计微服务代码模型。没错!微服务代码模型就是依据 DDD 分层架构模型设计出来的。那为什么是 DDD 分层架构模型呢?</p>
<p><img src="assets/a308123994f87a5ce99adc85dd9b4d01-1628936632403.jpg" alt="img" /></p>
<p>我们先简单回顾一下 [第 07 讲] 介绍过的 DDD 分层架构模型。它包括用户接口层、应用层、领域层和基础层,分层架构各层的职责边界非常清晰,又能有条不紊地分层协作。</p>
<p>用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。</p>
<p>应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。</p>
<p>领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。</p>
<p>基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。</p>
<p>业务逻辑从领域层、应用层到用户接口层逐层封装和协作对外提供灵活的服务既实现了各层的分工又实现了各层的协作。因此毋庸置疑DDD 分层架构模型就是设计微服务代码模型的最佳依据。</p>
<h2>微服务代码模型</h2>
<p>现在,我们来看一下,按照 DDD 分层架构模型设计出来的微服务代码模型到底长什么样子呢?</p>
<p>其实DDD 并没有给出标准的代码模型,不同的人可能会有不同理解。下面要说的这个微服务代码模型是我经过思考和实践后建立起来的,主要考虑的是微服务的边界、分层以及架构演进。</p>
<h3>微服务一级目录结构</h3>
<p>微服务一级目录是按照 DDD 分层架构的分层职责来定义的。从下面这张图中,我们可以看到,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。</p>
<p><img src="assets/d1bea7dc6bd93f3bd30ced821f36bb71.jpg" alt="img" /></p>
<p>这些目录的职能和代码形态是这样的。</p>
<p>**Interfaces用户接口层**它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。</p>
<p>**Application应用层**它主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。</p>
<p>**Domain领域层**它主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。</p>
<p>**Infrastructure基础层**它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。</p>
<h3>各层目录结构</h3>
<p><strong>1. 用户接口层</strong></p>
<p>Interfaces 的代码目录结构有assembler、dto 和 façade 三类。</p>
<p><img src="assets/c6ea040a520c91dfe6400f206ff36fef.jpg" alt="img" /></p>
<p>**Assembler**实现 DTO 与领域对象之间的相互转换和数据交换。一般来说 Assembler 与 DTO 总是一同出现。</p>
<p>**Dto**它是数据传输的载体,内部不存在任何业务逻辑,我们可以通过 DTO 把内部的领域对象与外界隔离。</p>
<p>**Facade**提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。</p>
<p><strong>2. 应用层</strong></p>
<p>Application 的代码目录结构有event 和 service。</p>
<p><img src="assets/30caee3ceaf1085b7aa2cc388f996e61.jpg" alt="img" /></p>
<p>**Event事件**这层目录主要存放事件相关的代码。它包括两个子目录publish 和 subscribe。前者主要存放事件发布相关代码后者主要存放事件订阅相关代码事件处理相关的核心业务逻辑在领域层实现</p>
<p>这里提示一下:虽然应用层和领域层都可以进行事件的发布和处理,但为了实现事件的统一管理,我建议你将微服务内所有事件的发布和订阅的处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。</p>
<p>**Service应用服务**这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。你可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。</p>
<p><strong>3. 领域层</strong></p>
<p>Domain 是由一个或多个聚合包构成共同实现领域模型的核心业务逻辑。聚合内的代码模型是标准和统一的包括entity、event、repository 和 service 四个子目录。</p>
<p><img src="assets/688dd55b8399779baff8fc5b7c124c2c.jpg" alt="img" /></p>
<p>而领域层聚合内部的代码目录结构是这样的。</p>
<p>**Aggregate聚合**它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。</p>
<p>以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务之间聚合的重组。聚合之间清晰的代码边界,可以让你轻松地实现以聚合为单位的微服务重组,在微服务架构演进中有着很重要的作用。</p>
<p>**Entity实体**它存放聚合根、实体、值对象以及工厂模式Factory相关代码。实体类采用充血模型同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。</p>
<p>**Event事件**它存放事件实体以及与事件活动相关的业务逻辑代码。</p>
<p>**Service领域服务**它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。</p>
<p>**Repository仓储**它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。</p>
<p>特别说明:按照 DDD 分层架构,仓储实现本应该属于基础层代码,但为了在微服务架构演进时,保证代码拆分和重组的便利性,我是把聚合仓储实现的代码放到了聚合包内。这样,如果需求或者设计发生变化导致聚合需要拆分或重组时,我们就可以将包括核心业务逻辑和仓储代码的聚合包整体迁移,轻松实现微服务架构演进。</p>
<p><strong>4. 基础层</strong></p>
<p>Infrastructure 的代码目录结构有config 和 util 两个子目录。</p>
<p><img src="assets/5bbe3454e2ecf4ff4770e887a4967b5a.jpg" alt="img" /></p>
<p>**Config**主要存放配置相关代码。</p>
<p>**Util**主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。</p>
<h3>代码模型总目录结构</h3>
<p>在完成一级和二级代码模型设计后,你就可以看到下图这样的微服务代码模型的总目录结构了。</p>
<p><img src="assets/915ad8d830d925a893cd09ff6cbdadb8.jpg" alt="img" /></p>
<h2>总结</h2>
<p>今天我们根据 DDD 分层架构模型建立了标准的微服务代码模型,在代码模型里面,各代码对象各据其位、各司其职,共同协作完成微服务的业务逻辑。</p>
<p>那关于代码模型我还需要强调两点内容。</p>
<p>第一点:聚合之间的代码边界一定要清晰。聚合之间的服务调用和数据关联应该是尽可能的松耦合和低关联,聚合之间的服务调用应该通过上层的应用层组合实现调用,原则上不允许聚合之间直接调用领域服务。这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用。</p>
<p>第二点:你一定要有代码分层的概念。写代码时一定要搞清楚代码的职责,将它放在职责对应的代码目录内。应用层代码主要完成服务组合和编排,以及聚合之间的协作,它是很薄的一层,不应该有核心领域逻辑代码。领域层是业务的核心,领域模型的核心逻辑代码一定要在领域层实现。如果将核心领域逻辑代码放到应用层,你的基于 DDD 分层架构模型的微服务慢慢就会演变成传统的三层架构模型了。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/DDD实战课/12 领域建模:如何用事件风暴构建领域模型?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/DDD实战课/14 代码模型(下):如何保证领域模型与代码模型的一致性?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996ef59e243d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>