It’s Not Just About Authorization, It’s About the Ubiquitous Language

Recently someone asked questions about the IDDD sample code and authorization. Basically the question was, why can’t I authorize the user in the model to start a new Forum Discussion? The developer would pass in a “session” object to the Forum startDiscussion() and have the Forum double dispatch to the “session” to check for authorization:

public class Forum ... {
    ...
    public Discussion startDiscussion(Session aSession, String anAuthorId, ...) {
        aSession.assertAuthorizedDiscussionAuthor(this.tenant(), anAuthorId);
        ...
    }
    ...
}

Take a look at IDDD pages 75-79, and specifically at the refactored code on pages 78-79. It’s not that it is absolutely wrong to in essence authorize the user in the model. Rather, it’s that the authorization is done for free when obtaining the necessary Author instance by way of the CollaboratorService.

Further, it’s a matter that the Author is an essential part of the Ubiquitous Language, while the “session” is not. If anything, rather than pass in a “session” object (or worse yet, SecuritySession), pass in CollaboratorService instead, and double dispatch on the CollaboratorService to get the Author from the Forum’s own Tenant and parameter String anAuthorId:

public class Forum ... {
    ...
     public Discussion startDiscussion(
        CollaboratorService aCollaboratorService,
        String anAuthorId,
        ...) {
        Author author = aCollaboratorService.authorFrom(this.tenant(), anAuthorId);
        if (author == null) {
            throw new IllegalArgumentException("Not a valid author.");
        }
        ...
    }
    ...
}

In my sample code I show the Forum Application Service obtaining the Author via CollaboratorService. This is not wrong because normally the API would serve as the natural client of the model. When using the Hexagonal (Ports and Adapters) architecture, requests from disparate user agent types would all be adapted to use a single API call. This is based on use case, not on user agent type. But you could pass in CollaboratorService to the Forum if you prefer.

Comments

  1. I have been looking for an example just like this. After studying it for a bit, I’m in love with it!

    However, how would you make subsequent method calls on forum? E.g. if you called something like $discussion->lock() to lock the discussion, would you pass in ModeratorId (assuming Moderators can lock a discussion)? Perhaps “lock” is a concept that belongs in a Domain Service?

    Another example (one specific to my needs) is that I have an entity, Course, that contains a value object, VenueOfferings, and only a SubjectMatterExpert is allowed to update it. There are other things on a Course that can only be updated by a SubjectMatterExpert as well, such as name, description, etc.

    In this case, with the limited information I provided, would it make sense to call $course->changeVenueOfferings($offerings, $subjectMatterExpertId)?

    • My preference is to have an Application Service (the layer above/around the Domain Model) provide the SubjectMatterExpert rather than passing an id. The course offering venue is not being changed by an id, but by a SubjectMatterExpert.

  2. Vaughn – I’m really enjoying your book. I’ve been practicing DDD for some time now – or at least I thought was. Turns out, I had fallen into anemic domains with application services doing all the business logic. Your book has really inspired me to write stronger models and use Domain Events for notifications across Bounded Contexts. But I’m having a hard time seeing how all the classes in your sample code “work” together. We only get to see single classes and examples in the appropriate chapters. Is the complete sample code available for download? It would be great to have it for reference. Thanks again for a fantastic book!

    Michael

  3. Anton Kozlov says:

    I’m sorry, Vaughn, I can’t find any dedicated forums to post questions about your book. So I try to ask here…

    I look at TenantProvisioningService.ProvisionTenant in IdentityAccess BC. And I see – you violate aggregate rule “only one aggragate instance can be changed in one transaction”. There are several aggragetes created in one transaction – Tenant, admin User and admin Role. Why you choose that way? It seems to me there should be “long running process” to provision tenant

    • Remember that the reason to commit only one Aggregate in one transaction is to ensure transactional success. If two or more Aggregate instances are committed in a single transaction, there is a higher likelihood that two or more users could be racing to commit the same instances.

      With creation, however, it is a different matter. The Aggregate instances are brand new, and no other user could be using the specific Aggregate instances at the same time. True, it is possible for more than one user to attempt to create the same Aggregates instances, but that’s where defining and asserting unique constraints on business keys is necessary. This will prevent duplicating the same created Aggregates in a race condition. Yet, this is still different from the above reasoning.

      Vaughn

  4. I am not entirely sure I understand your question. If you register the subscriber in the Application Service method that you are currently executing, the subscriber-handler will be called back on in the same thread. Thus, you would have access to the tenantId Application Service method parameter inside your subscriber’s anonymous class.