design-patternsdomain-driven-designstate-pattern

Role dependent behaviour in state pattern


I have a a class course which uses the state pattern to model the state of a course. A course starts as a draft, can then be handed in to be approved by an admin. If the course was approved it can be published via the rest api. Before being published courses may be canceled or deleted by the creator itself. If the course was already published only admins should be able to edit the course.

Until now I ended up with a mixture of conditionals to verify a certain user has required permission to change the state of the course and state pattern to verify correct state transitions.

Is there a way to integrate this behaviour in a single pattern? As I'm using DDD integrating permissions directly into the business rules this would violate DDD rules if I'm right


Solution

  • Permissions should not be in the domain model. Identity and access management (IAM) is a bounded context in and of itself, and you would have the permission system modelled there.

    Invariants should be in the domain model.

    The integration concern (layer) would ensure that the relevant identity has the required permission to perform the command. The domain model would be called from the integration concern (web-api / message handler / etc.)

    I like to use the example of a physical calculator. It is a mathematical domain model and it can do whatever the calculator is capable of doing with absolutely no care for who is allowed to do any operation on the calculator. If you need to restrict access to the calculator or certain operations on the calculator, then that is handled outside of the calculator.

    For an invariant such as the one where only the creator of the Course may cancel or delete the course, one would pass in some value indicating the user attempting the operation. That user is an aggregate and, per DDD guidelines, an aggregate may be passed to another aggregate. However, your domain may not want to care about aggregates in the IAM world. In such cases a value object in your domain, or simply the ID, would suffice.

    The Course could then check whether the operation is being requested by the creator. This is much the same as Vaughn's example as linked to by @Sylvain Lecoy:

        public boolean isModeratedBy(Moderator aModerator) {
            return this.moderator().equals(aModerator);
        }
    
        public void moderatePost(
                Post aPost,
                Moderator aModerator,
                String aSubject,
                String aBodyText) {
    
            this.assertStateFalse(this.isClosed(), "Forum is closed.");
            this.assertArgumentNotNull(aPost, "Post may not be null.");
            this.assertArgumentEquals(aPost.forumId(), this.forumId(), "Not a post of this forum.");
            this.assertArgumentTrue(this.isModeratedBy(aModerator), "Not the moderator of this forum.");
    
            aPost.alterPostContent(aSubject, aBodyText);
        }
    

    What you would notice is that the isModeratedBy method is publicly available which means you can check the invariant before passing in the moderator. This is preferrable to having to respond to an exception, but that is a design decision.