领域驱动设计(DDD)学习笔记之:战术设计

聚合(Aggregates)

聚合根的定义和职责

在领域驱动设计(DDD)中,聚合(Aggregate)是由一个或多个实体和值对象组合而成的业务模型。聚合根(Aggregate Root)是聚合的入口点和主要控制者,它是聚合的一部分,负责维护聚合内的一致性和完整性。理解和正确使用聚合根是确保领域模型设计合理、业务逻辑清晰的关键。

1. 聚合根的定义

定义

聚合根是聚合内部的一个特殊实体,作为聚合的唯一入口点,负责聚合内的所有操作和状态变更。聚合根通过维护聚合内对象的引用和关系,确保聚合的一致性和业务规则的完整性。

特点
  • 唯一入口:聚合内的所有操作必须通过聚合根进行,外部不能直接访问聚合内的其他对象。
  • 唯一标识:聚合根具有唯一标识符,用于区分不同的聚合实例。
  • 一致性边界:聚合根负责维护聚合内的一致性和业务规则,确保事务在聚合内的一致性。

2. 聚合根的职责

管理聚合内部的对象

聚合根负责管理聚合内部的所有实体和值对象,维护它们之间的关系和引用。

确保业务规则的一致性

聚合根负责执行和验证聚合内的业务规则,确保任何状态变更都符合业务逻辑。

维护聚合的一致性边界

聚合根确保聚合内的一致性边界,任何操作和事务都必须通过聚合根进行,以保证数据的一致性和完整性。

处理外部请求

聚合根作为聚合的入口点,负责处理外部请求,并将请求分派给聚合内部的相关对象。

3. 聚合根的示例

以下是一个电子商务系统中订单聚合的示例,订单(Order)作为聚合根,管理订单项(OrderItem)和订单的状态变更。

 
public class Order {private String orderId;private OrderStatus status;private List<OrderItem> items;// 构造方法public Order(String orderId, List<OrderItem> items) {this.orderId = orderId;this.items = items;this.status = OrderStatus.PENDING;}// 获取订单IDpublic String getOrderId() {return orderId;}// 获取订单状态public OrderStatus getStatus() {return status;}// 添加订单项public void addItem(OrderItem item) {items.add(item);}// 支付订单public void pay() {if (status == OrderStatus.PENDING) {status = OrderStatus.PAID;} else {throw new IllegalStateException("Order cannot be paid in its current state.");}}// 取消订单public void cancel() {if (status == OrderStatus.PENDING) {status = OrderStatus.CANCELED;} else {throw new IllegalStateException("Order cannot be canceled in its current state.");}}
}

4. 聚合根的设计原则

高内聚低耦合

聚合根应遵循高内聚低耦合的原则,确保聚合内部对象之间的紧密关联,同时减少外部对象对聚合内部的依赖。

明确边界

聚合根应明确聚合的边界,定义哪些对象属于聚合的一部分,确保聚合内部的一致性和完整性。

适当的粒度

聚合根的粒度应适当,既不能过大导致复杂度过高,也不能过小导致频繁的跨聚合操作。

事务一致性

聚合根应确保聚合内部的事务一致性,所有操作都必须通过聚合根进行,以确保数据的一致性和完整性。

5. 聚合根的最佳实践

设计单一职责

聚合根应具有单一职责,专注于管理聚合内部的对象和业务逻辑,避免过多的职责分散。

避免过度嵌套

聚合内部的对象层次应尽量扁平化,避免过度嵌套导致复杂度增加。

定期评审和优化

定期评审聚合根的设计和实现,根据业务需求和系统变化进行必要的调整和优化。

总结

聚合根在DDD中扮演着重要角色,它作为聚合的入口点和主要控制者,负责管理聚合内部的对象和业务逻辑,确保聚合的一致性和完整性。通过合理设计和使用聚合根,可以有效提升领域模型的清晰性和可维护性。在实际项目中,持续关注和优化聚合根的设计,确保其符合业务需求和系统发展的需要。

聚合间的关系和交互

在领域驱动设计(DDD)中,聚合是用于定义业务逻辑和数据一致性的边界。每个聚合都有一个聚合根来管理内部的一致性和状态变更。在复杂系统中,不同的聚合之间往往需要进行交互和协作以完成业务流程。正确处理聚合间的关系和交互,有助于保持系统的松耦合、高内聚,确保业务逻辑的清晰和数据的一致性。

1. 聚合间的关系

弱关联(Loose Coupling)

聚合之间应尽量保持弱关联。一个聚合不应该直接引用另一个聚合的内部对象,只能通过唯一标识符(ID)来引用其他聚合。

通过事件进行通信

聚合之间的交互可以通过发布和订阅领域事件来实现。这种方式可以实现松耦合和异步处理,提高系统的扩展性和灵活性。

通过服务进行通信

聚合之间的直接交互可以通过应用服务(Application Service)进行,这样可以避免直接引用和依赖,保持聚合的独立性。

2. 聚合间的交互方式

领域事件(Domain Events)
定义

领域事件是指在领域模型中发生的重要事件,这些事件对系统中的其他部分有意义,并可以被其他聚合监听和处理。

特点
  • 松耦合:聚合之间通过事件进行通信,减少了直接依赖。
  • 异步处理:事件可以异步处理,提高系统的响应能力和扩展性。
  • 事件溯源:领域事件可以用于记录和追踪系统中的重要行为和状态变化。
示例

假设在一个电子商务系统中,当一个订单被创建时,需要通知库存系统进行库存扣减。

订单聚合发布领域事件

 
public class Order {private String orderId;private OrderStatus status;private List<OrderItem> items;private List<DomainEvent> domainEvents = new ArrayList<>();// 构造方法public Order(String orderId, List<OrderItem> items) {this.orderId = orderId;this.items = items;this.status = OrderStatus.PENDING;this.domainEvents.add(new OrderCreatedEvent(orderId, items));}// 获取领域事件public List<DomainEvent> getDomainEvents() {return domainEvents;}// 其他方法
}

库存系统订阅领域事件

 
public class InventoryService {public void handleOrderCreatedEvent(OrderCreatedEvent event) {// 扣减库存逻辑for (OrderItem item : event.getItems()) {reduceInventory(item.getProductId(), item.getQuantity());}}private void reduceInventory(String productId, int quantity) {// 执行库存扣减逻辑}
}

应用服务(Application Services)
定义

应用服务负责协调多个聚合之间的交互,处理跨聚合的业务逻辑。应用服务不包含业务逻辑,它们只是调用聚合中的方法以完成业务操作。

特点
  • 协调多个聚合:应用服务可以调用多个聚合的方法来完成一个业务操作。
  • 保持聚合独立:聚合之间的交互通过应用服务进行,保持聚合的独立性。
示例

假设在一个电子商务系统中,处理一个订单支付的流程需要同时更新订单状态和支付记录。

应用服务实现

 
public class OrderApplicationService {private final OrderRepository orderRepository;private final PaymentRepository paymentRepository;public OrderApplicationService(OrderRepository orderRepository, PaymentRepository paymentRepository) {this.orderRepository = orderRepository;this.paymentRepository = paymentRepository;}public void processPayment(String orderId, Payment payment) {Order order = orderRepository.findById(orderId);if (order != null) {order.pay();paymentRepository.save(payment);orderRepository.save(order);} else {throw new OrderNotFoundException(orderId);}}
}

REST API 或 RPC
定义

使用REST API或RPC(远程过程调用)等技术,通过网络协议在不同的微服务或系统之间进行通信。这种方式适用于分布式系统中的跨聚合交互。

特点
  • 跨服务通信:适用于微服务架构中的跨服务通信。
  • 标准化协议:使用标准化的网络协议,如HTTP、gRPC等。
示例

假设在一个微服务架构的电子商务系统中,订单服务需要调用支付服务的API以完成支付。

订单服务调用支付服务的API

 
public class PaymentServiceClient {private final RestTemplate restTemplate;private final String paymentServiceUrl;public PaymentServiceClient(RestTemplate restTemplate, String paymentServiceUrl) {this.restTemplate = restTemplate;this.paymentServiceUrl = paymentServiceUrl;}public void processPayment(PaymentRequest paymentRequest) {restTemplate.postForEntity(paymentServiceUrl + "/payments", paymentRequest, Void.class);}
}

3. 聚合间交互的最佳实践

设计松耦合接口

设计聚合间的交互时,尽量保持接口的松耦合,使用唯一标识符而不是直接引用对象。

使用领域事件

尽量通过领域事件进行聚合间的通信,实现异步处理和松耦合。

应用服务协调

通过应用服务协调多个聚合的交互,避免聚合之间的直接依赖。

避免跨聚合事务

尽量避免跨聚合的事务操作,使用最终一致性策略,如通过事件溯源和补偿交易等方式实现。

总结

在DDD中,聚合之间的关系和交互需要精心设计,以保持系统的松耦合和高内聚。通过使用领域事件、应用服务和REST API等技术,可以实现聚合间的有效通信和协调。在实际项目中,持续评审和优化聚合间的交互模式,确保系统的稳定性和扩展性。

实体和值对象(Entities & Value Objects)

实体的特征和行为

在领域驱动设计(DDD)中,实体(Entity)是领域模型的核心构建块之一。实体用于表示具有唯一身份标识和生命周期的业务对象。理解实体的特征和行为对于构建高质量的领域模型至关重要。以下是实体的主要特征和行为的详细解释。

1. 实体的特征

唯一标识符(Unique Identifier)

定义:实体的一个关键特征是具有唯一标识符,用于区分不同的实体实例。这个标识符在实体的整个生命周期内保持不变。

示例:在一个订单管理系统中,每个订单都有一个唯一的订单ID。

 
public class Order {private String orderId; // 唯一标识符private OrderStatus status;private List<OrderItem> items;// 构造方法public Order(String orderId, List<OrderItem> items) {this.orderId = orderId;this.items = items;this.status = OrderStatus.PENDING;}// 获取订单IDpublic String getOrderId() {return orderId;}
}

可变状态(Mutable State)

定义:实体通常具有可变的状态,可以随着时间的推移和业务操作的执行发生变化。这些状态变化反映了业务过程中的实际变化。

示例:订单的状态可以从“待支付”变为“已支付”或“已取消”。

 
public class Order {private String orderId;private OrderStatus status;private List<OrderItem> items;// 获取订单状态public OrderStatus getStatus() {return status;}// 支付订单public void pay() {if (status == OrderStatus.PENDING) {status = OrderStatus.PAID;} else {throw new IllegalStateException("Order cannot be paid in its current state.");}}// 取消订单public void cancel() {if (status == OrderStatus.PENDING) {status = OrderStatus.CANCELED;} else {throw new IllegalStateException("Order cannot be canceled in its current state.");}}
}

复杂行为(Complex Behavior)

定义:实体不仅包含数据,还包含业务逻辑和行为,这些行为定义了实体的操作和规则。通过这些行为,实体能够执行各种业务操作并维护其内部的一致性。

示例:订单实体可以包含添加订单项、支付订单、取消订单等行为。

 
public class Order {private String orderId;private OrderStatus status;private List<OrderItem> items;// 构造方法public Order(String orderId, List<OrderItem> items) {this.orderId = orderId;this.items = items;this.status = OrderStatus.PENDING;}// 添加订单项public void addItem(OrderItem item) {items.add(item);}// 获取订单项列表public List<OrderItem> getItems() {return items;}
}

2. 实体的行为

业务操作(Business Operations)

实体的行为通常通过业务操作来实现,这些操作反映了业务过程中的实际动作。业务操作需要维护实体的内部状态和业务规则的一致性。

示例:订单实体中的支付和取消操作。

 
public class Order {private String orderId;private OrderStatus status;private List<OrderItem> items;// 支付订单public void pay() {if (status == OrderStatus.PENDING) {status = OrderStatus.PAID;} else {throw new IllegalStateException("Order cannot be paid in its current state.");}}// 取消订单public void cancel() {if (status == OrderStatus.PENDING) {status = OrderStatus.CANCELED;} else {throw new IllegalStateException("Order cannot be canceled in its current state.");}}
}

业务规则(Business Rules)

实体需要遵循特定的业务规则,这些规则可以通过业务操作中的逻辑来实现。业务规则确保实体在业务过程中的一致性和正确性。

示例:订单在支付前必须处于“待支付”状态,支付后不能取消。

 
public class Order {private String orderId;private OrderStatus status;private List<OrderItem> items;// 支付订单public void pay() {if (status == OrderStatus.PENDING) {status = OrderStatus.PAID;} else {throw new IllegalStateException("Order cannot be paid in its current state.");}}// 取消订单public void cancel() {if (status == OrderStatus.PENDING) {status = OrderStatus.CANCELED;} else {throw new IllegalStateException("Order cannot be canceled in its current state.");}}
}

事件处理(Event Handling)

实体可以发布和处理领域事件,这些事件表示实体状态的变化或业务过程中的重要动作。领域事件有助于实现聚合之间的松耦合和异步处理。

示例:订单创建后发布订单创建事件。

 
public class Order {private String orderId;private OrderStatus status;private List<OrderItem> items;private List<DomainEvent> domainEvents = new ArrayList<>();// 构造方法public Order(String orderId, List<OrderItem> items) {this.orderId = orderId;this.items = items;this.status = OrderStatus.PENDING;this.domainEvents.add(new OrderCreatedEvent(orderId, items));}// 获取领域事件public List<DomainEvent> getDomainEvents() {return domainEvents;}// 清除领域事件public void clearDomainEvents() {domainEvents.clear();}
}

3. 实体的设计原则

单一职责原则(Single Responsibility Principle)

每个实体应有明确的职责,专注于管理自身的状态和行为。不要让实体承担过多的责任。

高内聚低耦合(High Cohesion and Low Coupling)

实体的内部应具有高内聚,保持相关属性和行为的一致性。同时,实体之间应保持低耦合,尽量通过唯一标识符而不是直接引用来关联其他实体。

保护不变量(Protect Invariants)

实体应保护其业务规则和不变量,确保任何状态变更都符合业务逻辑和规则。

领域事件驱动(Event-Driven Domain)

使用领域事件来处理跨聚合的业务逻辑,实现聚合之间的松耦合和异步处理。

总结

在DDD中,实体是领域模型的核心构建块,具有唯一标识符、可变状态和复杂行为。实体的设计和实现应遵循单一职责、高内聚低耦合和保护不变量的原则。通过合理设计实体,可以确保领域模型的清晰性和可维护性,有效支持业务逻辑的实现和系统的扩展。在实际项目中,持续关注和优化实体的设计,确保其符合业务需求和系统发展的需要。

值对象的特性和使用场景

在领域驱动设计(DDD)中,值对象(Value Object)是用于表示领域模型中的属性和行为的一个重要概念。与实体不同,值对象没有唯一标识符,其身份由属性值决定。值对象通常用于表示一些基本的业务概念,并且这些概念在业务操作中具有不可变性和可重用性。以下是值对象的特性和使用场景的详细解释。

1. 值对象的特性

不可变性(Immutability)

定义:值对象一旦创建,其状态就不能改变。所有的操作都会返回一个新的值对象,而不是修改原有的值对象。

示例:货币金额(Money)是一个典型的值对象,一旦创建其值不能改变。

 
public class Money {private final BigDecimal amount;private final String currency;// 构造方法public Money(BigDecimal amount, String currency) {this.amount = amount;this.currency = currency;}// 获取金额public BigDecimal getAmount() {return amount;}// 获取货币单位public String getCurrency() {return currency;}// 加法操作,返回新的Money对象public Money add(Money other) {if (!this.currency.equals(other.currency)) {throw new IllegalArgumentException("Currencies must match for addition.");}return new Money(this.amount.add(other.amount), this.currency);}
}

无标识性(No Identity)

定义:值对象没有唯一标识符,其身份由属性值决定。两个值对象只要其属性值相同,就被认为是相同的。

示例:颜色(Color)可以作为一个值对象,其身份由颜色的属性(如红、绿、蓝值)决定。

 
public class Color {private final int red;private final int green;private final int blue;// 构造方法public Color(int red, int green, int blue) {this.red = red;this.green = green;this.blue = blue;}// 获取红色值public int getRed() {return red;}// 获取绿色值public int getGreen() {return green;}// 获取蓝色值public int getBlue() {return blue;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Color color = (Color) o;return red == color.red && green == color.green && blue == color.blue;}@Overridepublic int hashCode() {return Objects.hash(red, green, blue);}
}

自包含行为(Encapsulated Behavior)

定义:值对象可以包含业务逻辑和行为,这些行为通常是与值对象的属性相关的操作。

示例:坐标(Coordinate)可以作为一个值对象,包含计算距离的行为。

 
public class Coordinate {private final double x;private final double y;// 构造方法public Coordinate(double x, double y) {this.x = x;this.y = y;}// 获取X坐标public double getX() {return x;}// 获取Y坐标public double getY() {return y;}// 计算两个坐标之间的距离public double distanceTo(Coordinate other) {double dx = this.x - other.x;double dy = this.y - other.y;return Math.sqrt(dx * dx + dy * dy);}
}

2. 值对象的使用场景

表示基本业务概念

值对象通常用于表示一些基本的业务概念,这些概念具有明确的属性和行为,并且这些属性和行为在业务操作中是不可变的。

示例:货币金额、坐标、颜色、电话号码等。

作为实体的属性

值对象可以作为实体的属性,帮助定义实体的属性值和行为。这样可以使实体的设计更加清晰和简洁。

示例:订单实体中的货币金额和地址。

 
public class Order {private String orderId;private Money totalAmount;private Address shippingAddress;// 构造方法public Order(String orderId, Money totalAmount, Address shippingAddress) {this.orderId = orderId;this.totalAmount = totalAmount;this.shippingAddress = shippingAddress;}// 获取订单总金额public Money getTotalAmount() {return totalAmount;}// 获取订单配送地址public Address getShippingAddress() {return shippingAddress;}
}

实现值对象的行为

值对象不仅包含数据,还可以包含相关的行为。这些行为通常是与值对象的属性相关的操作,如计算、转换等。

示例:货币金额的加法操作、坐标的距离计算。

值对象的集合

值对象可以组成集合,用于表示复杂的业务概念和关系。

示例:订单项列表、地址列表等。

 
public class Order {private String orderId;private List<OrderItem> items;// 构造方法public Order(String orderId, List<OrderItem> items) {this.orderId = orderId;this.items = items;}// 获取订单项列表public List<OrderItem> getItems() {return items;}// 计算订单总金额public Money calculateTotalAmount() {Money total = new Money(BigDecimal.ZERO, "USD");for (OrderItem item : items) {total = total.add(item.getTotalPrice());}return total;}
}

3. 值对象的设计原则

不可变性

值对象应设计为不可变,确保其一旦创建,其状态就不能改变。所有的修改操作都应该返回一个新的值对象。

无标识性

值对象不应具有唯一标识符,其身份由属性值决定。两个值对象只要其属性值相同,就被认为是相同的。

自包含行为

值对象可以包含与其属性相关的行为,确保这些行为能够独立地完成业务操作。

尽量小而简单

值对象应尽量设计为小而简单,确保其易于理解和使用。避免将过多的逻辑和状态放在值对象中。

总结

在DDD中,值对象是领域模型的一个重要组成部分,具有不可变性、无标识性和自包含行为等特性。值对象通常用于表示基本的业务概念,可以作为实体的属性,帮助定义实体的属性值和行为。通过合理设计和使用值对象,可以有效提高领域模型的清晰性和可维护性,确保业务逻辑的准确和一致。在实际项目中,持续关注和优化值对象的设计,确保其符合业务需求和系统发展的需要。

仓储(Repository)

仓储模式的实现

在领域驱动设计(DDD)中,仓储模式(Repository Pattern)用于管理聚合的持久化和访问。仓储提供了一个抽象层,隔离了领域模型与数据存储的具体实现细节,使得领域模型可以专注于业务逻辑,而不需要考虑数据的持久化和检索。以下是仓储模式的实现的详细讲解。

1. 仓储模式的定义

定义

仓储是一个用于管理聚合根(Aggregate Root)及其相关实体的存储库。它提供了查找、保存、更新和删除聚合的方法,确保领域模型与底层数据存储的解耦。

主要职责
  • 管理聚合:负责聚合根及其内部实体的持久化、更新和删除。
  • 提供抽象接口:通过接口定义仓储的方法,隐藏具体的数据存储实现。
  • 保持数据一致性:确保聚合的完整性和一致性,在保存和更新操作时,维护业务规则。

2. 仓储接口的设计

仓储接口定义了对聚合的基本操作,如查找、保存、更新和删除。它为领域模型提供了一个抽象层,使得领域模型可以不依赖于具体的数据存储技术。

示例:订单仓储接口
 
public interface OrderRepository {Order findById(String orderId);void save(Order order);void delete(Order order);
}

3. 仓储接口的实现

仓储接口的实现类负责具体的数据存储逻辑。实现类通常会依赖数据存储技术(如关系数据库、NoSQL数据库)和数据访问框架(如JPA、Hibernate)来完成具体的持久化操作。

示例:基于JPA的订单仓储实现
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;public class JpaOrderRepository implements OrderRepository {@PersistenceContextprivate EntityManager entityManager;@Overridepublic Order findById(String orderId) {return entityManager.find(Order.class, orderId);}@Overridepublic void save(Order order) {if (entityManager.contains(order) || order.getOrderId() == null) {entityManager.persist(order);} else {entityManager.merge(order);}}@Overridepublic void delete(Order order) {entityManager.remove(entityManager.contains(order) ? order : entityManager.merge(order));}
}

4. 使用仓储模式

仓储模式通过依赖注入的方式将仓储接口的实现注入到领域服务或应用服务中,使得领域服务可以通过仓储接口访问和操作聚合。

示例:订单应用服务
 
public class OrderApplicationService {private final OrderRepository orderRepository;// 通过构造方法注入仓储public OrderApplicationService(OrderRepository orderRepository) {this.orderRepository = orderRepository;}// 处理创建订单的业务逻辑public void createOrder(String orderId, List<OrderItem> items) {Order order = new Order(orderId, items);orderRepository.save(order);}// 处理支付订单的业务逻辑public void payOrder(String orderId) {Order order = orderRepository.findById(orderId);if (order != null) {order.pay();orderRepository.save(order);} else {throw new OrderNotFoundException(orderId);}}
}

5. 测试仓储模式

通过定义仓储接口,可以方便地为仓储实现类编写单元测试和集成测试。使用模拟对象(Mock Object)可以测试领域服务在不同场景下的行为。

示例:订单应用服务的单元测试
 
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import java.util.Collections;public class OrderApplicationServiceTest {@Testpublic void testCreateOrder() {OrderRepository orderRepository = mock(OrderRepository.class);OrderApplicationService service = new OrderApplicationService(orderRepository);service.createOrder("order1", Collections.emptyList());verify(orderRepository, times(1)).save(any(Order.class));}@Testpublic void testPayOrder() {OrderRepository orderRepository = mock(OrderRepository.class);OrderApplicationService service = new OrderApplicationService(orderRepository);Order order = new Order("order1", Collections.emptyList());when(orderRepository.findById("order1")).thenReturn(order);service.payOrder("order1");verify(orderRepository, times(1)).save(order);assertEquals(OrderStatus.PAID, order.getStatus());}
}

6. 仓储模式的最佳实践

保持接口简洁

仓储接口应尽量保持简洁,只包含必要的操作方法。复杂的查询操作可以通过规格模式(Specification Pattern)或查询对象(Query Object)来实现。

关注聚合根

仓储应仅对聚合根提供持久化操作,而不直接操作聚合内的其他实体。这确保了聚合的一致性和完整性。

使用依赖注入

通过依赖注入将仓储实现注入到领域服务或应用服务中,确保领域模型与数据存储的解耦。

考虑事务管理

在使用仓储模式时,确保事务管理的一致性。通常在应用服务层开启事务,以保证业务操作的原子性和数据一致性。

总结

仓储模式在DDD中扮演着重要角色,通过提供抽象的接口和具体的实现,隔离了领域模型与数据存储的具体实现细节。合理设计和实现仓储模式,可以提高系统的可维护性和可测试性,确保业务逻辑的清晰和数据的一致性。在实际项目中,持续关注和优化仓储模式的实现,确保其符合业务需求和系统发展的需要。

仓储与持久化技术的集成

在领域驱动设计(DDD)中,仓储模式(Repository Pattern)用于管理聚合的持久化和访问。集成持久化技术是实现仓储模式的关键步骤。常见的持久化技术包括关系数据库(如MySQL、PostgreSQL)、NoSQL数据库(如MongoDB、Cassandra)以及其他数据存储方式(如文件系统、内存数据库)。以下是关于如何将仓储模式与持久化技术集成的详细讲解。

1. 仓储模式与持久化技术的概述

仓储模式的核心概念
  • 抽象接口:定义聚合根的存储和访问操作。
  • 具体实现:实现这些接口以操作实际的持久化存储。
持久化技术的角色
  • 数据存储:提供持久化存储的数据结构和访问方式。
  • 数据访问框架:提供用于与持久化存储交互的工具和库,如JPA、Hibernate、MyBatis、Spring Data等。

2. 集成关系数据库

关系数据库是最常用的持久化技术之一。以下是使用JPA和Hibernate与关系数据库集成的示例。

仓储接口定义
 
public interface OrderRepository {Order findById(String orderId);void save(Order order);void delete(Order order);
}

使用JPA实现仓储
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;public class JpaOrderRepository implements OrderRepository {@PersistenceContextprivate EntityManager entityManager;@Overridepublic Order findById(String orderId) {return entityManager.find(Order.class, orderId);}@Override@Transactionalpublic void save(Order order) {if (entityManager.contains(order) || order.getOrderId() == null) {entityManager.persist(order);} else {entityManager.merge(order);}}@Override@Transactionalpublic void delete(Order order) {entityManager.remove(entityManager.contains(order) ? order : entityManager.merge(order));}
}

配置JPA

配置文件persistence.xml

 
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1"><persistence-unit name="orderPU"><provider>org.hibernate.jpa.HibernatePersistenceProvider</provider><class>com.example.domain.Order</class><properties><property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/><property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/mydb"/><property name="javax.persistence.jdbc.user" value="root"/><property name="javax.persistence.jdbc.password" value="password"/><property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/><property name="hibernate.hbm2ddl.auto" value="update"/></properties></persistence-unit>
</persistence>

3. 集成NoSQL数据库

NoSQL数据库在处理大数据和分布式系统方面有独特的优势。以下是使用Spring Data MongoDB与MongoDB集成的示例。

仓储接口定义
 
import org.springframework.data.mongodb.repository.MongoRepository;public interface OrderRepository extends MongoRepository<Order, String> {
}

Spring Data MongoDB配置

application.properties配置文件:

 
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=mydb

使用Spring Data MongoDB实现仓储

Spring Data MongoDB已经提供了基本的CRUD操作实现,无需额外编写实现类。

4. 集成文件系统

有时需要将数据持久化到文件系统中,例如日志文件、配置文件等。以下是使用Java I/O与文件系统集成的示例。

仓储接口定义
 
public interface OrderRepository {Order findById(String orderId);void save(Order order);void delete(String orderId);
}

文件系统实现
 
import java.io.*;
import java.util.HashMap;
import java.util.Map;public class FileOrderRepository implements OrderRepository {private final String directoryPath = "orders";private final Map<String, Order> orderCache = new HashMap<>();public FileOrderRepository() {File directory = new File(directoryPath);if (!directory.exists()) {directory.mkdir();}}@Overridepublic Order findById(String orderId) {if (orderCache.containsKey(orderId)) {return orderCache.get(orderId);}File file = new File(directoryPath + "/" + orderId + ".txt");if (!file.exists()) {return null;}try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {Order order = (Order) ois.readObject();orderCache.put(orderId, order);return order;} catch (IOException | ClassNotFoundException e) {throw new RuntimeException("Error reading order from file", e);}}@Overridepublic void save(Order order) {orderCache.put(order.getOrderId(), order);File file = new File(directoryPath + "/" + order.getOrderId() + ".txt");try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {oos.writeObject(order);} catch (IOException e) {throw new RuntimeException("Error writing order to file", e);}}@Overridepublic void delete(String orderId) {orderCache.remove(orderId);File file = new File(directoryPath + "/" + orderId + ".txt");if (file.exists()) {file.delete();}}
}

5. 使用依赖注入

通过依赖注入将仓储实现注入到领域服务或应用服务中,确保领域模型与数据存储的解耦。

示例:订单应用服务
 
public class OrderApplicationService {private final OrderRepository orderRepository;// 通过构造方法注入仓储public OrderApplicationService(OrderRepository orderRepository) {this.orderRepository = orderRepository;}// 处理创建订单的业务逻辑public void createOrder(String orderId, List<OrderItem> items) {Order order = new Order(orderId, items);orderRepository.save(order);}// 处理支付订单的业务逻辑public void payOrder(String orderId) {Order order = orderRepository.findById(orderId);if (order != null) {order.pay();orderRepository.save(order);} else {throw new OrderNotFoundException(orderId);}}
}

6. 事务管理

在集成持久化技术时,确保事务管理的一致性至关重要。通常在应用服务层开启事务,以保证业务操作的原子性和数据一致性。

示例:Spring事务管理
 
import org.springframework.transaction.annotation.Transactional;public class OrderApplicationService {private final OrderRepository orderRepository;public OrderApplicationService(OrderRepository orderRepository) {this.orderRepository = orderRepository;}@Transactionalpublic void createOrder(String orderId, List<OrderItem> items) {Order order = new Order(orderId, items);orderRepository.save(order);}@Transactionalpublic void payOrder(String orderId) {Order order = orderRepository.findById(orderId);if (order != null) {order.pay();orderRepository.save(order);} else {throw new OrderNotFoundException(orderId);}}
}

总结

将仓储模式与持久化技术集成是DDD实现中的关键步骤。通过定义仓储接口和具体的实现类,可以隔离领域模型与数据存储的具体实现细节,提高系统的可维护性和扩展性。在实际项目中,选择合适的持久化技术并合理设计和实现仓储模式,可以确保系统的稳定性和业务逻辑的清晰性。

领域服务(Domain Services)

领域服务的职责和作用

在领域驱动设计(DDD)中,领域服务(Domain Service)是用于封装业务逻辑的组件,它们负责处理那些不适合放在实体或值对象中的业务操作。领域服务帮助保持领域模型的纯粹性和内聚性,使得业务逻辑的实现更加清晰和易于维护。以下是领域服务的职责和作用的详细讲解。

1. 领域服务的定义

定义

领域服务是封装了业务逻辑的无状态对象,它们负责实现那些跨多个实体或聚合的业务操作。这些操作通常不属于单一实体或值对象的职责范围,因此需要在领域服务中实现。

特点
  • 无状态性:领域服务通常是无状态的,它们不持有任何实体的状态,仅仅是操作逻辑的封装。
  • 业务逻辑集中:领域服务集中管理复杂的业务逻辑,确保业务规则的一致性。
  • 职责单一:每个领域服务应有明确的职责,专注于处理特定的业务操作。

2. 领域服务的职责

封装跨聚合的业务逻辑

领域服务负责实现那些涉及多个聚合或实体的业务操作,确保这些操作在领域层的一致性和正确性。

示例:在电子商务系统中,处理订单支付时需要同时更新订单和支付记录,这种跨聚合的操作可以由领域服务来实现。

 
public class OrderService {private final OrderRepository orderRepository;private final PaymentRepository paymentRepository;public OrderService(OrderRepository orderRepository, PaymentRepository paymentRepository) {this.orderRepository = orderRepository;this.paymentRepository = paymentRepository;}public void processPayment(String orderId, Payment payment) {Order order = orderRepository.findById(orderId);if (order != null) {order.pay();paymentRepository.save(payment);orderRepository.save(order);} else {throw new OrderNotFoundException(orderId);}}
}

实现复杂业务规则

领域服务可以封装复杂的业务规则和算法,这些规则和算法可能涉及多个实体或值对象,单一实体无法完整实现。

示例:计算订单总金额时,需要遍历所有订单项并计算总和,这种逻辑可以封装在领域服务中。

 
public class OrderCalculationService {public Money calculateTotalAmount(Order order) {Money total = new Money(BigDecimal.ZERO, "USD");for (OrderItem item : order.getItems()) {total = total.add(item.getTotalPrice());}return total;}
}

协调多个领域对象的交互

领域服务协调多个领域对象的交互,确保它们在业务操作中的一致性和协同工作。

示例:在订单处理过程中,可能需要调用用户服务、库存服务和支付服务,这些交互可以由领域服务来协调。

 
public class OrderProcessingService {private final UserService userService;private final InventoryService inventoryService;private final PaymentService paymentService;public OrderProcessingService(UserService userService, InventoryService inventoryService, PaymentService paymentService) {this.userService = userService;this.inventoryService = inventoryService;this.paymentService = paymentService;}public void processOrder(String orderId, String userId) {User user = userService.findById(userId);Order order = inventoryService.createOrderForUser(user);Payment payment = paymentService.createPaymentForOrder(order);inventoryService.reserveInventory(order);paymentService.processPayment(payment);inventoryService.confirmInventory(order);}
}

3. 领域服务的作用

保持领域模型的纯粹性

通过将复杂的业务逻辑从实体和值对象中分离出来,领域服务帮助保持领域模型的纯粹性,使得领域模型专注于核心业务属性和行为。

提高业务逻辑的可复用性

领域服务可以被多个不同的业务流程调用,确保业务逻辑的可复用性和一致性,减少代码重复。

增强业务逻辑的可测试性

领域服务将业务逻辑集中在一个单独的组件中,使得业务逻辑的单元测试更加容易和高效。

4. 领域服务的最佳实践

明确职责范围

每个领域服务应有明确的职责范围,确保其专注于处理特定的业务操作和规则。

保持无状态性

领域服务应尽量保持无状态性,避免持有任何实体的状态。所有的状态操作应通过参数传递和返回值来完成。

依赖注入

通过依赖注入将需要的仓储和其他服务注入到领域服务中,确保领域服务的独立性和可测试性。

编写单元测试

为领域服务编写详细的单元测试,确保业务逻辑的正确性和一致性。

总结

领域服务在DDD中扮演着重要角色,通过封装复杂的业务逻辑、实现跨聚合的业务操作和协调多个领域对象的交互,领域服务帮助保持领域模型的纯粹性和内聚性。合理设计和使用领域服务,可以提高系统的可维护性、可复用性和可测试性。在实际项目中,持续关注和优化领域服务的设计,确保其符合业务需求和系统发展的需要。

服务的设计和实现

领域服务(Domain Service)是领域驱动设计(DDD)中的重要组件,用于封装那些不适合放在实体或值对象中的业务逻辑。领域服务帮助保持领域模型的纯粹性和内聚性,使得复杂的业务逻辑得以集中管理。以下是领域服务的设计和实现的详细讲解。

1. 领域服务的设计原则

明确职责

每个领域服务应有明确的职责范围,专注于处理特定的业务操作和规则,避免职责过于分散或模糊。

保持无状态

领域服务应尽量保持无状态性,不持有任何实体的状态。所有的状态操作应通过方法参数传递和返回值来完成。

依赖注入

通过依赖注入(Dependency Injection, DI)将需要的仓储和其他服务注入到领域服务中,确保领域服务的独立性和可测试性。

高内聚低耦合

设计领域服务时,应确保其内部逻辑的高内聚,同时尽量减少与其他服务或组件的耦合。

2. 领域服务的实现步骤

定义领域服务接口

首先定义领域服务的接口,接口中包含领域服务需要实现的业务操作方法。这有助于明确领域服务的职责范围和使用方式。

 
public interface OrderService {void processOrder(String orderId, Payment payment);Money calculateTotalAmount(String orderId);
}

实现领域服务接口

根据定义的接口实现领域服务的具体逻辑。在实现类中,通过依赖注入的方式获取需要的仓储和其他服务。

 
public class OrderServiceImpl implements OrderService {private final OrderRepository orderRepository;private final PaymentRepository paymentRepository;// 通过构造方法注入仓储public OrderServiceImpl(OrderRepository orderRepository, PaymentRepository paymentRepository) {this.orderRepository = orderRepository;this.paymentRepository = paymentRepository;}@Overridepublic void processOrder(String orderId, Payment payment) {Order order = orderRepository.findById(orderId);if (order != null) {order.pay();paymentRepository.save(payment);orderRepository.save(order);} else {throw new OrderNotFoundException(orderId);}}@Overridepublic Money calculateTotalAmount(String orderId) {Order order = orderRepository.findById(orderId);if (order != null) {return order.calculateTotalAmount();} else {throw new OrderNotFoundException(orderId);}}
}

配置依赖注入

在应用配置文件或框架配置类中配置依赖注入,将领域服务实现类注入到需要使用它们的组件中。

 
@Configuration
public class AppConfig {@Beanpublic OrderRepository orderRepository() {return new JpaOrderRepository();}@Beanpublic PaymentRepository paymentRepository() {return new JpaPaymentRepository();}@Beanpublic OrderService orderService(OrderRepository orderRepository, PaymentRepository paymentRepository) {return new OrderServiceImpl(orderRepository, paymentRepository);}
}

使用领域服务

在应用服务或其他组件中,通过依赖注入使用领域服务,调用其方法完成业务操作。

 
public class OrderApplicationService {private final OrderService orderService;public OrderApplicationService(OrderService orderService) {this.orderService = orderService;}public void handleOrderPayment(String orderId, Payment payment) {orderService.processOrder(orderId, payment);}public Money getOrderTotalAmount(String orderId) {return orderService.calculateTotalAmount(orderId);}
}

3. 领域服务的测试

为确保领域服务的正确性和可靠性,需要为领域服务编写单元测试。使用模拟对象(Mock Object)可以测试领域服务在不同场景下的行为。

示例:订单服务的单元测试
 
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;public class OrderServiceTest {@Testpublic void testProcessOrder() {OrderRepository orderRepository = mock(OrderRepository.class);PaymentRepository paymentRepository = mock(PaymentRepository.class);OrderService orderService = new OrderServiceImpl(orderRepository, paymentRepository);Order order = new Order("order1", new ArrayList<>());when(orderRepository.findById("order1")).thenReturn(order);Payment payment = new Payment("order1", new Money(BigDecimal.valueOf(100), "USD"));orderService.processOrder("order1", payment);verify(orderRepository, times(1)).save(order);verify(paymentRepository, times(1)).save(payment);}@Testpublic void testCalculateTotalAmount() {OrderRepository orderRepository = mock(OrderRepository.class);OrderService orderService = new OrderServiceImpl(orderRepository, null);Order order = new Order("order1", Arrays.asList(new OrderItem("item1", new Money(BigDecimal.valueOf(50), "USD"), 2)));when(orderRepository.findById("order1")).thenReturn(order);Money totalAmount = orderService.calculateTotalAmount("order1");assertEquals(new Money(BigDecimal.valueOf(100), "USD"), totalAmount);}
}

4. 领域服务的最佳实践

明确职责范围

确保每个领域服务有明确的职责范围,专注于处理特定的业务操作和规则,避免职责分散。

保持无状态性

设计领域服务时,应尽量保持无状态性,避免持有任何实体的状态,所有状态操作通过方法参数传递和返回值来完成。

依赖注入

通过依赖注入将仓储和其他服务注入到领域服务中,确保其独立性和可测试性。

编写单元测试

为领域服务编写详细的单元测试,确保其业务逻辑的正确性和一致性。

持续优化和重构

定期评审和优化领域服务的设计和实现,根据业务需求和系统变化进行必要的重构。

总结

领域服务在DDD中扮演着重要角色,通过封装复杂的业务逻辑、实现跨聚合的业务操作和协调多个领域对象的交互,领域服务帮助保持领域模型的纯粹性和内聚性。合理设计和实现领域服务,可以提高系统的可维护性、可复用性和可测试性。在实际项目中,持续关注和优化领域服务的设计,确保其符合业务需求和系统发展的需要。

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

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

相关文章

Day06-Mybatis

1. Mybatis介绍 2. Mybatis连接数据库并返回数据事例 连接oracle数据的设置方式 spring.application.namespringboot-mybatis spring.datasource.driver-class-nameoracle.jdbc.OracleDriver spring.datasource.urljdbc:oracle:thin:192.168.100.66:1521:orcl spring.datasour…

adb的常见操作和命令

最近学习adb的时候&#xff0c;整理了一些adb的使用场景&#xff0c;如&#xff1a;adb与设备交互&#xff0c;adb的安装、卸载&#xff0c;adb命令启动&#xff0c;通过命令清除缓存&#xff0c;文件传输和日志操作。 adb的两大作用&#xff1a;在app测试的时候可以提供监控日…

GB-T 43698-2024 网络安全技术 软件供应链安全要求

编写背景 随着信息技术的快速发展&#xff0c;软件供应链安全问题日益凸显。软件供应链是指软件从开发、分发到最终用户使用过程中涉及的所有环节。在这个过程中&#xff0c;任何一个环节的安全问题都可能影响到整个供应链的安全。GB-T 43698-2024《网络安全技术 软件供应链安…

【头歌】计算机网络DHCP服务器配置第二关access口配置答案

头歌计算机网络DHCP服务器配置第二关access口配置操作步骤 任务描述 本关任务&#xff1a;创建 vlan &#xff0c;并且将与 pc 机相连接口划分 vlan 。 操作要求 在第一关的拓扑图的基础上&#xff0c;配置交换机&#xff0c;具体要求如下&#xff1a; 1、在特权模式下进入 vla…

Python学习备份

2023年1月19日15:25:16 1. vsIDE编程python python路径&#xff1a;C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_86 python_pip路径(可添加到环境变量)&#xff1a;C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_86\Scripts 第三方库路…

Flutter 中的 CupertinoTextSelectionToolbar 小部件:全面指南

Flutter 中的 CupertinoTextSelectionToolbar 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;CupertinoTextSelectionToolbar 是一个专门用于 iOS 风格的文本选择工具栏的小部件。它提供了一组与 iOS 系统相一致的文本操作控件&#xff0c;如复制、粘贴等。这个工具栏…

Docker 快速更改容器的重启策略(Restart Policies)以及重启策略详解

目录 1. 使用 docker update 命令2. 在启动容器时指定重启策略3. 在 Docker Compose 文件中指定重启策略4. 总结 官方文档&#xff1a;Start containers automatically 1. 使用 docker update 命令 Docker 提供了 docker update 命令&#xff0c;可以在容器运行时更改其重启策…

python 队列生产者消费者爬虫

当使用Python编写一个基于队列的生产者消费者爬虫时&#xff0c;我们通常会使用threading或multiprocessing模块来处理并发&#xff0c;并使用queue模块来管理数据队列。下面是一个详细的示例&#xff0c;该示例展示了如何使用生产者线程生成URL&#xff0c;消费者线程爬取这些…

循序渐进Docker Compose

文章目录 1.概述1.1 Docker Compose 定义1.2 Docker Compose背景1.3 Docker Compose核心概念 2.安装2.1 Official Repos2.2 Manual Installation2.3 v1.x 兼容性 3. YAML 配置说明3.1 Services3.2 Volumes & Networks 4. 解析 Service4.1 Pulling一个Image4.2 Building一个…

Unknown module(s) in QT: texttospeech

目录 Qt TextToSpeech Engines tts测试代码&#xff1a; pro配置&#xff1a; 报错&#xff1a;connection to speech-dispatcher failed 不支持中文播报 1. 安装 espeak-ng 和中文语音库 2. 配置 Speech Dispatcher 使用 espeak-ng 3. 配置 espeak-ng 支持中文 4. 重…

Java基础:面向对象(二)

Java基础&#xff1a;面向对象&#xff08;二&#xff09; 文章目录 Java基础&#xff1a;面向对象&#xff08;二&#xff09;1. 面向对象编程思想2. 类与对象2.1 类2.1.1 类的定义2.1.2 成员变量2.1.3 局部变量 2.2 对象2.2.1 对象的定义2.2.2 对象的使用2.2.3 对象创建的原理…

DataGrip使用ssh连接数据库的操作流程

1 选择数据源种类 2 配置ssh 3 填写host、port和认证方式 我选择的密码方式&#xff0c;也可选择其他方式连接&#xff1a; 本文由博客一文多发平台 OpenWrite 发布&#xff01;

【放球问题】920. 播放列表的数量

本文涉及知识点 【组合数学 隔板法 容斥原理】放球问题 本题同解 【动态规划】【组合数学】【C算法】920播放列表的数量 LeetCode 920. 播放列表的数量 你的音乐播放器里有 n 首不同的歌&#xff0c;在旅途中&#xff0c;你计划听 goal 首歌&#xff08;不一定不同&#x…

Selenium 模拟操作与 pytest 断言的结合使用

Selenium 模拟操作与 pytest 断言的结合使用 在使用 Selenium 进行 UI 自动化测试时&#xff0c;通常会结合 pytest 作为测试框架来编写和执行测试用例。pytest 提供了丰富的断言功能&#xff0c;可以用来验证 Selenium 模拟操作的结果是否符合预期。 断言的基本用法 在 pyt…

释放 OSINT 的力量:在线调查综合指南

开源情报 (OSINT) 是从公开信息中提取有价值见解的艺术。无论您是网络安全专业人士、道德黑客还是情报分析师&#xff0c;OSINT 都能为您提供先进的技术&#xff0c;帮助您筛选海量的数字数据&#xff0c;发现隐藏的真相。 在本文中&#xff0c;我们将深入研究大量的OSINT 资源…

冯喜运:5.29市场避险情绪升温,黄金原油小幅收涨

【黄金消息面分析】&#xff1a;周二&#xff08;5月28日&#xff09;美盘时段&#xff0c;由于美元走弱且市场情绪出现负面变化&#xff0c;黄金收复早前跌幅&#xff0c;站上2350美元关口。金价早盘一度走弱&#xff0c;源于美联储降息可能性降低带来压力&#xff0c;投资者在…

Spring (24)Spring中的ORM支持

在Spring框架中&#xff0c;ORM&#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;支持是实现数据访问层的关键特性之一。Spring提供了与多个ORM框架的集成支持&#xff0c;包括Hibernate, JPA, JDO以及MyBatis等。Spring的ORM支持主要通过以下几方面…

AWS联网和内容分发之Transit Gateway

将Amazon VPC、AWS账户和本地网络连接到一个网关中。AWS Transit Gateway通过中央枢纽连接Amazon虚拟私有云&#xff08;VPC&#xff09;和本地网络。此连接简化了您的网络&#xff0c;并且结束了复杂的对等关系。Transit Gateway充当高度可扩展的云路由器&#xff0c;每个新的…

李廉洋:5.29黄金早盘2365-2345区间,今日行情走势分析及策略。

黄金消息面分析&#xff1a;当前美国存在一个令人担忧且未被充分关注的问题&#xff1a;房地产行业低迷、高利率和抵押贷款利率、租金高涨以及美联储的紧缩政策构成了一个恶性循环。由于高房价和高抵押贷款利率&#xff0c;美国住房经济活动远低于两年前的水平。为了让该行业好…

基于 RNNs 对 IMDB 电影评论进行情感分类

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…