I'm designing a generic multi-tenant identity system (Authentication / Authorization), but I have some doubt if I'm designing it the correct way.
Currently, the design is this:
AR User with reference to Tenant and a specific organizational unit where the user belongs to. AR Tenant has a list of entity Organisational unit
The Organisational unit in this context is a tree structure describing the organization, for example Aphabet -> Google -> Development
My question is
Should the organizational unit be a root entity as it is referenced by a user or is this allowed?
I have modelled it as an entity of Tenant because the organizational unit cannot exist without a Tenant, so in my mind, the organizational unit should not be an AR.
Any guidance is appreciated
From your question it's not clear to me what your application should look like and what's the purpose of it. What are you modeling? Can you provider more detailed information?
From what I can understand your entities are: Tenant, OrganizationalUnit and User.
What are the relationships between Tenant and OrganizationalUnit? It seems that a Tenant should contain many OrganizationalUnits but what is the purpose of OrganizationalUnit? Is it to group and organize Users (for example when working in specific teams or being part of specific parts of a company (dev, sales, design etc.)?
The first thing that people come across when they start with DDD are Aggregates. They are the most misunderstood concept and are difficult to understand. I think that when a person starts modeling applications he/she should not start with trying to define Aggregates. Start with defining the entities and clarifying the relationships between them. After you do that, analize your application and the technical issues that it will have and think of how you can organize your entities into Aggregates to solve these problems.
Have you read the DDD book by Eric Evans? If you haven't I strongly recommend it.
As a pattern Aggregates are used to solve couple of problems (it's not a complete list):
Define transactional boundaries of changes to your entities. What entities should change together as a whole and what entities can change independently of one another? Most applications today are multi-user, so you may have concurrent operations (like adding multiple Users at the same time to OrganizationalUnit from different Admins)
Limit the number of references one entity has to other entities so when you load them from the database you don't load one entity with a collection that contains 1 000 0000 other entities that may cause your server to ran out of memory and crash. That is not very practical and wont work.
Clarify traversal directions. Bidirectional associations are hard to maintain. For example you may have a User that belongs to an Organizational Unit and an OgranizationalUnit contains many Users so you have many to many relationship. Loading one User with all OrganizationalUnits and loading all other Users that belong to those OrganizationalUnits just to change the name of that User isn't very practical.
That's where Repositories can help. If you want to get OrganizationalUnits for a specific User you will have OrganizationUnitRepository with getOrganizationalUnitsForUser(userID) method. If you want to find Users for OrganizationalUnit you will have UserRepository with getUsersForOrganizationalUnit(unitID) method. This way you will use Repositories to traverse relationships between entities organized into aggregates.
Start with defining the entities first with their relationships, analyze the issues of you app and then organize them in Aggregates based on the problems that you have (refactor later if you don't get it right).
In your case you can:
Have all entities be part of separate aggregates and have each entity be the root of it's aggregate. Have repositories for all of them. In your Services you will use Repositories to retrieve multiple entities if you need them for the specific operation.
If you don't think that a Tenant will have thousands of OgranizationalUnits you can have it part of the Tenant Aggregate and load them together. In this case since OgranizationalUnits are hierarchical you will have to load parent and children together. If you don't want that use ReferenceByID and have a property with parentID only for each OgranizationalUnit instead of loading a collection of children and parent.
One problem with distributing entities to different aggregates is consistency boundary. This way you have to introduce eventual consistency between different aggregates and it's harder to enforce some rules.
For example lets say that you have a limit of how many Users can be part of a Tenant. If Tenant entity and OrganizationalUnit entity are part of the same aggregate then they are in the same consistency boundary. This way you will load the aggregate, make the check and if it's violated raise an error, if not add the User and save the aggregate. In the case of multi-user scenario, if two Admins load the same aggregate and each one adds a User to OrganizationalUnit, during the save you can use Optimistic Concurrency to resolve conflicts so that you don't end up with Users count that exceeds the limit.
If you break down the aggregate in two, then they are in different consistency boundaries and enforcing this rule becomes harder. This is called Set Based Validation. You can search for information on this subject it but it's difficult to find any, so I cant point to anywhere.. sorry about that.
If you have such issues with set validation, and you can go with clustering entities without having performance issues with loading them, then do it and don't do Set Based Validation
For more details you can check Effective Aggregate Design. It discusses these problems and solutions in details. It actually has a Tenant part of it's model too :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With