用于插入和删除的不同聚合根

时间:2019-06-25 15:14:31

标签: domain-driven-design aggregateroot

假设我们有以下Service

public class ExoplayerService extends Service {

    public static Intent createIntent(Context context, LectureCompositeId id) {
        Intent intent = new Intent(context, ExoplayerService.class);
        intent.putExtra(EXTRA_LECTURE_COMPOSITE_ID, (Parcelable) id);
        return intent;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = null;
        LectureCompositeId lectureCompositeId = null;
        if (intent != null) {
            action = intent.getAction();
            lectureCompositeId = intent.getParcelableExtra(EXTRA_LECTURE_COMPOSITE_ID);
        }

        L.leaveBreadcrumb(TAG, "onStartCommand >> startId: " + startId + " flags: " + flags +
                " intent: " + intent + "action: " + action + " lectureCompositeId: " + lectureCompositeId);

        // Actions are send by Notification and lock screen controls as intent params
        if (intent != null) {
            if (ACTION_SHUTDOWN.equals(action) || ACTION_SHUTDOWN_TASK.equals(action)) {
                boolean fromNotification = intent.getBooleanExtra(EXTRA_SOURCE_NOTIFICATION, false);
                attemptShutdown(startId, fromNotification, ACTION_SHUTDOWN_TASK.equals(action));
            } else if (StringUtils.isNotBlank(action)) {
                exoplayerManager.ensureLecture(lectureCompositeId);
                handleNotificationMediaButtonAction(action);
            } else {
                initPlayerService();
            }
        }
        return START_STICKY;
    }

    /**
     * Queue a shutdown of the service.  Ensures requests that need a notification have a chance to process.
     */
    private void queueShutdown(boolean taskRemoved) {
        Intent shutdown = new Intent(this, ExoplayerService.class);


        if (taskRemoved) {
            shutdown.setAction(ACTION_SHUTDOWN_TASK);
        } else {
            shutdown.setAction(ACTION_SHUTDOWN);
        }

        startService(shutdown);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
// other methods
}

当我们想添加一个新的aggregate root时,我们需要确保它不会与现有的冲突。这就是说public class Resource { public IEnumerable<Schedule> schedules {get;private set;} ... } 确保schedule方法的不变性。

另一方面,aggregate root的删除没有不变量。如果我们使用resource.AddSchedule(schedule)删除schedule,则需要预先加载所有Resource aggregate root才能删除一个。

基于此,我们是否应该将schedule提升为schedules,仅加载所需的Schedule,然后将其删除?还是应该继续使用以前的aggregate root

3 个答案:

答案 0 :(得分:1)

  

当我们想添加一个新时间表时,我们需要确保它不会与现有时间表发生冲突。

您要在此处实现的目标的总称是set validation

如果需要绝对保证集合中没有冲突的条目(有时称为“立即一致性”),则必须确保在检查时不会同时更改条目。这意味着您在检查中所关心的值必须成为检查它们的汇总的一部分-我们需要确保答案在我们下面不会改变。

更常见的情况是“尽力而为”就足够了,并且有一些协议可以解决贯穿裂缝的冲突。在这种情况下,您通常可以将集合视为标识符列表,而不是值列表。因此,详细信息可以保留在“计划”聚合中,而“资源”聚合仅跟踪计划成员身份。

还有一个特殊的情况-如果时间表是值而不是实体...意味着给定时间表的详细信息是不可变的,那么您也许也可以吃蛋糕-为每个时间表计算一个唯一的哈希计划,并将哈希值存储在Resource中,以后可以在必要时将其用于查找不可变的计划。

这里的很大一部分工作是正确了解业务需求的细节。在大多数成熟的业务域中,冲突经常发生,以至于存在解决冲突的协议。换句话说,race conditions don't exist

  

微妙的时间差异不应改变核心业务行为。

答案 1 :(得分:0)

在实践上,您的用户最有可能提出以下两个建议?

  • 当我们尝试删除日程表时,应用程序太慢了
  • 我们希望在删除时间表时应用规则X

就个人而言,我会避免过早的优化,否则可能会使您日后陷入困境。

答案 2 :(得分:0)

  

基于此,我们是否应该将Schedule提升为聚合根

如果Schedule拥有自己的生命周期,那么无论如何它都必须是一个聚合,这会稍微改变您的设计。

但是,假设您在Resource-> Schedule之间的关系与Order-> OrderItem相同,那么您总是可以让存储库公开一个更简单的删除Schedule

public interface IResourceRepository
{
   void Add(Resource resource);
   void RemoveSchedule(Resource resource, Resource.Schedule schedule); // or some such
}

对于Order,鉴于存在可能可能是相关的不变量,这可能不会起作用。

更新

如果您的Schedule是一个聚合根,那么您可能会独立于Resource以及之前创建它。正如{VoiceOfUnreason所提到的那样,Schedule的唯一性属于集合验证的范围。这样,您的Resource仍然需要一个对Schedule实例的引用列表,但是这些引用将仅仅是Id或某个包含Id以及其他任何相关对象的值对象位。 Resource会强制确保您没有重复的时间表。

为了从资源中删除计划,您将回到第一个选项:加载Resource并删除相关计划Id或直接“通过存储库将其删除”。

总是存在“硬性对软性删除”事件,通常应该只更改状态(“软性删除”),因为问题较少。然后,Schedule的硬删除可能会被DRI在数据存储上停止(如果已配置),或者将删除级联到相关表。