DDD分享

1、什么是DDD

1.1、DDD的描述

  • 全称 Domain Driven Design(领域驱动设计)。
  • 一套应对复杂软件系统分析和设计的面向对象建模方法论。
  • 微服务拆分,重构。

总结一下:

DDD作为一种战略思想可用于指导微服务的拆分,DDD不是一个软件架构,而是一种指导思想.

1.2、普遍的需求开发方式

举个例子,比如我们每一次需求的开发方式:

在这里插入图片描述

存在的问题:

  1. 用户的需求进行多次转化,可能失去最原始的意图。
  2. 研发的实现是从数据库开始的。也就是说那着数据库去匹配业务需求;如果需求变更是不是意味着表也会变更。如果下一期有新需求会不会这个表结构就不适用了。
  3. 现在的开发模式,就会导致业务逻辑会大量的存留在services层。
  4. 需求的边界也是比较模糊的,一旦考虑不全,就会对业务逻辑做出重大变更。
  5. 代码难以维护,程序员不考虑整体设计,对于这个系统,就会有一个一个的补丁。
  6. 代码中的if/else判断很多,因为需要考虑和涵盖的场景会多。

1.3、DDD的需求开发方式

这里列举下,由产品,测试,运营和开发人员共同参与的需求讨论的开发方式

在这里插入图片描述

这样做的好处:

  1. 消除信息的不对称。
  2. 产品与研发的讨论,往往不能够深刻的互相理解。初次的讨论可以在产品与运营,测试与研发之间进行。最后进行业务的大讨论。
  3. 领域模型的划分,就是对大的需求进行拆分。
  4. DDD倡导以业务为主,自顶向下的进行业务领域划分。

1.4、自顶向下的开发方式

1.4.1、现在的采用的架构模式

在这里插入图片描述

在这种架构中,往往采用的都是自下而上的开发方式,只有确定了数据库的结构,才能划分清晰的业务逻辑。开发的代码往往都是耦合在一起的。

1.4.2、DDD倡导的分层架构

在这里插入图片描述

  1. controller层更加纯粹,在原三层架构中,controller可能会聚合多个service层的调用。在新的分层架构中,则纯粹作为与前端或外部系统交互的数据转换层,不再包含业务逻辑。
  2. 应用层相比于service层,体现为更高级别的抽象,不在包含业务逻辑。
  3. 抽离原有的service层的逻辑,拆分在领域服务和领域对象中。
  4. 领域层为聚合和领域服务。其中聚合包括实体+值对象。这里有一个充血模型的概念。

1.4.3、贫血模型

说到充血模型,就要介绍下贫血模型。下面看一下大家日常开发过程中的实体类。

在这里插入图片描述

这里只是举个例子,大家可以看下我们的日常的开发中使用的实体类,大致也是类似的。针对与这样的实体类我们最对的操作是什么,就是生成get/set方法,toString方法等方法。其他的情形再没有了。大家可以思考下这样的实体类的价值在哪里,难倒只是给成员变量赋值么;如果只是赋值,那改成如下这种方式,不是更好,连get/set

方法可以是不写了。

在这里插入图片描述

总结一下:以上这种实体类便是贫血模型的典型体现。

什么是面向对象呢?

  • 对象由属性、有行为。
  • 更关注的是对象有哪些行为,通过对象的这些行为,我们可以使得一个事件完整执行。
  • 贫血模型更像是拿着对象去做面向过程编程;将业务逻辑放在service层中的一个个很少复用的方法中。

###1.4.4、充血模型

对于一个实体对象,会有属性和行为,对于行为中,我们可能会封装一些业务逻辑,那么这种业务逻辑恰恰就是这个类的一个行为

这里结合我们的业务场景举个例子,在组织这个实体类中,是不是可以定义获取子组织,上级组织等一些行为呢。而不是在各个自服务去重复编码查询,这些查询往往耦合一定的业务逻辑,无法复用;后者除了作者自己,其他人无法知道有这样一个功能呢。

区别:

大多数业务逻辑放到 Domain 中,Service 是很薄的一层,封装少量业务逻辑,并且不和 DAO 打交道。

Service (事务封装) —> Domain Object <—> DAO

优点:

  • 更加符合 OO 的原则
  • Service 层很薄

缺点:

  • 如何划分 Service 层逻辑和 Domain 层逻辑是非常含混的。

2、DDD的基本概念

这里就介绍下基本概念的理解,不介绍官方的语音定义了。

2.1、领域

领域就是一个确定的范围。重点是边界的确定。

域的核心思想是将问题逐级细分来减低业务和系统的复杂度。

##2.2、子域

领域可以进一步划分成子领域,即子域

##2.3、核心域

在领域划分过程中,会不断划分子域,子域按重要程度会被划分成三类:核心域、通用域、支撑域。

2.4、通用域

中间件服务或第三方服务。

被多个子域使用的通用功能就是通用域,没有太多企业特征,比如权限认证。

2.5、支撑域

企业公共服务。

对于功能来讲是必须存在的,但它不对产品核心竞争力产生影响,也不包含通用功能,有企业特征,不具有通用性,比如数据代码类的数字字典系统。

2.6、统一语言

统一概念。

可以在事件风暴(开会)中来统一语言,甚至是中英文的映射、业务与代码模型的映射等。可以使用一个表格来记录。

2.7、限界上下文

服务职责划分的边界。

领域模型存在边界之内。对于同一个概念,不同上下文会有不同的理解,比如商品,在销售阶段叫商品,在运输阶段就叫货品。

在这里插入图片描述

理论上,限界上下文的边界就是微服务的边界,因此,理解限界上下文在设计中非常重要。

2.8、聚合

聚合概念类似于你理解的包的概念,每个包里包含一类实体或者行为,它有助于分散系统复杂性,也是一种高层次的抽象,可以简化对领域模型的理解。

2.9、聚合根

一个上下文内可能包含多个聚合,每个聚合都有一个根实体,叫做聚合根,一个聚合只有一个聚合根。

聚合的定义规则:

  • 聚合边界内必须具有哪些信息,如果没有这些信息就不能称为一个有效的聚合。
  • 聚合内的某些对象的状态必须满足某个业务规则。
  • 一个聚合只有一个聚合根,聚合根是可以独立存在的,聚合中其他实体或值对象依赖与聚合根。
  • 只有聚合根才能被外部访问到,聚合根维护聚合的内部一致性。

2.10、实体

实体是具有身份连贯性的领域概念。

文章可以是实体,作者也可以是,因为它们有 id 作为唯一标示。

2.11、值对象

为了更好地展示领域模型之间的关系,制定的一个对象,本质上也是一种实体,但相对实体而言,它没有状态和身份标识,它存在的目的就是为了表示一个值,通常使用值对象来传达数量的形式来表示。

3、需求开发中怎么应用DDD呢?

3.1、首先是需求的理解

在产品,测试,运营和研发之间进行需求的讨论与使用场景的分割。

在这里插入图片描述

3.2、包模块的划分

这里就拿一个文章管理系统举例子:

模块

一般尽量用一个模块(Module)来表示一个领域的限界上下文

一般的工程中包的组织方式为 {com.公司名.组织架构.业务.上下文.*},这样的组织结构能够明确地将一个上下文限定在包的内部。

import com.company.team.bussiness.counter.*;//计数上下文
import com.company.team.bussiness.category.*;//分类上下文
import com.company.team.bussiness.comment.*;//评论上下文

对于模块内的组织结构,一般情况下我们是按照领域对象、领域服务、领域资源库、适配层等组织方式定义的。

import com.company.team.bussiness.cms.domain.valobj.*;//领域对象-值对象
import com.company.team.bussiness.cms.domain.entity.*;//领域对象-实体
import com.company.team.bussiness.cms.domain.aggregate.*;//领域对象-聚合根
import com.company.team.bussiness.cms.service.*;//领域服务
import com.company.team.bussiness.cms.repo.*;//领域资源库
import com.company.team.bussiness.cms.facade.*;//领域适配层

领域对象

在文章领域对象中,我们需要定义个一个方法,来获取文章的计数量,用于页面上显示。我们可以定义聚合根(文章)和值对象(计数器);这里计数器之所以被列为值对象,而非实体的一个属性,是因为计数器是由多部分组成的,比如真实阅读量、推广阅读量等。

package com.company.team.bussiness.domain.aggregate;
import ...;

public class Article {

    @Resource
    private CategoryRepository categoryRepository;

    private int articleId; //文章id
    ...
    private ArticleCount articleCount; //文章计数器
    //getter & setter

    //查询计数显示数量,这里简化一些逻辑,甚至是不符合实际业务场景,这不重要,这里只为直观表达意思
    public Integer getShowArticleCount() {
            if(this.articleCount == null){
            return 0;
        }
        return this.articleCount.realCount + categoryRepository.getCategoryWeight(this.category) + (this.articleCount.adCount * DayUtils.calDaysByNow(this.articleCount.deadDays));
    }
}

可以看到,领域功能的内聚性更强,职责更加明确。

资源库

领域对象需要资源存储,资源库可以理解成 DAO,但它比 DAO 更宽泛,存储的手段可以是多样化的,常见的无非是数据库、分布式缓存、本地缓存等。资源库(Repository)的作用,就是对领域的存储和访问进行统一管理的对象。

import com.company.team.bussiness.repo.dao.ArticleDao;//数据库访问对象-文章
import com.company.team.bussiness.repo.dao.CommentDao;//数据库访问对象-评论
import com.company.team.bussiness.repo.dao.po.ArticlePO;//数据库持久化对象-文章
import com.company.team.bussiness.repo.dao.po.CommentPO;//数据库持久化对象-评论
import com.company.team.bussiness.repo.cache.ArticleObj;//分布式缓存访问对象-文章缓存访问
package com.company.team.bussiness.repo;
import ...;

@Repository
public class ArticleRepository {
    @Autowired
    private ArticleDao articleDao;
    @AutoWired
    private articleDaoCacheAccessObj articleCacheAccessObj;

    public Article getArticleById(int articleId) {
        Article article = articleCacheAccessObj.get(articleId);
        if(article!=null){
            return article;
        }
        article = getArticleFromDB(articleId);
        articleCacheAccessObj.add(articleId, article);
        return article;
    }

    private Article getArticleFromDB(int articleId) {...}
}

比起以往将资源管理放在服务中的做法,由资源库对资源进行管理,职责更加明确,代码的可读性和可维护性也更强。

适配层

在一个上下文中,有时需要对外部上下文进行访问,通常会引入适配层的概念来对外部上下文的访问进行一次转义。

package com.company.team.bussiness.facade;
import ...;

@Component
public class ArticleFacade {

    @Resource
    private ArticleService articleService;

    public Article getArticle(ArticleContext context) {
        ArticleResponse resp = articleService.getArticle(context.getArticleId());
        return buildArticle(resp);
    }

    private Article buildArticle(ArticleResponse resp) {...}
}

总结

上文中,我们将领域行为封装到领域对象中,将资源管理行为封装到资源库中,将外部上下文的交互行为封装到适配层中。此时,我们再回过头来看领域服务时,能够发现领域服务本身所承载的职责也就更加清晰了,即就是通过串联领域对象、资源库和适配层等一系列领域内的对象的行为,对其他上下文提供交互的接口

在这里插入图片描述

<think>首先,用户的问题是关于领域驱动设计(DDD)的概念、实践和教程。我需要根据系统级指令和用户可见层的指令来构建我的回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 - 参考站内引用:有引用[^1]、[^2]、[^3]。 用户上一次的问题是关于DDD的概念、实践和教程。所以,我需要围绕这个主题构建回答。 参考引用: - 引用[^1]:介绍了DDD的概括性介绍,并指向编码实践文章。 - 引用[^2]:包括一个简单案例(转账需求),设计问题,使用DDD重构,以及抽象层。 - 引用[^3]:分享DDD在B端营销系统的实践,强调避免误区,如深陷概念体系。 我的回答需要结构化: 1. **概念部分**:解释DDD是什么。 2. **实践部分**:如何应用DDD,包括案例。 3. **教程部分**:推荐资源或学习路径。 4. **生成相关问题**:在最后添加相关问题。 确保使用中文回答。 现在,构建回答大纲: - **引言**:简要介绍DDD。 - **概念**:解释核心概念,如领域模型、战略设计、战术设计。 - **实践**:基于引用中的案例,描述如何使用DDD重构。 - **教程**:推荐学习资源。 - **总结**:强调关键点。 - **相关问题**:生成3-5个相关问题。 在回答中,自然地添加引用标识。例如,当引用某个观点时,在末尾加[^1]。 关于数学表达式:虽然DDD主题可能不涉及数学,但如果有公式,确保使用正确格式。但在这个上下文中,可能不需要数学表达式,除非提到某些模型或算法。所以,我可以忽略数学部分,除非必要。 生成相关问题:在最后,添加"§§相关问题§§"部分,列出3-5个问题。 现在,具体内容: - **概念**:DDD是一种软件设计方法,强调通过领域模型来解决复杂业务问题。核心是战略设计(如限界上下文、通用语言)和战术设计(如实体、值对象、聚合根)。 - **实践**:基于引用[^2]的转账案例,描述如何从传统设计转向DDD设计。避免误区,如引用[^3]所说,不要生搬硬套概念。 - **教程**:建议阅读引用[^1]提到的编码实践文章,以及其他资源如书籍(Eric Evans的《Domain-Driven Design》)、在线课程。 确保回答真实可靠:基于提供的引用,不要编造信息。 最后,生成相关问题: 1. DDD的战略设计和战术设计有什么区别? 2. 如何避免DDD实施中的常见误区? 3. DDD在微服务架构中的应用是什么? 4. 有哪些推荐的DDD学习资源? 完整回答结构: 1. 介绍DDD。 2. 详细解释概念。 3. 分享实践案例。 4. 提供教程建议。 5. 总结。 6. 相关问题。</think>### 领域驱动设计(DDD)概念、实践与教程指南 领域驱动设计(Domain-Driven Design,DDD)是一种软件设计方法论,专注于通过构建领域模型来解决复杂业务问题。它强调开发人员与领域专家协作,使用通用语言(Ubiquitous Language)来统一业务和技术概念,从而提升软件的可维护性和扩展性。DDD 的核心包括战略设计(如限界上下文、核心域)和战术设计(如实体、值对象、聚合根),这些元素共同帮助团队在业务变化中保持系统弹性[^1]。以下我将逐步介绍 DDD 的概念、实践和教程资源,确保回答基于可靠参考和实际案例。 #### 1. **DDD 核心概念** DDD 的核心在于将软件设计与业务领域紧密结合,避免技术实现与业务需求脱节。关键概念包括: - **战略设计**:定义业务边界,如限界上下文(Bounded Context),它划分不同业务领域,防止模型污染。例如,在电商系统中,“订单”和“库存”可能属于不同限界上下文[^2]。 - **战术设计**:实现领域模型的具体元素: - **实体(Entity)**:具有唯一标识的对象(如用户ID)。 - **值对象(Value Object)**:无标识的对象,仅通过属性定义(如地址)。 - **聚合根(Aggregate Root)**:管理一组相关对象的入口点,确保业务规则一致性。 - **领域服务(Domain Service)**:封装跨多个实体的业务逻辑。 - **通用语言(Ubiquitous Language)**:开发团队和领域专家共享的词汇表,确保沟通一致。例如,在银行系统中,“转账”操作应使用业务术语而非技术术语[^1][^2]。 这些概念不是生搬硬套的,而是从业务中抽象出来的。引用[^3]强调,DDD 的精髓在于理解业务本质,而非过度设计概念体系。否则,会增加系统复杂性,适得其反。 #### 2. **DDD 实践:从案例到应用** 实践 DDD 时,需从业务问题出发,逐步重构模型。以下基于引用[^2]的转账案例,说明如何应用 DDD。 **案例背景**:转账需求涉及用户账户间的资金转移。传统设计可能导致代码耦合,如违反单一职责原则(一个类处理数据库、逻辑和外部服务)。 **问题分析**: - 传统设计:直接调用数据库和第三方支付服务,业务逻辑分散,难以测试。 - 违反原则:高耦合导致修改风险(如添加新支付方式需改动多处)[^2]。 **使用 DDD 重构**: 1. **抽象数据存储层**:创建仓储(Repository)接口,隔离数据库操作。例如,定义 `AccountRepository` 接口,隐藏具体实现。 2. **抽象第三方服务**:通过适配器模式封装支付网关,确保领域模型不依赖外部细节。 3. **封装业务逻辑**:在聚合根中实现核心规则。例如,`TransferService` 作为领域服务,处理转账逻辑: - 检查账户余额。 - 确保事务一致性(通过聚合根管理)。 - 使用通用语言定义方法名(如 `transferFunds(fromAccountId, toAccountId, amount)`)。 4. **重构后的架构**: - 分层设计:用户界面层 → 应用层(协调服务) → 领域层(业务逻辑) → 基础设施层(数据库/外部服务)。 - 结果:代码更清晰,测试更容易,业务变化时只需修改领域层[^2]。 **实践建议**: - **避免误区**:引用[^3]指出,不要深陷概念体系(如过度讨论聚合根 vs. 值对象)。DDD 是工具,应从业务出发灵活取舍。例如,在 B 端营销系统中,简化事件模型,聚焦核心域。 - **适用场景**:DDD 适合复杂业务系统(如金融、电商),其中业务规则频繁变化。在微服务架构中,限界上下文可自然映射到服务边界[^1][^3]。 - **挑战**:团队协作是关键,需定期与领域专家对齐通用语言。 #### 3. **DDD 教程资源与学习路径** 学习 DDD 应结合理论和实践。推荐以下资源: - **入门教程**: - 阅读引用[^1]提到的[领域驱动设计(DDD)编码实践文章](链接),它通过实际案例和代码示例(如 Java/Python),深入讲解战术设计模式。 - 免费资源:Martin Fowler 的博客或 DDD 社区网站,提供基础概念解析。 - **书籍**: - 《领域驱动设计:软件核心复杂性应对之道》(Eric Evans 著):经典著作,覆盖战略和战术设计。 - 《实现领域驱动设计》(Vaughn Vernon 著):侧重实践,包含代码示例。 - **在线课程**: - Udemy 或 Coursera 的 DDD 课程(如 “Domain-Driven Design in Practice”),结合项目练习。 - **实战建议**: - 从小项目开始:尝试重构一个简单模块(如用户管理),应用限界上下文和聚合根。 - 工具支持:使用框架如 Spring Boot(Java)或 Django(Python)实现 DDD 分层。 - 社区参与:加入 DDD 论坛或 Meetup,分享经验[^1][^2]。 #### 总结 DDD 的核心是通过领域模型桥接业务与技术,但成功取决于团队对业务的抽象能力,而非生硬套用概念[^3]。实践时,优先解决业务问题,逐步引入战术元素。如果您是初学者,建议从引用[^1]的编码实践文章入手,再结合书籍深化理解。DDD 不仅能提升代码质量,还能在微服务等现代架构中发挥优势[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值