我正在用C# .NET以DDD的方式开发一个应用程序。我也检查了 商店容器但它没有解释我想知道的东西,所以让我在这里给一个问题。我的问题是
详细介绍 我正在开发一个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
在 AutoRemindRules
定期,如果 NextTriggerDate
是比现在更老的,批处理会给没有投RSVP的用户发送邮件。最终,批处理会更新 NextTriggerDate
呼叫 SetNextTriggerDate
. 如果我在Usecase(Application)层的批处理中写下如下的代码,就可以实现我想要的东西,但我认为这些代码并不遵循DDD规则,因为聚合根的更新是部分的。但是,我认为这些代码不符合DDD规则,因为它部分更新了聚合根。在应用层写这样的代码可以吗,如果不可以,谁能告诉我一个更好的编码方式?
//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
在聚合根目录下,批处理利用该方法。但是,我担心的是性能和对DB的负载。AutoRemindRule
记录。我想知道是否有另一种方法可以在保持DDD方式的同时减少DB的负载。
可以直接从批处理操作中部分更新根集合吗?
简单的回答是。不可以
聚合体的意义在于封装数据和暴露业务操作,以便检查和执行验证和不变性。如果让外部进程修改聚合体的数据,那么就完全违背了聚合体的目的。
潜在的解决方案。
不要使用聚合体 如果你有的只是一个带有日期和一些数据的表和一个批处理作业,但没有或几乎没有业务逻辑可以执行,那么就只做:一个表和一个批处理作业。乍一看,似乎你唯一需要强制执行的是NextTriggerDate是未来的某个时间,你可以将其编码到批处理作业中。但我可以想象,你可以有更多的规则,比如不要触发超过X次,不要早于1天触发等等。甚至让聚合体根据一些内部逻辑来决定下一个触发日期(1天后第一次触发,2天后第二次触发等等)。聚合体对于这些事情是非常方便的,因为每个聚合体都会存储他们计算下一个状态变化所需要的状态。
评估一下加载完整的RSVP聚合是否真的有问题。对于大多数应用来说,加载一些额外的列应该不会对性能造成很大的影响,而且为了获得聚合体封装其业务逻辑的好处,在你需要做像我在上一点提到的那些事情时,这是一个必要的代价。如果相反,你正在构建一个规模巨大而业务逻辑很少的功能,那么就可以考虑采用不同的方法。
如果问题是加载完整的聚合意味着加载一个像TimeSlots这样的大集合,而这个集合恰好不是该业务操作所需要的,你可以在你的仓库中提供一个特定的方法来绕过它,在不加载该集合的情况下加载聚合。你应该将你的聚合根操作编码为需要该集合的操作,以验证该集合是否被加载,否则就会失败,以避免bug。