Implementing a use case (II) — Command Pattern

Make your use cases explicit in your code

Maikel González Baile
6 min readJul 19, 2018

This post is part of Implementing a Use Case, a series of posts where I share my learnings on designing, architecting and implementing use cases. I suggest reading the previous posts where I already explained some concepts you might find here.

You can either take a look at the code of the application example or the different tags which show the state of the application at the moment of the post.

As said in the previous post, I want to share with you my experience to develop use cases in a way that let me implement easier and more maintainable code. See the code of the post here.

But, what is a use case? Wikipedia says:

A use case is a list of actions or event steps typically defining the interactions between a role (known in the Unified Modeling Language as an actor) and a system to achieve a goal. The actor can be a human or other external system.Define explicitly the use cases your application provides.

In other words, it represents the actions that an actor (being the actor a human or an external system) can do with your application. From our code review example we can see the next use cases:

  • A writer can create a Pull Request.

Actor: Writer | Action: create a PR

  • A reviewer can request changes on a Pull Request.

Actor: Reviewer | Action: request changes on a PR

  • A reviewer can approve a Pull Request.

Actor: Reviewer | Action: Approve a PR

  • Once two reviewers approve a Pull Request, the Pull Request is automatically merged.

Actor: System | Action: Merge the code

Use Cases as a first class citizens in your code

As use cases represent all the actions that your application provides, which in other words is the behavior that stakeholders want you to implement, seems like it makes sense to have an easy way to see clearly the list of all of them in your code. Therefore, we will make use cases explicit by having a class for each one: CreatePullRequest, RequestPullRequestChanges, ApprovePullRequest and MergePullRequest. This way, if you look at the source tree of your application you should see something like:

CodeReview/
├── src/
│ ├── Application/
│ │ ├── CreatePullRequest.php
│ │ ├── RequestPullRequestChanges.php
│ │ ├── ApprovePullRequest.php
| │ └── MergePullRequest.php

Now, we just need to expand a directory to see all the capabilities your application brings to the user. You can imagine how helpful is for new devs joining your team to have use cases explicitly shown in your code base.

Command Pattern

The command pattern fits perfectly with our goal to explicitly represent the use cases defined by business into our code. The pattern consists mainly of two different classes:

  • The command: which is a request to execute an action on your application. It just carries the data needed to perform the use case, no behavior. Examples are: CreatePullRequestCommand, RequestPullRequestChangesCommand, ApprovePullRequestCommand, MergePullRequestCommand.
  • The command handler: which is a service that knows how to orchestrate the logic to execute the requested command. Following the convention name for commands, command handlers are normally named as follows: CreatePullRequestCommandHandler, RequestPullRequestChangesCommandHandler, ApprovePullRequestCommandHandler, MergePullRequestCommandHandler.

TDD (Test Driven Development)

Let’s now implement the CreatePullRequest command to see some code in action.

First thing you need to do is to clearly understand the acceptance criteria of the use case. As a developer, you should be involved in conversations with your stakeholders, considered as the domain experts, to ask them the right questions to learn what are the invariants your code must assert in order to fulfill their functional requirements. In this case, we can imagine the next conversation:

Stakeholder: Writers can create a Pull Request to push code to our system so other people, the reviewers, can read it and provide feedback.

Developer: Can a Pull Request be created without any code?

Stakeholder: I don’t think it makes sense since it will be confusing having empty PRs. In that case, the writer should be informed that he probably forgot to commit the code before creating the PR.

From the short conversation, we are not only gathering the preconditions that the use case must assert to success from an expert on the domain, but also we know how business people name concepts in their domain and the same language must be reflected in your code. If you don’t have this conversation, you could name the writer as “user”, so later on if you talk about “users” with your stakeholders to solve some doubts they can misunderstand you by thinking you are talking about reviewers (also users of your system) instead of writers.

Now that we know what to assert to make sure whether a Pull Request can be created or not, we can start implementing it. I highly recommend applying TDD which is defined as:

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved to pass the new tests, only. This is opposed to software development that allows software to be added that is not proven to meet requirements.

When applying TDD we focus first on the test. This forces us to just think about the invariants and behavior, and forget about implementation details such as which library should I use to properly manage exceptions and so on. In this case the test has to ensure that:

  • When a CreatePullRequest is commanded with a given writer and a piece of code, a Pull Request should be created with the name of the writer and the code.
  • When a CreatePullRequest is commanded without code, an exception is expected.
  • When a CreatePullRequest is commanded without writer, an exception is expected.

If we now run the tests, they will fail because we still don’t have the code that implements the Command and Command Handler. The next step is to leave our tests green so let’s implement the missing classes.

The Command class is just a data struct with all the info required to execute the use case.

The Command Handler orchestrates the logic to receive in the constructor all the possible dependencies that the PullRequest class, the one that has the behavior, needs to apply the business rules.

The PullRequest class manages both the data and behavior associated to a Pull Request

At this point, all your tests should be green and anyone can easily see which is the new use case the application supports now, and also (by looking at the tests) it is very easy to read when the use case should fail.

Benefits

As you can see, so far we have not implemented any database query nor controller code. However, we are already designing and testing the application’s use cases. We can implement some use cases and see how the result looks like giving us the chance to choose another strategy to model our code. Another important benefit is that the implementation of the whole use case can be split into different tasks so many people can work simultaneously on the feature without having code conflicts:

  • Someone could focus and implement just the test, this way both the one implementing the code and reviewers (if you do code review in your team which I highly recommend) can focus just on the code of the test.
  • Someone could focus on the implementation of the business logic (CommandHandler, Command, PullRequest classes). Since he will be responsible for leaving the test in green he can make use of an InMemory implementation of the repository for the sake of the test.
  • Someone could focus on the implementation of the Repository with a concrete database (MySql, PostgreSql, MongoDb, etc).
  • Someone could focus on the implementation of the controller that calls the use case and implements the API along with the API doc.

This way, we have some benefits such as:

  • The whole team is focused on the same use case, meaning that it will be delivered sooner than if everyone in the team is focused on a different feature.
  • It can be a good practice assigning the implementation of the test and/or the business logic to the most senior people, as the behavior is the most important part of the business, while more junior people implement the outer parts such as the controller and the database query.
  • Also, it encourages people to work closer and apply collaborative techniques such as pair programming as the whole team has the same goal, deploy and release the use case in production. It’s always better having one use case done than three in progress at the end of a sprint.
  • Allows the creation of smaller Pull Requests which are faster to review and thus merge and deploy to production.

In the next post I show a pattern to enrich your use cases with more functionality easily.

--

--