我正在尝试实施更多面向DDD的解决方案来管理时间序列数据。下面的代码示例和模式可以在这里找到eShopOnWeb。实质上有三个实体。 Site
,Signal
和Sample
。 Site
可以有Signals
的集合,而Signal
可以有一组样本。
public class Site: BaseEntity, IAggregateRoot
{
// Collection loaded by EFCore through Repository
private List<Signal> signals = new List<Signal>();
// Public read only access
public IEnumerable<Signal> Signals => this.signals.AsReadOnly();
}
public class Signal: BaseEntity, IAggregateRoot
{
// Signal has to belong to Site
public int SiteId { get; private set; }
// Typical EF Nav property removed
// Signal should have no access to it's 'parent' properties
// public Site Site { get; set;}
private List<Sample> samples = new List<Sample>();
public IEnumerable<Sample> Samples => this.samples.AsReadOnly();
}
public class Sample : BaseEntity
{
public int SignalId { get; private set; }
public DateTime TimeStamp { get; set; }
public double? Value { get; set; }
}
作为第一关,并且在没有Evans或Vernon书籍的情况下挣扎(他们在帖子中),我已经确定有两个与Qazxswpoi相关的AggregateRoots更为重要。这是一个Site
聚合应该真正通过Signal
访问。
我发现的主要问题是将Site
的子集加载到Samples
。
根据Signal
示例中使用的Specification
模式,我可以很容易地使用eShopOnWeb聚合并加载它的Site
聚合集合,并在Signals
层中调用SiteRepository
:
Infrastructure
如果我在 public sealed class SiteFilterSpecification : BaseSpecification<Site>
{
public SiteFilterSpecification(int id)
: base(s => s.Id == id)
{
this.AddInclude(s => s.Signals);
}
}
类中我已经提供了一个站点和一段时间来计算某些东西,通常涉及多个Service
,规范模式会提示如下:
Signals
我在这里发现的问题是,在规范中,不可能过滤 public double GetComplexProcess(Site site, DateTime start, DateTime end)
{
var specification = new SiteSignalsWithSamplesSpec(site.Id, start, end);
var signals = this.SignalRepository.List(specification);
// signals should be loaded with the appropriate samples...
}
中包含的Samples
Signal
您可以使用此方法并加载所有 public sealed class SiteSignalsWithSamplesSpecification : BaseSpecification<Signal>
{
public SiteSignalsWithSamplesSpecification(int siteId, DateTime from, DateTime end)
: base(s => s.SiteId == siteId)
{
// This throws exception at runtime
this.AddInclude(s => s.Samples.Where(sa => sa.TimeStamp >= from && sa.TimeStamp <= end));
}
}
,但在处理时间序列数据时,这可能意味着成千上万的实体,而我们真正需要的是对它们的集中选择。
我现在在做什么;并且这并不特别“干净”是实现Generic Repository类的一个版本,专门用于在Samples
实体上部分加载Sample
数据。
Signal
public interface ISignalRepository : IAsyncRepository<Signal>
{
Task<IEnumerable<Signal>> GetBySiteIdWithSamplesAsync(int siteId, DateTime from, DateTime to);
}
这可能只是在新模式中发展所带来的最初的不确定性,但这在某种程度上感觉不对。
我使用两个Aggregates是否正确?
更难的问题是如何加载Sample实体
我发现我需要小心区分两种不同的信息;我的模型是权限的信息,以及参考数据。
您可能想查看 public class SignalRepository : EfRepository<Signal>, ISignalRepository
{
public SignalRepository(ForecastingContext dbContext) : base(dbContext)
{
}
public async Task<IEnumerable<Signal>> GetBySiteIdWithSamplesAsync(int siteId, DateTime from, DateTime to)
{
var signals = await this.dbContext.Signals.Where(s => s.SiteId == siteId).ToListAsync();
foreach (var signal in signals)
{
this.dbContext.Entry(signal)
.Collection(s => s.Samples)
.Query()
.Where(s => s.TimeStamp >= from && s.TimeStamp <= to)
.Load();
}
return signals;
}
}
。
来自现实世界中的传感器的信号不属于我们的模型。我们只是在这里存储它的副本,因为这比尝试将它存储在那里更具成本效益。因此,当手头的任务是参考数据捕获时,我们不需要“聚合”。
也就是说,我们正在捕获数据,因为我们想要对它做一些事情,当然 - 所以我们可能有一个域模型将我们捕获的数据的分区聚合在一起以执行有趣的计算。但是 - 在我所拥有的经验中 - 是一种并发的行为;聚合数据的过程不应阻止我们收集更多。
相反,它通常看起来像是来自外部世界的数据流,以及进行簿记的内部过程,其中更新的参考信息可以跟踪已经到达的信号历史中的位置。