可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have few different Bounded Contexts in the domain. The validation of a CRUD operation is built in each Bounded Context.
For example, I can create an entity called GAME only if the person creating it is a Group Leader.
I have two Bounded Contexts (BC) in this example. One is the Game BC and the other is the User BC. To solve the problem, in the Game BC, I have to make a domain service call like IsGroupLeader() to the User BC before proceeding on creating the Game.
I don't think this type of communication is recommended by DDD. I can have a User entity also in the Game BC, but I don't want to because the same User entity is being used differently in a different context in a different BC.
My questions are:
Should I use Domain events where the Game BC has to send an event to the User BC asking the status of the User? With this approach, I don't make a synchronous call like IsGroupLeader but an event called is_group_leader. Then the Game BC has to wait for the User BC to process the event and return the status. The Game BC will create the Game entity only after the User BC process the event.
Is CQRS a solution to my problem?
Any idea appreciated.
回答1:
When integrating BCs, you have a few options. The reason that calling out to an external BC is discouraged is because it requires for both BCs to be operational at the same time. However, this is often quite acceptable and is simpler than the alternative. An alternative is to have the Game BC subscribe to events from the User BC and keep local copies of the data it needs, which in this case is information about whether a user is a group leader. In this way, when the Game BC needs to determine whether a user is a group leader, it doesn't need to call out to the User BC, it just reads locally stored data. The challenge of this event-driven alternative is synchronizing the events. You have the make sure the Game BC receives all appropriate events from the User BC. Another challenge is dealing with eventual consistency, since the BCs may be slightly out of sync at any given point in time.
CQRS is somewhat orthogonal to this problem.
回答2:
Here is how I would reason about it.
I would argue that the Game BC doesn't know about "Users", it might however know about "Players".
If the Game BC is dependant on an active/current player then it should be passed into the BC when creating the Game BC instance.
eg.
Player currentPlayer = GetPlayerSomehow...();
GameBC gameBC = new GameBC(currentPlayer);
gameBC.DoStuff();
Now your two BC's are still separate, you can test them separately etc.
And to make it all work you simply do something like:
User currentUser = GetCurrentUser();
Player currentPlayer = new Player();
currentPlayer.IsGroupLeader = currentUser.IsGroupLeader;
GameBC gameBC = new GameBC(currentPlayer);
gameBC.DoStuff();
This serves as an anticorruption layer between the UserBC and the GameBC, you can move and validate the state you want from the UserBC into the state you need for your GameBC.
And if your GameBC needs to access many users, you can still pass some sort of mapping service into the game BC that does this kind of transformation internally.
回答3:
To cope with the kind of problems you are facing, we use bounded roles a modeling pattern that emerged over the years and proved to work very well. Bounded contexts are defined after semantical units that often, in enterprise organizations, can be mapped to specific roles.
That should be obvious considering that different roles face different problems and thus speak slightly (or entirely) different languages.
Thus, we always model the roles that interact with our application as a junction point between applicative requirements (infrastructure, persistence, UI, localization etc...) and business rules (the domain) and we code them in different modules (aka assemblies or packages).
As for your second question, CQRS can be a way to code the kind of interactions between BCs that you are describing, but I don't like it in this particular context.
回答4:
I think you're almost there. Close to a good solution. I'm not so sure you have to split these two into two BC's. Your User Aggregateroot (?) and Game maybe belong in one BC and depend on each other. A User "has a" Membership "to one or many" Games (just guessing your entity relations).
But I'm just brainstorming now. Try to follow :) Different approaches follows:
First
GameBC has a Create() method that actually take a UserMembership as param. Create(UserMembership).
You then through UserMembership entity know what kind of membership and User this. If accepted ,game is created. If not exception is thrown or Game gets a broken rule message, depends on what approach you want to communicate back to client. The coordination can be done in application layer without leakage of domain knowledge.
Second
You do as one of the other answers. You raise a CreateGameEvent within Game.Create(UserId) method. That Event is caught by an EventHandler (registered by IoC in Application startup) that resides in Application layer and look up UserMembership through repository. The small leakage of domain knowledge is that business rule that knows who are allowed yo do what are verified in application layer. This can be solved by letting the CreateGameEventHandler take UserId and RuleRef (can be string "CAN_CREATE_GAME" or enum) and letting a UserPermission object verify the permission. If not. Exception is thrown and catched in application layer.
Drawback can be that you do mot want permission reference strings be hardcoded in Create method.
Third
...continues where second approach ends. You know that GameBC may not be the right place to do user permission lookups if you follow SRP principle. But the action is triggered around that method somehow. An alternative an be a Create(GroupLeader user). OR you can have Game.Create(User user) then do a validation that User is GroupLeader type. Create(GroupLeader) tells you what you need to call this method.
Last
Maybe an alternative that I like more now when writing this. When you want to create an entity I usually let that Create(Save) method be on the repository. The IGameRepository interface is located next to Game Entity in domain assembly project. But You can also create a GameFactory that are responsible for starting the lifecycle of Game Entity. Here is also a good place to put Create method... GameFactory.Create(GroupLeader) { return new Game.OwnerUserId = GroupLeader.Id; }
Then you just save it IGameRepository.Save(Game)
Then you have an intuitive and self describing way of telling other developers that "You must have a GroupLeader instance to create a Game".
Finally I hope you realize that you know the domain and you will figure out what suits you best. Be pragmatic and don't go Eric Evan hardcore. There is so many devs out there that are stuck in a "religion" in how things are gonna be done. Size of project, money, time, and dependencies to other systems etc. also affect how well you can be strict in doing DDD.
Good luck.
回答5:
I think I may have to follow a different approach where I will make the User entity part of the Game BC (the same entity is part of User BC also). I will use the Repository to read the IsGroupLeader flag from the db in the Game BC. This way, the dependency on the User BC is removed and no communication is needed with the User BC.
What do you think?