8、聚合和聚合根:怎样设计聚合

8、聚合和聚合根:怎样设计聚合在本章中 我们详细探讨了聚合和聚合根的概念 设计原则以及实际应用

大家好,欢迎来到IT知识分享网。

        在构建复杂软件系统时,聚合(Aggregate)和聚合根(Aggregate Root)是领域驱动设计(DDD)的核心概念,它们帮助我们管理业务对象的边界和一致性。在本章中,我们将详细探讨这些概念,并结合《领域驱动设计》一书的内容来深入理解和应用它们。

1、 聚合的概念
聚合是什么?

聚合是指在领域模型中,将一组业务相关的实体和值对象封装在一起,形成一个逻辑上的整体。聚合通过定义一致性边界,确保在这个边界内的所有对象在业务操作后保持一致性。聚合的主要目标是通过高内聚、低耦合的方式,简化系统的复杂性,提高系统的可维护性和扩展性。

《领域驱动设计》一书中提到,聚合是“强一致性事务的范围”,它封装了一组紧密相关的对象,并定义了这些对象之间的关系和业务规则。通过这种封装,可以确保在聚合内的所有对象在任何时候都保持一致性。

聚合的组成

一个聚合通常由以下几个部分组成:

  • 实体(Entity):具有唯一标识的业务对象,代表系统中的某个具体业务实体。
  • 值对象(Value Object):不具有唯一标识的业务对象,用于描述实体的某些属性或状态。
  • 聚合根(Aggregate Root):聚合中的一个特殊实体,它是聚合的入口点和唯一访问点,负责管理和协调聚合内部的实体和值对象。

《领域驱动设计》一书强调,聚合根是聚合的一部分,并且是唯一允许外部对象持有引用的对象。其他对象只能通过聚合根来访问,以确保聚合的一致性和封装性。

聚合的作用

聚合通过封装和管理一组业务相关的实体和值对象,确保在这个封装内的数据和业务规则的一致性。聚合的作用包括:

  • 定义一致性边界:聚合内的所有对象的数据变更必须符合聚合的一致性规则,确保数据的一致性和有效性。
  • 简化模型:通过聚合,可以将复杂的业务模型简化为多个高内聚、低耦合的子模型,降低系统的复杂度。
  • 事务控制:一个聚合的修改应作为一个事务进行,确保操作的原子性和一致性。

《领域驱动设计》指出,通过定义聚合,我们可以更好地理解和管理复杂的业务规则和关系,确保系统在处理复杂业务逻辑时具有良好的可维护性和扩展性。

2、 聚合根的概念
聚合根是什么?

聚合根是聚合的入口点和唯一访问点。聚合根是聚合内部所有对象的代表,负责管理和协调聚合内部的实体和值对象的行为。

《领域驱动设计》一书中提到,聚合根“负责保持聚合内所有对象的完整性”。这意味着,聚合根不仅仅是一个普通的实体,它还承担了管理和协调聚合内部所有对象的职责。

聚合根的特点
  • 唯一标识:聚合根具有全局唯一标识,用于区分不同的聚合实例。
  • 生命周期管理:聚合根负责管理聚合内部所有对象的生命周期,包括创建、修改和删除等操作。
  • 业务规则的执行者:聚合根执行聚合内的业务规则,确保所有变更操作符合聚合的一致性要求。
  • 外部接口:聚合根对外提供接口,外部系统只能通过聚合根与聚合内部的其他对象进行交互,保持聚合的封装性和一致性。

《领域驱动设计》强调,聚合根的主要目的是避免聚合内由于复杂数据模型缺少统一的业务规则控制,而导致聚合内实体和值对象等领域对象之间数据不一致。聚合根管理了聚合内所有实体和值对象的生命周期,我们通过聚合根就可以获取到聚合内所有实体和值对象等领域对象。

聚合根的职责

在聚合根类的方法中,可以组织聚合内部的领域对象,完成跨多个实体的复杂业务逻辑。聚合根的职责包括:

  • 管理聚合内对象的创建和变更:聚合根负责创建、修改和删除聚合内的对象,并确保这些操作符合业务规则。
  • 维护聚合的一致性:聚合根通过执行业务规则,确保聚合内的所有对象在任何时候都保持一致性。
  • 提供聚合的访问接口:外部系统只能通过聚合根访问聚合内的对象,以确保聚合的封装性。

《领域驱动设计》指出,聚合根在管理聚合内对象的生命周期和维护业务规则一致性方面起到了关键作用。通过聚合根,我们可以确保聚合内的所有对象始终处于一致和有效的状态。

3、 聚合的设计步骤

在设计聚合时,我们需要遵循一定的步骤,以确保聚合的合理性和一致性。这些步骤包括:

1. 识别实体和值对象

首先,通过业务分析,识别出系统中的所有实体和值对象。这些实体和值对象是构建聚合的基础。

2. 找出聚合根

在识别出所有实体和值对象后,需要找出适合作为聚合对象管理者的根实体,即聚合根。判断一个实体是否是聚合根,可以结合以下内容进行分析:

  • 是否有独立的生命周期:聚合根通常具有独立的生命周期,可以单独创建、修改和删除。
  • 是否有全局唯一ID:聚合根具有全局唯一标识,用于区分不同的聚合实例。
  • 是否可以创建或修改其他对象:聚合根通常负责管理聚合内其他对象的生命周期,包括创建、修改和删除。
  • 是否有专门的模块来管理这个实体:聚合根通常由专门的模块或服务来管理,以确保其一致性和有效性。

《领域驱动设计》建议,聚合根的选择应基于业务逻辑和系统需求,确保其能够有效管理聚合内的所有对象。

3. 构建聚合

根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象,构建出一个包含聚合根(唯一)、多个实体和值对象的领域对象的集合,这个集合就是聚合。

《领域驱动设计》强调,聚合的构建应遵循高内聚、低耦合的设计原则,确保聚合内部对象之间的紧密关联和聚合之间的松散耦合。

4. 确定引用和依赖关系

在聚合内,根据聚合根、实体和值对象的依赖关系,找出它们的引用和依赖关系。确保所有对象的引用和依赖关系都在聚合的边界内。

5. 划分限界上下文

多个聚合根据业务语义和上下文边界,划分到同一个限界上下文内,就完成了领域建模,聚合的构建过程也结束了。

《领域驱动设计》指出,限界上下文是定义领域模型边界的重要工具,通过划分限界上下文,可以更好地管理领域模型的复杂性和一致性。

4、 聚合的设计原则

在设计聚合时,需要遵循一些设计原则,以确保聚合的合理性和一致性。这些原则包括:

1. 保持聚合小而聚焦

聚合应该尽量保持小而聚焦,以减少并发冲突和锁争用。设计时应遵循单一职责原则,每个聚合只负责一块特定的业务逻辑。小而聚焦的聚合更容易管理和维护,也更容易适应业务需求的变化。

《领域驱动设计》强调,聚合的设计应以简单和聚焦为原则,避免将过多的业务逻辑和对象纳入同一个聚合,从而增加系统的复杂性。

2. 跨聚合引用采用唯一标识

聚合之间的引用应通过唯一标识,而不是对象引用。这有助于降低聚合之间的耦合度,增强系统的可扩展性和可维护性。通过唯一标识引用,可以在不改变聚合内部结构的情况下,轻松实现聚合之间的关联。

《领域驱动设计》指出,通过唯一标识引用,可以有效避免聚合之间的紧密耦合,从而提高系统的灵活性和扩展性。

3. 最终一致性

在跨聚合的操作中,应采用最终一致性的策略,确保系统在事务提交后最终达到一致的状态,而不是在事务内强求一致性。通过领域事件驱动机制,可以实现跨聚合的最终一致性。

《领域驱动设计》建议

,在设计跨聚合操作时,应考虑使用最终一致性的策略,以提高系统的可用性和容错能力。

4. 应用层协调跨聚合事务

对于涉及多个聚合的复杂业务操作,应在应用层进行协调,而不是在领域层直接处理。这有助于保持聚合的独立性和领域模型的清晰性。应用层的应用服务可以组织和协调各个聚合的领域服务,解耦聚合并实现跨聚合的服务调用。

《领域驱动设计》强调,应用层在协调跨聚合事务时,应尽量避免直接操作领域对象,而是通过调用领域服务来实现业务逻辑,以保持领域模型的独立性和一致性。

5. 聚合的设计原则

《实现领域驱动设计》一书中提到了一些聚合设计原则,这些原则有助于在设计聚合时保持一致性和可维护性:

  • 在一致性边界内建模真正的不变条件:聚合是用来封装真正的业务不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,保证聚合内对象数据的一致性。
  • 设计小聚合:如果聚合设计得过大,聚合会因为包含过多的实体和值对象,导致实体之间的管理过于复杂,以及领域逻辑实现复杂。在高频操作时,可能会出现并发冲突或者数据库锁,最终导致系统可用性变差。小聚合设计可以降低由于业务过大,在业务变化时导致聚合重构的可能性,使领域模型更能适应业务的变化。
  • 通过唯一标识引用其他聚合:聚合之间是通过引用聚合根ID的方式,而不是通过直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。
  • 在边界之外使用最终一致性:在聚合内采用数据强一致性,在聚合之间采用数据最终一致性。通过领域事件驱动机制,可以实现跨聚合的最终一致性。
  • 通过应用层实现跨聚合的服务调用:聚合是领域层的业务逻辑单元,当聚合之间需要交互时,为了避免在领域层聚合之间发生耦合,可以将聚合之间的服务调用上升到应用层,通过应用层的应用服务来组织和协调各个聚合的领域服务,解耦聚合并实现跨聚合的服务调用。

《领域驱动设计》强调,这些设计原则不仅能够确保聚合的一致性和可维护性,还能提高系统的扩展性和灵活性,使系统能够更好地适应业务需求的变化。

5、 聚合的设计模式

在设计聚合时,如果聚合内领域对象比较多,领域对象的初始化和持久化就会变得比较复杂。为了简化领域对象的创建和持久化操作,可以采用一些设计模式,包括仓储模式(Repository Mode)和工厂模式(Factory Mode)。

1. 仓储模式(Repository Mode)

仓储模式用于完成领域对象的持久化操作。仓储提供了一个抽象层,将领域对象的持久化细节与领域模型隔离开来,使领域模型更加清晰和可维护。

《领域驱动设计》建议,使用仓储模式来管理领域对象的持久化,可以有效地分离业务逻辑和数据访问逻辑,提高系统的可维护性和扩展性。

  • 实现示例:以下是一个简单的仓储模式实现示例,用于管理订单聚合。
public interface OrderRepository {     void save(Order order);     Order findById(String orderId);     void delete(Order order); } public class OrderRepositoryImpl implements OrderRepository {     // 假设使用某个持久化框架,如JPA     private EntityManager entityManager;     @Override     public void save(Order order) {         entityManager.persist(order);     }     @Override     public Order findById(String orderId) {         return entityManager.find(Order.class, orderId);     }     @Override     public void delete(Order order) {         entityManager.remove(order);     } } 
2. 工厂模式(Factory Mode)

工厂模式用于聚合领域对象的创建和数据初始化。工厂方法可以简化对象的创建过程,确保领域对象在创建时的状态是一致的。

《领域驱动设计》指出,通过工厂模式,可以简化复杂领域对象的创建过程,确保领域对象在创建时的一致性和有效性。

  • 实现示例:以下是一个简单的工厂模式实现示例,用于创建订单聚合。
public class OrderFactory {     public static Order createOrder(String orderId, List<OrderItem> orderItems) {         Order order = new Order(orderId);         for (OrderItem item : orderItems) {             order.addOrderItem(item);         }         return order;     } } 

通过使用仓储模式和工厂模式,可以简化聚合领域对象的创建和持久化操作,提高领域模型的可维护性和扩展性。

6、广告业务中的聚合和聚合根设计示例

以广告业务为例,我们需要设计一个系统,能够根据监播需求创建监播拍摄任务,并将任务分配到每个门店的设备上。我们将通过聚合和聚合根的设计来实现这一目标。

1. 广告主聚合

广告主聚合包括广告主实体和与广告主相关的所有投放计划。广告主是聚合根,管理所有投放计划的创建和变更。

// 广告主实体 public class Advertiser {     private String advertiserId;     private String name;     private List<Campaign> campaigns;     public Advertiser(String advertiserId, String name) {         this.advertiserId = advertiserId;         this.name = name;         this.campaigns = new ArrayList<>();     }     public void addCampaign(Campaign campaign) {         this.campaigns.add(campaign);     }     // 其他业务逻辑和方法 } // 投放计划实体 public class Campaign {     private String campaignId;     private String name;     private Date startDate;     private Date endDate;     private double budget;     private List<String> storeIds;     public Campaign(String campaignId, String name, Date startDate, Date endDate, double budget) {         this.campaignId = campaignId;         this.name = name;         this.startDate = startDate;         this.endDate = endDate;         this.budget = budget;         this.storeIds = new ArrayList<>();     }     public void addStore(String storeId) {         this.storeIds.add(storeId);     }     // 其他业务逻辑和方法 } 
2. 媒体主聚合

媒体主聚合包括媒体主实体和与媒体主相关的所有门店。媒体主是聚合根,管理所有门店的创建和变更。

// 媒体主实体 public class MediaOwner {     private String mediaOwnerId;     private String name;     private List<Store> stores;     public MediaOwner(String mediaOwnerId, String name) {         this.mediaOwnerId = mediaOwnerId;         this.name = name;         this.stores = new ArrayList<>();     }     public void addStore(Store store) {         this.stores.add(store);     }     // 其他业务逻辑和方法 } // 门店实体 public class Store {     private String storeId;     private String name;     private String address;     private List<Location> locations;     public Store(String storeId, String name, String address) {         this.storeId = storeId;         this.name = name;         this.address = address;         this.locations = new ArrayList<>();     }     public void addLocation(Location location) {         this.locations.add(location);     }     // 其他业务逻辑和方法 } // 点位实体 public class Location {     private String locationId;     private String description;     private List<Device> devices;     public Location(String locationId, String description) {         this.locationId = locationId;         this.description = description;         this.devices = new ArrayList<>();     }     public void addDevice(Device device) {         this.devices.add(device);     }     // 其他业务逻辑和方法 } // 设备实体 public class Device {     private String deviceId;     private String type;     private List<PlaybackFrequency> playbackFrequencies;     public Device(String deviceId, String type) {         this.deviceId = deviceId;         this.type = type;         this.playbackFrequencies = new ArrayList<>();     }     public void addPlaybackFrequency(PlaybackFrequency playbackFrequency) {         this.playbackFrequencies.add(playbackFrequency);     }     // 其他业务逻辑和方法 } // 播放频次实体 public class PlaybackFrequency {     private String frequencyId;     private Date playTime;     private int duration;     public PlaybackFrequency(String frequencyId, Date playTime, int duration) {         this.frequencyId = frequencyId;         this .playTime = playTime;         this.duration = duration;     }     // 其他业务逻辑和方法 } 
3. 监播需求聚合

监播需求聚合包括监播需求实体和与监播需求相关的所有监播任务。监播需求是聚合根,管理所有监播任务的创建和分配。

// 监播需求实体 public class MonitoringRequirement {     private String requirementId;     private String advertiserId;     private List<String> campaignIds;     private List<MonitoringTask> monitoringTasks;     public MonitoringRequirement(String requirementId, String advertiserId, List<String> campaignIds) {         this.requirementId = requirementId;         this.advertiserId = advertiserId;         this.campaignIds = campaignIds;         this.monitoringTasks = new ArrayList<>();     }     public void addMonitoringTask(MonitoringTask task) {         this.monitoringTasks.add(task);     }     // 其他业务逻辑和方法 } // 监播任务实体 public class MonitoringTask {     private String taskId;     private String storeId;     private String deviceId;     private String campaignId;     private Date scheduledTime;     public MonitoringTask(String taskId, String storeId, String deviceId, String campaignId, Date scheduledTime) {         this.taskId = taskId;         this.storeId = storeId;         this.deviceId = deviceId;         this.campaignId = campaignId;         this.scheduledTime = scheduledTime;     }     // 其他业务逻辑和方法 } 
4. 每日定时任务的实现

每日定时任务的主要职责是根据监播需求创建监播拍摄任务,并将任务分配到每个门店的设备上。

public class MonitoringScheduler {     private AdvertiserRepository advertiserRepository;     private MediaOwnerRepository mediaOwnerRepository;     private MonitoringRequirementRepository monitoringRequirementRepository;     public void scheduleMonitoringTasks() {         List<MonitoringRequirement> requirements = monitoringRequirementRepository.findAll();         for (MonitoringRequirement requirement : requirements) {             Advertiser advertiser = advertiserRepository.findById(requirement.getAdvertiserId());             if (advertiser != null) {                 for (String campaignId : requirement.getCampaignIds()) {                     Campaign campaign = advertiser.findCampaignById(campaignId);                     if (campaign != null) {                         for (String storeId : campaign.getStoreIds()) {                             Store store = mediaOwnerRepository.findStoreById(storeId);                             if (store != null) {                                 for (Location location : store.getLocations()) {                                     for (Device device : location.getDevices()) {                                         for (PlaybackFrequency frequency : device.getPlaybackFrequencies()) {                                             if (isCampaignActive(campaign, frequency.getPlayTime())) {                                                 MonitoringTask task = new MonitoringTask(                                                     UUID.randomUUID().toString(),                                                     store.getStoreId(),                                                     device.getDeviceId(),                                                     campaign.getCampaignId(),                                                     frequency.getPlayTime()                                                 );                                                 requirement.addMonitoringTask(task);                                             }                                         }                                     }                                 }                             }                         }                     }                 }             }             monitoringRequirementRepository.save(requirement);         }     }     private boolean isCampaignActive(Campaign campaign, Date playTime) {         return playTime.after(campaign.getStartDate()) && playTime.before(campaign.getEndDate());     } } 

通过上述设计,我们可以确保监播系统能够根据业务需求,创建和分配监播拍摄任务,确保系统的一致性和可靠性。这种设计方式不仅满足业务需求,还可以提高系统的扩展性和灵活性,使系统能够轻松应对未来的变化和扩展。

总结

在本章中,我们详细探讨了聚合和聚合根的概念、设计原则以及实际应用。通过结合《领域驱动设计》一书的内容,我们深入理解了聚合和聚合根在构建复杂软件系统中的重要性。通过聚合和聚合根的设计,我们可以构建出高内聚、低耦合的领域模型,确保系统在处理复杂业务逻辑时具有良好的可维护性和扩展性。在实际应用中,我们通过广告业务的示例,展示了如何设计和实现聚合和聚合根,满足具体的业务需求,提高系统的可靠性和可维护性。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/137952.html

(0)
上一篇 2025-06-16 18:00
下一篇 2025-06-16 18:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信