Hello my name is Rodrigo Lopes. The purpose of this article is to share a little about clean architecture in the flutter. I will talk in general about the concepts and at the end show a practical implementation of how it works.
Explaining the concept of clean architecture:
The idea behind the clean architecture concept is to make the project scalable, easy to maintain and testable, creating separate layers and always depending on abstractions and not concrete classes.
- Independence of frameworks: Your project can never be dependent on an external framework, there must always be a layer that abstracts logic, thus making it possible to remove the framework without impacting the application.
- Testable: The business rule layer must be tested without dependence on UI, Bank or Web Service.
- Independence of the UI: Your system should not be aware of the existence of a UI, being possible to replace its graphical interface with a terminal without problems.
- Independence from external agents: Your business rules should not know about the existence of the world around you. In other words, she just needs to know what she needs to perform her responsibility.
The image portrays a little of the essence of clean architecture and its only rule the principle of dependency the direction of the arrows says the sense that the dependencies of the classes must follow, in other words, a class from within must never know those from outside. Entities don’t know use case, but use case knows entities. The further you look into the highest level your system is, the external circles are responsible for mechanisms (UI…), the internal ones have business rules and policies for your system.
- Entities: This layer encapsulates the general and higher-level rules of the application, normally this layer is not affected by external changes.
- Use Cases: This layer is where your application’s business rules are located, for example SignInUseCase where you call the api to login, get a token and save the token locally.
- Interface Adapters: This layer is responsible for converting data that is more convenient for the respective layers, example where you will have your mapper that turns your pojo into a model that will be used in useCase, and will also be where your controller is, presenter, viewModel, bloc (Presentation logics)… To manipulate the data and deliver the view.
- Frameworks and Drivers: Layer composed with frameworks and tools, example database libs, wm.
S: Single responsibility:
*A class must have one, and only one, reason to change. *
This principle states that a class must have only one responsibility and one reason to change, and it is very common to see classes like Controllers or Activity’s with giant numbers of lines that make a million things, it violates this principle.
O: Open closed:
*Separate the extensible behavior behind an interface and reverse the dependencies. *
Objects or entities must be open for extension but closed for modifications, you must not change a class that already exists to add a new behavior, this increases the chances of bugs in your code. Example: there is the class Clt with the method calculates salary, and there is the class payroll where the payment is calculated through the type of class, if there is a new type of payment Stage we would have a new class, and a new check in the child class payment, the correct thing would be to have an interface where all types of employees implement and describe the type of salary, in the payroll class she would just call the calculaSalario method…
L: Liskov Substitution:
*Subtypes must be substitutable for their base types *
This principle says that every class that inherits from a super class must be able to be replaced by another example: You have a Vehicle class that has startEngine () and engageEngine () methods, and you have an EletricBus class that implements Vehicle, if you need to substitute one for the other you will be able to, this concept is very important because it addresses concepts of interface segregation and dependency inversion which in the end is what allows us to always work with abstract classes and not concrete classes
Example of violation of liskov substitution and you have a rectangle class and a square class that extends from a rectangle, in geometry a square and a special type of rectangle, but in doing this inheritance you would be changing the original behavior of the square rectangle so that the rectangle be a square.
I: Interface segregation:
*A class should not be forced to implement interfaces and methods that they will not use. *
This principle helps to create more specific interfaces for each context instead of having an interface with several methods and all need to be implemented. Example the birds interface contains fly () and walk () methods, and implemented by parrot and penguin, the penguin class does not need to implement the fly method because penguin does not fly, so we should have the birds and birdsQueVooam interface that extends from birds.
D: Dependency inversion:
*Depend on abstractions, not implementations. *
Important: Dependency Inversion is not the same as Dependency Injection, be aware of that! Dependency Inversion is a principle (Concept) and Dependency Injection is a design pattern (Design Pattern) .
You should not have a high level of coupling in your classes, for example: There is a class where you instantiate the connection to your database, but right now you are creating a coupling in this class, in case you need to change your database this class would be affected, the correct thing would be to receive an interface via constructor (To respect the principle of open and closed, if your bank changes this class will not be affected), and the database class implements this interface and thus has the logic to start the bank …
Having said all that, talking a little about the uncle bob’s clean architecture book, he talks a little about how to be a good architect, and at this moment he says the following:
A good architect can postpone as many decisions as possible
Say that, you do not need to decide which type of database you are going to use while planning the architecture, you just need an abstraction interface, and it will not matter if you use a text file, firebase or SQLite…
Still following this line of thought in the book Lean Startup he talks about three pillars that a startup should follow:
Build (Experiments) -> Mesure (Metrics) -> Learn (Pivot / Persevere)
I believe that what uncle bob says has a certain relationship with this concept because the more you can postpone a decision the more experience you will have to make the right decision, and if necessary, take another approach.
Basically the architecture is divided into the following layers:
api/local/dB -> remote/local (dataSource) -> repository -> useCase -> viewModel -> view
The data flow must always follow the same direction (call flow)
Application in practice
- Presentation logic holder (BLOC, ViewModel, Controller …):
Here I made use of a kind of bloc, using get_it as service_locator.
2. Use Cases business logics:
Remembering the clean architecture concept of always depending on abstractions and not concrete classes, we always inject from this moment on interfaces such as Repository.
3. Repository data abstraction:
4. Data source (Data management):
5. Raw Data:
At this point we have the abstraction of sdks and frameworks that make it possible to change them without the rest of the project being modified. Example: if I want to replace the HttpManager class, a lib as a chopper would be easier…
At this point, the correct thing would be to use encrypted storage to guarantee the security of our token, but for example …
Link repo github: https://github.com/Rodrigolmti/flutter_clean_architecture
This is basically all I have to share.