领域驱动设计 (Domain-Driven Design, DDD)
文章目录
- 1. 引言
- 1.1 什么是领域驱动设计
- 1.2 为什么需要DDD
- 1.3 DDD适用场景
- 2. DDD基础概念
- 2.1 领域(Domain)
- 2.2 模型(Model)与领域模型(Domain Model)
- 2.3 通用语言(Ubiquitous Language)
- 3. 战略设计
- 3.1 限界上下文(Bounded Context)
- 3.2 上下文映射(Context Mapping)
- 3.3 大型核心拆分
- 3.4 战略设计的实践步骤
- 4. 战术设计
- 4.1 实体(Entity)
- 4.2 值对象(Value Object)
- 4.3 聚合(Aggregate)
- 4.4 领域服务(Domain Service)
- 4.5 领域事件(Domain Event)
- 4.6 仓储(Repository)
- 4.7 工厂(Factory)
- 5. DDD实现方法与模式
- 5.1 分层架构
- 5.2 六边形架构(端口与适配器)
- 5.3 命令查询职责分离(CQRS)
- 5.4 事件溯源(Event Sourcing)
- 5.5 领域服务与应用服务
- 5.6 工作单元与仓储模式
- 6. 事件风暴
- 6.1 事件风暴的目的与价值
- 6.2 事件风暴的过程
- 6.3 事件风暴的输出
- 6.4 从事件风暴到代码
- 6.5 事件风暴与其他技术的结合
- 7. DDD与其他架构模式的关系
- 7.1 DDD与微服务架构
- 7.2 DDD与整洁架构/洋葱架构
- 7.3 DDD与事件驱动架构
- 7.4 DDD与响应式架构
- 7.5 DDD与REST架构
- 8. DDD应用实例
- 8.1 电子商务系统实例
- 8.1.1 业务场景
- 8.1.2 领域分析
- 8.1.3 限界上下文划分
- 8.1.4 聚合设计示例
- 8.1.5 应用服务示例
- 8.1.6 上下文集成
- 8.2 银行系统实例
- 8.2.1 业务场景
- 8.2.2 限界上下文示例
- 8.2.3 聚合示例(账户上下文)
- 8.2.4 领域服务示例(转账服务)
- 8.2.5 CQRS实现示例(账户查询)
- 9. DDD常见挑战和解决方案
- 9.1 学习曲线陡峭
- 9.2 领域专家参与度不够
- 9.3 过度设计或过早引入复杂性
- 9.4 技术基础设施与DDD不匹配
- 9.5 限界上下文边界划分困难
- 9.6 团队结构与上下文不一致
- 9.7 遗留系统集成
- 9.8 性能优化挑战
- 10. DDD最佳实践
- 10.1 建立通用语言
- 10.2 聚焦核心域
- 10.3 模型驱动设计
- 10.4 建立清晰边界
- 10.5 演进式设计
- 10.6 测试驱动开发
- 10.7 持续集成与反馈
- 10.8 实用平衡
- 11. 总结
- 11.1 DDD的核心价值
- 11.2 何时使用DDD
- 11.3 采用DDD的路径
- 11.4 未来展望
- 10.5 演进式设计
- 10.6 测试驱动开发
- 10.7 持续集成与反馈
- 10.8 实用平衡
- 11. 总结
- 11.1 DDD的核心价值
- 11.2 何时使用DDD
- 11.3 采用DDD的路径
- 11.4 未来展望
1. 引言
1.1 什么是领域驱动设计
领域驱动设计(Domain-Driven Design,简称DDD)是一种软件开发方法,由Eric Evans在2003年出版的同名书籍中首次提出。DDD的核心理念是将业务领域模型作为软件设计的中心,通过深入理解业务领域知识,建立一套能够准确反映业务规则和流程的领域模型,从而指导软件设计和开发。
DDD不是一种技术,而是一种思想和方法论,它强调:
- 关注核心领域和领域逻辑
- 将复杂的领域模型为可管理的模块
- 领域专家和技术团队的紧密协作
- 使用通用语言进行交流
- 模型驱动的设计方法
1.2 为什么需要DDD
在传统的软件开发中,我们往往面临以下挑战:
- 业务需求与技术实现之间存在断层
- 领域知识分散在不同人员间,无法有效集成
- 复杂系统的模块边界模糊,代码高度耦合
- 业务变化导致系统难以维护和扩展
DDD提供了一套系统化的方法来应对这些挑战:
- 消除沟通障碍:建立统一的通用语言(Ubiquitous Language),帮助技术团队和业务专家更好沟通
- 处理复杂性:通过限界上下文(Bounded Context)划分复杂领域
- 聚焦核心业务:区分核心领域和支撑域,集中资源在核心业务上
- 模型驱动:使用领域模型驱动设计,保证软件系统准确反映业务规则
- 应对变化:构建柔性架构,更好适应业务变化
1.3 DDD适用场景
DDD并非适用于所有项目,它特别适合以下场景:
- 业务复杂度高:业务规则复杂、领域知识丰富的系统
- 需要长期演进:需要长期维护和不断发展的核心系统
- 团队协作要求高:需要跨职能团队紧密协作的项目
- 领域专业性强:需要深度领域知识的专业领域系统
对于简单的CRUD应用或者技术导向的项目,采用DDD可能会"杀鸡用牛刀",增加不必要的复杂性。
2. DDD基础概念
2.1 领域(Domain)
领域是指特定的业务或知识领域,它是一个组织所做的事情以及其中包含的相关规则。例如:
- 电子商务领域:包含商品、订单、支付、物流等
- 银行领域:包含账户、交易、贷款、信用卡等
- 医疗领域:包含患者、诊断、治疗、药品等
领域通常可以分解为多个子领域(Subdomain):
- 核心域(Core Domain):组织的核心竞争力所在,最具价值和独特性的部分
- 支撑域(Supporting Domain):支持核心域的业务,对组织有价值但不是核心竞争力
- 通用域(Generic Domain):各组织都需要但无差异化的业务,可以考虑购买或外包
2.2 模型(Model)与领域模型(Domain Model)
模型是对现实的一种抽象和简化,目的是为了更好地理解和解决问题。领域模型则是对特定业务领域的概念性表示,它:
- 包含业务概念、规则、流程和它们之间的关系
- 反映了领域专家对领域的理解
- 是业务人员和技术人员交流的基础
- 指导软件设计和实现
好的领域模型应该:
- 能够解释领域中的关键概念和术语
- 描述业务实体及其行为
- 捕获业务规则和约束
- 反映领域专家的心智模型
2.3 通用语言(Ubiquitous Language)
通用语言是DDD中最基础也是最重要的概念之一,它是:
- 团队成员(包括开发人员、领域专家、产品经理等)共同使用的语言
- 基于领域模型建立的一套术语和概念体系
- 在所有沟通、文档和代码中一致使用
通用语言的建立过程:
- 通过与领域专家交流,识别关键术语和概念
- 明确定义每个术语的含义,消除歧义
- 在团队中推广使用这些术语
- 将这些术语直接反映在代码设计中
- 持续精炼和丰富这些术语
示例:在电子商务系统中
- 不当用语:用户在网站上选择了一些商品并完成了支付流程
- 通用语言:顾客将商品加入购物车,提交订单并通过支付网关完成支付
通用语言的价值:
- 消除沟通障碍,减少误解
- 加深对领域的理解
- 使代码更好地反映业务概念
- 降低业务逻辑的翻译成本
在实践中,通用语言通常记录在词汇表(Glossary)中,并在团队内持续使用和演进。
3. 战略设计
战略设计(Strategic Design)是DDD的第一个主要部分,它关注"宏观层面"的设计,帮助我们定义清晰的系统边界和各个部分之间的关系。战略设计的核心概念包括限界上下文、上下文映射、通用语言等。
3.1 限界上下文(Bounded Context)
限界上下文是DDD中最核心的概念之一,它是一个边界,在这个边界内:
- 特定的领域模型有效且一致
- 通用语言在边界内保持一致的含义
- 一个特定的团队工作在其中
限界上下文的识别原则:
- 语义边界:同一个术语在不同上下文中可能有不同含义
- 团队边界:通常由一个团队负责一个或多个限界上下文
- 技术边界:可能使用不同的技术栈或数据存储
- 业务能力:通常对应一个明确的业务能力
例如,在一个电商系统中可能存在以下限界上下文:
- 商品上下文:负责商品信息管理
- 订单上下文:负责订单处理
- 支付上下文:负责支付处理
- 用户上下文:负责用户管理
- 物流上下文:负责物流配送
在不同上下文中,同一术语可能有不同含义:
- "产品"在商品上下文中指销售的商品
- "产品"在内部系统上下文中可能指公司提供的服务
限界上下文的价值:
- 降低模型复杂度
- 使设计更加内聚
- 允许不同团队独立工作
- 简化系统集成
3.2 上下文映射(Context Mapping)
当系统被划分为多个限界上下文后,我们需要定义它们之间的关系和交互方式,这就是上下文映射。
常见的上下文映射模式:
-
合作关系(Partnership):两个上下文紧密合作,共同成功
- 团队紧密协作,共同规划和开发
- 两个上下文相互依赖
-
共享内核(Shared Kernel):两个上下文共享一部分模型
- 共享部分由双方共同维护
- 改变需要双方协调
- 谨慎使用,避免过度耦合
-
客户-供应商(Customer-Supplier):上游上下文作为供应商,下游上下文作为客户
- 上游考虑下游需求但保持独立决策
- 明确定义服务协议
- 协商但不完全绑定
-
遵奉者(Conformist):下游上下文完全接受上游上下文的模型
- 下游没有话语权或影响力
- 下游完全采用上游模型,减少翻译成本
-
防腐层(Anticorruption Layer, ACL):下游上下文通过转换层与上游交互
- 保护自身模型不受外部影响
- 转换外部模型到内部模型
- 适用于与遗留系统或外部系统集成
-
开放主机服务(Open Host Service):上下文通过一组定义良好的服务对外提供功能
- 提供稳定的服务接口
- 通常与发布语言配合使用
-
发布语言(Published Language):定义标准的交流语言
- 定义清晰的数据交换格式
- 可以是XML、JSON、Protobuf等
-
各自独立(Separate Ways):决定不集成
- 两个上下文没有有意义的关系
- 重复实现优于复杂集成
上下文映射的表示:
通常使用上下文映射图来可视化表示各限界上下文之间的关系:
+-------------------+ +-------------------+
| | ACL | |
| 订单上下文 |------>| 支付上下文 |
| (Order Context) | | (Payment Context)|
+-------------------+ +-------------------+|客户-供应商V
+-------------------+ +-------------------+
| |共享内核| |
| 物流上下文 |<------>| 仓储上下文 |
| (Logistics) | | (Warehouse) |
+-------------------+ +-------------------+
3.3 大型核心拆分
随着业务发展,核心领域可能变得过于庞大和复杂。DDD提供了几种策略来处理这种情况:
-
提炼核心(Distillation):
- 识别真正的核心领域,将其与支撑子域分离
- 专注核心领域的建模和优化
- 其余部分可以简化处理
-
责任层(Responsibility Layers):
- 将领域按责任划分为多层
- 例如:基础层、政策层、操作层等
-
知识层次(Knowledge Level):
- 将通用规则与特定实例分离
- 元模型与实例模型分离
3.4 战略设计的实践步骤
实施DDD战略设计的一般步骤:
-
领域探索:
- 与领域专家密切合作
- 学习业务术语和流程
- 识别关键业务概念
-
识别子域:
- 划分核心域、支撑域和通用域
- 确定投入优先级
-
定义限界上下文:
- 基于业务能力划分
- 确保语义一致性
- 考虑团队结构
-
建立上下文映射:
- 确定各上下文间关系
- 设计集成策略
- 绘制上下文映射图
-
演进设计:
- 不断精炼模型
- 适应业务变化
- 重构限界上下文
4. 战术设计
战术设计(Tactical Design)是DDD的第二个主要部分,它关注"微观层面"的设计,提供了构建领域模型的具体构建块。战术设计帮助我们实现限界上下文内部的精确建模。
4.1 实体(Entity)
实体是领域模型中具有唯一标识的对象,它在整个生命周期中保持身份的连续性。
特征:
- 具有唯一标识(ID)
- 可变的(状态可以改变)
- 通过ID而非属性进行相等性比较
- 代表领域中有生命周期的事物
示例代码:
public class User {private final UserId id; // 唯一标识private String name;private String email;private Address address;public User(UserId id, String name, String email) {this.id = id;this.name = name;this.email = email;}public void changeName(String newName) {this.name = newName;}public void changeEmail(String newEmail) {// 可能包含邮箱格式验证逻辑this.email = newEmail;}// ID访问方法public UserId getId() {return id;}// 相等性比较基于ID@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;return id.equals(user.id);}@Overridepublic int hashCode() {return id.hashCode();}
}
4.2 值对象(Value Object)
值对象是通过其属性值而非身份定义的不可变对象。
特征:
- 无唯一标识
- 不可变(创建后不能修改)
- 通过所有属性进行相等性比较
- 代表领域中的描述性概念
示例代码:
public final class Address {private final String street;private final String city;private final String zipCode;private final String country;public Address(String street, String city, String zipCode, String country) {this.street = street;this.city = city;this.zipCode = zipCode;this.country = country;}// 值对象的修改返回新实例,不修改原对象public Address withNewStreet(String newStreet) {return new Address(newStreet, this.city, this.zipCode, this.country);}// 获取属性的访问方法public String getStreet() { return street; }public String getCity() { return city; }public String getZipCode() { return zipCode; }public String getCountry() { return country; }// 相等性比较基于所有属性@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Address address = (Address) o;return street.equals(address.street) && city.equals(address.city) && zipCode.equals(address.zipCode) && country.equals(address.country);}@Overridepublic int hashCode() {return Objects.hash(street, city, zipCode, country);}
}
4.3 聚合(Aggregate)
聚合是一组相关对象的集合,作为一个整体被视为数据变更的单元。
特征:
- 由聚合根(一个实体)和边界内的其他实体、值对象组成
- 确保业务规则和不变性的一致性
- 外部只能引用聚合根,不能直接引用内部成员
- 作为一个整体被持久化
设计原则:
- 保持聚合小巧
- 一次事务只修改一个聚合
- 聚合间通过ID引用,而非对象引用
示例代码:
// 聚合根
public class Order {private final OrderId id;private CustomerId customerId; // 引用其他聚合根private List<OrderItem> items; // 聚合内的实体private Address shippingAddress; // 值对象private OrderStatus status;private Money totalAmount;public Order(OrderId id, CustomerId customerId) {this.id = id;this.customerId = customerId;this.items = new ArrayList<>();this.status = OrderStatus.CREATED;this.totalAmount = Money.ZERO;}// 添加商品项(封装内部集合操作)public void addItem(ProductId productId, int quantity, Money unitPrice) {// 业务规则:已支付订单不能修改if (status == OrderStatus.PAID) {throw new IllegalStateException("Cannot modify paid order");}// 业务规则:检查是否已存在相同商品for (OrderItem item : items) {if (item.getProductId().equals(productId)) {item.increaseQuantity(quantity);recalculateTotal();return;}}// 添加新商品项OrderItem newItem = new OrderItem(new OrderItemId(), productId, quantity, unitPrice);items.add(newItem);recalculateTotal();}// 重新计算总金额private void recalculateTotal() {this.totalAmount = items.stream().map(OrderItem::getSubtotal).reduce(Money.ZERO, Money::add);}// 其他业务方法public void confirm() {if (items.isEmpty()) {throw new IllegalStateException("Cannot confirm order with no items");}this.status = OrderStatus.CONFIRMED;}public void pay() {if (status != OrderStatus.CONFIRMED) {throw new IllegalStateException("Order must be confirmed before payment");}this.status = OrderStatus.PAID;}// 访问方法public OrderId getId() { return id; }public OrderStatus getStatus() { return status; }public Money getTotalAmount() { return totalAmount; }public CustomerId getCustomerId() { return customerId; }// 提供内部集合的不可变视图public List<OrderItem> getItems() {return Collections.unmodifiableList(items);}
}
4.4 领域服务(Domain Service)
领域服务表示领域中的操作或行为,这些操作不自然地属于任何实体或值对象。
特征:
- 无状态
- 表示领域概念
- 执行跨多个实体的操作
- 名称反映领域活动或过程
示例代码:
public class PaymentService {// 领域服务方法,协调多个聚合public PaymentResult processPayment(Order order, PaymentMethod paymentMethod) {// 验证订单状态if (order.getStatus() != OrderStatus.CONFIRMED) {throw new IllegalArgumentException("Order must be confirmed before payment");}// 执行支付逻辑PaymentTransaction transaction = paymentMethod.createTransaction(order.getId(),order.getTotalAmount());// 验证支付结果if (transaction.isSuccessful()) {// 更新订单状态order.pay();return new PaymentResult(true, transaction.getId(), "Payment successful");} else {return new PaymentResult(false, transaction.getId(), transaction.getFailureReason());}}
}
4.5 领域事件(Domain Event)
领域事件表示在领域中发生的、具有业务意义的事件。
特征:
- 不可变
- 表示过去已发生的事实
- 命名为过去时态(如OrderPlaced)
- 包含事件发生时的相关数据
示例代码:
public class OrderPlacedEvent {private final OrderId orderId;private final CustomerId customerId;private final Money totalAmount;private final LocalDateTime occurredOn;public OrderPlacedEvent(OrderId orderId, CustomerId customerId, Money totalAmount) {this.orderId = orderId;this.customerId = customerId;this.totalAmount = totalAmount;this.occurredOn = LocalDateTime.now();}// 获取事件数据的访问方法public OrderId getOrderId() { return orderId; }public CustomerId getCustomerId() { return customerId; }public Money getTotalAmount() { return totalAmount; }public LocalDateTime getOccurredOn() { return occurredOn; }
}
领域事件的发布与订阅:
// 领域事件发布者
public interface DomainEventPublisher {void publish(Object event);
}// 在聚合中发布事件
public class Order {// ... 其他代码 ...private final DomainEventPublisher eventPublisher;public Order(OrderId id, CustomerId customerId, DomainEventPublisher eventPublisher) {this.id = id;this.customerId = customerId;this.items = new ArrayList<>();this.status = OrderStatus.CREATED;this.totalAmount = Money.ZERO;this.eventPublisher = eventPublisher;}public void place() {if (items.isEmpty()) {throw new IllegalStateException("Cannot place empty order");}this.status = OrderStatus.PLACED;// 发布领域事件eventPublisher.publish(new OrderPlacedEvent(id, customerId, totalAmount));}
}
4.6 仓储(Repository)
仓储提供了对聚合的持久化和检索的抽象,使领域层与基础设施层解耦。
特征:
- 每个聚合类型通常有一个仓储
- 提供集合类似的接口
- 封装持久化细节
- 领域模型中以接口形式存在
示例代码:
// 仓储接口(领域层)
public interface OrderRepository {Order findById(OrderId id);List<Order> findByCustomerId(CustomerId customerId);void save(Order order);void remove(Order order);
}// 仓储实现(基础设施层)
public class JpaOrderRepository implements OrderRepository {private final EntityManager entityManager;public JpaOrderRepository(EntityManager entityManager) {this.entityManager = entityManager;}@Overridepublic Order findById(OrderId id) {return entityManager.find(Order.class, id);}@Overridepublic List<Order> findByCustomerId(CustomerId customerId) {return entityManager.createQuery("SELECT o FROM Order o WHERE o.customerId = :customerId", Order.class).setParameter("customerId", customerId).getResultList();}@Overridepublic void save(Order order) {if (entityManager.contains(order)) {entityManager.merge(order);} else {entityManager.persist(order);}}@Overridepublic void remove(Order order) {entityManager.remove(order);}
}
4.7 工厂(Factory)
工厂负责创建复杂对象和聚合,封装创建逻辑。
特征:
- 封装复杂对象的创建
- 确保创建过程中的不变性规则
- 可以是独立类或聚合上的工厂方法
示例代码:
// 独立工厂类
public class OrderFactory {private final OrderIdGenerator idGenerator;public OrderFactory(OrderIdGenerator idGenerator) {this.idGenerator = idGenerator;}public Order createOrder(CustomerId customerId, List<OrderItemDto> items) {// 生成新的订单IDOrderId orderId = idGenerator.nextId();// 创建订单Order order = new Order(orderId, customerId);// 添加订单项for (OrderItemDto itemDto : items) {order.addItem(itemDto.getProductId(), itemDto.getQuantity(), itemDto.getUnitPrice());}return order;}
}// 或者作为聚合上的工厂方法
public class Order {// ... 其他代码 ...public static Order create(OrderId id, CustomerId customerId, List<OrderItemDto> items) {Order order = new Order(id, customerId);for (OrderItemDto item : items) {order.addItem(item.getProductId(), item.getQuantity(), item.getUnitPrice());}return order;}
}
5. DDD实现方法与模式
5.1 分层架构
DDD通常采用分层架构来组织代码,清晰分离关注点:
经典四层架构:
- 用户界面层/表现层(User Interface/Presentation Layer): 负责向用户显示信息和解释用户指令
- 应用层(Application Layer): 定义软件要完成的任务,协调领域对象完成任务
- 领域层(Domain Layer): 负责表达业务概念、业务状态和业务规则
- 基础设施层(Infrastructure Layer): 为上面各层提供通用的技术能力支持
依赖规则:
- 上层可以依赖下层,下层不能依赖上层
- 理想情况下,领域层不依赖任何其他层
┌───────────────────┐
│ 用户界面/表现层 │
└─────────┬─────────┘│▼
┌───────────────────┐
│ 应用层 │
└─────────┬─────────┘│▼
┌───────────────────┐
│ 领域层 │
└─────────┬─────────┘│▼
┌───────────────────┐
│ 基础设施层 │
└───────────────────┘
各层职责详解:
-
用户界面/表现层:
- 显示信息给用户
- 解释用户命令,控制UI元素
- 将用户请求转发给应用层
-
应用层:
- 定义软件功能(用例和场景)
- 编排领域对象解决问题
- 事务管理
- 无业务规则,只有业务流程
- 保持轻薄,无状态
-
领域层:
- 包含业务逻辑和规则
- 表达业务概念为模型
- 反映业务流程和规则
- 状态变更
- 包含实体、值对象、聚合、领域服务等
-
基础设施层:
- 提供技术能力支持
- 实现持久化机制
- 提供与外部系统通信的能力
- 提供对领域层的技术服务
5.2 六边形架构(端口与适配器)
六边形架构(Hexagonal Architecture),也称为端口与适配器(Ports and Adapters)架构,是实现DDD的另一种架构方式。
核心思想:
- 应用核心(业务逻辑)与外部系统隔离
- 通过端口(接口)定义与外部世界交互的方式
- 通过适配器实现端口,连接外部系统
┌────────────────────────────────────┐│ 适配器层 ││ ┌─────────┐ ┌─────────┐ ││ │ Web │ │ 数据库 │ ││ │ 控制器 │ │ 适配器 │ ││ └────┬────┘ └────┬────┘ │└───────┼─────────────────┼─────────┘│ │┌──────▼─────┐ ┌─────▼──────┐ │输入端口接口 │ │输出端口接口 │ └──────┬─────┘ └─────┬──────┘│ │┌───────┼────────────────┼─────────┐│ │ │ ││ │ 领域模型 │ ││ │ │ ││ └────────────────┘ ││ 应用核心 │└────────────────────────────────────┘
优势:
- 领域模型完全独立于外部系统
- 外部依赖可以轻松替换
- 便于测试,可以替换真实适配器为模拟适配器
- 业务逻辑不受框架或技术选择的影响
实现步骤:
- 定义核心领域模型
- 确定应用需要的端口(接口)
- 为每个外部系统开发适配器
5.3 命令查询职责分离(CQRS)
CQRS(Command Query Responsibility Segregation)是一种将系统操作分为命令(写操作)和查询(读操作)的模式。
核心思想:
- 将修改状态的操作(命令)与查询状态的操作(查询)分离
- 可以使用不同的模型处理命令和查询
- 可以独立优化读写操作
用户界面│┌────────┴────────┐│ │▼ ▼┌─────────────┐ ┌───────────────┐│ 命令处理器 │ │ 查询处理器 │└──────┬──────┘ └───────┬───────┘│ │▼ ▼┌─────────────┐ ┌───────────────┐│ 命令模型 │ │ 查询模型 │└──────┬──────┘ └───────────────┘│ ▲│ │▼ │┌─────────────┐ ││ │ ││ 数据存储 ├──────────┘│ │└─────────────┘
基本实现形式:
- 简单CQRS: 同一数据存储,但使用不同的模型和API处理读写操作
- 分离存储CQRS: 使用不同的数据存储分别优化读写操作
- 事件溯源CQRS: 结合事件溯源,命令生成事件,查询从事件投影生成视图
适用场景:
- 读写比例严重不平衡的系统
- 写操作需要进行复杂验证而读操作相对简单
- 需要不同的扩展策略(读扩展、写扩展)
- 需要支持复杂的报表查询而不影响事务处理
5.4 事件溯源(Event Sourcing)
事件溯源是一种存储状态变化而非当前状态的模式。
核心思想:
- 将对象的状态变化记录为一系列事件
- 通过回放事件重建对象的当前状态
- 事件是不可变的且按时间顺序追加
命令 ──► 处理器 ──► 生成事件 ──► 事件存储│▼投影│▼查询模型
优势:
- 完整的审计跟踪和历史记录
- 能够重建任意时间点的状态
- 避免并发更新冲突
- 事件可用于分析和集成
实现考虑:
- 事件设计: 事件应表达业务语言,包含足够信息
- 事件存储: 专门的事件存储或使用关系型数据库模拟
- 状态重建: 高效处理大量事件的回放
- 快照: 定期保存状态减少回放开销
- 投影: 从事件生成优化的查询视图
示例代码:
// 银行账户实体
public class BankAccount {private AccountId id;private Money balance;private List<DomainEvent> changes = new ArrayList<>();// 通过回放事件创建账户public static BankAccount load(AccountId id, List<DomainEvent> events) {BankAccount account = new BankAccount(id);events.forEach(account::apply);return account;}// 应用事件到当前状态private void apply(DomainEvent event) {if (event instanceof AccountCreatedEvent) {this.balance = ((AccountCreatedEvent) event).getInitialBalance();} else if (event instanceof DepositedEvent) {this.balance = this.balance.add(((DepositedEvent) event).getAmount());} else if (event instanceof WithdrawnEvent) {this.balance = this.balance.subtract(((WithdrawnEvent) event).getAmount());}}// 命令处理方法public void deposit(Money amount) {if (amount.isNegativeOrZero()) {throw new IllegalArgumentException("Deposit amount must be positive");}// 创建事件DepositedEvent event = new DepositedEvent(id, amount);// 应用事件更新状态apply(event);// 记录事件用于持久化changes.add(event);}public void withdraw(Money amount) {if (amount.isNegativeOrZero()) {throw new IllegalArgumentException("Withdrawal amount must be positive");}if (balance.isLessThan(amount)) {throw new InsufficientFundsException();}WithdrawnEvent event = new WithdrawnEvent(id, amount);apply(event);changes.add(event);}// 获取未提交的事件public List<DomainEvent> getUncommittedChanges() {return new ArrayList<>(changes);}// 标记事件已提交public void markChangesAsCommitted() {changes.clear();}
}// 事件存储接口
public interface EventStore {void saveEvents(AccountId accountId, List<DomainEvent> events, int expectedVersion);List<DomainEvent> getEventsForAccount(AccountId accountId);
}
5.5 领域服务与应用服务
在DDD中,服务被划分为两种类型:领域服务和应用服务,它们有着明确的职责区分。
领域服务(Domain Service):
- 实现领域逻辑,但不自然地属于实体或值对象
- 处理多个聚合之间的协作
- 无状态,表示领域中的活动或行为
- 名称反映领域术语和活动
应用服务(Application Service):
- 定义用例和协调领域对象
- 管理事务和安全
- 转换输入输出数据(DTO转换)
- 不包含业务逻辑
- 轻薄的协调者
对比示例:
// 领域服务 - 关注业务规则
public class TransferService {public void transfer(Account source, Account destination, Money amount) {if (source.getBalance().isLessThan(amount)) {throw new InsufficientFundsException();}source.withdraw(amount);destination.deposit(amount);}
}// 应用服务 - 关注用例协调
public class MoneyTransferApplicationService {private final AccountRepository accountRepository;private final TransferService transferService;private final TransactionManager transactionManager;public TransferResultDto transfer(TransferRequestDto request) {return transactionManager.inTransaction(() -> {// 获取聚合Account source = accountRepository.findById(request.getSourceAccountId());Account destination = accountRepository.findById(request.getDestinationAccountId());Money amount = Money.of(request.getAmount(), request.getCurrency());// 使用领域服务执行业务逻辑transferService.transfer(source, destination, amount);// 保存变更accountRepository.save(source);accountRepository.save(destination);// 返回结果return new TransferResultDto(request.getTransferId(),"Transfer completed successfully",source.getBalance().getAmount());});}
}
5.6 工作单元与仓储模式
工作单元(Unit of Work)模式:
- 跟踪业务事务期间发生的所有变更
- 协调变更的提交或回滚
- 维护对象的一致性
仓储(Repository)模式:
- 提供对聚合的持久化和检索抽象
- 像集合一样工作,隐藏查询细节
- 领域模型与持久化机制解耦
工作单元与仓储的协作:
// 工作单元接口
public interface UnitOfWork {void registerNew(Object entity);void registerDirty(Object entity);void registerDeleted(Object entity);void commit();void rollback();
}// 基于工作单元的仓储实现
public class OrderRepository {private final UnitOfWork unitOfWork;private final OrderMapper mapper;public void save(Order order) {if (order.isNew()) {unitOfWork.registerNew(order);} else {unitOfWork.registerDirty(order);}}public void remove(Order order) {unitOfWork.registerDeleted(order);}public Order findById(OrderId id) {// 查询数据库OrderDto dto = mapper.findById(id.getValue());if (dto == null) return null;// 创建领域对象Order order = mapper.toDomain(dto);// 注册到工作单元以跟踪变更unitOfWork.registerClean(order);return order;}
}// 应用服务中使用
public class OrderApplicationService {private final OrderRepository orderRepository;private final UnitOfWork unitOfWork;public void processOrder(OrderId orderId) {try {Order order = orderRepository.findById(orderId);order.process();orderRepository.save(order);unitOfWork.commit();} catch (Exception e) {unitOfWork.rollback();throw e;}}
}
6. 事件风暴
事件风暴(Event Storming)是一种由Alberto Brandolini创建的协作建模技术,用于快速探索复杂业务领域。它特别适合DDD项目,能够帮助团队建立共享的领域模型和通用语言。
6.1 事件风暴的目的与价值
目的:
- 发现领域事件、命令、聚合、策略和业务流程
- 建立统一的业务语言
- 发现系统边界和上下文
- 识别关键业务规则和约束
价值:
- 快速获取领域知识
- 促进领域专家与技术专家的协作
- 识别模型的不一致和冲突
- 为限界上下文的划分提供依据
- 发现系统中的瓶颈和痛点
6.2 事件风暴的过程
事件风暴通常在一个大的工作空间(通常是墙壁)上使用彩色便利贴进行,遵循以下步骤:
-
准备阶段:
- 确定领域范围
- 邀请关键利益相关者(领域专家、开发人员、产品经理等)
- 准备材料(便利贴、记号笔、大空间)
- 解释规则和目标
-
收集领域事件:
- 事件是已经发生的事实,用橙色便利贴表示
- 使用过去时态命名(如"订单已创建")
- 快速brainstorm,收集尽可能多的事件
- 按时间顺序排列在墙上
-
添加引起事件的命令/行为:
- 命令是导致事件发生的行为,用蓝色便利贴表示
- 使用祈使句命名(如"创建订单")
- 将命令放在相应事件的左侧
-
识别聚合:
- 聚合是处理命令生成事件的对象,用黄色便利贴表示
- 聚合应该能回答"谁处理这个命令"的问题
- 将聚合放在相应命令的左侧
-
添加策略和业务规则:
- 策略决定如何响应事件,用紫色便利贴表示
- 策略可以触发新的命令
- 将策略放在相应事件的右侧
-
识别读取模型:
- 读取模型是用户做决策所需的信息,用绿色便利贴表示
- 将读取模型放在相应命令的上方
-
标记问题和不确定点:
- 使用红色便利贴记录问题或不确定点
- 这些问题可能需要进一步讨论或研究
-
识别限界上下文:
- 根据语义完整性和业务聚合度划分上下文边界
- 用垂直线或区域划分不同的上下文
-
总结与行动计划:
- 总结关键发现和见解
- 识别需要进一步探索的领域
- 制定后续行动计划
6.3 事件风暴的输出
成功的事件风暴会产生以下输出:
- 领域事件的完整图景
- 命令与事件的因果关系
- 聚合的初步识别
- 业务规则与策略
- 限界上下文的边界
- 领域专家与开发人员的共识
- 需要进一步探索的领域问题
6.4 从事件风暴到代码
事件风暴的结果可以转化为代码设计:
-
领域事件 → 领域事件类
public class OrderPlaced {private final OrderId orderId;private final CustomerId customerId;private final LocalDateTime timestamp;// 构造函数、getter等 }
-
命令 → 命令对象或应用服务方法
public class PlaceOrderCommand {private final CustomerId customerId;private final List<OrderItem> items;// 构造函数、getter等 }
-
聚合 → 聚合根和实体
public class Order {private OrderId id;private CustomerId customerId;private List<OrderItem> items;private OrderStatus status;public void place() {// 业务逻辑this.status = OrderStatus.PLACED;// 发布事件} }
-
策略 → 领域服务或策略类
public class InventoryReservationPolicy {public void handle(OrderPlaced event) {// 实现策略逻辑} }
-
读取模型 → 查询模型或DTO
public class CustomerOrderSummary {private CustomerId customerId;private List<OrderSummary> recentOrders;private int totalOrderCount;// 构造函数、getter等 }
6.5 事件风暴与其他技术的结合
事件风暴可以与其他设计和建模技术结合使用:
-
用户故事映射:
- 从用户视角理解流程
- 识别关键用户价值
-
影响映射:
- 理解业务目标和度量
- 将业务目标与系统功能连接
-
示例映射:
- 通过具体示例澄清需求
- 为行为驱动开发(BDD)提供输入
-
上下文映射:
- 细化限界上下文之间的关系
- 确定集成策略
事件风暴在DDD实践中的重要性不能被低估,它是从复杂业务中提取模型和发现边界的强大工具,让团队成员建立共识,并为后续设计和开发奠定基础。
7. DDD与其他架构模式的关系
领域驱动设计(DDD)可以与多种架构模式结合使用,它们相互补充而非互斥。了解DDD与其他架构模式的关系,有助于我们在不同场景下选择合适的组合。
7.1 DDD与微服务架构
微服务架构是一种将应用程序构建为一组小型服务的方法,每个服务运行在自己的进程中,可以独立部署。
协同要点:
-
限界上下文与微服务边界:
- DDD中的限界上下文为微服务提供了自然的边界
- 每个微服务通常对应一个或多个限界上下文
- 共同追求高内聚、低耦合的目标
-
聚合作为数据一致性边界:
- 微服务中的数据一致性问题可通过正确设计聚合解决
- 聚合提供了事务边界的指导
-
上下文映射对应服务集成模式:
- 上下文映射关系可以转化为微服务集成策略
- 共享内核可能对应共享库
- 防腐层对应适配器或转换服务
-
领域事件用于服务间通信:
- 领域事件成为微服务间异步通信的基础
- 事件驱动架构与DDD的领域事件自然契合
实施策略:
- 使用DDD战略设计识别微服务边界
- 每个微服务内部使用DDD战术设计构建领域模型
- 服务间通过领域事件或开放主机服务集成
- 使用上下文映射指导服务间关系设计
┌─────────────────────┐ ┌─────────────────────┐
│ 订单微服务 │ │ 支付微服务 │
│ │ │ │
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ 订单聚合根 ├──┼──────┼─▶│ 支付聚合根 │ │
│ └───────────────┘ │ │ └───────────────┘ │
│ │ │ │
│ ┌───────────────┐ │ │ │ 支付仓储 │ │
│ └───────────────┘ │ │ └───────────────┘ │
└─────────────────────┘ └─────────────────────┘│ ▲│ 领域事件 │└────────────────────────────┘
7.2 DDD与整洁架构/洋葱架构
整洁架构(Clean Architecture)和洋葱架构(Onion Architecture)是关注依赖方向的架构模式,强调业务逻辑的独立性。
核心共识:
-
依赖方向:
- 依赖指向领域核心而非外围
- 领域模型不依赖基础设施
-
关注点分离:
- 业务规则与技术实现分离
- 接口位于内层,实现位于外层
-
领域模型的核心地位:
- 领域模型是系统的心脏
- 技术细节服务于领域模型,而非相反
架构对比:
Clean Architecture DDD Layers
────────────────── ──────────────────
│ Entities │ │ Domain Layer │
│ │ │ │
│ Use Cases │ ≈ │ Application │
│ │ │ Layer │
│ Interface │ │ UI/API Layer │
│ Adapters │ │ │
│ │ │ Infrastructure │
│ Frameworks │ │ Layer │
────────────────── ──────────────────
结合策略:
- 使用DDD定义领域模型(实体、值对象、聚合)
- 将领域服务和应用服务对应到整洁架构的用例层
- 仓储接口定义在领域层,实现在基础设施层
- 依赖注入用于解决依赖倒置原则
7.3 DDD与事件驱动架构
事件驱动架构(Event-Driven Architecture, EDA)是一种设计模式,其中系统的不同部分通过事件进行通信。
协同要点:
-
领域事件作为通信基础:
- DDD的领域事件自然对应EDA中的事件
- 领域事件捕获业务变化,触发后续流程
-
聚合作为事件源:
- 聚合是领域事件的自然发源地
- 聚合保证事件生成的业务一致性
-
事件流转与业务流程:
- 事件流转路径反映业务流程
- 事件历史记录业务活动
实现策略:
- 使用领域事件捕获业务状态变化
- 采用事件总线或消息队列传递领域事件
- 考虑事件溯源保存事件历史
- 使用CQRS分离读写职责
┌───────────┐ ┌─────────────┐ ┌───────────┐
│ │ │ │ │ │
│ 聚合A ├───▶│ 事件总线 ├───▶│ 处理器B │
│ │ │ │ │ │
└───────────┘ └─────────────┘ └───────────┘││▼┌───────────┐│ ││ 处理器C ││ │└───────────┘
7.4 DDD与响应式架构
响应式架构强调系统对变化的响应能力,关注弹性、可伸缩性和消息驱动。
协同要点:
-
事件驱动与异步通信:
- 领域事件自然契合响应式编程的消息传递
- 异步、非阻塞处理提高系统弹性
-
隔离与容错:
- 限界上下文的隔离支持故障隔离
- 聚合作为一致性边界便于分区
-
可伸缩性:
- 领域模型的正确拆分有利于水平扩展
- 读写分离(CQRS)支持差异化扩展策略
实现策略:
- 使用响应式编程框架处理领域事件
- 采用异步消息传递进行上下文间通信
- 结合CQRS实现读写分离
- 使用断路器等模式提高系统弹性
7.5 DDD与REST架构
REST(Representational State Transfer)是一种网络应用程序的架构风格,主要用于构建Web API。
协同要点:
-
资源与聚合:
- 聚合自然对应REST中的资源
- 聚合ID可以映射为资源URI
-
状态转换与领域操作:
- 领域模型中的操作映射为资源的状态转换
- HTTP方法对应聚合操作(POST/PUT/PATCH/DELETE)
-
表现层与模型:
- API响应是领域模型的表现层
- DTO设计反映领域概念
实现策略:
- 基于聚合设计REST资源
- 使用领域事件触发资源状态变更通知
- 考虑使用HATEOAS表达业务流程
- 采用恰当的媒体类型表达领域概念
HTTP请求 REST控制器 应用服务 领域模型
────────── ───────────── ───────────── ─────────────GET ┌─────────┐ ┌───────────────┐ ┌─────────┐
/orders/1 │ 获取订单 ├──▶│查询订单 ├──▶│ 订单仓储 │
────────── │ 资源 │ │应用服务 │ │ │POST │ │ │ │ │ │
/orders │ 创建订单 ├──▶│创建订单 ├──▶│ 订单聚合 │
────────── └─────────┘ └───────────────┘ └─────────┘
8. DDD应用实例
8.1 电子商务系统实例
电子商务系统是应用DDD的典型场景。下面我们将展示如何应用DDD原则构建一个电商系统。
8.1.1 业务场景
在线电商平台,用户可以浏览商品、将商品加入购物车、下单、支付、跟踪物流等。
8.1.2 领域分析
核心子域:
- 商品管理
- 订单处理
- 支付
支撑子域:
- 用户管理
- 库存管理
- 物流管理
通用子域:
- 消息通知
- 评论与评级
8.1.3 限界上下文划分
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 商品上下文 │ │ 订单上下文 │ │ 支付上下文 │
│ │ │ │ │ │
│ - 商品 │ │ - 订单 │ │ - 支付 │
│ - 类别 │ │ - 购物车 │ │ - 退款 │
│ - 价格 │ │ - 促销 │ │ - 支付方式 │
└──────────────────┘ └──────────────────┘ └──────────────────┘│ │ │└─────────────────────┼─────────────────────┘│
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 用户上下文 │ │ 库存上下文 │ │ 物流上下文 │
│ │ │ │ │ │
│ - 用户 │ │ - 库存项 │ │ - 物流单 │
│ - 地址 │ │ - 库存变动 │ │ - 配送 │
│ - 会员 │ │ - 仓库 │ │ - 物流商 │
└──────────────────┘ └──────────────────┘ └──────────────────┘
8.1.4 聚合设计示例
订单上下文中的聚合:
// 订单聚合根
public class Order {private OrderId id;private CustomerId customerId;private List<OrderItem> items;private OrderStatus status;// 创建新订单public static Order create(OrderId id, CustomerId customerId, ShippingAddress address) {Order order = new Order();order.id = id;order.customerId = customerId;order.shippingAddress = address;order.status = OrderStatus.CREATED;order.items = new ArrayList<>();order.totalAmount = Money.ZERO;// 发布领域事件DomainEvents.publish(new OrderCreatedEvent(order.id, customerId));return order;}// 添加商品public void addItem(ProductId productId, int quantity, Money unitPrice) {validateStateForModification();// 检查是否已有该商品for (OrderItem item : items) {if (item.getProductId().equals(productId)) {item.increaseQuantity(quantity);recalculateTotal();return;}}// 添加新商品OrderItem newItem = new OrderItem(new OrderItemId(), productId, quantity, unitPrice);items.add(newItem);recalculateTotal();}// 确认订单public void confirm() {validateStateForConfirmation();this.status = OrderStatus.CONFIRMED;DomainEvents.publish(new OrderConfirmedEvent(this.id));}// 标记为已支付public void markAsPaid(PaymentId paymentId) {if (status != OrderStatus.CONFIRMED) {throw new OrderNotConfirmedException(id);}this.status = OrderStatus.PAID;DomainEvents.publish(new OrderPaidEvent(this.id, paymentId));}// 标记为已发货public void markAsShipped(TrackingId trackingId) {if (status != OrderStatus.PAID) {throw new OrderNotPaidException(id);}this.status = OrderStatus.SHIPPED;DomainEvents.publish(new OrderShippedEvent(this.id, trackingId));}// 取消订单public void cancel(String reason) {if (status == OrderStatus.SHIPPED || status == OrderStatus.DELIVERED) {throw new OrderCannotBeCancelledException(id);}this.status = OrderStatus.CANCELLED;DomainEvents.publish(new OrderCancelledEvent(this.id, reason));}// 重新计算总金额private void recalculateTotal() {this.totalAmount = items.stream().map(OrderItem::getSubtotal).reduce(Money.ZERO, Money::add);}// 验证状态是否允许修改private void validateStateForModification() {if (status != OrderStatus.CREATED) {throw new OrderCannotBeModifiedException(id);}}// 验证状态是否允许确认private void validateStateForConfirmation() {if (status != OrderStatus.CREATED) {throw new OrderCannotBeConfirmedException(id);}if (items.isEmpty()) {throw new EmptyOrderCannotBeConfirmedException(id);}}// Getterspublic OrderId getId() { return id; }public OrderStatus getStatus() { return status; }public Money getTotalAmount() { return totalAmount; }public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
}// 订单项实体
public class OrderItem {private OrderItemId id;private ProductId productId;private int quantity;private Money unitPrice;// 构造函数public OrderItem(OrderItemId id, ProductId productId, int quantity, Money unitPrice) {validateQuantity(quantity);validateUnitPrice(unitPrice);this.id = id;this.productId = productId;this.quantity = quantity;this.unitPrice = unitPrice;}// 增加数量public void increaseQuantity(int additionalQuantity) {validateQuantity(additionalQuantity);this.quantity += additionalQuantity;}// 获取小计public Money getSubtotal() {return unitPrice.multiply(quantity);}// 验证数量private void validateQuantity(int quantity) {if (quantity <= 0) {throw new InvalidOrderQuantityException();}}// 验证单价private void validateUnitPrice(Money unitPrice) {if (unitPrice == null || unitPrice.isNegativeOrZero()) {throw new InvalidOrderPriceException();}}// Getterspublic OrderItemId getId() { return id; }public ProductId getProductId() { return productId; }public int getQuantity() { return quantity; }public Money getUnitPrice() { return unitPrice; }
}
8.1.5 应用服务示例
public class OrderApplicationService {private final OrderRepository orderRepository;private final ProductRepository productRepository;private final CustomerRepository customerRepository;private final DomainEventPublisher eventPublisher;// 创建订单@Transactionalpublic OrderId createOrder(CreateOrderCommand command) {// 验证客户存在Customer customer = customerRepository.findById(command.getCustomerId());if (customer == null) {throw new CustomerNotFoundException(command.getCustomerId());}// 生成新订单IDOrderId orderId = orderRepository.nextId();// 创建订单聚合Order order = Order.create(orderId,command.getCustomerId(),command.getShippingAddress());// 添加订单项for (OrderItemDto itemDto : command.getItems()) {// 检查商品是否存在Product product = productRepository.findById(itemDto.getProductId());if (product == null) {throw new ProductNotFoundException(itemDto.getProductId());}// 验证商品价格与传入价格是否一致if (!product.getPrice().equals(itemDto.getUnitPrice())) {throw new PriceMismatchException(itemDto.getProductId());}// 添加到订单order.addItem(itemDto.getProductId(),itemDto.getQuantity(),itemDto.getUnitPrice());}// 保存订单orderRepository.save(order);return orderId;}// 确认订单@Transactionalpublic void confirmOrder(ConfirmOrderCommand command) {Order order = orderRepository.findById(command.getOrderId());if (order == null) {throw new OrderNotFoundException(command.getOrderId());}order.confirm();orderRepository.save(order);}// 订单支付@Transactionalpublic void markOrderAsPaid(OrderPaidCommand command) {Order order = orderRepository.findById(command.getOrderId());if (order == null) {throw new OrderNotFoundException(command.getOrderId());}order.markAsPaid(command.getPaymentId());orderRepository.save(order);}// 取消订单@Transactionalpublic void cancelOrder(CancelOrderCommand command) {Order order = orderRepository.findById(command.getOrderId());if (order == null) {throw new OrderNotFoundException(command.getOrderId());}order.cancel(command.getReason());orderRepository.save(order);}// 查询订单public OrderDto getOrder(OrderId orderId) {Order order = orderRepository.findById(orderId);if (order == null) {throw new OrderNotFoundException(orderId);}return mapToDto(order);}// 映射到DTOprivate OrderDto mapToDto(Order order) {OrderDto dto = new OrderDto();dto.setId(order.getId().toString());dto.setStatus(order.getStatus().name());dto.setTotalAmount(order.getTotalAmount().getAmount());// ... 设置其他属性 ...List<OrderItemDto> itemDtos = order.getItems().stream().map(this::mapToDto).collect(Collectors.toList());dto.setItems(itemDtos);return dto;}private OrderItemDto mapToDto(OrderItem item) {OrderItemDto dto = new OrderItemDto();dto.setProductId(item.getProductId().toString());dto.setQuantity(item.getQuantity());dto.setUnitPrice(item.getUnitPrice().getAmount());dto.setSubtotal(item.getSubtotal().getAmount());return dto;}
}
8.1.6 上下文集成
// 领域事件处理器(库存上下文)
@Component
public class OrderEventHandler {private final InventoryService inventoryService;@EventListenerpublic void handleOrderPaid(OrderPaidEvent event) {// 当订单支付成功后,减少库存inventoryService.reserveInventory(event.getOrderId());}@EventListenerpublic void handleOrderCancelled(OrderCancelledEvent event) {// 当订单取消后,恢复库存inventoryService.releaseInventory(event.getOrderId());}
}// 防腐层(集成外部支付系统)
public class PaymentServiceAdapter {private final ExternalPaymentService externalService;private final PaymentRepository paymentRepository;public PaymentResult processPayment(PaymentRequest request) {// 转换为外部系统格式ExternalPaymentRequest externalRequest = mapToExternalRequest(request);// 调用外部系统ExternalPaymentResponse response = externalService.processPayment(externalRequest);// 转换回内部模型Payment payment = mapFromExternalResponse(response);// 保存到仓储paymentRepository.save(payment);return new PaymentResult(payment.getId(),payment.getStatus(),payment.getTransactionReference());}// 映射方法...
}
8.2 银行系统实例
银行系统是另一个适合应用DDD的复杂业务领域。
8.2.1 业务场景
银行系统允许客户开立账户、存取款、转账、申请贷款等。
8.2.2 限界上下文示例
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 客户上下文 │ │ 账户上下文 │ │ 交易上下文 │
│ │ │ │ │ │
│ - 客户 │ │ - 账户 │ │ - 交易 │
│ - 身份验证 │ │ - 余额 │ │ - 转账 │
│ - KYC流程 │ │ - 账户类型 │ │ - 交易费用 │
└──────────────────┘ └──────────────────┘ └──────────────────┘│ │ │└─────────────────────┼─────────────────────┘│
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 贷款上下文 │ │ 风控上下文 │ │ 报表上下文 │
│ │ │ │ │ │
│ - 贷款申请 │ │ - 风险评估 │ │ - 账户报表 │
│ - 还款计划 │ │ - 欺诈检测 │ │ - 交易历史 │
│ - 利率 │ │ - 限额管理 │ │ - 财务摘要 │
└──────────────────┘ └──────────────────┘ └──────────────────┘
8.2.3 聚合示例(账户上下文)
// 账户聚合根
public class Account {private AccountId id;private CustomerId customerId;private Money balance;private AccountType type;private AccountStatus status;private List<Transaction> transactions;private DailyLimit dailyLimit;// 创建新账户public static Account open(AccountId id, CustomerId customerId, AccountType type, Money initialDeposit) {if (initialDeposit.isLessThan(type.getMinimumInitialDeposit())) {throw new InsufficientInitialDepositException();}Account account = new Account();account.id = id;account.customerId = customerId;account.type = type;account.status = AccountStatus.ACTIVE;account.balance = initialDeposit;account.transactions = new ArrayList<>();account.dailyLimit = type.getDefaultDailyLimit();// 记录开户交易Transaction openingTx = new Transaction(TransactionId.newId(),TransactionType.DEPOSIT,initialDeposit,"Initial deposit",LocalDateTime.now());account.transactions.add(openingTx);// 发布领域事件DomainEvents.publish(new AccountOpenedEvent(id, customerId, type));return account;}// 存款public void deposit(Money amount, String description) {validateAccountIsActive();if (amount.isNegativeOrZero()) {throw new InvalidAmountException("Deposit amount must be positive");}this.balance = this.balance.add(amount);Transaction tx = new Transaction(TransactionId.newId(),TransactionType.DEPOSIT,amount,description,LocalDateTime.now());this.transactions.add(tx);DomainEvents.publish(new MoneyDepositedEvent(id, amount));}// 取款public void withdraw(Money amount, String description) {validateAccountIsActive();if (amount.isNegativeOrZero()) {throw new InvalidAmountException("Withdrawal amount must be positive");}// 检查余额if (balance.isLessThan(amount)) {throw new InsufficientFundsException(id);}// 检查每日限额dailyLimit.validateWithdrawal(amount);this.balance = this.balance.subtract(amount);Transaction tx = new Transaction(TransactionId.newId(),TransactionType.WITHDRAWAL,amount.negate(),description,LocalDateTime.now());this.transactions.add(tx);DomainEvents.publish(new MoneyWithdrawnEvent(id, amount));}// 冻结账户public void freeze(String reason) {if (status == AccountStatus.CLOSED) {throw new AccountAlreadyClosedException(id);}this.status = AccountStatus.FROZEN;DomainEvents.publish(new AccountFrozenEvent(id, reason));}// 解冻账户public void unfreeze() {if (status != AccountStatus.FROZEN) {throw new AccountNotFrozenException(id);}this.status = AccountStatus.ACTIVE;DomainEvents.publish(new AccountUnfrozenEvent(id));}// 关闭账户public void close(String reason) {validateAccountIsActive();if (balance.isPositive()) {throw new NonZeroBalanceException(id);}this.status = AccountStatus.CLOSED;DomainEvents.publish(new AccountClosedEvent(id, reason));}// 验证账户状态private void validateAccountIsActive() {if (status == AccountStatus.CLOSED) {throw new AccountClosedException(id);}if (status == AccountStatus.FROZEN) {throw new AccountFrozenException(id);}}// Getterspublic AccountId getId() { return id; }public Money getBalance() { return balance; }public AccountStatus getStatus() { return status; }public List<Transaction> getTransactionHistory() {return Collections.unmodifiableList(transactions);}
}
8.2.4 领域服务示例(转账服务)
public class TransferService {private final AccountRepository accountRepository;public void transfer(AccountId sourceId, AccountId destinationId, Money amount, String description) {// 参数验证if (amount.isNegativeOrZero()) {throw new InvalidAmountException("Transfer amount must be positive");}if (sourceId.equals(destinationId)) {throw new SameAccountTransferException();}// 获取账户Account sourceAccount = accountRepository.findById(sourceId);if (sourceAccount == null) {throw new AccountNotFoundException(sourceId);}Account destinationAccount = accountRepository.findById(destinationId);if (destinationAccount == null) {throw new AccountNotFoundException(destinationId);}// 执行转账sourceAccount.withdraw(amount, "Transfer to " + destinationId + ": " + description);destinationAccount.deposit(amount, "Transfer from " + sourceId + ": " + description);// 保存变更accountRepository.save(sourceAccount);accountRepository.save(destinationAccount);// 发布领域事件DomainEvents.publish(new MoneyTransferredEvent(sourceId, destinationId, amount, description));}
}
8.2.5 CQRS实现示例(账户查询)
// 查询模型
public class AccountSummary {private String accountId;private String customerId;private String accountType;private String status;private BigDecimal balance;private LocalDateTime lastActivityDate;// Getters and setters
}// 查询服务
public class AccountQueryService {private final AccountSummaryRepository repository;public AccountSummary getAccountSummary(String accountId) {return repository.findById(accountId).orElseThrow(() -> new AccountNotFoundException(accountId));}public List<AccountSummary> getAccountsByCustomer(String customerId) {return repository.findByCustomerId(customerId);}public List<TransactionDto> getTransactionHistory(String accountId, LocalDate from, LocalDate to, int page, int size) {return repository.findTransactions(accountId, from, to, page, size);}
}// 事件处理器(更新查询模型)
@Component
public class AccountEventHandler {private final AccountSummaryRepository repository;@EventListenerpublic void handleMoneyDeposited(MoneyDepositedEvent event) {// 更新查询模型AccountSummary summary = repository.findById(event.getAccountId()).orElseThrow(() -> new AccountNotFoundException(event.getAccountId()));summary.setBalance(summary.getBalance().add(event.getAmount()));summary.setLastActivityDate(LocalDateTime.now());repository.save(summary);}@EventListenerpublic void handleMoneyWithdrawn(MoneyWithdrawnEvent event) {// 更新查询模型AccountSummary summary = repository.findById(event.getAccountId()).orElseThrow(() -> new AccountNotFoundException(event.getAccountId()));summary.setBalance(summary.getBalance().subtract(event.getAmount()));summary.setLastActivityDate(LocalDateTime.now());repository.save(summary);}
}
9. DDD常见挑战和解决方案
在实施DDD的过程中,团队通常会面临各种挑战。本节将讨论这些常见挑战及其解决方案。
9.1 学习曲线陡峭
挑战:
- DDD概念和术语较多,新手容易感到困惑
- 理解聚合、限界上下文等抽象概念需要时间
- 从传统数据驱动开发转向领域驱动需要思维转变
解决方案:
-
循序渐进的学习计划:
- 从战术设计开始,逐步学习战略设计
- 通过简单的业务场景实践基础概念
- 使用可视化工具帮助理解抽象概念
-
建立学习小组:
- 组织读书会和案例讨论
- 邀请有经验的DDD实践者分享经验
- 鼓励团队成员相互教学
-
实践优先:
- 将DDD应用于小型、非关键项目积累经验
- 采用增量式应用,逐步引入DDD概念
- 通过代码审查和结对编程传播知识
9.2 领域专家参与度不够
挑战:
- 领域专家时间有限,难以持续参与
- 沟通障碍导致对领域理解不充分
- 专家知识隐性化,难以提取和形式化
解决方案:
-
创造结构化的交流机制:
- 设计高效的研讨会形式,最大化专家时间价值
- 采用事件风暴等互动性强的方法提高效率
- 建立定期反馈循环机制
-
使用能力倍增技术:
- 录制领域专家解释,创建知识库
- 使用通用语言表映射领域概念
- 建立可视化的领域模型,便于讨论和理解
-
培养领域大使:
- 识别和培养懂技术也理解业务的"领域大使"
- 让他们成为技术团队和领域专家之间的桥梁
- 由领域大使负责持续深化领域模型
9.3 过度设计或过早引入复杂性
挑战:
- 试图一开始就建立完美的领域模型
- 过早引入事件溯源、CQRS等高级模式
- 在简单问题上应用过于复杂的解决方案
解决方案:
-
渐进式设计:
- 从简单开始,随着理解加深再细化模型
- 遵循"最简单可行的领域模型"原则
- 保持设计可演进性
-
识别核心领域:
- 将复杂设计集中在核心领域
- 对支撑域和通用域采用简化方法
- 避免在非核心领域过度投入
-
持续重构:
- 接受领域模型会随着理解加深而演进
- 定期安排重构时间,改进模型
- 使用测试保护重构过程
9.4 技术基础设施与DDD不匹配
挑战:
- 现有框架对DDD概念支持不足
- ORM映射可能难以表达复杂领域关系
- 技术约束妨碍领域模型的表达能力
解决方案:
-
构建支撑层:
- 创建适配器隔离领域模型与基础设施
- 开发领域特定的基础设施组件
- 使用六边形架构分离关注点
-
明智选择持久化策略:
- 考虑使用文档数据库存储聚合
- 探索事件溯源作为存储聚合状态的方式
- 采用混合持久化策略,针对不同聚合选择合适的方案
-
避免技术泄漏:
- 严格保持领域模型的纯粹性
- 避免为适应技术而扭曲领域模型
- 使用防腐层隔离外部系统影响
9.5 限界上下文边界划分困难
挑战:
- 上下文边界初期难以精确定义
- 过大或过小的上下文影响系统效率
- 跨上下文概念重叠导致混淆
解决方案:
-
迭代边界定义:
- 承认初始边界可能不完美,预期会调整
- 通过事件风暴发现自然边界
- 定期审查和重新评估边界
-
关注语义变化:
- 通过术语含义变化识别上下文边界
- 创建上下文映射,明确概念在不同上下文的含义
- 建立统一语言表,记录术语及其上下文
-
业务能力分析:
- 基于业务能力而非技术或组织结构划分边界
- 考虑业务流程和数据生命周期
- 评估变更频率和团队自主性
9.6 团队结构与上下文不一致
挑战:
- 团队划分与限界上下文不匹配
- 多个团队负责同一上下文导致协调困难
- 组织结构制约了上下文设计
解决方案:
-
康威法则反向运用:
- 调整团队结构以匹配限界上下文
- 围绕业务能力而非技术专长组建团队
- 培养跨职能团队自主性
-
明确责任边界:
- 对共享上下文建立清晰的所有权模型
- 实施"守护者"角色管理共享区域
- 建立跨团队沟通和决策机制
-
渐进式组织调整:
- 识别关键不匹配点并优先解决
- 逐步调整组织与领域模型对齐
- 寻求管理层支持,解释业务价值
9.7 遗留系统集成
挑战:
- 遗留系统通常不遵循DDD原则
- 对外部系统的依赖限制了模型的纯粹性
- 集成点可能成为系统脆弱环节
解决方案:
-
应用防腐层模式:
- 创建适配器转换遗留系统数据和接口
- 保护领域模型不受外部影响
- 在防腐层处理数据转换和验证
-
渐进式迁移:
- 识别系统的自然边界
- 逐块重构,从核心域开始
- 使用"绞杀者模式"(Strangler Pattern)逐步替换功能
-
建立集成测试:
- 确保与遗留系统交互符合预期
- 模拟外部系统行为进行测试
- 监控集成点,及时发现问题
9.8 性能优化挑战
挑战:
- 领域模型的纯粹性可能导致性能问题
- 聚合边界可能导致多次数据库查询
- CQRS和事件溯源增加系统复杂性
解决方案:
-
合理设计聚合:
- 平衡一致性需求与性能考虑
- 考虑数据访问模式设计聚合边界
- 适当使用延迟加载和预加载策略
-
采用读写分离:
- 对读多写少的场景实施CQRS
- 维护针对查询优化的只读模型
- 使用缓存提高频繁查询性能
-
性能测试与监控:
- 建立性能基准并持续监控
- 识别热点聚合和查询
- 针对性能瓶颈进行优化
10. DDD最佳实践
以下是实施DDD的一些最佳实践,这些经验总结来自众多成功应用DDD的项目。
10.1 建立通用语言
实践原则:
- 持续发展和完善通用语言
- 确保术语在代码、文档和交流中一致使用
- 定期审查和更新通用语言
具体做法:
-
创建并维护领域词汇表:
- 记录关键术语及其定义
- 明确每个术语的上下文
- 定期与领域专家一起审查和更新
-
将通用语言嵌入代码:
- 类名和方法名反映领域术语
- 避免技术术语污染领域模型
- 代码审查时检查语言一致性
-
在所有沟通中使用通用语言:
- 会议和文档中使用一致术语
- 新团队成员入职培训包含通用语言学习
- 鼓励质疑和澄清术语含义
10.2 聚焦核心域
实践原则:
- 识别并优先投资核心域
- 为核心域开发精细模型
- 对支撑域和通用域采用简化方法
具体做法:
-
领域投资地图:
- 创建视觉化地图,标明各子域的战略价值
- 分配资源优先开发核心域
- 定期重新评估投资优先级
-
差异化设计策略:
- 核心域:精心设计,深度建模
- 支撑域:适度投入,关注可靠性
- 通用域:考虑购买、外包或简化实现
-
提取核心复杂性:
- 识别核心域中的关键复杂性
- 构建表达这些复杂性的深度模型
- 隔离和封装复杂领域规则
10.3 模型驱动设计
实践原则:
- 以领域模型驱动技术决策
- 确保代码忠实反映领域模型
- 保持模型的纯粹性和表达力
具体做法:
-
可视化模型:
- 使用图表和可视化工具表达模型
- 建立模型与代码的映射
- 定期重新审视可视化模型
-
行为驱动开发:
- 从领域行为而非数据结构出发
- 使用测试表达领域规则
- 确保测试语言反映通用语言
-
持续精炼模型:
- 定期与领域专家一起审查模型
- 随着理解加深调整模型
- 在代码中反映模型变化
10.4 建立清晰边界
实践原则:
- 明确定义和维护限界上下文
- 设计上下文间的交互和集成
- 防止概念泄漏和模型侵蚀
具体做法:
-
上下文映射:
- 创建并维护上下文映射图
- 明确记录边界和关系类型
- 识别和监控上下文间交互
-
接口设计:
- 为上下文间交互设计清晰接口
- 使用公开的数据传输对象(DTO)
- 实施明确的转换和防腐层
-
团队协作边界:
- 确保团队组织与上下文边界一致
- 建立跨团队协作协议
- 定期召开上下文同步会议
10.5 演进式设计
实践原则:
- 接受模型会随着理解加深而演进
- 保持设计的灵活性和可塑性
- 平衡短期需求与长期演进
具体做法:
-
迭代模型开发:
- 从简单模型开始,随着理解加深再改进
- 明确版本化领域模型
- 计划并执行模型重构
-
重构友好的架构:
- 构建支持领域模型演进的架构
- 保持高测试覆盖率保护重构
- 隔离变化频率不同的组件
-
知识累积:
- 记录设计决策和权衡
- 建立模型演进历史
- 分享学习成果和模型改进
10.6 测试驱动开发
实践原则:
- 使用测试表达领域规则和行为
- 建立多层次测试策略
- 测试反映领域语言和概念
具体做法:
-
领域行为测试:
- 编写表达业务规则的测试
- 使用业务术语描述测试场景
- 关注领域行为而非实现细节
-
分层测试策略:
- 单元测试:验证聚合和实体行为
- 集成测试:验证上下文集成
- 端到端测试:验证关键业务场景
-
测试作为文档:
- 测试作为活的领域规范
- 新团队成员通过测试学习领域
- 与领域专家一起审查测试场景
10.7 持续集成与反馈
实践原则:
- 建立快速反馈循环
- 确保模型在实现过程中保持一致
- 持续验证领域理解
具体做法:
-
技术实践:
- 实施持续集成/持续部署
- 自动化测试和代码质量检查
- 监控系统行为与期望模型的一致性
-
领域反馈:
- 构建最小可行产品(MVP)验证模型
- 收集用户反馈调整模型
- 与领域专家定期回顾实现
-
知识共享:
- 代码审查关注领域表达
- 定期分享模型变化和洞见
- 建立领域知识库
10.8 实用平衡
实践原则:
- 在理论纯粹性和实用性间寻找平衡
- 识别何时简化或妥协是合理的
- 关注业务价值而非教条主义
具体做法:
-
价值驱动决策:
- 评估模型复杂性带来的业务价值
- 接受某些区域可能需要务实妥协
- 记录并理解设计妥协
-
渐进式采用:
- 从最有价值的领域概念开始
- 随着团队成熟度提高,逐步深化模型
- 允许不同子域采用不同深度的DDD
-
持续学习:
- 鼓励团队学习和实验
- 回顾并从实践中提炼经验
- 调整方法以适应团队和项目特点
11. 总结
领域驱动设计(DDD)是一种强大的方法论,它将业务领域置于软件设计的中心位置。通过深入理解和建模业务领域,DDD帮助团队构建能够准确反映业务规则和流程的软件系统。
11.1 DDD的核心价值
-
业务与技术对齐:
- 建立业务专家和技术团队的共同语言
- 确保软件系统准确反映业务需求
- 降低需求转换为代码时的信息损失
-
处理复杂性:
- 通过限界上下文分解复杂问题
- 使用领域模型表达复杂业务规则
- 提供清晰的概念框架处理复杂度
-
可持续发展:
- 构建能够适应业务变化的系统
- 支持长期演进和增量改进
- 在技术实现和业务理解间建立良性循环
11.2 何时使用DDD
DDD并非适用于所有软件项目,最适合以下场景:
- 业务复杂度高的系统
- 需要长期演进的核心业务系统
- 团队需要与领域专家紧密协作的项目
- 具有复杂业务规则的领域
- 希望降低业务变化带来的开发成本的系统
对于简单的CRUD应用或纯技术性系统,DDD可能过于复杂,应考虑更简单的方法。
11.3 采用DDD的路径
-
起步阶段:
- 学习DDD基础概念和术语
- 识别组织中适合DDD的问题域
- 在小规模项目中实验DDD概念
-
团队成长:
- 培养团队DDD能力
- 建立与领域专家的合作机制
- 发展适合组织的DDD实践
-
扩大应用:
- 将DDD应用于更广泛的项目
- 建立组织级DDD实践社区
- 持续优化和调整方法
11.4 未来展望
随着软件开发的持续演进,DDD也在不断发展:
-
与新兴技术的结合:
- DDD与云原生架构的结合
- 在人工智能和机器学习系统中应用DDD
- 区块链和分布式系统中的领域建模
-
实践的成熟化:
- 更多工具支持DDD实践
- 标准化的模式和实践
- 行业特定的领域模型参考
-
思想的扩展:
- 将DDD原则应用于更广泛的问题领域
- 跨团队和组织的领域建模
- 结合设计思维和DDD
领域驱动设计不仅是一种技术方法,更是一种思维方式。它鼓励我们超越代码和技术细节,深入理解和表达业务领域的本质。通过DDD,我们不仅构建符合需求的软件,更构建能够表达领域知识、支持业务创新、并能随业务演进的系统。
**:
- 为上下文间交互设计清晰接口
- 使用公开的数据传输对象(DTO)
- 实施明确的转换和防腐层
- 团队协作边界:
- 确保团队组织与上下文边界一致
- 建立跨团队协作协议
- 定期召开上下文同步会议
10.5 演进式设计
实践原则:
- 接受模型会随着理解加深而演进
- 保持设计的灵活性和可塑性
- 平衡短期需求与长期演进
具体做法:
-
迭代模型开发:
- 从简单模型开始,随着理解加深再改进
- 明确版本化领域模型
- 计划并执行模型重构
-
重构友好的架构:
- 构建支持领域模型演进的架构
- 保持高测试覆盖率保护重构
- 隔离变化频率不同的组件
-
知识累积:
- 记录设计决策和权衡
- 建立模型演进历史
- 分享学习成果和模型改进
10.6 测试驱动开发
实践原则:
- 使用测试表达领域规则和行为
- 建立多层次测试策略
- 测试反映领域语言和概念
具体做法:
-
领域行为测试:
- 编写表达业务规则的测试
- 使用业务术语描述测试场景
- 关注领域行为而非实现细节
-
分层测试策略:
- 单元测试:验证聚合和实体行为
- 集成测试:验证上下文集成
- 端到端测试:验证关键业务场景
-
测试作为文档:
- 测试作为活的领域规范
- 新团队成员通过测试学习领域
- 与领域专家一起审查测试场景
10.7 持续集成与反馈
实践原则:
- 建立快速反馈循环
- 确保模型在实现过程中保持一致
- 持续验证领域理解
具体做法:
-
技术实践:
- 实施持续集成/持续部署
- 自动化测试和代码质量检查
- 监控系统行为与期望模型的一致性
-
领域反馈:
- 构建最小可行产品(MVP)验证模型
- 收集用户反馈调整模型
- 与领域专家定期回顾实现
-
知识共享:
- 代码审查关注领域表达
- 定期分享模型变化和洞见
- 建立领域知识库
10.8 实用平衡
实践原则:
- 在理论纯粹性和实用性间寻找平衡
- 识别何时简化或妥协是合理的
- 关注业务价值而非教条主义
具体做法:
-
价值驱动决策:
- 评估模型复杂性带来的业务价值
- 接受某些区域可能需要务实妥协
- 记录并理解设计妥协
-
渐进式采用:
- 从最有价值的领域概念开始
- 随着团队成熟度提高,逐步深化模型
- 允许不同子域采用不同深度的DDD
-
持续学习:
- 鼓励团队学习和实验
- 回顾并从实践中提炼经验
- 调整方法以适应团队和项目特点
11. 总结
领域驱动设计(DDD)是一种强大的方法论,它将业务领域置于软件设计的中心位置。通过深入理解和建模业务领域,DDD帮助团队构建能够准确反映业务规则和流程的软件系统。
11.1 DDD的核心价值
-
业务与技术对齐:
- 建立业务专家和技术团队的共同语言
- 确保软件系统准确反映业务需求
- 降低需求转换为代码时的信息损失
-
处理复杂性:
- 通过限界上下文分解复杂问题
- 使用领域模型表达复杂业务规则
- 提供清晰的概念框架处理复杂度
-
可持续发展:
- 构建能够适应业务变化的系统
- 支持长期演进和增量改进
- 在技术实现和业务理解间建立良性循环
11.2 何时使用DDD
DDD并非适用于所有软件项目,最适合以下场景:
- 业务复杂度高的系统
- 需要长期演进的核心业务系统
- 团队需要与领域专家紧密协作的项目
- 具有复杂业务规则的领域
- 希望降低业务变化带来的开发成本的系统
对于简单的CRUD应用或纯技术性系统,DDD可能过于复杂,应考虑更简单的方法。
11.3 采用DDD的路径
-
起步阶段:
- 学习DDD基础概念和术语
- 识别组织中适合DDD的问题域
- 在小规模项目中实验DDD概念
-
团队成长:
- 培养团队DDD能力
- 建立与领域专家的合作机制
- 发展适合组织的DDD实践
-
扩大应用:
- 将DDD应用于更广泛的项目
- 建立组织级DDD实践社区
- 持续优化和调整方法
11.4 未来展望
随着软件开发的持续演进,DDD也在不断发展:
-
与新兴技术的结合:
- DDD与云原生架构的结合
- 在人工智能和机器学习系统中应用DDD
- 区块链和分布式系统中的领域建模
-
实践的成熟化:
- 更多工具支持DDD实践
- 标准化的模式和实践
- 行业特定的领域模型参考
-
思想的扩展:
- 将DDD原则应用于更广泛的问题领域
- 跨团队和组织的领域建模
- 结合设计思维和DDD
领域驱动设计不仅是一种技术方法,更是一种思维方式。它鼓励我们超越代码和技术细节,深入理解和表达业务领域的本质。通过DDD,我们不仅构建符合需求的软件,更构建能够表达领域知识、支持业务创新、并能随业务演进的系统。
在软件复杂性持续增长的今天,DDD为我们提供了一种管理复杂性的有效方法,帮助我们在快速变化的业务环境中构建持久、有弹性的软件系统。
相关文章:
领域驱动设计 (Domain-Driven Design, DDD)
文章目录 1. 引言1.1 什么是领域驱动设计1.2 为什么需要DDD1.3 DDD适用场景 2. DDD基础概念2.1 领域(Domain)2.2 模型(Model)与领域模型(Domain Model)2.3 通用语言(Ubiquitous Language) 3. 战略设计3.1 限界上下文(Bounded Context)3.2 上下文映射(Context Mapping)3.3 大型核…...

单卡4090部署Qwen3-32B-AWQ(4bit量化)-vllm
单卡4090部署Qwen3-32B-AWQ(4bit量化) 模型:Qwen3-32B-AWQ(4bit量化) 显卡:4090 1 张 python版本 python 3.12 推理框架“vllm 重要包的版本 vllm0.9.0创建GPU云主机 这里我使用的是优云智算平台的GPU,使用链接可以看下面的 https://blog.…...

漫画Android:Handler机制是怎么实现的?
线程之间通信会用到Handler,比如,在子线程中进行耗时的网络请求任务,子线程在获取到数据后,更新界面的时候就需要用到Handler; 子线程在获取到数据后,不直接去更新 界面,而是把数据通过一个消息…...

多部手机连接同一wifi的ip一样吗?如何更改ip
通常情况下,多部手机连接同一个WiFi时,它们的IP地址是各不相同的(在局域网内)。但是,从互联网(外网)的角度看,它们共享同一个公网IP地址。让我详细解释一下,并说明如何更…...

飞牛fnNAS的Docker应用之迅雷篇
目录 一、“迅雷”应用安装 二、启动迅雷 三、迅雷账号登录 四、修改“迅雷”下载保存路径 1、下载路径准备 2、停止“迅雷”Docker容器 3、修改存储位置 4、重新启动Docker容器 5、再次“启用”迅雷 五、测试 1、在PC上添加下载任务 2、手机上管理 3、手机添加下…...
C++中指针与引用的区别详解:从原理到实战
C中指针与引用的区别详解:从原理到实战 1. 引言:指针与引用的重要性 在C编程中,指针和引用是两个极其重要的概念,也是许多初学者容易混淆的地方。作为C的核心特性,它们直接操作内存地址,提供了对内存的直…...

SQLMesh 用户定义变量详解:从全局到局部的全方位配置指南
SQLMesh 提供了灵活的多层级变量系统,支持从全局配置到模型局部作用域的变量定义。本文将详细介绍 SQLMesh 的四类用户定义变量(global、gateway、blueprint 和 local)以及宏函数的使用方法。 一、变量类型概述 SQLMesh 支持四种用户定义变量…...

inviteflood:基于 UDP 的 SIP/SDP 洪水攻击工具!全参数详细教程!Kali Linux教程!
简介 一种通过 UDP/IP 执行 SIP/SDP INVITE 消息泛洪的工具。该工具已在 Linux Red Hat Fedora Core 4 平台(奔腾 IV,2.5 GHz)上测试,但预计该工具可在各种 Linux 发行版上成功构建和执行。 inviteflood 是一款专注于 SIP 协议攻…...
软件工程:关于招标合同履行阶段变更的法律分析
关于招标合同履行阶段建设内容变更的法律分析 一、基本原则 合同严守原则 根据《民法典》第465条,依法成立的合同受法律保护,原则上双方应严格按照约定履行。招标合同作为特殊类型的民事合同,其履行过程应当遵循更为严格的变更规则。 禁止…...
mysql一主多从 k8s部署实际案例
一、Kubernetes配置(MySQL主从集群) 主库StatefulSet配置(master-mysql.yaml): apiVersion: apps/v1 kind: StatefulSet metadata:name: mysql-master spec:serviceName: "mysql-master"replicas: 1select…...

Visual Studio 2022 设置自动换行
Visual Studio 2022 设置自动换行 一、在 Visual Studio 菜单栏上,选择 工具>选项二、选择“文本编辑器”>“所有语言”>“常规” 全局设置此选项。 一、在 Visual Studio 菜单栏上,选择 工具>选项 二、选择“文本编辑器”>“所有语言”&…...
沉浸式 “飞进” 鸟巢:虚拟旅游新体验
(一)全方位视角探秘 开启鸟巢虚拟旅游,借助 VR 技术,能从任意角度欣赏其外观。高空俯瞰,独特的钢结构如精美编织画卷,钢梁交织,阳光下闪耀银光,与绿树、蓝天相衬。拉近镜头&#x…...
Ubuntu 下同名文件替换后编译链接到旧内容的现象分析
Ubuntu 下同名文件替换后编译链接到旧内容的现象分析 在使用 Ubuntu 操作系统编译程序时,常常会遇到一个问题:当我们替换同名文件内容后,若不改变当前命令行目录,再次编译时,系统实际编译的仍是被覆盖前的旧文件内容。…...

【Linux网络篇】:简单的TCP网络程序编写以及相关内容的扩展
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:Linux篇–CSDN博客 文章目录 一.简单的TCP网络程序相关接口代码实现服务器单进程版服务器多…...
22.代理模式:思考与解读
原文地址:代理模式:思考与解读 更多内容请关注:深入思考与解读设计模式 引言 在软件开发中,尤其是当对象的访问需要控制时,你是否遇到过这样的问题:某些操作或对象可能需要进行额外的检查、优化或延迟加载ÿ…...

Scratch节日 | 粽子收集
端午节怎么过?当然是收粽子啦!这款 粽子收集 小游戏,让你一秒沉浸节日氛围,轻松收集粽子,收获满满快乐! 🎮 玩法介绍f 开始游戏:点击开始按钮,游戏正式开始!…...

stl三角面元文件转颗粒VTK文件
效果展示: import os import sys import json import argparse import numpy as np import pandas as pd import open3d as o3d from glob import globPARTICLE_RADIUS 0.025def stl_to_particles(objpath, radiusNone):if radius is None:radius PARTICLE_RADIU…...

Java String的使用续 -- StringBuilder类和StringBuffer
文章目录 字符串的不可变性StringBuilder和StringBuffer函数使用 字符串的不可变性 字符串不可变是因为有private修饰,只能在类的内部使用不可以在类外使用,因此使用时是不可以修改字符串的 public class test {public static void main(String[] args…...
Android学习之定时任务
Android定时任务的实现方式 在Android开发中,定时任务主要可以通过以下两类方式实现: Android系统组件 Handler消息机制:通过Handler.postDelayed()实现延时任务,适合简单UI线程操作AlarmManager:系统级定时服务&…...
WEB安全--RCE--webshell HIDS bypass4
继WEB安全--RCE--webshell HIDS bypass3的补充: 十三、时间开关 webshell: <?php ini_set("display_errors",1); function foo($test, $bar FSYSTEM) {echo $test . $bar; } $function new ReflectionFunction(foo); $q new ParseEr…...

基于python+Django+Mysql的校园二手交易市场
文章目录 基于pythonDjangoMysql的校园二手交易市场运行步骤系统设计功能设计任务目标用户特点参与者列表基本要求功能模块图 数据库设计会员用户信息表(user_userinfo)商品信息表(goods_goodsinfo)管理员用户信息表(a…...

从零打造算法题刷题助手:Agent搭建保姆级攻略
我用Trae 做了一个有意思的Agent 「大厂机试助手」。 点击 https://s.trae.com.cn/a/d2a596 立即复刻,一起来玩吧! Agent 简介 Agent名称为大厂机试助手,主要功能有以下三点。 解题: 根据用户给出的题目给出具体的解题思路引导做…...
Oracle 12c新增的数字转换验证VALIDATE_CONVERSION函数
Oracle 12c新增的数字转换验证函数 一、VALIDATE_CONVERSION函数(12c R2新增) Oracle 12c Release 2引入了原生验证函数,可直接判断字符串能否转换为指定类型: SELECT VALIDATE_CONVERSION(123.45 AS NUMBER) FROM dual; -- 返…...
参数/非参数检验和连续/离散/分类等变量类型的关系
参数统计方法通常应用于参数变量,但参数变量并不都是连续型变量。参数变量是指那些可以用参数(如均值、方差等)来描述其分布特征的变量。参数变量可以是连续型变量,也可以是离散型变量,只要它们遵循某种特定的分布&…...

懒人云电脑方案:飞牛NAS远程唤醒 + 节点小宝一键唤醒、远程控制Windows!
后台高频问题解答: “博主,飞牛NAS能定时开关机了,能不能让它顺便把家里Windows电脑也远程唤醒控制?最好点一下就能连,不用记IP端口那种!” 安排!今天这套方案完美实现: ✅ 飞牛NAS…...

【Python】第一弹:对 Python 的认知
目录 一、Python 的背景 1.1. Python 的由来 1.2 Python 的作用 1.3 Python 的优缺点 1.4 Python 的开发工具 一、Python 的背景 1.1. Python 的由来 Python 由荷兰数学和计算机科学研究学会的吉多・范罗苏姆 (Guido van Rossum)在 20 世纪 80 年代…...

直播预告 | 聚焦芯必达|打造可靠高效的国产 MCU 与智能 SBC 汽车解决方案
随着汽车电子国产化快速推进,车规级 MCU 与 CAN/LIN SBC 作为车身控制的核心组件,正面临更高的安全与可靠性挑战。品佳集团将携手芯必达微电子,深入剖析国产 MCU/SBC/智能 SBC 的最新技术与应用,助力企业打造高性能、可量产的国产…...

Java源码中有哪些细节可以参考?(持续更新)
欢迎来到啾啾的博客🐱。 记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。 有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。 目录 String的比较final的使用transient避免序列化 St…...

GelSight Mini触觉传感器:7μm精度+3D 映射,赋能具身智能精密操作
GelSight Mini 高分辨率视触觉传感器采用先进的光学成像与触觉感知技术,赋予机器人接近人类的触觉能力。该设备可捕捉物体表面微观细节,并生成高精度的2D/3D数字映射,帮助机器人识别形状、纹理及接触力,从而执行更复杂、精准的操作…...

day 23 机器学习管道(pipeline)
在机器学习领域,“pipeline” 常被翻译为 “管道” 或 “流水线”,它是机器学习中极为重要的概念。在构建机器学习模型时,通常需按特定顺序对数据执行预处理、特征提取、模型训练以及模型评估等步骤,而使用 “pipeline” 能有效管…...