领域驱动设计(五) - 战术设计 - 【2/3】领域服务、事件、模块

news2024/11/26 8:26:31

一、领域服务

当操作不适合放在聚合和值对象上时,最好的方式就是使用领域服务。领域服务是一个无状态的操作,一个领域服务有可能操作多个领域对象,它用于实现特定于某个领域的任务。领域服务需要处理逻辑,不建议做为soap接口对外直接暴露。一般在下列情况下可以抽取服务,否则还是放在聚合或实体中会比较合适:

  • 执行一个显著的业务操作过程;
  • 对领域对象进行转换;
  • 以多个领域对象作为输入进行计算,输出一个值对象;

1.1、领域服务设计

值得注意的是,过度的使用领域服务会导致失血的领域模型,即所有的业务和逻辑都位于领域服务中。java实现时领域服务表现为一个接口或一个服务类,使用类还是接口取决于业务,如果业务不存在多态直接用实现类可能会更方便一些。

虽然接口有利于概念定义和解耦,但如果采用依赖注入、工厂,把领域服务作为参数传递时这两者就没有太大的区别了,这时可以从测试是否方便的角度来考虑采用哪种落地实现。领域服务一般会有以下几种实现方案:

  • 服务工厂,不建议使用抽象工厂,原因是不利于表述业务;
  • 接口实现,接口和实现分离;
  • 构造函数注入实现,即直接用实现类,最方便测试;

实现领域服务时需要注意三点:1、消息去重;2、幂等操作;3、顺序执行;

1.2、领域服务测试

领域服务是针对特定业务的,所以建议采用单元测试的方式来验证,同时注意封装性即可,如下例;

public void testAuthenticationSuccess() throws Exception {
    User user = this.userAggregate();
    DomainRegistry
        .userRepository()
        .add(user);

    UserDescriptor userDescriptor =
        DomainRegistry.authenticationService() .authenticate(
                    user.tenantId(),
                    user.username(),
                    FIXTURE_PASSWORD);

    assertNotNull(userDescriptor);
    assertFalse(userDescriptor.isNullDescriptor());
    assertEquals(userDescriptor.tenantId(), user.tenantId());
    assertEquals(userDescriptor.username(), user.username());
    assertEquals(userDescriptor.emailAddress(), user.person().emailAddress().address());
}

领域服务应用服务

    应用服务负责提问题,主要职责是处于安全和事务,领域服务负责回答应用服务的问题,主要职责是处理业务逻辑

二、领域事件

将领域中所发生的活动建模成一系列的离散事件,每个事件都用领域对象来表示,它表示领域中所发生的事情,这就是领域事件。领域事件是领域模型的的重要组成部分,用来维护事务一致性,也可支持聚合(聚合设计的原则之一就是在单个事务中,只允许对一个聚合实例进行修改,关联的聚合修改必须在单独的事务中完成)。

领域事件可发生在同一个限界上下文中,也可发生在多个限界上下文中,在同一个上下文中可以采用类似springEvent的方式实现,在多个限界上下文中多数采用基于消息(mq)的方式实现。领域事件设计方案如下:

领域事件可以由聚合、领域服务甚至是用户发布,典形的适用场景如下:

  • 聚合发出:比如在构造函数中创建后发出;
  • 领域服务:适合把把部分业务过程封装成领域服务时,这时可以采用事件通知的方式触发;
  • 用户发出:由用户从操作端发布,由领域服务接收,创建再发布;

2.1、领域事件例子

为了更好的理解领域事件,下面讲述一个由聚合发布领域事件的例子,使大家有一个理性的感知:

事件模型声明

public interface DomainEvent {
    public int eventVersion();
    public Date occurredOn();
}

实例化的特定事件

/*规则1:命令要反映出执行成功之后所发生的事情###BacklogItem提交给sprint完毕后*/
class CommittedToSprinted implements DomainEvent{
    private EventId eventId;//事件标识

    /*规则2:必要的属性*/
    private Date occurredOn;//事件发生的时间
    private BacklogItemId BacklogItemid//事件发起方
    private SprintId committedToSprintId;//事件参与方
    /*事件参数*/
    private TenantId tenantId//租房ID,限定上下文
}

发布事件

//发布事件
public class Product extends Entity {
    private Set<ProductBacklogItem> backlogItems;
    public Product(TenantId aTenantId) {
    this();
    DomainEventPublisher.instance().publish(new ProductCreated(
                this.tenantId(),
                this.productId())
                );
    }
}

2.2、领域事件架构设计

笔者并不建议在系统中采用领域事件,原因是其非常复杂且难以控制,本节中笔记不会详细展开,只从宏观上带大家对事件有个整体的认识。

  • 领域命令:由用户触发,是一种请求,可被拒绝;
  • 领域事件:一般由系统或定时系统触发,是一种事实,不可被拒绝;
  • 领域快照:减少查询压力;
  • 异步事件:用于填充快照数据;

同一上下文中发布领域事件的设计

在同一领域模型中需要注意,注册要先于发布,是在领域模型的方法流中进行注册的,即要在同一线程中。这也就是一般用应用服务来注册,而不用领域服务来注册的原因,即越靠前越好;

领域事件事件存储的设计

事件存储是一个可选的模块,如果不需要,则不需要设计。如果设计了事件存储那么就需要两步:存储+转发。事件存储主要是做如下事情:

  • 在不同上下文中集成,维护一致性;
  • 检查由模型的命令方法所产生的所有结果的历史记录,跟踪BUG;
  • 使用事件存储中的数据来进行业务预测和分析;
  • 当从资源库中获取一个聚合实例时,使用事件来重建此聚合实例;
  • 撤销对聚合的操作,添加补丁来修复系统中的bug;

2.3、DDD中的事件源设计

事件源就通过事件来表示一个聚合的完整状态,这里的事件是自聚合创建以来的一系列事件。通过按照产生时的顺序重放这些事件,我们可以重建聚合的状态。这些用于重建聚合状态的事件位于同一个事件流中,事件源确保每次聚合改变的原因都不会丢失。这种设计的优缺点如下:

    优点:

  • 源于同一个事件源,且所有事件流都将被持久化到事件存储中;可用于追踪、回放、下载移植;
  • 只能向流中追加到尾部,追加后聚合状态将会进一步改变;且状态变化不可回退,这是基于事件是已经发生过的事实的约束设计的;
  • 各个事件彼此分离,一般采用根实体的唯一标识;

   缺点:

  • 需要对业务领域非常了解,同时模型又非常复杂才适合;
  • 需要相应的工具以及一致的知识体系支持;比如事件序列器工具、协议生成工具(事件定义契约、事件发布契约)等;
  • 必须采用CQRS架构,学习成本和实际成本会很高;
  • 需要不同的开发、构建、部署方案;

三、模块

模块并不是领域的一部分,但却有必要把模块名称做为通用语言在团队范围内同步,模块表示了一个命名的容器,用于存放领域中内聚在一起的类以达到更粗粒度松耦合的目的。模块是一种概念,它位于限界上下文和模型之间。

模块的概念有可能对软件开发人员产生引导作用,把模块与工程结构以及工程抽包甚至应用部署联系起来,这种概念的混淆需要纠正,因为模块关注的是领域模型的设计是否合理,而工程结构需要的是在代码可读性和维护成本间做出权衡。

模块的设计原则

  • 模块应该和领域概念保持协调一致:通常对于一个或一组内聚的聚合来说,我们都可以相应地创建一个模块;
  • 根据通用语言来命名模块;
  • 不建议按功能把相同功能的类放在一起,比如工厂放在工厂包中,领域服务放在单独中;这无形中给我们的层次调用增加了限制;
  • 设计松耦合的模块,模块应该是独立和可插拔的,必要时可以使用版本设计;
  • 杜绝循环依赖,但同层次调用有时认为是允许的;
  • 不要将模块设计成一个静态的概念,最好与模型中的对象一起进行建模,随着模型的演进而演进;

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

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

相关文章

C++day7(异常处理机制、Lambda表达式、类型转换、STL标准库模板、迭代器、list)

#include <iostream>using namespace std; template <typename T> class vector { private:T* first;T* last;T* end; public:vector():first(new T),last(first),end(first){cout<<"无参构造"<<endl;}//无参构造vector(T* f):first(f),last…

如何写好代码

一、什么是好代码 抛开性能、并发、一致性等技术因素&#xff0c;好的业务代码&#xff0c;应当如一篇显浅易懂的业务叙实文章&#xff0c;满足以下几个基本条件&#xff1a; 词要达意&#xff1a;最基础的变量、函数、类的命名&#xff0c;是否名达其意。 结构清晰&#xff…

supersqli

这个题&#xff0c;其实之前做过&#xff0c;这里只是换了个名字而已 输入1&#xff0c;提交后可以明显发现url发生变化 &#xff0c;可以猜测SQL注入 源码提示sqlmap是没有灵魂的&#xff0c;说明确实是sql注入 万能密码可以可以到&#xff0c;所包含的表 利用order by查看&a…

安卓之事件分发机制

安卓之事件分发机制 简介 事件分发的”事件“是指什么&#xff1f; 答&#xff1a;点击事件&#xff08;Touch事件&#xff09;。当用户触摸屏幕&#xff08;VIew或ViewGroup&#xff09;时&#xff0c;将产生点击事件&#xff0c;即Touch事件。Touch事件的细节&#xff08;如…

SpringBoot整合MyBatisPlus入门

SpringBoot整合MyBatisPlus入门 1. MyBatisPlus概述1.1 MyBatis介绍1.2 MyBatisPlus特性 2. SpringBoot整合MyBatisPlus入门2.1 创建新模块&#xff0c;选择Spring初始化&#xff0c;并配置模块相关基础信息2.2 选择当前模块需要使用的依赖&#xff08;JDBC即可&#xff09;2.3…

【前端知识】React 基础巩固(三十一)——store数据的订阅和Redux的优化

React 基础巩固(三十一)——store数据的订阅和Redux的优化 一、store数据的订阅 store/index.js const { createStore } require("redux");// 初始化数据 const initialState {name: "test",title: "hello redux", };function reducer(state …

自然语言处理NLP介绍——NLP简介

目录 内容先进性说明内容大纲概要云服务器的使用 内容先进性说明 内容大纲概要 云服务器的使用

基于Java+SpringBoot+Vue+echarts健身房管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

工作中的二三事(非技术 向)

DB更换节点导致系统无法访问案 XX年X月X日&#xff0c;周一。spotfire所有预加载全显示在排队状态&#xff0c;end user无法打开&#xff0c;良率无法及时汇报&#xff0c;影响挺大。 背景&#xff1a; 两台spotfire服务器 处理过程&#xff1a; 开始怀疑和另一现地的情况一…

阿里云远程调用接口api

1.云市场--->api--->搜索那你想要的功能 2.举例想要天气预报功能 3.用postman进行演示

【云计算小知识】云环境是什么意思?有什么优点?

随着云计算的快速发展&#xff0c;了解云计算相关知识也是运维人员必备的。那你知道云环境是什么意思&#xff1f;有什么优点&#xff1f;云环境安全威胁有哪些&#xff1f;如何保证云环境的运维安全&#xff1f;这里我们就来简单聊聊。 云环境是什么意思&#xff1f; 云环境是…

Linux系列---【Aerospike的介绍】

Aerospike的介绍 Aerospike(以下简称AS)是一个以分布式为核心基础&#xff0c;可基于行随机存取内存中索引、数据或SSD存储中数据的数据库。它主要用于百G、数T等大数据量并且在数万以上高并发情况下&#xff0c;对性能也有毫秒级读取插入要求的场景。 B站视频链接:https://www…

SWF格式视频怎么转换成AVI格式?简单的转换方法分享

当你想要在不同的设备上播放视频时&#xff0c;将SWF格式视频转换成AVI格式是非常有用的。因为SWF格式通常只能在特定的软件或网页上播放&#xff0c;而AVI格式则可以在更广泛的设备上播放&#xff0c;包括智能手机&#xff0c;平板电脑和电视机等。那么我们怎么将SWF转换成AVI…

数据库基础之——索引事务

索引&#xff08;index|key&#xff09; 1 概念 是数据库内部维护的一套数据结构&#xff0c;专门用于搜索的数据结构。目标就是提升搜索速度&#xff08;性能&#xff09;&#xff01; 数据存储的数据主要保存在硬盘上&#xff0c;维护的索引数据结构同样也保存在硬盘上。…

动手学DL——深度学习预备知识随笔【深度学习】【PyTorch】

文章目录 2、预备知识2.1、数据操作2.2、线性代数&矩阵计算2.3、导数2.4、基础优化方法 2、预备知识 2.1、数据操作 batch&#xff1a;以图片数据为例&#xff0c;一次读入的图片数量。 小批量样本可以充分利用GPU进行并行计算提高计算效率。 数据访问 数组&#xff1a;np…

mfc100u.dll丢失的4种解决方法分享,快速修复mfc100u.dll文件

在现代数字化时代&#xff0c;计算机已经成为了我们生活和工作的不可或缺的一部分。然而&#xff0c;随着软件的不断发展和更新&#xff0c;有时我们可能会遇到一些令人头疼的问题&#xff0c;例如MFC100U.DLL文件的丢失。当你第一次看到这个错误提示时&#xff0c;估计是一脸懵…

学好Elasticsearch系列-核心概念

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 文章目录 节点角色master&#xff1a;候选节点data&#xff1a;数据节点Ingest&#xff1a;预处理节点ml&#xff1a;机器学习节点remote_ cluster_ client&#xff1a;候选客户端节点transform&#xff1a;…

2022 China Open Source Report

| 翻译&#xff1a;黄绍雅、岳扬、刘文涛、李思颖 | 编辑&#xff1a;胡欣元 | 设计&#xff1a;胡欣元 As 2022 finally came to an end, we also emerged from the challenging years of the three-year-long COVID pandemic. The new edition of the "China Open Sourc…

【多选框、表格全选】element el-checkbox、el-table

话不多说 先看效果&#xff1a; 多选框&#xff1a; 表格全选&#xff1a; <template><div><div class"titleLabel"><div class"lineStyle"></div>统计部门</div><div style"display: flex"><e…

服务网格简介:探索现代微服务架构中的服务网格概念和价值

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…