Cleaner Flutter Vol. 5: Establishing use cases

Cleaner Flutter Vol. 5: Establishing use cases

ยท

5 min read

๐Ÿ’ก Note: This article is part of the Cleaner Flutter series by my friend Marcos Sevilla to which I contribute. You can find the complete series here.

Hello and welcome to the last volume where we will talk about the domain layer of our Clean Architecture proposal. Last time we talked about a fundamental part: the repositories, which allow us to create the communication of our software with the outside world.

On this occasion, with the use cases we are going to achieve independence from the specific interactions of the user with the system, using use cases and thus obtain maintainable, reusable code that truly meets the needs of the business.

But before we get into technical matters, we must remember that there are 2 perspectives: a developer's and a business perspective of the product. This differentiation is a very important topic since by being clear about the objectives of the product we can develop software in a better way.

Remember that all software products should first go through a process where their requirements and the interactions of a user with the software product are defined, whether this is a login, addToShoppingCart, or processPayment.

Ok, let's get into it.

What are use cases?

In Uncle Bob's words...

The software in this layer contains the application-specific business rules. These encapsulate and implement all the use cases of the system.

I remind you that the use cases mentioned in the definition do not refer coding part or module, but to the use cases that came from de planning phase of the project.

Each event is an interaction of the user with the system and we can call this a use case. As this is also a business concept the most common way of displaying the use cases of the system is by the use case graph, which looks like this:

0_D5fgzZp_5AeUFkKP.png

Taken from Use Case Tutorial for Dummies.

Here we have objects like:

  • Actor: Users who interact with the system (might be another software too).
  • UseCase: How the actor uses the system to fulfill some functionality.
  • Relationship: The relationship between the actors and the use cases.

The example shows some use cases for a passenger at an airport. Which, as I mentioned before, is the user's interactions with the system, in this case, the logic of an airport.

In code

In my experience, I have noticed that the use case layer is usually bypassed and replaced using methods directly in the software's business logic. But when we implement it we achieve better decoupling and other advantages.

By adding the use cases layer the folder structure for the domain layer would look like this:

gUDaTXF2V.jpeg

Complying with the principles

They end up being simpler repositories. When we create the use cases, we manage to facilitate communication with the repositories and we make sure to have better abstractions that meet the specific objective of that use case, and as we saw in previous articles, with this we comply with the single responsibility principle.

We use interfaces, not implementations. In use cases we are normally going to depend on a repository, which rather must be an interface, this way we would be complying with the Liskov substitution principle, since we could replace the repository with any other that implements the same interface and this will not affect the base logic of our use case.

Implementing in Dart

Like any of the components we've talked about throughout the series, each can be implemented in many ways. This time with the use cases we are going to use one of Dart's not very well-known features.

In Dart, there is something called callable classes. This allows us to use the instance of a class as if it were a function. To achieve this, all we have to do is implement the call method inside the class.

We get something like this:

class CallableClass {
  String call() => 'Thanks for calling!';
}

void main() {
  final callable = CallableClass();
  print(callable()); // Thanks for calling!
}

Using this Dart feature, we can create a class for each of our use cases and implement the call method with the respective functionality.

Putting the pieces together

With all the information we have, we can already see an example with all the pieces that we have carried so far: entities, repositories, and use cases.

The code is simplified to facilitate the example. I also remind you that to understand the examples you must read the previous articles. Here's the previous one.

Let's imagine a very simple case of a signIn, for this, we are going to have a user entity and a repository interface that contains a method to make thesignIn using an email and password.

class User {
  const User({
    required this.email,
    required this.password,
  });

  final String email;
  final String password;
}

abstract class AuthRepositoryInterface {
  Future<User> signIn({
    required String email,
    required String password,
  });
}

This is where many developers decide not to implement the use case layer, so they call the repositories directly from the application's business logic.

But since we are using use cases, we create a SignInUser class which will be our use case. This will have a dependency on the AuthRepositoryInterface, then we implement the call method and call signIn. And so we have a use case with sole responsibility and decouple the repositories from the business logic (state manager).

class UserSignIn {
  const UserSignIn({
    required AuthRepositoryInterface authRepository,
  }) : _authRepository = authRepository;

  final AuthRepositoryInterface _authRepository;

  Future<User> call({
    required String email,
    required String password,
  }) async {
    return await _authRepository.signIn(
      email: email,
      password: password,
    );
  }
}

It doesn't matter who wins the endless battle of which is the best state management tool, since it does not matter which one we select. If we even decide to change, this does not affect our implementation at all.

This example is quite simple, but for the moment what we are trying to do is understand each of the components that are part of this Clean Architecture proposal. Later we will make examples and videos applying all the theories with production code.

giphy (1).gif

Continue with the Cleaner Flutter Series

You can go back to the series list here.

ย