Seth
Seth

Reputation: 6832

How and when to hydrate domain objects in the CQRS command stack

Suppose I have a command that saves an application Role along with some application Permissions. My roles and permissions have (or will have) business rules, so I'll use domain objects:

class Role {
    ...
    IEnumerable<Permission> Permissions { ... }
    AddPermission(...)
    ...
}

class Permission {
    ...
    int ID { ... }
    string Foo { ... }
    string Bar { ... }
    string Baz { ... }
}

When I'm saving a Role, I need a full Role object and will probably receive everything I need from the presentation layer. I do not need full Permission objects, though, because I only need to associate their ID with the Role. I don't need the Foo, Bar, and Baz properties.

When I'm saving a Permission, I obviously need the full Permission object.

My Question: What is the right way to handle that Permission object? If I only have one Permission class, then, when I'm saving a Role, I will either:

  1. Have to query/hydrate full Permission objects from the database so the Role has legitimate Permission objects in its collection, or
  2. Attach incomplete Permission objects (IDs only) to avoid the trip to the database.

Option #1 sounds like the kind of command/query complexity CQRS aims to avoid, and #2 sounds like an invalid object floating around--I don't even want to be able to create invalid objects, much less use them.

I could also create a PermissionSummary class and the full Permission class derives from it. I've done this before, and it inevitably leads to a creep of properties from the "full" class up to the "summary" class.

Upvotes: 1

Views: 1027

Answers (2)

Joe
Joe

Reputation: 1357

Batavia's response to CQRS is great and as I have no experience with that pattern Ill try and answer the 'what do I save' question.

The answer to this one depends strongly on your Model design and the proposed behavior of the Permission entities. DDD does not work without a strong knowledge of the business domain.

Having said that I can think of 3 scenarios:

1) Permissions are immutable and Roles get changed. In this scenario Role becomes the aggregate root and the collection of Permissions in re-hydrated on each fetch. The properties and methods of each Permissions entity is available to Role entity enabling operations like

partial class role() {
   public enumAccessType AccessToAction(enumAction action) {
      foreach(var p in Permissions) 
         if p.HasFullAccess(action) return enumAccesssType.Full;
      foreach(var p in Permissions) 
         if p.HasLimitedAccess(action) return enumAccesssType.Restricted;
      return enumAccesssType.None;
   }
}

There is a Permission repository for saving new Permissions and a Role Repository for maintaining the roles and the role_Permission tables

NOTE: Just because the domain object has the full PERMISSION objects doesn't mean that the persistence layer needs to update the PERMISSION table for each of the permissions added. The RoleRepository should only update ROLE (roleId, roleName) and ROLE_PERMISSION (roleId, permissonId) tables.

2) Permissions are mutable But roles are static. In this situation is may make more sense for your model to have Permission as the Aggregate root and a collection of Roles as role is just a bucket for grouping Permissions:

class Permission {
   Ienumerable<RoleId> Roles {get;private set;}
   PermissionId ID { ... }
   string Foo { ... }
   string Bar { ... }
}

Again there is Permission repository for saving new Permissions and maintaining the Permission-Role relations. The Role Repository just handles roles.

3) Roles and Permissions change all the time - your security requirement is complex and will difficult for most end users to comprehend. Bring in the Domain Expert to explain how Role and Permissions relate and affect the rest of the system and WHY the have to be so flexible. Look at the why and try and identify processes or behavior that may be incorrectly being forced into a roles-permissions pattern. This will probably require its own bounded context and services to work.

It helps to remembers 'Database Tables' != 'DDD Entities' != 'User Interface'. At first blush it looks like your coming at this from a database design point of view rather than the domain behaviour point of view.

A big 'Am I Doing This Right' is this check: If I save changes to one entity , do I need to reload any other entities that reference that original entity in order for those other entities to work? If the answer is yes then you need to revisit your model.

Upvotes: 3

Batavia
Batavia

Reputation: 2497

so when using CQRS i think the answer is neither.

I think it's important to differentiate between DDD and CQRS.

When saving a role (In the CQRS pattern) you would be sending a "RoleChangeRequest" command to your backend. This would then raise a RoleChangeRequested event. (this could be done by a domain service, possibly even by the domain layer itself). This event would be handled by your database layer but this event would look more closely to this

class RoleChangeRequested {
    IEnumerable<int> PermissionIds {....}
    string name {....}
}

The key here is that in raising the event that would save your data you only need to know about the Id's of the permissions. There is no need to either query permissions (ok, maybe you want some check that they actually exist. but a foreign key relation could handle this). or to attach incomplete objects.

NOTE: In just this example CQRS is going to make your application a lot more complex. CQRS is weapons-grade architecture and should be handled with extreme caution. Now why would you want to use CQRS, that's because your next requirement is to make a full and guaranteed audit trail of all your role and or permission changes. This is just another event handler to the same events. And you could even have an event handler to the generic ievent interface and then you are guaranteed that you audit every event being raised in the application. That you get (almost) for free and is why CQRS can be a benifit.

Upvotes: 2

Related Questions