Use Cases

The use case layer is the easiest to understand but probably the hardest to implement in a clear way. Here you can find all the business logic implementation:

Above you can see an example of how we organize the use cases’ code.

By looking at folders/files inside the use cases folder, you can see what are the main actions you can do with this app, and how those actions are implemented. We like to map the resources and actions from our app to folders inside use cases folder. For example, if we have a resource called User and an action AssignBadge, it will be mapped like: /use_cases/users/assign_badge.

In every action folder there’s a base.rb file. This file is the interface for a specific action, it defines the order that the nested use cases are executed and its a standard for our implementation. To know how an action is executed and which steps are made, we just need to look at action’s base.rb file.

Here’s an example for the AssignBadge action:

You may be asking yourself: “But wait, do you map your Web API routes to the use case folders?”… Yes, we do! The use cases are the API from our app, we then expose them as a Web API. It’s that simple and simple is a requirement for our everyday coding.

By extracting all the business logic implementation from controllers and models into this layer, you are separating the application core from whatever interface adapter you use. It can be Rails, Sinatra or even CLI, but the implementation of a use case is business logic, it needs to be independent from the framework you use.

One of the biggest wins of this extraction, is that the Models (Entities) become really small and can even be represented as an OpenStruct object.

I will be talking about the Entities Layer on the next post of the series.

Implementing Use Cases

Now… there are many ways to implement this, but the main idea that you need to keep in mind is that you want to have one or more objects with small responsibilities, that encapsulates application business logic and represents the actions that your application does.

In our case, we use a gem called usecasing, created by @thiagochapa. This gem gives us quick tweaks like: dependency between use case objects, shared context and control flow.

Using the example on the usecasing github page:

Just by looking at the FinalizeInvoiceUseCase, we can tell which are the use case precedences and which is the data flow sequence. This was a game changer for us. It created a standard for our code implementation.

Testing Use Cases

Testing use cases is really simple. There’s an input and an output. In the usecasing gem example, it returns a Context object with every object manipulated inside a use case.

The main advantage is that these use cases are isolated, they are plain ruby objects with small responsibilities and few lines of code.

Our current approach for testing is outside-in method. Our app (or web API) will consume these use cases directly, so we test the output Context of a given use case. Then, if something fails inside the use case, we write a unit test for that specific failure, and so on.

After some iterations, we manage to get 100% coverage for our use cases, and it’s not that hard, they are really small pieces of code.