DDD - 没有域服务的条件业务规则

时间:2021-01-29 08:00:55

标签: design-patterns domain-driven-design

假设你有一个像这样的简单聚合根:

Playlist {
  String name
  List<Song> songs

  add(Song song) {
    // Some business rules

    songs.add(song)
  }
}

现在假设您想在 add(Song) 方法中引入一个依赖于其他聚合根的业务规则。例如: 一首歌不能出现在 3 个以上的播放列表中。一种方法是在应用层中获取此信息(包含歌曲的播放列表的数量)并将其传递给 add(Song) 方法。

但现在进一步假设此业务规则仅适用于特定条件。例如,想象一下名称以“M”开头的播放列表没有这样的限制(完全任意)。现在在应用层获取信息意味着要么在错误的级别实现域逻辑,要么获取你不会使用的数据。随着业务规则变得更加复杂,这会变得更加昂贵。

现在显而易见的解决方案是:使用可以访问播放列表存储库的域服务并在那里执行您的逻辑。虽然这可行,但我想知道是否有任何模式/架构技巧/重组可以在不使用服务来封装逻辑的情况下解决此问题?

谢谢

1 个答案:

答案 0 :(得分:1)

我知道你说“没有服务”,但我决定只列出我能想到的解决这个问题的所有各种方法(并非全部等效):

  1. AR 方法接收纯外部数据。就像你说的,这是最简单的策略,但对于更复杂的场景来说有点不足。它还将 AR 与特定类型的歌曲播放列表添加规则相结合。

  2. AR 方法接收策略/规则。 AR 方法提供了一个策略(可能是不纯的)来执行规则。例如,您可以有一个 SongPlaylistAdditionRule#check(Song, Playlist) 接口。 Playlist AR 只会调用 rule.check(song, this) 来执行它。应用层会利用某种规则提供者来获取正确的规则,或者通过 IoC 容器注入正确的规则。

  3. 域服务。这个很明显,不解释了。

  4. 同步策略(事件处理程序)适用于同一事务。您将有一个 SongPlaylistAdditionPolicy,它通过内存中的 SongAddedToPlaylist 侦听 DomainEventPublisher 之类的事件,允许策略参与/执行同一事务。 SongPlaylistAssociationPolicy 将位于域中。它类似于#2 & #3,但间接调用。

  5. 最终一致的政策。与上面相同的概念,但会在另一个事务中异步执行。您很可能会允许违规,但随后通知用户该歌曲已从播放列表中删除,因为违反了政策或任何其他类型的补偿措施。

  6. 传奇/流程管理器。您可以将此方法视为保留模式,您可以在其中引入朝着最终目标移动的小的强一致转换状态。每个状态转换通常在它自己的事务中处理,并在发生故障时通过补偿操作回滚。例如

    a) playlist.add(song) ⟶ 触发 SongAddedToPlaylist/throws:检查特定于播放列表的不变量(例如播放列表中的最大歌曲数)

    b) song.apply(songAddedToPlaylist) ⟶ 触发 SongAdditionToPlaylistConfirmed/SongAdditionToPlaylistFailed:检查歌曲特定的不变量(例如它属于多少播放列表)

重要的是要注意,在上述所有内容中,#1、#2、#3 和可能的 #4 都可能是陈旧的检查,这意味着它们不一定会通过并发防止规则违规,除非您以某种方式锁定所有在给定事务中读取的数据。

#5 可以与 #1-#4 结合使用,以减少意外违规的次数,但也涵盖通过并发违反规则的场景。

#6 可能是最自然的一种,因为从域的角度来看,它几乎都是 AR。如果您不想处理最终一致性,您可以始终以类似于 #4 的方式进行集成,并在单个 TX 中修改所有 AR。在这种情况下,您也不必对过渡状态进行建模。然后,当您需要扩展时,您会转向最终一致性。

希望这会给您一些有关如何有效解决特定问题的想法!没有万能的解决方案!

相关问题