Cleaner Flutter Vol. 3: Dominating entities

Cleaner Flutter Vol. 3: Dominating entities

Getting into the domain layer with entities

Table of contents

We already saw enough of the game rules with CLEAN in the previous volumes of A Cleaner Flutter, now we have to go and write some code.

So go get your developer glasses and let's get our hands a little dirty.

⚠️ All the code that we're going to write in this article is pure Dart, with no dependencies on the Flutter SDK.

https://cdn.hashnode.com/res/hashnode/image/upload/v1644198796601/WX6TUaU5t.gif

Domain layer

Domain layer components

⚠️ I'm going to alternate between these two terms quite a bit in the next section so you don't get confused:

Abstract classes are the same as interfaces. In Dart, there is no keyword for creating an interface, but the abstract class does functionally the same.

Before explaining entities, I notify you that with this article we begin to explain the domain layer, which consists of the contracts that we're going to set up in our project.

We're not going to sign anything, it's an expression. I'll explain what contracts are.

We call the abstract classes, or interfaces if you come from other languages, contracts because they settle rules: properties, actions... that must be fulfilled for a specific class.

abstract class INetworkManager {
  Future<bool> get isConnected;
}

In this example, we can see an abstract class/interface that defines a network manager. This class is called a contract because any class that extends from it has to implement its methods and has access to its properties. As we can see in the following image:

Alt Text

Here we see that since NetworkManager doesn't implement isConnected, it throws an error. Then, NetworkManager is forced to sign a contract to implement INetworkManager.

Got it? Great. And now, why do we make contracts?

As we talked about in the previous articles, we need our code to be scalable and that means it's easy to extend. That way, having abstract classes we can make the implementations that we want of the interfaces taking into consideration different types of technologies, or in the case of Flutter, they can be different packages with a separate implementation for each one.

And in addition to all this, contracts make testing our app much easier, since we can create test classes with packages like mocktail and thus run our tests in classes that have the methods established in our contracts.

Now that we make that clear, let's go with the entities.

Entities

When you are going to start a project from scratch, which part do you code first?

In my case, I code the classes of the data that I'm going to use throughout my entire application. I consider this to be a good practice as it helps us lay down the information that will flow through all of our logic.

The entities can be ordinary classes that represent abstractions of actors in real life, or something very abstract such as classes of a framework like Flutter with RenderObject.

We are not going to complicate it much.

The entities must be our data types or classes that are used in different parts of our software.

At least that's what Uncle Bob says, and it seems right to me. Although everything ends up being an entity even in the outermost layer, we define in our version of Clean that our entities are the objects that can be returned to us --or we can send to-- an API, taking the most famous example.

https://cdn.hashnode.com/res/hashnode/image/upload/v1644198803861/6QnkbP1q-.gif

When we look at the models in the data layer you probably understand the usefulness of first abstracting an entity that simply defines properties and some general behaviors and then extending its functionality into models that implement more specific methods.

Let's say that an entity's purpose is to define the essential characteristics of an object. The specific features are implemented by the model because the models are in charge of being used for specific use cases.

That's a lot specific.

The code would be like this:

class Response {
  const Response({required this.message});

  final String message;
}

Response states the properties that every API response must have. These are simple responses to simplify the example, I know that not all answers in an API will return what this class has. Let me finish.

class ResponseModel extends Response {
  const ResponseModel({
    required String message,
  }) : super(message: message);

  factory ResponseModel.fromJson(Map<String, dynamic> json) {
    return ResponseModel(message: json['message']);
  }

  Map<String, dynamic> toJson() {
    return <String, dynamic>{'message': message};
  }
}

Now that we have our contract, we can create a model that "signs" it, basically I mean implement or extend it. I say to sign because by inheriting from that class we are agreeing to implement all the methods it has and comply with the properties it sets.

For example with ResponseModel, we create a String in the same constructor and simply pass that property to the class we are inheriting from, to fulfill that property.

class ResponseModel extends Response {
  const ResponseModel({
    required String message,
    required this.statusCode,
  }) : super(message: message);

  final int statusCode;

  factory ResponseModel.fromJson(Map<String, dynamic> json) {
    return ResponseModel(
      message: json['message'],
      statusCode: json['statusCode'],
    );
  }

  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'message': message,
      'statusCode': statusCode,
    };
  }
}

In this other example, we do the same as in the previous one, with the only difference that we add a property that is no longer the basis of our Response entity and thus we fulfill one of the characteristics that we talked about in the previous volumes: we extend the existing functionality without altering it in the process.

A valid extra point to mention is that as this is the basis of our entire graph, when we make a change at the entity level we will have errors in our models and for the same reason, in any other part that these models are used.

There we see the rule of dependency in action and how it helps us to have control of the changes in our code by the parts that depend on others.


Perhaps I have written long and hard on something very simple but I'm interested in understanding the purpose of this separation. We could easily make our models all at once with the features we consider necessary, but the code would become a bit repetitive and even messy.

I hope you have assimilated well the concepts and especially the separation into layers that we are doing to extend the functionality and order of our code.

This was the simplest part and the basis for the rest (out of the concepts), in the next volume we will continue exploring the domain layer with the repositories, I promise you that it will be a little more entertaining.

https://cdn.hashnode.com/res/hashnode/image/upload/v1644198811448/Scf77vh7JJ.gif