Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
171 views
in Technique[技术] by (71.8m points)

design patterns - DDD - Conditional business rules without a domain service

Suppose you have a simple aggregate root like this:

Playlist {
  String name
  List<Song> songs

  add(Song song) {
    // Some business rules

    songs.add(song)
  }
}

Now suppose you want to introduce a business rule in the add(Song) method that depends on other aggregate roots. For example: A song may not appear in more than 3 playlists. One way to do this would be to fetch this information (number of playlists containing the song) in the application layer and pass it to the add(Song) method.

But now suppose furthermore that this business rule only applies under certain conditions. Imagine for example that playlists whose name starts with "M" don't have such limitation (completely arbitrary). Now fetching the information at the application layer would mean either implementing domain logic at the wrong level, or fetching data that you won't use. As business rules become more complicated this becomes more costly.

Now the obvious solution is: Use a domain service that has access to the Playlist repository and do your logic there. While this works, I was wondering if there is any pattern/architectural tip/reorganization that could be done to solve this problem without using a service to encapsulate the logic?

Thanks

question from:https://stackoverflow.com/questions/65950720/ddd-conditional-business-rules-without-a-domain-service

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I know you said, "without a service", but I decided to just list all the various approaches I could think of to solve this problem (not all equivalent):

  1. AR method receives pure external data. Like you said, this is the simplest strategy, but falls a bit short for more complex scenarios. It also couples the AR to a specific kind of song playlist addition rule.

  2. AR method receives a strategy/rule. The AR method is provided with a strategy (that could be impure) to enforce the rule. For instance, you could have a SongPlaylistAdditionRule#check(Song, Playlist) interface. The Playlist AR would just call on rule.check(song, this) to enforce it. The application layer would leverage some kind of rule provider to get the proper kind of rule or would be injected the right one through the IoC container.

  3. A domain service. This one's pretty obvious, not going to explain.

  4. A synchronous policy (event handler) applies in the same transaction. You'd have a SongPlaylistAdditionPolicy which listens to an event like SongAddedToPlaylist through an in-memory DomainEventPublisher allowing the policy to participate/execute in the same transaction. The SongPlaylistAssociationPolicy would live in the domain. It's similar to #2 & #3, but invoked indirectly.

  5. An eventual consistent policy. Same concept as above, but would execute asynchronously in another transaction. You'd most likely permit the violation, but then inform the user later that the song was removed from the playlist because the policy got violated, or any other kind of compensating action.

  6. A saga/process manager. You can think of this approach as the reservation pattern, where you introduce small strongly consistent transition states that moves towards a final goal. Each state transition is usually processed in it's own transaction and is rolled back through compensating actions in case of a failure. e.g.

    a) playlist.add(song) ? fires SongAddedToPlaylist/throws: checks playlist-specific invariants (e.g. max number of songs in playlist)

    b) song.apply(songAddedToPlaylist) ? fires SongAdditionToPlaylistConfirmed/SongAdditionToPlaylistFailed: checks song-specific invariants (e.g. how many playlists it belongs to)

It's important to note that of all the above, #1, #2, #3 and possibly #4 are all possibly stale checks, meaning they wouldn't necessarily prevent a rule violation through concurrency, unless you somehow lock all the data that's been read in the given transaction.

#5 could be used in combination of #1-#4 to reduce the number of accidental violations, but also cover scenarios where the rule got violated through concurrency.

#6 is probably the most natural one as from the domain's perspective it's pretty much all ARs. If you do not want to deal with eventual consistency you could always integrate in a way similar to #4 and have all ARs modified in a single TX. In that case you do not have to model the transition states either. Then when you need to scale you move to eventual consistency.

Hopefully this will give you some ideas on how to tackle your specific problem effectively! There's no one fits all solution!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...