DDD核心理念解析 -- 知识铺
探索技术深度,尽在’芋道源码’。每天上午10:33,我们准时为您推送技术精品文章,涵盖Java超神之路、源码解析等多个领域。从Dubbo、Netty到RocketMQ,再到Sharding-JDBC、MyCAT,我们深入挖掘每个技术细节,助您成为技术领域的弄潮儿。不仅如此,Elastic-Job、TCC-Transaction、Eureka与Hystrix等中间件的源码解析,也是我们精心准备的技术盛宴。加入我们,一起深入源码,探索技术的无限可能。 在探索软件开发的领域中,模型扮演着至关重要的角色。它们不仅帮助我们理解和组织业务需求,还是软件开发过程中不可或缺的一部分。在本章节中,我们将深入讨论领域驱动设计(Domain Driven Design,简称DDD),这是一种专注于通过统一语言、业务抽象、领域划分和领域建模等手段来控制软件复杂性的软件开发方法论。
DDD的概念最早由Eric Evans在其2003年出版的著作《领域驱动设计:软件核心复杂性应对之道》中提出。这种设计方法论的核心在于利用面向对象的特性,如封装和多态,来有效应对业务逻辑的复杂性。与此相对的是传统的事务性编程模型,例如J2EE或Spring+Hibernate,它们通常只关注数据层面,而将业务逻辑以过程式的方式实现在Service层中。虽然这种模型易于上手,但随着业务的发展,系统复杂性也会随之增加。
为了更直观地理解DDD,我们可以通过一个简单的银行转账案例来进行比较。在传统的事务脚本模式下,业务逻辑被集中在MoneyTransferService中,而Account对象则仅包含基本的getters和setters,这种设计被称为“贫血模式”。
此外,我们还提到了一个基于Spring Boot、MyBatis Plus以及Vue和Element实现的后台管理系统和用户小程序,该系统支持RBAC动态权限、多租户、数据权限、工作流、三方登录、支付、短信和商城等功能。项目的具体实现和更多细节可以在GitHub上的项目地址查看,同时提供了视频教程以供学习。
public class MoneyTransferServiceTransactionScriptImpl
implements MoneyTransferService {
private AccountDao accountDao;
private BankingTransactionRepository bankingTransactionRepository;
. . .
@Override
public BankingTransaction transfer(
String fromAccountId, String toAccountId, double amount) {
Account fromAccount = accountDao.findById(fromAccountId);
Account toAccount = accountDao.findById(toAccountId);
. . .
double newBalance = fromAccount.getBalance() - amount;
switch (fromAccount.getOverdraftPolicy()) {
case NEVER:
if (newBalance < 0) {
throw new DebitException("Insufficient funds");
}
break;
case ALLOWED:
if (newBalance < -limit) {
throw new DebitException(
"Overdraft limit (of " + limit +") exceeded: " + newBalance);
}
break;
}
fromAccount.setBalance(newBalance);
toAccount.setBalance(toAccount.getBalance() + amount);
BankingTransaction moneyTransferTransaction =
new MoneyTranferTransaction(fromAccountId,toAccountId,amount);
bankingTransactionRepository.addTransaction(moneyTransferTransaction);
return moneyTransferTransaction;
}}
许多读者可能对上述代码感到熟悉,因为它是大多数系统编写代码时的常见模式。在完成需求评审后,工程师通常会绘制一些UML图来完成设计工作,随后便按照上述过程式风格编写业务代码,这种方式几乎不需要太多思考。
public class Account {
private String id;
private double balance;
private OverdraftPolicy overdraftPolicy;
. . .
public double balance() { return balance; }
public void debit(double amount) {
this.overdraftPolicy.preDebit(this, amount);
this.balance = this.balance - amount;
this.overdraftPolicy.postDebit(this, amount);
}
public void credit(double amount) {
this.balance = this.balance + amount;
}}
透支策略OverdraftPolicy也不仅仅是一个Enum了,而是被抽象成包含业务规则并采用策略模式的对象。
public interface OverdraftPolicy {
void preDebit(Account account, double amount);
void postDebit(Account account, double amount);}public class NoOverdraftAllowed implements OverdraftPolicy {
public void preDebit(Account account, double amount) {
double newBalance = account.balance() - amount;
if (newBalance < 0) {
throw new DebitException("Insufficient funds");
}
}
public void postDebit(Account account, double amount) {
}}public class LimitedOverdraft implements OverdraftPolicy {
private double limit;
. . .
public void preDebit(Account account, double amount) {
double newBalance = account.balance() - amount;
if (newBalance < -limit) {
throw new DebitException(
"Overdraft limit (of " + limit + ") exceeded: "+newBalance);
}
}
public void postDebit(Account account, double amount) {
}}
而Domain Service只需要调用Domain Entity对象完成业务逻辑。
public class MoneyTransferServiceDomainModelImpl
implements MoneyTransferService {
private AccountRepository accountRepository;
private BankingTransactionRepository bankingTransactionRepository;
. . .
@Override
public BankingTransaction transfer(
String fromAccountId, String toAccountId, double amount) {
Account fromAccount = accountRepository.findById(fromAccountId);
Account toAccount = accountRepository.findById(toAccountId);
. . .
fromAccount.debit(amount);
toAccount.credit(amount);
BankingTransaction moneyTransferTransaction =
new MoneyTranferTransaction(fromAccountId,toAccountId,amount);
bankingTransactionRepository.addTransaction(moneyTransferTransaction);
return moneyTransferTransaction;
}}
领域驱动设计(DDD)的实践虽然增加了类的数量,但每个类都更加专注于单一职责,这显著提升了代码的可读性和系统的可维护性。例如,采用Spring Cloud Alibaba框架结合Gateway、Nacos、RocketMQ等技术,配合Vue和Element UI构建的后台管理系统和用户小程序,不仅支持RBAC权限管理、多租户架构、数据权限控制、工作流程定制,还集成了第三方登录、支付、短信和商城等多样化功能。该项目的代码库托管在GitHub上,地址为:https:##github.com/YunaiV/yudao-cloud,同时,提供了详尽的视频教程,可通过链接https:##doc.iocoder.cn/video/进行学习。在传统数据驱动开发模式中,开发者通常首先设计数据库模型,随后实现业务逻辑。这种模式简单直观,便于快速上手。但随着项目规模的扩大,数据模型的复杂性也会随之增加,导致软件的后期开发和维护面临较大挑战。 在客户关系管理(CRM)系统中,数据驱动研发流程至关重要。该流程涉及几个核心概念,包括销售人员、销售机会、客户、私有领域和公共领域。以下是这些概念的详细定义: - 销售人员(Sales):指公司的销售代表,他们可以管理多个销售机会。 - 销售机会(Opportunity):指潜在的销售交易,每个机会至少关联一个客户,并且由特定的销售人员负责。 - 客户(Customer):销售过程中的目标对象,是公司产品或服务的潜在购买者。 - 私有领域(Private sea):指分配给特定销售人员的客户群体,其他销售人员无权干预。 - 公共领域(Public sea):指所有销售人员均可访问的客户资源池,销售人员可以从中选择客户并转移到自己的私有领域。 根据数据库建模理论,这些概念可以通过实体关系图(ER Diagram)来表示,以展示它们之间的联系和区别。 在图7-2展示的客户关系管理(CRM)的实体-关系(ER)图中,我们注意到缺少了所谓的公海和私海的概念。公海和私海在CRM系统中通常指的是销售机会的归属状态。如果一个销售机会被分配给了特定的销售人员,即具有salesId的标识,那么这个机会就被认为是在私海中,也就是被某个销售所占有。相反,如果销售机会没有被分配,即没有salesId,那么这个机会就处于公海中,意味着它对所有销售人员都是开放的。
在这种开发模式下,最终的成果包括创建了若干数据库表以及用于操作这些表中数据的事务性脚本,这在图7-3中有所展示。 领域驱动设计是一种软件开发方法论,它侧重于业务领域内的划分与建模。它强调以业务逻辑为核心,而非传统以数据模型为出发点的开发方式。在这一设计思想下,开发流程从构建领域模型开始,这些模型通常以类、聚合根和值对象的形式在程序中体现。领域模型的目的是将业务概念和业务规则以代码形式明确表达出来,使得业务逻辑更加直观和易于理解。与数据驱动设计相比,领域驱动设计更注重业务语义的显性化,而不是仅仅关注数据存储和数据间的关系。
图7-4 领域驱动研发过程
仍以上面的CRM为例。假如我们先不考虑数据模型,而是采用面向对象分析(Object Oriented Analysis,OOA)对这个场景进行领域建模,那么可以得到图7-5所示的领域模型。
领域模型图7-5展示了CRM系统的核心业务概念和术语,使得业务人员和产品经理能够轻松理解并参与到模型的构建过程中。这种模型的设计不仅有助于保持业务语义的完整性,还便于非技术人员与开发人员之间的沟通和协作。通过领域驱动设计(DDD)的战略和战术规划,我们能够对CRM系统的问题域进行细分,并构建出符合业务需求的领域模型。如图7-6所示,我们为CRM系统实施了领域战略设计,进一步明确了业务领域和子域的界限。 在CRM系统的构建过程中,领域模型与数据模型之间通常存在不一致性。尽管在某些特定情况下,两者可能表现出一致性,但大多数情况下,需要通过特定的技术手段来实现它们之间的映射。这种技术被称为对象关系映射(Object Relationship Mapping,简称ORM)。ORM技术通过将领域模型中的实体与数据库中的数据表进行对应,从而解决了领域模型和数据模型之间的差异问题,如图7-7所展示。 对象关系映射(ORM)技术一度备受追捧,记得Hibernate刚推出时,我尝试了各种高级特性,如继承映射和多对多关系映射,但最终结果却不尽如人意,既不完全符合实体模型也不符合数据对象模型。ORM的困境在于其过于理想化,试图将数据模型与领域模型合二为一,这在实践中往往难以实现。以CRM系统为例,其数据模型中并不存在私海和公海这两个概念,而ORM工具却无法映射这些抽象概念。因此,Hibernate和JPA的流行度下降是可预见的。目前,MyBatis因其简单性而广受欢迎,它避免了复杂的关系映射,专注于数据库表与数据对象之间的直接映射。复杂数据库关系与对象关系的差异本质上是数据模型与领域模型的差异,这种差异的多样性和灵活性很难通过预设规则来定义,这也是ORM工具局限性的原因。大型互联网公司更倾向于使用MyBatis,正是因为它的灵活性。如果你正在实践领域驱动设计(DDD),那么请记住不要依赖工具来建模,工具无法进行抽象思考,建模工作还需亲力亲为。
已在知识星球更新源码解析如下:
[ 《芋道 SpringBoot 2.X 入门》系列已经更新超过101篇文章,内容涵盖了MyBatis、Redis、MongoDB、Elasticsearch、数据库分库分表、读写分离技术、SpringMVC、Webflux、权限管理、WebSocket通信、Dubbo、RabbitMQ、RocketMQ、Kafka以及性能测试等多个方面。此外,还提供了近3万行代码的SpringBoot示例项目和超过4万行代码的电商微服务项目。想要获取这些资源,只需点击文章下方的“在看”,关注相关公众号,并回复关键词“666”,即可领取。更多优质内容将会持续更新。
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240723/DDD%E6%A0%B8%E5%BF%83%E7%90%86%E5%BF%B5%E8%A7%A3%E6%9E%90--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com