领域驱动设计-领域层

领域驱动设计标准分层架构

四层模型演变

当前,业界比较通用的DDD架构采用的是四层模型,从下到上依次为基础设施层、领域层、应用层和用户接口层。具体的分层架构见图。

这里我们主要聊聊领域层设计。

领域层

领域层,亦称模型层,属于业务系统中最核心的一层,整个系统中几乎所有的业务逻辑均会在该层实现。本层主要包含领域模型和领域服务。

(1)领域模型

领域模型用来抽象复杂的业务逻辑,将其转换为便于理解的概念图模型,一般由实体和值对象构成。它与数据模型的不同点在于:数据模型描述的是对象的持久化方式,而领域模型表述的是领域中各个类,以及各类之间的关系。

(2)领域服务

领域服务可以认为是领域模型的一种补充,因为在实际建模过程中,一些概念本质上是一些操作,它们会涉及到多个领域对象,并且需要协调这些领域对象来完成这个操作,如果强行将这个操作归类到某个对象,那么这个对象就会承担一些本不属于它的职责,进而出现对象职责不明确的现象,此时,就需要领域服务来承载这些操作,用来串联多个领域对象。比如,在书籍管理项目中,我们在建模时,考虑到合理性将书籍和章节分别定义为了单独的实体。当作者要发布书籍新章节内容时,我们需要同时新增章节内容 和 更新书籍章节总数信息,这时我们便引入了领域服务,用来承载聚合操作。

如下是一个用户转账的领域服务,涉及2个领域对象,一个是源账号,另一个是目标账号。

  public class AccountTransferService {
        public void transfer(Account source,Account target,Money money) {

        }
    }

DDD几个核心领域概念

实体 —— 唯一标识

实体对应的英语单词为Entity。提到实体,你可能立马就想到了代码中定义的实体类。在使用一些ORM框架时,比如Entity Framework,实体作为直接反映数据库表结构的对象,就更尤为重要。特别是当我们使用EF Code First时,我们首先要做的就是实体类的设计。在DDD中,实体作为领域建模的工具之一,也是十分重要的概念。

但DDD中的实体和我们以往开发中定义的实体是同一个概念吗?
不完全是。在以往未实施DDD的项目中,我们习惯于将关注点放在数据上,而非领域上。这也就说明了为什么我们在软件开发过程中会首先做数据库的设计,进而根据数据库表结构设计相应的实体对象,这样的实体对象是数据模型转换的结果。
在DDD中,实体作为一个领域概念,在设计实体时,我们将从领域出发。

实体是一个具有身份和连贯性的概念,它具有以下几个特征:

  •  实体是数据(属性)和行为(业务逻辑关系)的结合体;
  • 每个实体都有自己的唯一标识,判断两个实体对象是否相等,是通过唯一标识来判断的。比如,两个实体对象,如果唯一标识相等,即使其他属性不相等,这两个实体也会认为是同一个。实体的其他属性不相等,表征的是同一个实体在其生命周期的不同阶段。
  •  实体的唯一标识属性值是不可变的,其他属性值是可变的。

值对象 —— 不变性

值对象一般会作为一个属性存放于一个实体内部,它具有以下几个特征:

  • 值对象不需要唯一标识,判断两个值对象是否相等,是通过值对象内部所有属性值是否相等来判断的。
  • 值对象的属性值是不允许变化的,即值对象的实体在创建之后就不会变了,如果要改变其属性值,就需要先把此对象删除,然后重新创建一个新对象。

订单对象中的商品信息、地址信息就是值对象。

书籍对象中的书籍分类,书籍标签等就是值对象。

聚合

聚合是一组具有内聚关系的领域对象(包括实体和值对象)的集合,这里的一组可以是一个或多个实体。每个聚合都会有一个根实体(亦称聚合根),它主要用来和外界交互,即外部对象如果想访问聚合内的实体,必须先访问聚合根,然后聚合根再和内部要访问的实体进行交互。

还是拿小说平台项目举例说明,一篇小说,它包含小说基础信息 [标题,封面,分类,书签,作者,书源等信息],这一组合就是一个聚合,其中,“小说信息”可以设置为这个组合的聚合根。

注意:

聚合内的内容具有一致性,即:需要在事务中修改一个聚合的内容。如果没有一致性要求,那么应该就不属于一个聚合。
通过唯一标识来引用其他聚合或实体。
如果聚合创建复杂,推荐使用工厂方法来屏蔽内部复杂的创建逻辑。
在传统数据模型中,一般认为每个实体都是对等的,可以单独修改任意一个实体;在DDD中,聚合内对象的修改必须按照统一的业务规则来完成,聚合是数据修改、持久化的基本单元。

聚合设计的原则:

设计小聚合。小聚合可以降低数据冲突,规避业务过大。
通过唯一标识引用其他聚合。
聚合内保持数据强一致,聚合外保持数据最终一致。
通过应用层实现跨聚合调用。

聚合代码
public class Aggregate<R extends Versionable> {
    protected R root;
    protected R snapshot;
    protected DeepComparator deepComparator;

    Aggregate(R root, DeepCopier copier, DeepComparator deepComparator) {
        this.root = root;
        this.snapshot = (Versionable)copier.copy(root);
        this.deepComparator = deepComparator;
    }

    public R getRoot() {
        return this.root;
    }

    public R getRootSnapshot() {
        return this.snapshot;
    }

    public boolean isChanged() {
        return !this.deepComparator.isDeepEquals(this.root, this.snapshot);
    }

    public boolean isNew() {
        return this.root.getVersion() == 0;
    }
}

什么是聚合根 

简单的理解就是把关联紧密的实体放到一起,对外提供统一的访问,外界不能直接访问内部的实体。

--作者“杰克”

举例解释 

简单的理解  聚合根就是一个泛型List,比如:学生有基本信息,班级信息;那么我们就把 “学生”当做聚合根,  这个学生聚合根里有有学生的基本信息和班级信息。

 官方解释 

聚合是一个关联对象的集群,我们将其作为一个单元来处理数据更改。每个集合都有一个根和一个边界。边界定义了聚合内部的内容。根是聚合中包含的单个特定实体。
根是聚合中唯一允许外部对象保存对[.]的引用的成员。
聚合根具有关联实体的ID,关联实体,以及对关联实体的行为,将关联比较紧密的实体组成一个整体,对外提供统一的访问。

怎样设计聚合?

DDD 领域建模通常采用事件风暴,它通常采用用例分析、场景分析和用户旅程分析等方法,通过头脑风暴列出所有可能的业务行为和事件,然后找出产生这些行为的领域对象,并梳理领域对象之间的关系,找出聚合根,找出与聚合根业务紧密关联的实体和值对象,再将聚合根、实体和值对象组合,构建聚合。

下面我们以小说阅读场景为例,看一下聚合的构建过程主要都包括哪些步骤。

仓储

首先说明下仓储被设计出来的初衷,在领域模型中,对象被创建出来后一般会在内存中活动,待其不活动了后,需要将其进行持久化存储。然后,当我们需要重建对象时,需要根据对象当前状态进行重建。可见这整个过程中,会频繁的与数据库(广义的数据库,包括关系型数据库、NoSql数据库等)打交道,进行对象的创建、组装等。因而,能否提供一种机制,帮助我们管理领域对象以及做对象持久化,仓储并应运而生了。

仓储,又称资源库,它具有以下几个特征:
  • 仓储是连接领域层和基础设施层的桥梁,一般将仓储接口定义放在领域层,仓储的具体实现放在基础设施层。这样做的好处是:解耦了领域层与ORM之间的联系,任何ORM相关的变更,只需要修改仓储的实现便可,对于领域层仓储接口的定义一般是不需要做修改的。
  • 仓储里面存储的对象一定是聚合,因为领域模型中都是以聚合来划分业务边界的,所以在实际应用中,我们只会对聚合设计仓储。同理,我们在仓储中做数据更新、删除等操作时,应该以聚合为单位进行操作,而不是仅操作聚合中的某一个实体。
   仓储接口定义 
/**
 * 仓储范型接口
 *
 * @author yangyanping
 * @date 2023-07-20
 */
public interface Gateway<K, T extends Versionable> {
    Aggregate<T> getByKey(K key);

    void store(Aggregate<T> aggregate);

    void remove(Aggregate<T> aggregate);
}

Factory(工厂模式)

在创建对象时,有些聚合需要实体或值对象较多,或者关系比较复杂,为了确保聚合内所有对象都能同时被创建,同时避免在聚合根中加入与其本身领域无关的内容,一般会将这些内容交给Factory处理。
Factory的主要作用:封装聚合内复杂对象的创建过程,完成聚合根、实体、值对象的创建。

工厂类接口代码:

public interface DomainFactory<T> {
}

DDD学习

DDD(领域驱动设计)_ddd领域模型设计_小飞哥wzf的博客-CSDN博客

领域驱动设计(DDD)在有赞教育线索资源管理的实践

DDD-经典四层架构应用_ddd四层架构_是下雨天啊的博客-CSDN博客

迄今为止最完整的DDD实践_阿里技术的博客-CSDN博客

13 | 代码模型(上):如何使用DDD设计微服务代码模型?_-停泊的博客-CSDN博客

【实践篇】手把手教你落地 DDD | 京东云技术团队_业务_架构_Service 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值