首页 > 解决方案 > 如何以 DDD 方式部分更新聚合

问题描述

我正在以 DDD 方式使用 C# .NET 开发应用程序。我也检查了eshopcontainers,但它没有解释我想知道的内容,所以让我在这里提出一个问题。我的问题是

详细信息
我正在开发一个 RSVP 应用程序。现在我想做的是向尚未投票的人发送提醒邮件。我的领域模型和基础设施层是这样的。

//Domain Model
class RSVP //RootAggreagate
{
    public long Id {get; private set;}
    public List<TimeSlot> TimeSlots {get; private set;}  

    public AutoRemindRule AutoRemindRule {get; private set;}
}

class AutoRemindRule 
{
    public long Id {get; private set;}
    public int IntervalHour { get; private set; }
    public DateTimeOffset NextTriggerDate { get; private set; }
    public DateTimeOffset RemindBeginDate { get; private set; }

    //Foreign Key for Plan
    public long RSVPId


    void SetNextTriggerDate() 
    {
        //Compute NewNextTriggerDate based on IntervalHour and RemindBeginDate field.
        NextTriggerDate = NewNextTriggerDate;
    }
}

//Infrastructure Layer (EF Core)
public class MyDbContext : DbContext
{
    //Plan Aggregate
    public DbSet<RSVP> RSVPs { get; set; }
    public DbSet<TimeSlot> TimeSlots { get; set; }
    public DbSet<AutoRemindRule> AutoRemindRules { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ...
    }
}

在这个模型中,我想运行一个定期签NextTriggerDate入的批处理,如果比现在更旧,该批处理将邮件发送给没有投票 RSVP 的用户。最终,批处理通过调用. 如果我在批处理的 Usecase(Application) 层编写如下代码,我可以实现我想要的。但是,我不认为这些代码遵循 DDD 规则,因为它会部分更新聚合根。在应用层写这样的代码可以吗,或者如果没有,谁能告诉我更好的编码方式?AutoRemindRulesNextTriggerDateNextTriggerDateSetNextTriggerDate

//Usecase(Application) layer in a batch
using (var context = new MyDbContext) 
{
    var rules = context.AutoRemindRules.Where(i => i.NextTiggerdate < DateTimeOffset.Now);
    foreach (var rule in rule)
    {
        SendRemindMail();
        rule.SetNextTriggerDate();

        context.SaveChanges();
    }
}

更新
另一种方法是AutoRemindRule在聚合根中创建一个更新方法,并且批处理使用该方法。但是,我关心的是性能和数据库上的负载。在这种情况下,除了记录之外,批处理还必须读取一堆不必要的 RSVPAutoRemindRule记录。我想知道是否有另一种方法可以在保持 DDD 方式的同时减少 DB 的负载。

标签: c#.netdomain-driven-designef-core-2.0

解决方案


直接从批处理操作部分更新根聚合是否可以?

最简洁的答案是不。

聚合的重点是封装数据并公开业务操作,以便检查和执行验证和不变量。如果您让外部进程修改聚合的数据,那么它完全违背了它们的目的。

潜在的解决方案:

  1. 不要使用聚合。如果您拥有的只是一个包含日期和一些数据的表和一个批处理作业,但没有或几乎没有要强制执行的业务逻辑,那么只需执行此操作:一个表和一个批处理作业。乍一看,您似乎唯一需要强制执行的是 NextTriggerDate 是在将来的某个时间,您可以将其编码到批处理作业中。但是我可以想象你可以有更多的规则,比如不要触发超过X次,不要早于1天触发等等。或者甚至让聚合决定下一个触发日期,基于一些内部逻辑(首先1 天后触发,2 天后触发第二次,依此类推)。聚合对于这些事情非常方便,因为每个聚合都将存储它们计算下一次状态更改所需的状态。

  2. 评估加载完整的 RSVP 聚合是否真的有问题。对于大多数应用程序来说,加载一些额外的列不应该对性能造成很大的影响,并且为了获得聚合封装其业务逻辑的好处,以防你需要做我在前面提到的那些事情时所需要付出的必要代价观点。相反,如果您正在构建具有大规模和少量业务逻辑的功能,那么请考虑另一种方法。

  3. 如果问题是加载完整聚合意味着加载像 TimeSlots 这样的大集合,而这恰好对于该业务操作不是必需的,您可以通过在存储库中提供特定方法来加载聚合而不加载该集合来解决它. 您应该对需要该集合的聚合根操作进行编码,以验证该集合是否已加载,否则会失败,以避免出现错误。


推荐阅读