2

Suppose I have a command like below:

public sealed class UpdateExampleCommand
{
    public int Field1 { get; set; }
    public string Field2 { get; set; }
    public bool Field3 { get; set; }
    public double Field4 { get; set; }
    public DateTime Field5 { get; set; }
}

There is a permission that determines whether the user can perform the UpdateExampleCommand. This form of authorization is very simple and can be treated as a cross-cutting concern by checking the user's permissions in a decorator before rejecting or accepting the command.

However, there are also permissions that determine whether a user can edit particular fields that's modified by the UpdateExampleCommand.

e.g. A user requires the Can_Edit_Field_3 permission to make changes to Field3's value, Can_Edit_Field_4 permission to make changes to Field4, and Can_Edit_Field_5 permission to make changes to Field5.

Solution 1
Would this qualify as business logic and belong in the aggregate root like below?

public void UpdateExample(int field1, string field2, bool field3, double field4, DateTime field5, Editor editor)
{
    // Protect invariants
    // ...

    // Perform updates
    Field1 = field1;
    Field2 = field2;

    // Editor is a value object encapsulating the user permissions.
    // It is created in the command handler based on the user context
    // and passed into the aggregate root's method.
    //
    // There are several other permissions like this that aren't strictly
    // bound to roles, so I can't use an approach like the "CollaboratorService"
    // from the IDDD book which performs an implicit permission check
    // by creating a Moderator, Author, etc. role object if the current
    // user has that role.
    if (editor.CanEditField3) Field3 = field3;
    if (editor.CanEditField4) Field4 = field4;
    if (editor.CanEditField5) Field5 = field5;
}

Solution 2
Or would it be preferable to create separate methods in the aggregate root and check permissions in the command handler before calling the appropriate method?

public async Task<Unit> Handle(UpdateExampleCommand request, CancellationToken cancellationToken)
{
    // Load aggregate
    // ...

    entity.UpdateExample(request.Field1, request.Field2);

    if (user.HasPermission("Can_Edit_Field_3"))
    {
       entity.UpdateField3(request.Field3);
    }

    if (user.HasPermission("Can_Edit_Field_4"))
    {
       entity.UpdateField4(request.Field4);
    }

    if (user.HasPermission("Can_Edit_Field_5"))
    {
       entity.UpdateField5(request.Field5);
    }

    // Save aggregate
    // ...
}

Solution 3
Or should I create multiple different commands each with their own authorization decorators?

e.g. UpdateExampleCommand, UpdateField3Command, UpdateField4Command, UpdateField5Command

This would extract all authorization concerns from the handlers and models, so the code would be cleaner and easier to reason with, but it would come at a performance cost because the aggregate would be loaded multiple times for a single process (it might be loaded 8 times in this example if the authorization decorators need to check the state of the aggregate as well), and the responsibility to issue the right commands would be shifted to the application client.

Conclusion
Ultimately, I'm not sure what should influence the business processes modeled by the domain.

The command was initially made into a single command because that's how the UI is laid out and it's how the business works in general. However, allowing the UI to influence the domain model causes authorization logic to leak into the domain model.

Allowing authorization concerns to influence the domain model (so that the single command is split into multiple) allows the code to be cleaner, but it may make the business process less expressive, as the application client is charged with calling the commands in the correct order.

Is there a general rule of thumb for these situations or is there another way to do this that I'm missing?

  • Look into attribute-based access control (ABAC) (also known as externalized authorization or dynamic authorization management). It addresses your concerns. – David Brossard Sep 24 '19 at 5:18
3

The main focus of Domain Driven Design is to put the business first. The rest of the application (database, rest, input-output) has to depend on business.

It would be good if you design this based on your business. So you could ask the business people which statement (1 or 2) sounds more natural to them. I would put the question to the UX expert as well. Not to the UI developer however.

Statement 1: User requests for change of 5 items together in one short. And we inform him which part of the changes were successful.

Statement 2: User has the ability to make 5 different requests for change. And we inform him about the result

If statement 1 sounds natural, I would choose solution 1 or 2. (Choice between 1 and 2 is depending on how your code is organized) If statement 2 sounds natural, I would choose solution 3

There are few minor questions that comes to my mind. They are

Question 1: If you user requests for something to be done, it would be fair to let him know the status of his request. If the user request for some operation (update 5), and part of the operation succeeded, how are we going to tell him about the status? (It is possible to inform about partial success of a request in REST, but its complicated)

Question 2: If you decide based on your business that "these are better organized as separate operations (commands)". Then you would have 5 different commands, and have 5 commanding mechanisms in your UI. May be at this stage you could inform the user if he has the rights to perform each of the command or not (probably by disabling/enabling the command). There is a possibility of doing this by a read model

| improve this answer | |
1

Having the application client be responsible for calling commands in the correct order is going down a slippery path. Simple changes, like adding a new field, can have ripple effects throughout the system. So breaking up the authorization process by fields (Solution 3) is a no go.

Solution 2 has the side effect that it can grow very complex over time because permissions are generally linked to personas and are different for different personas. There can also be an additional complexity that each tenant can define custom ACL rules for his users. So I would Solution 2 would not scale beyond a certain point.

Solution 1 is the right way to go but still gets too complicated beyond a point, IMHO. I have found useful to introduce a meta-layer (something like a JSON structure configurable per tenant or deployment instance) from which you can derive permissions dynamically. That gives sufficient leeway to have custom-defined rules as per need, and even store them in tables or JSON columns/documents in the database. But building the meta-layer the first time will take a bit of upfront effort.

I would say you start with Solution 1, observe patterns that form in code over time, refactor to remove complexity, and introduce the meta-layer if and when necessary.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.