7、实体和值对象:领域模型的基础单元

news2024/7/7 16:39:25

        DDD战术设计中有两个重要的概念:实体(Entity)和值对象(Value Object)。二者是领域模型中非常重要的基础领域对象(Domain Object,DO)。

从DDD战略设计到战术设计会经历从业务建模到技术落地的多个不同阶段,阶段不同,这些领域对象的形态表现也会不同。在用户旅程分析或场景分析构建领域模型时,实体和值对象是偏业务领域的,主要体现为业务属性和业务行为。而当它们从领域模型映射到代码模型时,这些领域对象会变成代码对象,这时候的我们会更关注这些领域对象的依赖关系,关注如何一起按照聚合的业务规则实现业务逻辑。当这些领域对象持久化存储到数据库时,它们的名称和状态可能又会发生变化,此时我们需要将这些领域对象转换为持久化对象(Persistent Object,PO),完成数据的持久化。

所以,理解和区分实体和值对象在不同阶段的形态很重要,形态发生了变化,我们就需要对它进行转换。这些内容与微服务设计和代码实现有着非常密切的关系。

那么,实体和值对象在领域模型中起到什么样的作用?在战术设计时又该如何将它们映射到代码模型和数据模型中去呢?这是我们这一章要重点讲解的内容。带着这些问题,我们看看能不能从文章中找到答案。

1、 实体

我们先来看看实体是什么?

在DDD的领域模型中有这样一类对象,它们拥有唯一标识符,并且它们的标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是属性,而是其延续性和标识,这种对象的延续性和标识会跨越甚至超出软件的生命周期。我们把领域模型中这样的领域对象称为实体。

没理解?没关系!请继续阅读。

  1. 实体的业务形态

在DDD不同的设计阶段中,实体的形态是不同的。

在战略设计时,实体是领域模型的一个重要对象,它是业务形态的业务对象,集多个业务属性、业务操作或行为于一体。在进行用户旅程或业务场景分析时,我们可以根据命令、业务操作或者领域事件,找出产生这些业务行为的实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。

你可以这么理解,实体和值对象是组成领域模型的基础单元。

  1. 实体的代码形态

在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务行为和业务逻辑。

DDD更强调面向对象的设计方法。这些实体类通常采用充血模型,与实体相关的所有业务逻辑都在实体类方法中实现,跨多个实体的领域逻辑则在领域服务中实现。

注意:

充血模型与贫血模型的关键差异:

在充血模型中,业务逻辑都在领域实体对象中实现,实体本身不仅包含了属性,还包含了它的业务行为。DDD领域模型中实体是一个具有业务行为和逻辑的对象。

而在贫血模型中领域对象大多只有setter和getter方法,业务逻辑统一放在业务逻辑层实现,而不是在领域对象中实现。

  1. 实体的运行形态

        实体以领域对象(DO)的形式存在,每个实体对象都有唯一的ID。

我们可以对一个实体对象进行多次修改,修改后的实体数据和原来的数据可能会大不相同。但是,由于拥有相同的ID,它们依然是同一个实体。

比如商品是商品限界上下文的一个实体,通过唯一的商品ID来标识。不管这个商品的数据如何变化,商品的ID一直保持不变,所以它始终是同一个商品。

  1. 实体的数据库形态

        与传统数据模型设计优先不同,DDD是先构建领域模型,通过场景分析找出实体对象和行为,再将实体对象映射到数据持久化对象。

在领域模型映射到数据模型时,一个实体可能对应0个、1个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。

在某些场景中,有些实体只是暂驻内存的一个运行态实体,它不需要持久化。比如,基于多个价格配置数据计算后生成的折扣实体。

而在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。比如,用户user与角色role两个持久化对象可生成权限实体,一个DO实体会对应两个持久化对象,这是一个一对多的场景。

再比如,有些场景为了避免数据库的联表查询,提升系统性能,会将客户信息customer和账户信息account两类数据保存到同一张数据库表中。客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景。

2、 值对象

        相对实体而言,值对象会更加抽象一些,在讲解概念时,我们会结合例子来讲。

我们先看一下《实现领域驱动设计》书中对值对象的定义:

        值对象是通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体,用于描述领域的某个特定方面,并且是一个没有标识符的对象。

也就是说,值对象描述了领域中的某一个东西,这个东西是不可变的,它将不同的关联属性组合成了一个概念整体。当度量和描述改变时,我们可以用另外一个值对象予以替换。它可以和其他值对象进行相等性比较,不过不是基于ID,而是基于值对象的属性。因为不可修改的特性,它不会对协作对象带来副作用。

上面这两段对于值对象定义的阐述,可能还会有些晦涩,下面用更通俗的语言把定义讲清楚。

        简单来说,值对象本质是一个属性集合,那这个集合里面有什么呢?它们是若干个基于描述目的、具有整体概念和不可修改的属性,在应用运行时,我们主要关注这些属性集的“值”。这个集合存在的意义又是什么?在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免出现零碎的属性。值对象通过抽象或标准化设计,可以采用数据冗余的方式在不同的业务领域实现数据流转。

这里举个简单的例子:

        人员实体原本包括:姓名、年龄、性别以及人员所在的省、市、县和街道等属性。这样在人员实体中,显示地址的多个属性就会显得很零碎了,对不对?

现在,我们可以将“省、市、县和街道”等属性拿出来,构成一个地址的属性集合,这个属性集合的名称就是地址值对象。

值对象的业务形态

        值对象是领域模型中的一个基础对象,它跟实体一样都来源于事件风暴所构建的领域模型,都包含若干个属性,并与实体一起构成聚合。

下面我们不妨对照实体来看值对象的业务形态,这样就更好理解了。

        实体和值对象都是若干属性的集合。实体一般是看得到、摸得着的实实在在的业务对象,具有业务属性、业务行为和业务逻辑。而值对象虽然也是若干个属性的集合,但它只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上你仍然可以认为它是实体属性的一部分,用于描述实体的特征。

        在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的、提供查询服务的数据类微服务,比如数据字典。

值对象的代码形态

        值对象在代码中有这样两种形态。如果值对象是单一属性,则直接定义为实体类的属性。如果值对象是属性集合,则将它设计为值对象类,这个类将具有整体概念的多个属性归集到属性集合,这样的值对象类在代码中有更好的独立性和复用性。

值对象的运行形态

        值对象的运行形态与业务形态和代码形态基本一致,它们在运行时是不可变的对象。由于不可变性,值对象是线程安全的,可以在多个线程中共享,从而提高系统性能。

鉴于值对象比实体更轻量级、高性能且线程安全,所以一般建议将领域对象优先设计为值对象,而非实体。你可以对照着以上这些优劣势,结合你的业务场景,好好想一想。如果在你的业务场景

中,值对象的这些劣势都可以避免掉,那就请放心大胆地使用值对象吧。

3、 实体和值对象的关系

        实体和值对象都是微服务底层的最基础的领域对象,一起实现领域模型最基本的核心领域逻辑。值对象和实体在某些场景下可以互换。

        其实,很多DDD专家在某些场景下,也很难判断到底应该将领域对象设计成实体还是值对象。可以说,值对象在某些场景下可以带来很好的价值,但并不是所有场景都适合值对象。你需要根据团队的设计和开发习惯,以及上面的优势和局限分析,选择最适合的实现方式。

        另外,很多值对象的数据可能来源于其他聚合,它们以数据冗余的方式完成不同领域中数据的流转和共享。在值对象的数据源头聚合,以实体或聚合根的形式存在,完成实体和数据的集中维护和生命周期管理。而在自己的聚合中它则以值对象的形式存在,被聚合内的某一个实体引用。例如:在订单聚合中,订单实体有收货地址这个值对象。在生成订单实体时,会从个人中心的客户聚合中,获取地址实体数据组合成订单聚合的地址值对象。订单实体可以整体引用和修改地址值对象的数据,但不允许单独修改地址值对象的某一个属性数据,如street。所有地址数据的新增和修改等维护操作,都只能在客户聚合中完成,这样就可以实现业务职责的高内聚,也就是说,如果你要修改某个业务行为或数据,只需要修改一处就可以了。

4、 本章小结

        本章介绍了DDD中两个非常重要的基础概念——实体和值对象。它们是领域模型的基础单元,是构建领域模型的重要组成部分。理解和区分实体和值对象的不同形态和作用,对于领域建模和微服务设计至关重要。通过本章的学习,希望你能够更好地理解实体和值对象的概念,并在实际项目中合理应用它们。

==============

举例说明:

广告业务中的实体和值对象示例

实体(Entity)
  1. 广告活动(AdCampaign)

    • 唯一标识符:每个广告活动都有一个唯一的ID。
    • 独立生命周期:广告活动可以创建、修改、暂停和结束。
    • 业务意义:广告活动的状态和属性变化对业务有重要影响。
    public class AdCampaign {
        private final String campaignId;
        private String name;
        private double budget;
        private LocalDateTime startDate;
        private LocalDateTime endDate;
    
        public AdCampaign(String campaignId, String name, double budget, LocalDateTime startDate, LocalDateTime endDate) {
            this.campaignId = campaignId;
            this.name = name;
            this.budget = budget;
            this.startDate = startDate;
            this.endDate = endDate;
        }
    
        // Getter and Setter methods
    }
    
  2. 广告客户(Advertiser)

    • 唯一标识符:每个广告客户都有一个唯一的ID。
    • 独立生命周期:广告客户的资料可以独立管理,包括创建、更新和删除。
    • 业务意义:广告客户的信息对业务的管理和广告活动的执行有重要影响。
    public class Advertiser {
        private final String advertiserId;
        private String name;
        private ContactInfo contactInfo;
    
        public Advertiser(String advertiserId, String name, ContactInfo contactInfo) {
            this.advertiserId = advertiserId;
            this.name = name;
            this.contactInfo = contactInfo;
        }
    
        // Getter and Setter methods
    }
    
  3. 广告订单(AdOrder)

    • 唯一标识符:每个广告订单都有一个唯一的ID。
    • 独立生命周期:广告订单有独立的生命周期,可以创建、更新和删除。
    • 业务意义:广告订单的状态和属性变化对业务有重要影响。
    public class AdOrder {
        private final String orderId;
        private String campaignId;
        private String advertiserId;
        private List<AdSlot> adSlots;
        private LocalDateTime orderDate;
    
        public AdOrder(String orderId, String campaignId, String advertiserId, LocalDateTime orderDate) {
            this.orderId = orderId;
            this.campaignId = campaignId;
            this.advertiserId = advertiserId;
            this.orderDate = orderDate;
            this.adSlots = new ArrayList<>();
        }
    
        // Getter and Setter methods
    }
    
  4. 设备(Device)

    • 唯一标识符:每个设备都有一个唯一的ID。
    • 独立生命周期:设备的状态和属性可以独立管理。
    • 业务意义:设备在广告投放中的状态和配置对业务有重要影响。
    public class Device {
        private final String deviceId;
        private String location;
        private List<AdSlot> adSlots;
    
        public Device(String deviceId, String location) {
            this.deviceId = deviceId;
            this.location = location;
            this.adSlots = new ArrayList<>();
        }
    
        // Getter and Setter methods
    }
    
值对象(Value Object)
  1. 联系方式(ContactInfo)

    • 无唯一标识符:联系方式只是信息的集合,不需要唯一标识符。
    • 不可变性:联系方式一旦设定通常不会改变,如果需要修改,可以创建一个新的联系方式值对象。
    • 描述特征:用于描述广告客户的联系方式。
    public class ContactInfo {
        private final String email;
        private final String phone;
    
        public ContactInfo(String email, String phone) {
            this.email = email;
            this.phone = phone;
        }
    
        public String getEmail() {
            return email;
        }
    
        public String getPhone() {
            return phone;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ContactInfo that = (ContactInfo) o;
            return email.equals(that.email) && phone.equals(that.phone);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(email, phone);
        }
    }
    
  2. 预算金额(BudgetAmount)

    • 无唯一标识符:预算金额只是一个数值,不需要唯一标识符。
    • 不可变性:预算金额一旦设定就不会改变,如果需要调整预算,可以创建一个新的预算金额值对象。
    • 描述特征:用于描述广告活动的预算。
    public class BudgetAmount {
        private final double amount;
    
        public BudgetAmount(double amount) {
            this.amount = amount;
        }
    
        public double getAmount() {
            return amount;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            BudgetAmount that = (BudgetAmount) o;
            return Double.compare(that.amount, amount) == 0;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(amount);
        }
    }
    
  3. 时间段(TimePeriod)

    • 无唯一标识符:时间段只是时间点的组合,不需要唯一标识符。
    • 不可变性:时间段一旦设定就不会改变,如果需要修改,可以创建一个新的时间段值对象。
    • 描述特征:用于描述广告投放的时间段。
    public class TimePeriod {
        private final LocalDateTime startTime;
        private final LocalDateTime endTime;
    
        public TimePeriod(LocalDateTime startTime, LocalDateTime endTime) {
            this.startTime = startTime;
            this.endTime = endTime;
        }
    
        public LocalDateTime getStartTime() {
            return startTime;
        }
    
        public LocalDateTime getEndTime() {
            return endTime;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TimePeriod that = (TimePeriod) o;
            return startTime.equals(that.startTime) && endTime.equals(that.endTime);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(startTime, endTime);
        }
    }
    
  4. 广告素材(AdContent)

    • 无唯一标识符:广告素材主要关注其内容,不需要唯一标识符。
    • 不可变性:广告素材一旦设定就不会改变,如果需要修改,可以创建一个新的广告素材值对象。
    • 描述特征:用于描述广告的具体内容。
    public class AdContent {
        private final String contentUrl;
    
        public AdContent(String contentUrl) {
            this.contentUrl = contentUrl;
        }
    
        public String getContentUrl() {
            return contentUrl;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            AdContent adContent = (AdContent) o;
            return contentUrl.equals(adContent.contentUrl);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(contentUrl);
        }
    }
    
结论

在广告业务中,合理地区分实体和值对象可以帮助我们更好地建模业务需求和实现系统。实体用于表示具有唯一标识符和独立生命周期的重要业务对象,而值对象用于描述这些对象的属性和特征,通过不可变性确保其一致性和可靠性。

理解和应用实体和值对象的概念,可以提高系统的可维护性和扩展性,使我们的领域模型更加清晰和健壮。

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

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

相关文章

最新抖音极速版双红包雨掘金助手

项目介绍&#xff1a; 抖音极速版目前小说里有双红包雨&#xff0c;单广告2000&#xff0c;金币1万比1&#xff0c;脚本自动看广告 设备需求&#xff1a; 安卓手机&#xff08;最高支持安卓13.0版本&#xff09; 购买后包含月卡脚本详细使用教程 百度网盘 请输入提取码百度…

SpringMVC 的工作流程和详细解释

Spring MVC&#xff08;Model-View-Controller&#xff09;框架是基于经典的 MVC 设计模式构建的&#xff0c;用于开发 Web 应用程序。下面是 Spring Boot MVC 的工作流程和详细解释&#xff1a; 1.客户端发起请求 1.客户端&#xff08;通常是浏览器&#xff09;发起 HTTP 请求…

中序遍历的两种实现——二叉树专题复习

递归实现&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right)…

K8s 集群(kubeadm) CA 证书过期解决方案

Author&#xff1a;Arsen Date&#xff1a;2024/07/04 目录 一、现象描述二、解决方案三、集群验证 一、现象描述 之前有篇文章《K8s Token 过期解决方案&#xff08;Kubeadm&#xff09;》提到了默认生成的 Token 有效期只有 24 小时&#xff0c;过期后 Token 将不可用&#…

Robust Test-Time Adaptation in Dynamic Scenarios--论文阅读

论文笔记 资料 1.代码地址 https://github.com/BIT-DA/RoTTA 2.论文地址 https://arxiv.org/abs/2303.13899 3.数据集地址 coming soon 1论文摘要的翻译 测试时间自适应(TTA)旨在使预先7训练的模型适用于仅具有未标记测试数据流的测试分布。大多数以前的TTA方法已经在…

实现统计n个数以下质数的个数

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h>int main() {int n 0;scanf("%d", &n);int sum 0;for (int i 1; i < n; i){for (int j 2; j < i; j) {if (i % j 0){sum;break;}}}printf("%d", n - sum-1);return 0; } n为输…

数字媒体技术基础之:DNG 文件

DNG&#xff08;Digital Negative&#xff09;文件是一种用于存储原始图像数据的文件格式&#xff0c;由 Adobe Systems 于2004年开发并推广。DNG 是一种开放的、非专利的原始图像格式&#xff0c;旨在为不同相机制造商提供一个统一的存储格式。DNG 文件保存了原始的、未处理的…

【C语言】刷题笔记 Day2

【笔记】 【1】局部变量不初始化&#xff0c;默认放的随机值。 1 int n0; 2 scanf("%d",&n); //13.141 【2】这里虽然输入的是一个浮点数&#xff0c;但是只取整数部分。 【3】3.156e7 表示的是3.156*10的7次方。 【4】多组输入&#xff0c;保存和不保存…

Studying-代码随想录训练营day29| 134. 加油站、135. 分发糖果、860.柠檬水找零、406.根据身高重建队列

第29天&#xff0c;贪心part03&#xff0c;快过半了(ง •_•)ง&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 134.加油站 135. 分发糖果 860.柠檬水找零 406.根据身高重建队列 134.加油站 文档讲解&#xff1a;代码随想录加油站 视频讲解&#xff1a;手撕加油站…

2.2 ROS2话题通信

场景 话题通信是ROS中使用频率最高的一种通信模式&#xff0c;话题通信是基于发布订阅模式的&#xff0c;也即&#xff1a;一个节点发布消息&#xff0c;另一个节点订阅该消息。话题通信的应用场景也极其广泛&#xff0c;比如如下场景&#xff1a; 机器人在执行导航功能&#…

5个文章生成器免费版,自动写作文章更轻松

在这个信息如洪流般涌动的时代&#xff0c;写作所具有的重要性不言而喻。不管是学生需要完成的作业&#xff0c;还是职场人士得提交的报告&#xff0c;亦或是自媒体创作者必须输出的内容&#xff0c;都迫切要求我们具备一定的写作技能。然而&#xff0c;写作对很多人来说&#…

基于 STM32 的智能睡眠呼吸监测系统设计

本设计的硬件构成&#xff1a; STM32F103C8T6单片机最小系统板&#xff08;包含3.3V稳压电路时钟晶振电路复位电路&#xff08;上电自复位&#xff0c;手动复位&#xff09;&#xff09;&#xff0c;心率传感器、气压传感器、液晶显示、按键、蜂鸣器、LED灯、蓝牙模块组合而成…

Nettyの网络聊天室扩展序列化算法

1、网络聊天室综合案例 客户端初始代码&#xff1a; Slf4j public class ChatClient {public static void main(String[] args) {NioEventLoopGroup group new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER new LoggingHandler(LogLevel.DEBUG);MessageCodecSharabl…

2024-07-04 base SAS programming学习笔记8(HTML)

当使用ODS来进行结果或数据集输出的时候&#xff0c;可以同时设置多个ODS 命令&#xff0c;同时输出到多个不同的文件。使用_ALL_ 表示关闭所有的ODS输出窗口&#xff0c;比如&#xff1a; ods html file(body)"html-file-pathname"; ods html file"pdf-file-pa…

【Ubuntu24.04无显示器远控】【Todesk远程桌面黑屏】【Linux虚拟显示器】解决方案

1️⃣版本 Ubuntu 24.04Todesk 4.7.2.0xserver-xorg-video-dummy 1:0.4.0-1build1 2️⃣安装配置虚拟显示器 sudo apt install xserver-xorg-video-dummy编辑/etc/gdm3/custom.conf&#xff0c;关闭Ubuntu24.04Wayland切换为X11 WaylandEnablefalse /usr/share/X11/xorg.con…

Python 插入、替换、提取、或删除Excel中的图片

Excel是主要用于处理表格和数据的工具&#xff0c;我们也能在其中插入、编辑或管理图片&#xff0c;为工作表增添视觉效果&#xff0c;提升报告的吸引力。本文将详细介绍如何使用Python操作Excel中的图片&#xff0c;包含以下4个基础示例&#xff1a; 文章目录 Python 在Excel…

三菱PLC标签使用(I/O的映射)与内容

今天&#xff0c;小编继续开始三菱PLC的学习&#xff0c;今天的内容是标签及其标签的内容说明&#xff0c;如果对你有帮助&#xff0c;欢迎评论收藏。 标签的种类&#xff0c;等级&#xff0c;定义 种类 三菱3U的PLC的种类分别为二种&#xff1a;全局标签与局部标签 全局标签…

新火种AI|AI搜索挑战百度谷歌,重塑信息检索的市场?

作者&#xff1a;一号 编辑&#xff1a;美美 AI正在颠覆传统的搜索引擎市场。 随着ChatGPT等大型语言模型的火爆&#xff0c;AI搜索技术成为了公众和业界关注的焦点。这些技术不仅能够提供快速、准确的信息检索&#xff0c;还能够通过自然语言处理技术理解用户的复杂查询&am…

Java项目:基于SSM框架实现的毕业论文管理系统【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的毕业论文管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能…

【全网最全】2024年APMCM第十四届亚太地区大学生数学建模竞赛(中文赛项)完整思路解析+代码+论文

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…