【问题标题】:Timeseries/temporal data in DDD on write side in CQRSCQRS 写入端 DDD 中的时间序列/时间数据
【发布时间】:2015-03-19 13:40:56
【问题描述】:

我很难理解如何在 DDD 中支持时间序列/时间数据,以及如何使用 CQRS 在写入端处理它。最终,我想找到一个与事件溯源配合得很好的解决方案。

以温度预测为例,温度变化也可能影响一个地区/位置的预测能源需求。假设温度预测可以持续很长时间(基于历史数据),将所有预测加载到 Location 聚合中,我认为如果不对加载的数据量施加一些限制,这是不切实际的。

在牢记事件溯源的情况下,同步/存储此类数据以用于 CQRS 中写入端的良好/推荐方法是什么?

我的以下任何尝试(选项 A 或 B)是否被视为合适的 DDD/CQRS 解决方案?

选项A:

允许独立更新温度并使用流程管理器/saga 订阅事件,然后重新计算需求。此解决方案将有助于保持较小的聚合大小,但感觉聚合边界可能是错误的,因为需求取决于温度并且现在分布在命令/事件中。

// OverrideTemperatureForecastCommandHandler.cs 
public void Handle(OverrideTemperatureForecast cmd)
{
    var from = cmd.TemperatureOverrides.Min(t => t.DateTime);
    var to = cmd.TemperatureOverrides.Max(t => t.DateTime);

    TemperatureForecasts forecasts = temperatureForecastRepository.GetByLocation(cmd.LocationId, from, to);

    forecasts.Override(cmd.TemperatureOverrides);

    temperatureForecastRepository.Save(forecasts);
    // raises 
    // TemperatureForecastsOverridden(locationId, overrides)
}

// TemperatureForecastsOverriddenProcessManager.cs 
public void Handle(TemperatureForecastsOverridden @event)
{
    var from = cmd.TemperatureOverrides.Min(t => t.DateTime);
    var to = cmd.TemperatureOverrides.Max(t => t.DateTime);

    // issue a command to recalculate the energy demand now temperature has changed...
    commandBus.Send(new RecalculateEnergyDemand 
       { 
          LocationId = @event.LocationId,
          From = from,
          To = to
       }));
}

// RecalculateEnergyDemandCommandHandler.cs 
public void Handle(RecalculateEnergyDemand cmd)
{
    EnergyDemand demandForecasts = energyDemandForecastRepository.GetByLocation(cmd.LocationId, cmd.From, cmd.To);

    // have to fetch temperature forecasts again...
    TemperatureForecasts temperatureForecasts = temperatureForecastRepository.GetByLocation(cmd.LocationId, cmd.From, cmd.To);

    demandForecasts.AdjustForTemperature(temperatureForecasts);

    energyDemandForecastRepository.Save(demandForecasts);
    // raises 
    // ForecastDemandChanged(locationId, demandforecasts)
}

选项 B:

创建单个聚合“位置”并根据给定日期范围在内部预加载预测数据。从 DDD 行为的角度来看,这感觉更清晰,但是加载受时间范围限制的聚合对我来说有点尴尬(或者只是我?)。在不限制预测值大小的情况下,“位置”聚合可能会变得很大。

// OverrideTemperatureForecastCommandHandler.cs 
public void Handle(OverrideTemperatureForecast cmd)
{
    var from = cmd.TemperatureOverrides.Min(t => t.DateTime);
    var to = cmd.TemperatureOverrides.Max(t => t.DateTime);

    // use from/to to limit internally the range of temperature and demand forecasts that get loaded in to the aggregate.
    Location location = locationRepository.Get(cmd.LocationId, from, to);

    location.OverrideTemperatureForecasts(cmd.TemperatureOverrides);

    locationRepository.Save(forecasts);
    // raises 
    // TemperatureForecastsOverridden(locationId, overrides)
    // ForecastDemandChanged(locationId, demandforecasts)
}

对于选项 A 或 B,读取端的非规范化器可能类似于:

// TemperatureDenormaliser.cs
public void Handle(TemperatureForecastsOverridden @event)
{
    var from = @event.Overrides.Min(t => t.DateTime);
    var to = @event.Overrides.Max(t => t.DateTime);

    var temperatureDTOs = storage.GetByLocation(@event.LocationId, from, to);

    // TODO ... (Add or update)

    storage.Save(temperatureDTOs);
}


// EnergyDemandDenormalizer.cs
public void Handle(ForecastDemandChanged @event)
{
    var from = @event.Overrides.Min(t => t.DateTime);
    var to = @event.Overrides.Max(t => t.DateTime);

    var demandDTOs = storage.GetByLocation(@event.LocationId, from, to);

    // TODO ... (Add or update)

    storage.Save(demandDTOs);
}

【问题讨论】:

  • 假设您已经在事件采购您的聚合。你打算如何实现你的 temperatureForecastRepository.GetByLocation 或 locationRepository.Get(LocationId, from, to)?您在事件存储中所拥有的只是用于聚合的一堆事件。您的存储库真正可以做的就是按顺序读取所有这些并应用。你得到了完整的聚合,就是这样,你不能只过滤掉“一些东西”。您可能需要考虑将 TemperatureForecasts 排除在域之外。
  • 感谢@alexeyzimarev,是的,这是有道理的,我的示例不适用于事件溯源。我认为您提出的将温度带出域的建议使我走上了正确的轨道。这个post 几乎总结了我一直在努力解决的问题,并从 Udi 和 Greg Young 那里得到了一些很好的反馈。这个answer 也很好地将职责拆分到不同的有界上下文中。我可能需要考虑级联事件(sagas)而不是命令。
  • 那么你最终做了什么?也许您可以回答自己的问题并展示一个可行的解决方案?

标签: c# domain-driven-design time-series cqrs event-sourcing


【解决方案1】:

您的任何一个示例都不能选择事件溯源。

随着新事件的出现,旧事件变得无关紧要。这些不一定需要放在一个集合中;对于整个阅读历史,没有不变量需要保护。

一系列事件可以在 saga 中进行管理,只保留有限的知识,并级联成结果事件。

【讨论】:

    猜你喜欢
    • 2018-08-13
    • 1970-01-01
    • 2014-02-20
    • 1970-01-01
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 1970-01-01
    • 2021-07-14
    相关资源
    最近更新 更多