Skip to content

Instantly share code, notes, and snippets.

@ChubV
Created September 28, 2016 16:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChubV/6e9260c1ae4206c2f1b206393257046e to your computer and use it in GitHub Desktop.
Save ChubV/6e9260c1ae4206c2f1b206393257046e to your computer and use it in GitHub Desktop.

Application level restrictions and domain contracts check.

Validation and all kinds of restrictions may be significant part of your application. And it is important to understand the difference between application level restrictions and domain contracts checks. So in this article I'm going to share my view on validation. It will be helpful for those who started practicing domain-driven design principles in their projects, but still too coupled in their minds to framework/application style of developing.

Domain level

Entities

At the domain level you should at first follow the simple rule "keep your entities in a valid state". And if there is invalid state - just throw exception.

class Post
{
    private $id;
    private $title;
    public function __construct(PostId $id, $title)
    {
        if (empty($title)) {
            throw new NoPostTitle('Post should have a title!');
        }
        $this->id = $id;
        $this->title = $title;
    }
}

Preventing invalid state of entity protects you from saving invalid model state and producing more and more errors in your application.

You can use beberlei/assert assertion library to reduce an amount of validation code.

Domain constraints

When you create a domain with domain expert there are a lot of business rules to be checked. And to keep things simple every check should also throw exception.

As for me, the best way of checking domain constraints is creating specification for every complex business rule that should be checked. If you do so, you can easily find your constrains looking at file/class names and also use this specifications at application level. But for simple check just use entity method (ex. $post->canBeEditedBy($author->id())

class PostCanBeEditedBy
{
    private $editor;

    public function __construct(User $editor)
    {
        $this->editor = $editor;
    }

    public function isSatisfiedBy(Post $post)
    {
        return $post->author() == $this->editor->id();
    }
}

/** Handle UpdatePost command */
class UpdatePostHandler
{
    /** @var PostRepository */
    private $repository;

    public function __construct(PostRepository $repository)
    {
        $this->repository = $repository;
    }

    public function handle(UpdatePost $updatePost)
    {
        $post = $this->repository->find($updatePost->getId());
        $canEdit = new PostCanBeEditedBy($updatePost->getEditor());
        if (!$canEdit->isSatisfiedBy($post) {
            throw new EditorIsNotAuthor($post->author(), $updatePost->getEditor();
        }
        $post->update($updatePost->getTitle(), $updatePost->getText());
        $this->repository->update($post);
    }
}

Application level

At one hand the purpose of application level is to provide user friendly access to your domain and at the other hand to prevent user from acting wrong. To achieve these goals frameworks are the best friends of developers. They provide ACL, form validation and a bunch of other cool mechanisms.

Well crafted application level code prevents any domain level exceptions. So the first thing that you have to do at your application level is to catch all domain exceptions and make proper logging and self-notification about misuse of a domain model.

For example, in Symfony2 you can register kernel.exception event listener.

public function onKernelException(GetResponseForExceptionEvent $event)
{
    $exception = $event->getException();
    if (!exception instanceof DomainException) {
        return;
    }

    // logging or emailing exception details

    // some cool response for user
    $response = new Response();
    $event->setResponse($response);
}

Remember, you shouldn't show raw exception details to user as it may contain sensitive information and in general isn't of any use to end user.

Also if your domain has some complex constraints that are hard to validate or validation requires some external API calls you can catch that specific domain exception at application level and provide user with some definite response.

$error = '';
try {
    $domainService->someComplexAction($a, $lot, $of, $arguments);
} catch (DomainExceptionThatIsHardToValidate $e) {
    $error = 'Some explanation of what is wrong that can be rendered to user somehow';
}

The most common mistake I see is when domain level constraint is checked at application level and at domain model code this check is skipped. For example, checking if user is allowed to edit post using Symfony2 Voters. Or checking if Post title is empty only as a part of form validation code. Application level validation can duplicate domain checks or catch domain exceptions, but it cannot be used instead of domain checks.

Summary

In this post i tried to show basic rules of validation and checking domain contracts:

  • keep your entities in valid state;
  • throw exceptions if something wrong in your domain;
  • catch exceptions at application level and prevent them by application level checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment