相互依赖的惰性值计算的组织模式

时间:2020-07-15 13:42:01

标签: scala event-sourcing organization code-organization

TL; DR,我正在寻求有关如何组织数百行静态函数的思想,这些函数将取代相互依赖的lazy val x = { a long block }评估。

我正在使用事件源模型重构一些旧的Scala代码,以使我们的团队更容易理解。我在下面草拟了一个总体计划,但对是否存在更好的模式感兴趣。我目前有一个主要的聚合类,具有许多相互依赖的lazy val = { ... }属性:

trait Aggregate extends AllTheNecessaryStateWeWillNowImplement {
  
  // define some minimal dependency up front
  def storedEvents: Seq[StoredEvent]
  
  lazy val activeEvents: Seq[Event] = 
    storedEvents.filterNot(e => e.isUndone || e.isIgnored).map(_.event)
  
  lazy val isFoo: Boolean = {
    activeEvents.foldLeft(false) {
      case (false, FooEvent) => true
      case (true, BarEvent)  => false
      case (prev, _)         => prev
    }
  }
  // ... and many more
}

case class DefaultAggregate(storedEvents: Seq[StoredEvent]) extends Aggregate
case class AnotherAggregate ...

因为此基本集合特征已混入其他几个(有时是嵌套的)集合中:

  • 这些相互依存的惰性值(其中一些包含更大的逻辑块)可能难以掌握:实际上,对单个值的评估可能会级联甚至更多。
  • 因为我们必须创建一长串事件来检查微不足道的逻辑,所以测试变得越来越困难,难以相信

我们研究过的一种简化方法是简单地移动 将所有这些逻辑转换为单独的静态方法:

trait Formulae {

  def activeEvents(storedEvents: Seq[StoredEvent]): Seq[Event] = 
    storedEvents.filterNot(e => e.isUndone || e.isIgnored)
    .map(_.event)
  
  def isFoo(activeEvents: Seq[Event]): Boolean = {
    activeEvents.foldLeft(false) {
      case (false, FooEvent) => true
      case (true, BarEvent)  => false
      case (prev, _)         => prev
    }
  }
}

// now the trait can coordinate the implementation, and cascading dependencies are more clear to the reader.
trait Aggregate extends StuffWeNeed with Formulae {
  def storedEvents: Seq[StoredEvent]
  lazy val activeEvents = getActiveEvents(storedEvents);
  lazy val isFoo = getIsFoo(activeEvents)
}

// And we can backfill new tests calculations without a dependency on long lists of events, but on the actual logic
val subjectUnderTest = new Formulae {}
subjectUnderTest.getIsBar(foo=true, barRule=ThisWasCalculatedElsewhere).shouldBe(false)

有什么想法吗?这仅仅是增加了并发症和流失率,还是代表着真正的改善?是将这些东西混合在一起的合适之处吗?

0 个答案:

没有答案
相关问题