How to model users and groups in DDD? - domain-driven-design

Except the question whether the following scenario is a good case for DDD at all, I'd like to discuss it and ask for advice:
Given we have users and groups. A user has a name, and a group has a name as well. Users may join of leave groups, and they may also change groups. The rules they have to obey are: A user may only be in maximally 2 groups, and a group may consist of maximally 10 users.
How do you model this? So far I can think of three options, where each option has its individual advantages and disadvantages:
Users and groups are entities, and both are aggregates as well. Join, Switch and Leave are commands on the group aggregate. While this works great for Join and Leave (as they only refer to a single group), it does not work for Switch, as this refers to two groups at the same time, and hence needs to modify two aggregates in a single transaction, which is not nice.
Users and groups are entities, and both are aggregates as well. Join, Switch and Leave are commands on the user aggregate. This works great for all three, and it is easy to check that a user is not in more than two groups at the same time, but how would you check that the rule of maximally 10 users per groups is not violated?
Users and groups are entities, both are aggregates. But there is also a third aggregate: Relationship. Join, Switch and Leave are now commands on the relationship aggregate. While this seems to be the best approach (since it gives the relation between users and groups a name and makes it explicit), I am now completely lost on how to model the constraints.
Can anybody give me a hint?
If there was only one of the two constraints it would be dead-easy: Then you could put the commands to the aggregate which also has the constraints. But if you have a constraint on both sides, I'm lost. Any help?

You may hand an instance of group to the user to have it check aggregate-encompassing invariants. Then let the user know it joined the group and let the group know that a user has joined.
class Application
handle(UserWantsToJoinGroup command)
user = users.withId(command.userId)
group = groups.withId(command.groupId)
user.join(group)
class User
join(Group g)
if g.isFull throw
if this.isMemberOf(g) throw
if this.numberOfGroupsImIn >= 2 throw
publish new JoinedGroup(this.userId, g.groupId)
handle(JoinedGroup evt)
// modify state
class Group
handle(JoinedGroup evt)
// modifiy state

Related

How to lower redundancy in my UML Class diagram?

I am modeling a course management system with the following requirements:
2 roles: Student can choose course; Administrator can make courses and then make groups for course, based on list of students who chose that course and based on list of tutors for that course.
In one course students can go only in one group, and teacher can teach more groups in one course. But student can go in 1 or more courses.
This diagram is a sketch of a class diagram. There is too much redundancy and I don't know how to solve it. Please help me to model my domain and ignore the mistakes in UML syntax or multiplicitycardinality.
More precisely, how can I make association between students and groups, and tutors and groups without redundancy, because I already have associations with courses?
Let's focus indeed on the domain model:
The direct association between Student and Course corresponds to the student's choice. The choice may or may not be fulfilled. And if it is fulfilled, it's only when the groups are made.
The indirect association between Student and Course via Group corresponds not only to fulfilled choices, but also to practical organisation of courses.
The two paths that exist are not redundant, but the representation of two different realities.
For the tutor, it's similar: the direct association tells what the tutor is able to do, and the indirect one tells what he/she has to do. So the model looks fine in this regard.
In some cases you could simplify (hint: not here)
In some situation a choice/plan can be combined with a fulfilled choice/implementation, by merging associations. For example, if there wasn't your group requirement, you could have only one association class between Student and Course and use a state of your association class to make the difference. But this will not work here, given your requirements.
You could also trick with the requirements. For example, you could have for every course a dummy group that corresponds to students that chose the course and are not assigned to a group. But shortcuts in the design can (and often will) backfire. Here for example, it would add complexity (need to create a dummy group for every course, make a difference between the dummy group and the real groups in almost every activity). Moreover, this trick would constrain your solution. For instance, not having an association class for the choice will prevent from enabling the students to prioritise their courses or providr other elements that facilitate the creation of groups that do not yet exist (e.g. pre-existing skill level).
In summary: your model should primarily aim at addressing the requirements. Premature optimisation is the root of all evil, in modelling as well.

Should a single command address multiple aggregates?

In CQRS and DDD, an aggregate is a transactional boundary. Hence I have been modeling commands always in such a way that each command always only ever addresses a single aggregate. Of course, technically, it would be possible to write a command handler that addresses multiple aggregates, but that would not be within a single transaction and hence would not be consistent.
If you actually have to address multiple aggregates, I usually go with a process manager, but this sometimes feels like pretty much overhead. In addition, from my understanding a process manager always only reacts to domain events, it is not directly addressed by commands. So you need to decide which aggregate to put the starting point to.
I have seen that some people solve this using so-called domain or application services, which can receive commands as well, and then work on multiple aggregates – but in this case the transactional nature of the process gets lost.
To give a simple example, to better illustrate the scenario:
A user shall join a group.
A user has a max number of groups.
A group has a max number of users.
Where to put the command that triggers the initial joining process, and what to call it? user.join(group) feels as right or wrong as group.welcome(user). I'd probably go for the first one, because this is closer to the ubiquitous language, but anyway…
If I had something above the aggregates, like the aforementioned services, then I could run something such as:
userManagement.addUserToGroup(user, group);
However, this addUserToGroup function would then need to call both commands, which in turn means it has to take care of both commands being processed – which is somewhat counterintuitive to having separate aggregates at all, and having aggregates as transactional boundaries.
What would be the correct way to model this?
It may be worth reviewing Greg Young on Eventual Consistency and Set Validation.
What is the business impact of having a failure
This is the key question we need to ask and it will drive our solution
in how to handle this issue as we have many choices of varying degrees
of difficulty.
And certainly Pat Helland on Memories, Guesses, and Apologies.
Short version: the two generals tell us that, if two pieces of information must be consistent, then we need to write both pieces of information in the same place. The "invariant" constrains our data model.
The invariant you describe is effectively a couple of set validation problems: the "membership" collection allows only so many members with user A, and only so many members with group B. And if you really are in a "we go out of business if those rules are violated" situation, then you cannot distribute the members of that set -- you have to lock the entire set when you modify it to ensure that the rule is not broken and that first writer wins.
An element that requires some care in your modeling: is the domain model the authority for membership? or is the "real world" responsible for membership and the domain model is just caching that information for later use? You want to be very careful about trying to enforce an invariant on the real world.
There's a risk that you end up over constraining the order in which information is accepted by the model.
Essentially what you have is many to many relationships between users and groups with restrictions on both sides:
Restriction on the number of groups a user can join
Restriction on the number of users a group can have
VoiceOfUnreason already gave a great answer, so I'll share one way I've solved similar problems and go straight to the model and implementation in case you have to ensure that these constraints are enforced at all costs. If you don't have to, do not make the model and implementation that complex.
Ensuring consistency with such constraints on both Group and User entities will be difficult in a single operation because of the concurrency of the operations.
You can model this by adding a collection of RegisteredUsers to a Group or vice versa, adding a collection of JoinedGroups to a User, and enforce the constraint on one side, but enforcing it on the other side is still an issue.
What you can do is introduce another concept in your domain. The concept of a
"Slot" in a Group. "Slots" are limited by the max number of Slots for a Group.
Then a User will issue a JoinGroupRequest that can be Accepted or Rejected.
A Slot can be either Taken or Reserved. Then you can introduce the concept of SlotReservation. The process of joining a User to a Group will be:
Issue a JoinGroupRequest from a User
Try to Reserve a Slot enforcing the MaxUsersPerGroup constraint.
Acquire a Slot or Reject the SlotReservation of a User enforcing the MaxGroupsPerUser constraint.
Accept or Reject the JoinGroupRequest depending on the outcome of the SlotReservation
If the SlotReservation is Rejected, another User will be able to use this Slot later.
For the implementation, you can add SlotReservation Queue Per Group to ensure that once a Slot is free after a Rejected SlotReservation, the next User that wants to join the Group will be able to.
For the implementation, you can add a collection of Slots to a Group, or you can make Slot an aggregate in its own right.
You can use a Saga for this process. The Saga will be triggered when a JoinGroupRequest is made by a User.
Essentially, this operation becomes a Tentative Operation.
For more details take a look and the Accountability Pattern and Life beyond distributed transactions an apostate's opinion and Life beyond distributed transactions an apostate's implementation.

DDD: Choosing aggregate root

In my case I have two main concepts: User (main citizen of the system) and Group.
Group has two sub-collections: ranks and roles. Without a group, ranks and roles have no meaning.
When User is assigned to the Group, we also have to pick 1 Role and 1 Rank and assign them to this relationship between User and the Group.
Diagram
Question:
How much aggregate roots do I have here? From the user side its obviously a user (main concept of the system), but what about its relationship with group? AFAIK its forbidden by rules of DDD to reference entities outside of aggregate root.
AFAIK its forbidden by rules of DDD to reference entities outside of aggregate root.
Well, I would't say it's "forbidden by rules of DDD"... Some times you have no choice. I have to consider the size of the "entities' collection" associated to the Root's Aggregate. Sometime you can maintain the association in the same aggregation and use some kind of "lazy load" to avoid resource consumption. The Vernon's iDDD book[1] has some advises and use cases around this specific case. Take a look on his blog post[2]
[1] https://www.amazon.com.br/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577
[2] https://vaughnvernon.co/?p=838
You have at least the following options, depending on your business requirements regarding consistency:
You have 5 aggregate roots: User, Group, Rank, Role and UserAssignment. The last one must protect the invariant "we also have to pick 1 Role and 1 Rank". For lifetime management you use eventual consistency between ARs. For example, when you delete a Group you must delete also the orphan Ranks, Roles and UserAssignments.
You have User (with UserAssignment as nested entity) and Group (with Role and Rank as nested entities). You have strong consistency inside ARs (when you delete a user all its asignments are also deleted) and eventual consistency between User and Group.
What should you use? Only you could decide. For example, if you choose the first option and delete a user then there could be a delay of seconds/minutes/hours before its assignments are also deleted.
Strong consystency should be used to protect only real business invariants because it is not cheap.
P.S. if you need to hold a reference to a nested entity from another AR then you should reconsider your aggregate roots boundaries because your design is most probable wrong.
I'm going to change some of the words and we'll see if it helps (hypothetical):
I have an Order and a Product. When I add a Product to an Order I have to choose a Store and a Colour.
How would you model this?
Colour may very well be a Value Object but Store is not. I would opt for an OrderItem Value Object which contains a Colour Value Object and a StoreId value to capture the relationship. An Order would contain a list of OrderItem entries.
Removing Colour entries is fine since we have denormalized that bit into the OrderItem. We could have another Value Object represent the Store but typically we would not delete the store or have some processing to handle a deletion or, even more typical, use referential integrity constraints to prevent deleting a used Store.
If you consider ever deleting the Order only the OrderItem association is also deleted.
In your case User and Group are probably Aggregate Roots and I'd add a UserGroup (or UserAssignment as Constantin used). The UserGroup contains the association and related bits. You would have to identify the real domain structure though.

Use case diagram: actor with more permissions

Regarding Use-Case Diagram:
Consider 2 kinds of actors: Customer & User. Customer is a User (generalization)
I want to model the use case of a Search option for items ('search items') which is allowed for all users without the need for a login : but only a customer (because he is registered) could take part in the use case add items. note that this means that the customer will also need to be logged in for this to happen.
I can't seem to make it work without messing up the whole diagram.
Thanks!
Simply add a constraint to the use case. You can make that visible in a diagram by using a constraint element which looks like a note but has the text enclosed in curly brackets.
As is noted in the specs:
NOTE. An Actor does not necessarily represent a specific physical entity but instead a particular role of some entity that is
relevant to the specification of its associated UseCases. Thus, a single physical instance may play the role of several different
Actors and, conversely, a given Actor may be played by multiple different instances.
Thus there is no reason to try to mash multiple use cases into single Actor; User and Customer represents two different roles and thus it is often better to represent it by two actors; the fact that Customer is derivation of User can represented by ­a generalization.
And finally you can add a note to the use-case association to specify that the Customer has to be logged in - but at least for me this seems unnecessary, because the User already had to log in in order to take on the "Customer" role.

Security in collaborative software (collaboration groups)

Lets say i have objects A, B, C, D, E, F, collaborator groups C1, C2, users C1U1 (admin), C1U2 (user), C2U1 (admin), C2U2 (user).
A, C, E belongs to group C1.
B, D, F belongs to group C2.
Thus only members of correpsonding group can view their respective objects. Also any object created ends up in group to which its creator (user) belongs.
Admin can create and edit every entity, user can only create entities and only edit his entities.
And ofcourse each user (admin or regular user) as said before can view only entities that belong to his group.
In my particular task there are number of type of entities (static) and number of types of users (static). There will be multiple collaboration groups (dynamic) where users will create some entities. Some of the users will have ability only to view entities (to which they are entitiled to view due to being member of correspodning collaboration group), create and edit. Some of them will have ability to edit entities of other users (but only ceratin types of entities). Essentialy its a little bit like JIRA.
That said, my question is quite simple: what pattern helps organising this stuff? I believe this is a common problem, thus it must have common name.
I know there is a blunt way to create this. Im using symfony 2 and it have both RBAC and ACL, but not RBACL afaik. RBAC will help me with user types (i use UserGroups and i create roles like ROLE_EDIT_ENTITYTYPE1), checks for ownership are performed with ACL.
But how do i implement those collaboration groups?
Im sorry if my explanation sounds vague or just stupid or anything else, i would like to hear anything about subject. I got a feeling that i'm missing something here.
PS: Not sure about "security" tag, if its related to this question.
For your case the just Role Hierachy is not suffiecient enough. What you need is complete ACL:
http://symfony.com/doc/current/cookbook/security/acl.html
You can create the ACL right in a Listener when Entities are saved and then grant the access for all the group members.
You can also work with different ACL Masks, in order to control which user can EDIT entities and which users can only view them:
http://symfony.com/doc/current/cookbook/security/acl_advanced.html
Sorry for mainly linking to the default documentation, but I think this basically covers everything you need.

Resources