Question

I am trying to figure out how to address this issue:

I have 3 tables with a many-to-many relationship.

Users *-* Roles *-* Permissions

I use a ORM to obtain data from them.

A method of my business layer must return users per permission, so I return objects with this class:

public class UsersPerPermission
{
    public User[] {get;set;}
    public Permission {get;set;}
}

But this class does not map to any table in the repository, it is something I generate from the existent tables. Where should this class live?

In other words:

  • Should I have a IRepository.GetUsersPerPermission()? And then that class should live in the repository.

  • Or should I have a IBusinessLayer.GetUsersPerPermission()? And then I have to invoke the CRUD methods in the repository?

It makes sense to put it in the business layer only, because the repository should just expose CRUD operations to tables... BUT, in order to execute this operation from the Business layer, I would have to execute several independent queries to get the data and create the 'UserPerPermission' class. In the other hand, if I place it in the repository, I can get that information in one shot using grouping.

Thanks!

PS: What is the name of this intermediate objects? 'transformations'?

Was it helpful?

Solution

In DDD, most entities and value objects should correspond to identified domain concepts that are part of your ubiquitous language. I usually try to limit many-to-many relationships and artificial association objects as much as possible. Eric Evans describes a few techniques allowing that in his book. When I have to create an association object, it must have a meaningful name with regard to the domain, basically I never name it Class1ToClass2. In your scenario, it's even more artificial since your object :

  • Redundantly models an association that already exists (indirectly) in the original model.
  • Has a name that doesn't reflect any particular business concept.

Note that this kind of object wouldn't be useless if we were in the presentation or application layer as it could come in handy to have a structure containing exactly what is displayed on the screen (DTO). But I'm talking about the domain layer here, which should be devoid of such composite objects.

So I wouldn't create a UsersPerPermission class in the first place. If what you want is a list of users and User is an aggregate root, just create a GetUsersByPermission() method in UserRepository. It doesn't mean that you can't have a GetUsersByPermission() method in an application service as well, if it matches a use case of your application (a screen that displays the details of one permission and the list of users with that permission).

OTHER TIPS

I agree with guillaume31 that there is no need to introduce a domain object "UsersPerPermission" to support a single use case.

There are two ways you can implement your use case using existing domain classes "User", "Role" and "Permission".


Solution one:

Assume you have: Permission --> Role --> User

Arrow denotes navigability. A Permission has association to a list of Roles and a Role has association to a list of Users.

I would add a method GetPermittedUsers() : List<User> to the Permission class, which is trivial to implement.

Th UI logic will invoke GetPermissions() of PermissionRepository then call GetPermittedUsers() on each Permission.

I assume that you use a ORM framework like hibernate(Nhibernate) and defines the many-to-many relationships correctly. If you defines eager loading for Role and User from Permission, the ORM will generate a query that joins Permission, Role and User tables together and load everything in one go. If you defines lazy loading for Role and User, you will load a list of Permissions in one query when you call PermissionRepository, and then load all associated Roles and Users in another query. Everything is load from database with up to three queries maximum. This is called a 1+n problem which most ORMs handle properly.


Solution two:

Assume you have: User --> Role --> Permission

Arrow denotes navigability. A User has a list of Roles. A role has a list of Permission.

I'd add getUsersByPermissions(List<long> permissionIds) : List<Users> to the UserRepository, and add getPermissions() : List<Permission> to the User class.

The implementation of the UserRepository need to join the User, Role and Permission tables together in a single query and load everything in one go. Again, most ORMs will handle it correctly.

Once you have a list of Users, you can create a method to build a Map<Permission, List<User>> quite easily.


To be honest, I muck like the solution one. I avoid to write a complicate method to convert a List of Users to to a map of Permission and Users, hence I don't need to worry about where to put this method. However solution one may create cyclic relationship between User, Role and Permission classes if you already have navigability in another direction. Some people don't like cyclic relationship. I think the cyclic relationship is acceptable even necessary sometime if you user cases demand it.

In a similar context I used a query method in a domain service that returns something like an

IEnumerable<KeyValuePair<PermissionName, IEnumerable<Username>>>

By using the KeyValuePair<> I avoided to pollute the domain model with an artificial concept (like UsersPerPermition). Moreover such a structure is immutable. I didn't used a query method on the repository because, in my context, no entity was coupled with the other. So it wasn't matter for any of the repositories.

However this solution is useful for your GUI, if and only if you modelled correctly the identifiers of your entities (in your example both Permissions and Users are entities). Indeed if they are shared identifiers that belong to the ubiquitous language that your users understand, they will be enough without further descriptions.

Otherwise you are just building a useful DTO for your GUI. It does not belong to the domain thus you should use the simplest possible thing that works (an ADO.NET query? something even simpler?). Indeed, in my own scenario both the GUI and the domain used such a service (the GUI showing a preview of an elaboration).

In general, the domain model must mirror the domain expert's language, capturing the knowledge relevant to the bounded context. Everything else must be outside the domain (but most of time can be expressed in terms of the domain's value objects).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top