0

我正在尝试实施一个更面向 DDD 的解决方案来管理时间序列数据。以下代码示例和模式可在此处找到eShopOnWeb。本质上存在三个实体。Site,SignalSample. 一个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 书籍的情况下苦苦挣扎(它们在帖子中),我已经确定有两个 AggregateRoots 和Site更突出的一个。那是一个Signal真正应该通过Site.

我发现的主要问题是将子集加载SamplesSignal.

根据eShopOnWebSpecification示例中使用的模式,我可以相当轻松地使用聚合并通过调用层中的a 来加载它的聚合集合:SiteSignalsSiteRepositoryInfrastructure

    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));
        }
    }

您可以使用这种方法并加载所有数据,Samples但在处理时间序列数据时,这可能意味着数十万个实体,而我们真正需要的是集中选择它们。

我目前在做什么;并且这感觉不是特别“干净”,是实现一个版本的 Generic Repository 类,专门用于在实体上部分加载Sample数据。Signal

    public interface ISignalRepository : IAsyncRepository<Signal>
    {
        Task<IEnumerable<Signal>> GetBySiteIdWithSamplesAsync(int siteId, DateTime from, DateTime to);
    }
    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;
        }
    }

这可能只是以新模式开发所带来的最初的不确定性,但这在某种程度上感觉是错误的。

我使用两个聚合是否正确?

4

1 回答 1

1

更困难的问题是如何加载 Sample 实体

我发现我需要小心区分两种不同的信息;我的模型是权威的信息,以及参考数据。

您可能想查看外部数据与内部数据

来自现实世界中传感器的信号不属于我们的模型。我们只是在此处存储它的副本,因为这比尝试将其全部存储在那里更具成本效益。因此,当手头的任务是参考数据捕获时,我们不需要“聚合”。

也就是说,我们正在捕获数据,因为我们想用它做一些事情,当然——所以我们可能有一个域模型,它将我们捕获的数据的分区聚合在一起以执行有趣的计算。但是——在我的经验中——是一种并发的行为。汇总数据的过程不应阻止我们收集更多数据。

相反,它通常看起来是来自外部世界的数据流,并且在进行簿记的内部流程中,具有更新的参考,以跟踪它在已到达信号历史中的位置。

于 2019-02-19T18:03:32.640 回答