Как использовать агрегаты DDD с TimeSeries? - PullRequest
0 голосов
/ 19 февраля 2019

Я пытаюсь реализовать более ориентированное на 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; }
    }

В качестве первого прохода и сражаться без Эвансаили доступные книги Вернона (они есть в посте), я остановился на том, что есть два AggregateRoots с Site, более выдающимся.То есть Signal агрегат действительно должен быть доступен через Site.

Основная проблема, которую я обнаружил, заключается в загрузке подмножеств Samples в Signal.

Согласно шаблону Specification, используемому в примерах eShopOnWeb , я могу довольно легко работать с агрегатом Site и загружать его совокупность Signals с вызовом против SiteRepository в Infrastructurelayer:

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

Это, вероятно,только первоначальная неопределенность, которая возникает при разработке нового шаблона, но это почему-то кажется неправильным.

Правильно ли, что я использую два Агрегата?

1 Ответ

0 голосов
/ 19 февраля 2019

Более сложный вопрос - как загрузить образцы сущностей

Я обнаружил, что мне нужно быть осторожным, различая два разных вида информации;информация о том, где моя модель является авторитетной, и справочные данные.

Возможно, вы захотите просмотреть Данные о внешнем и данные о внутреннем .

Сигналы от датчиков вреальный мир не принадлежит нашей модели.Мы просто храним его копии здесь , потому что это более выгодно, чем пытаться хранить все это там .Таким образом, нам не нужны «агрегаты», когда перед нами стоит задача сбора справочных данных.

При этом мы собираем данные, потому что хотим сделать с ними что-то ,конечно - поэтому у нас может быть модель предметной области, которая объединяет разделы захваченных данных для выполнения интересных расчетов.Но это - в опытах, которые у меня были - это одновременное поведение;процесс объединения данных не должен мешать нам собирать больше.

Вместо этого он часто выглядит как поток данных, поступающих из внешнего мира, и во внутреннем процессе, который ведет бухгалтерский учет, собновленный справочник, который отслеживает, где он находится в истории поступивших сигналов.

...