领域驱动设计理论实践

news2024/9/21 23:19:29

战略设计

战略设计是将“混沌”解构成“清晰”的过程,在该过程从开始到结束的历程之中,我们会划分出领域、界定通用语言范围、确定出系统限界上下文以及上下文之间的映射方式。

在这里插入图片描述

领域划分

战略设计在领域驱动设计中起着关键作用,因为其决定了前进的方向是否正确;而领域划分又是在战略设计中最关键的一点,它决定了系统最终架构的合理性。

领域划分要做的事情是识别出核心域和子域。核心域是系统的内核,是系统建设的重点方向,是资源倾斜的重点;子域又分支撑子域和通用子域,可以考量是否做成基座能力。

限界上下文

限界上下文的确定过程,是系统划分的过程。当领域划分完成后,存在多个领域,哪儿些域可以独立成一个系统?哪儿些域可以在共同建设在同一系统中?就是需要在这一步进行确定的。

就如何去确定限界上下文而言,我们应该从多个方面考量:

  • 领域划分的合理性
  • 限界系统建设成本
  • 限界系统维护成本

通用语言

通常在一个系统内有自己独立的通用语言,但是更希望将其形成全域的通用语言,形成共识,这样沟通起来才会形成概念上的统一。实际落地过程中,我们应该将相关术语进行详细阐述并统一维护,沉淀为领域资产。

上下文映射

上下文映射是限界系统之间的交互方式,在战略设计阶段,应该从架构合理性的角度进行考量。

战略设计案例

这里以一个简易的交易系统来进行战略分析,涉及场景如下:

  • 用户购买商品
  • 平台进行配送
  • 用户签收商品

此时可以做出如下战略设计:
在这里插入图片描述

从场景分析作为切入点,划分出商品域、交易域、用户域和配送通用域,并形成对应的限界上下文系统;同时,考虑到域之间的连接问题,可以将商品域和交易合并为一个更大一点的交易域。

战术设计

实体

何为实体?不是由属性来定义,而是通过一连串的连续事件和标识定义的一种对象。

实体对象拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致,而对象的其他属性相对于标识而言显得并不是那么得重要,这些属性只是该实体在某种时刻或某种状态下的一种内容修饰,用来丰富实体自身。

对实体来说,标识符是非常重要的概念,就像身份证之于人一样,是实体自身的身份认证,无论实体数据流转到哪儿个系统,标识符始终不会发生改变。例如实际场景中就订单实体来说,标识符这是订单ID,订单的生命周期无论发生怎样的变化,此ID在订单的整个轨迹生涯中以及在不同上下文中的流转,始终唯一。

所以,就领域实体的建模而言,标识符是必须存在的,且不应该同数据库的标识ID混淆,因为数据库只是实体的持久化方式,对于实体的持久化,既可以选择关系型、文档型、索引型数据库,亦可以选择内存存储,数据存储介质的ID在对于领域实体而言被称为派生标识。

以订单实体来说,建模样例简略如下:

public class OrderInfo {

    private String orderId;
    private String userId;
    private String addressId;
    private BigDecimal amount;
    private OrderStatus orderStatus;
    
    /* 其他字段信息省略*/

    public String getId() {
        return this.orderId;
    }
}

对于连续性事件,订单的状态则是订单实体连续性事件的体现,经历多个状态的变迁,实体的标识并不会发生变化,变化的知识实体用有的内容和处于某个阶段的状态。

值对象

何为值对象?一种描述了某种特征或属性,但没有概念标识的对象。

值对象是对实体属性的一种组合,进行概念化统一。它将多个相关属性组合为一个概念整体,可以保证属性归类的清晰和概念的完整性,避免属性零碎的散落在实体中。

值对象在代码中有两类形态存在:

  • 如果值对象是单一属性,则直接定义为实体类的属性。比如一个‘Integer’属性,一个枚举属性,都可以称之为值对象,索引从值对象的角度来看对于实体的基本类型属性而言,要定义为包装类型对象。
  • 如果是多个散落属性的组合概念,则会设计为一个类。比如金额,可以有金额大小和单位两个属性,从本质上来将其和‘Integer’一样,只不过‘Integer’已经被封装成了对象,而对于这里的‘金额’而言需要我们自己封装。

对于订单而言,金额值对象‘Amount’和用户id值对象‘addressId’如下所示:

public class OrderInfo {

    private String orderId;
    private String userId;
    private String addressId;
    private Amount amount;
    private OrderStatus orderStatus;
    
    /* 其他字段信息省略*/

    public String getId() {
        return this.orderId;
    }

    public static class Amount {
    	private BigDecimal amount;
    	private String unit;
    }
}

就值对象的定义而言,我们应该根据值对象的属性和特征进行业务语义的归类,语义内聚,避免属性无规则平铺且语义泛化的值对象存在;当然从领域建模角度,我们应尽量用值对象来承载业务,减少实体的数量,这样可以降低业务模型的复杂度。

就数据库存储而言,属性组合的值对象持久化可以是实体的某个属性,以序列化形式(如JSON)的存在,当然亦可以根据具体的业务评估来决策将其定义为仿真实体(非真实实体),用专门的表来存储。

实体和值对象是领域中最基础核心的对象,基于这些基础领域对象,可以衍生出多样化的业务领域逻辑。

聚合

何为聚合?聚合就是一组相关对象的集合,我们把聚合作为数据修改的单元,外部对象只能引用聚合中的一个成员,我们把它称为根。

从持久化的角度而言,在聚合的边界之内应用一致性规则,即保证聚合边界内的事务一致性;从查询的角度而言,可以查询出整个聚合,也可以查询出部分实体,整个需要根据实际业务情况和性能情况的角度来考量。

订单的聚合简略如下:

public class OrderInfo extends Aggregate<String> {
    private String orderId;
    private String userId;
    private String addressId;
    private BigDecimal amount;
    private OrderStatus orderStatus;
    private List<OrderDetail> orderDetailList;
    private OrderExtentInfo orderExtentInfo;
}

此时的聚合根为订单,聚合的相关对象包含:订单实体、子订单实体、订单扩展实体。

工厂

工厂是一种针对对象和聚合复杂创建逻辑的封装抽象,但不承担领域模型中的职责;其同设计模型中的工厂一样,是生产目标对象的一种方式。

在实际落地应用中,通常存在两种工厂概念的应用:

  • 实体、聚合、值对象等内部属性为参数进行构建对象,明确标识对象创建需要的参数;
  • 复杂对象生产,如静态编译、动态组合(运行依赖)等;

第一种方式以简单的查询条件为例,如下所示:

public class OrderQueryCondition {

    private String userId;
    private String orderId;
    private OrderStatus orderStatus;

    @DomainFactory
    public static OrderQueryCondition build(String userId, String orderId, OrderStatus orderStatus) {
        OrderQueryCondition condition = new OrderQueryCondition();
        condition.setUserId(userId);
        condition.setOrderId(orderId);
        condition.setOrderStatus(orderStatus);
        return condition;
    }
}

第二种方式以领域事件为例,如下所示:

@DomainFactory
public class OrderEventModelFactory {

    public static OrderEventModel build(OrderEventAction action, OrderInfo orderInfo) {

        OrderEventModel event = new OrderEventModel();
        event.setAction(action);
        event.setUserId(orderInfo.getUserId());
        event.setOrderId(orderInfo.getOrderId());
        event.setAddressId(orderInfo.getAddressId());
        event.setAmount(orderInfo.getAmount());
        event.setOrderStatus(orderInfo.getOrderStatus());
        return event;
    }
}

资源库

资源库通常是针对聚合设计,对聚合的存储和查询行为进行有效管理,可以是关系型数据库、索引、文档型数据库、内存数据库以及集合存储等。

在实际应用中,也就是我们通常见到的仓库(repository),遵循不同的实践方式存在不同的形式:

  • 从聚合的角度,资源库同聚合进行一对一映射,即一个聚合一个资源库;
  • 从实体的角度,资源库同实体进行一对一映射,即一个实体一个资源库;

聚合的角度资源库定义案例如下:

public interface OrderInfoRepository {
    String save(OrderInfo orderInfo);
    OrderInfo queryByOrderId(String userId, String orderId);
}

实体的角度资源库定义案例如下:

public interface OrderInfoRepository {
    String save(OrderInfo orderInfo);
    OrderInfo queryByOrderId(String userId, String orderId);
}
public interface OrderDetailRepository {
    void batchInsert(List<OrderDetail> orderDetailList);
    List<OrderDetail> queryByOrderId(String orderId);
}

领域服务

何为领域服务?当领域中的某个操作过程或者转换过程不是实体或者值对象的职责时,便可以将该操作放在一个单独的接口中,即封装一个领域服务。

领域服务可以存在于如下三种方式:

  • 显著的业务用例执行过程
  • 多个领域对象的业务编排
  • 可复用的应用层逻辑下层

什么是显著的业务用例?从交易系统的用例分析而言,如创建订单、取消订单、支付订单等等就是显著的业务用例,它们的执行过程包含业务规则校验和核心逻辑处理。

那么在实践中如何合理的使用应用服务和领域服务,并将领域服务做厚,将应用服务做薄?这里建议遵循一个原则:应用服务只做跨聚合跨应用的编排,核心规则下层领域

领域事件

何为领域事件?是将领域中聚合所发生的活动行为建模成一系列离散的事件行为,通常体现在聚合的状态变更。

在实际实际中可以分为应用内事件和应用外事件,应用内事件以监听器模式处理,应用外事件以消息模式处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/431796.html

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

相关文章

使用Bazel构建前端Sass

注&#xff1a;本文假设对Bazel有一定的了解。本文基于Bazel 4.2.2 版本 在web前端领域&#xff0c;前端样式&#xff0c;web浏览器只认CSS样式语言。而CSS样式语言又过于低级。于是有人发明了更高级的语言&#xff1a;Sass[1]&#xff0c;用于生成CSS代码。 这样的方案&#x…

【C++】队列模拟问题

文章目录队列模拟问题12.7.1 ATM问题12.7.2 队列类12.7.3 Queue类的接口12.7.4 **Queue类的实现**12.7.5 是否需要其他函数&#xff1f;12.7.6 Customer类queue.hqueue.cpp12.7.7 ATM模拟main.cpp队列模拟问题 12.7.1 ATM问题 Heather银行打算在Food Heap超市开设一个自动柜员…

【C++STL精讲】vector的基本使用与常用接口

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;vector是什么&#xff1f;&#x1f337;vector的基本使用&#x1f337;vector常用函数接口&#x1f490;专栏导读 &#x1f338;作者简介&#xff1a;花想云&#xff0c;在读本科生一枚&#xff0c;致力于 C/C…

HAL库版FreeRTOS(上)

目录 FreeRTOS 简介初识FreeRTOS什么是FreeRTOS?为什么选择FreeRTOS&#xff1f;FreeRTOS 的特点商业许可 磨刀不误砍柴工查找资料FreeRTOS 官方文档Cortex-M 架构资料 FreeRTOS 源码初探FreeRTOS 源码下载FreeRTOS 文件预览 FreeRTOS 移植FreeRTOS 移植移植前准备添加FreeRTO…

浏览器断点调试说明

断点调试 断点调试面板 功能按钮介绍 描述&#xff1a;继续执行脚本 或者叫&#xff08;逐过程执行&#xff09; 快捷键 &#xff08;F8&#xff09;或者是&#xff08;Ctrl\&#xff09; 作用&#xff1a;打断点了的地方&#xff08;比如有是三个断点地方&#xff09;就会 第一…

大数据能力提升项目|学生成果展系列之四

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

13.vue-cli

单页面应用程序&#xff1a;所有的功能只在index.html中完成 vue-cli是vue版的webpack 目录 1 安装vue-cli 2 创建项目 3 使用预设 4 删除预设 5 开启项目 6 项目文件内容 6.1 node_moduls 中是项目依赖的库 6.2 public 6.2.1 favicon.ico 是浏览器页签内部…

尚融宝——整合OpenFeign与Sentinel实现兜底方法——验证手机号码是否注册功能

一、整合过程 在项目添加依赖&#xff1a;添加位置 <!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> 在需要的服务中添加启动注…

spring中常见的注解

DI(依赖注入中常见的注解) Autowired&#xff1a;按类型自动装配Resource&#xff1a;按名称或类型自动装配&#xff0c;Qualifier&#xff1a;按名称自动装配&#xff0c;Value &#xff1a;注入int、float、String等基本数据类型&#xff0c;只能标注在成员变量、setter方法上…

【Gradle-1】入门Gradle,前置必读

1、为什么要学习Gradle Gradle作为Android开发默认的构建工具&#xff0c;你的每一次编译都会用到它。招聘要求从以前的熟悉加分&#xff0c;到现在的必备技能&#xff0c;可见Gradle的重要性。 做开发这么久了&#xff0c;你是否对Gradle又爱又恨&#xff1f;是否对Gradle的…

第三章(1):自然语言处理概述:应用、历史和未来

第三章&#xff08;1&#xff09;&#xff1a;自然语言处理概述&#xff1a;应用、历史和未来 目录第三章&#xff08;1&#xff09;&#xff1a;自然语言处理概述&#xff1a;应用、历史和未来1. 自然语言处理概述&#xff1a;应用、历史和未来1.1 主要应用1.2 历史1.3 NLP的新…

【科普】PCB为什么常用50Ω阻抗?6大原因

在PCB设计中&#xff0c;阻抗通常是指传输线的特性阻抗&#xff0c;这是电磁波在导线中传输时的特性阻抗&#xff0c;与导线的几何形状、介质材料和导线周围环境等因素有关。 对于一般的高速数字信号传输和RF电路&#xff0c;50Ω是一个常用的阻抗值。 为什么是50Ω&#xff1f…

《程序员面试金典(第6版)》面试题 10.09. 排序矩阵查找(观察法,二分法,分治算法入门题目,C++)

题目描述 给定MN矩阵&#xff0c;每一行、每一列都按升序排列&#xff0c;请编写代码找出某元素。 示例: 现有矩阵 matrix 如下&#xff1a;[[1, 4, 7, 11, 15],[2, 5, 8, 12, 19],[3, 6, 9, 16, 22],[10, 13, 14, 17, 24],[18, 21, 23, 26, 30] ]给定 target 5&…

wma格式怎么转换mp3,4种方法超快学

其实我们在任何电子设备上所获取的音频文件都具有自己的格式&#xff0c;每种格式又对应着自己的属性特点。比如wma就是一种音质优于MP3的音频格式&#xff0c;虽然很多小伙伴比较青睐于wma所具有的音质效果&#xff0c;但也不得不去考虑因wma自身兼容性而引起很多播放器不能支…

【高危】Apache Solr 代码执行漏洞(MPS-wic0-9hjb)

漏洞描述 Apache Solr 是一款开源的搜索引擎。 在Apache Solr 受影响版本中&#xff0c;由于Solr默认配置下存在服务端请求伪造漏洞&#xff0c;且SolrResourceLoader中实现了java SPI机制。当Solr以SolrCloud模式启动时&#xff0c;攻击者可以通过构造恶意的solrconfig.xml文…

几个最基本软件的环境变量配置

在Windows中配置环境变量位置&#xff1a; 控制面板->系统和安全->系统。可以点击&#xff1a;“此电脑”->“属性”直接进入。 点击“高级系统设置”->【环境变量】。在这里可以看见用户变量和系统变量&#xff0c;如果你这台机器不是你一个人使用设置为用户变量…

接口文档设计避坑指南

我们做后端开发的,经常需要定义接口文档。 最近在做接口文档评审的时候&#xff0c;发现一个小伙伴定义的出参是个枚举值&#xff0c;但是接口文档没有给出对应具体的枚举值。其实&#xff0c;如何写好接口文档&#xff0c;真的很重要。今天田螺哥&#xff0c;给你带来接口文档…

Vue学习笔记(4. 生命周期)

1. 生命周期写法&#xff08;vue2与vue3比对&#xff09; 创建前&#xff1a;vue3 setup, vue2 beforeCreate //组件创建前执行的函数 创建后&#xff1a;vue3 setup, vue2 created //组件创建后执行的函数 挂载前&#xff1a;vue3 onBeforeMount, vue2 beforeMount //挂…

FastViT: A Fast Hybrid Vision Transformer using Structural Reparameterization

FastViT: A Fast Hybrid Vision Transformer using Structural Reparameterization 论文地址&#xff1a;https://arxiv.org/pdf/2303.14189.pdf 概述 本文提出了一种通用的 CNN 和 Transformer 混合的视觉基础模型 移动设备和 ImageNet 数据集上的精度相同的前提下&#xf…

SpringBoot自动配置原理分析

前言&#xff1a; 虽然工作中一直使用的是自研的一款基于spring的框架&#xff0c;但是随着springboot在各公司的广泛使用&#xff0c;公司的一些新项目也开始逐渐使用springBoot了&#xff0c;那么springBoot的一些特性就要仔细学习一下了。 什么是自动配置&#xff1f; 还记…