Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

using API specifications (url , ...) in data layer violates Dependency Rule?! #11

Open
orca741 opened this issue Mar 28, 2024 · 4 comments

Comments

@orca741
Copy link

orca741 commented Mar 28, 2024

Hi.
Thanks for your work I learned a lot from that.
I wonder if we use this 3 layer architecture (Domain-Data-Presentation) as you did, using details from external data sources (such as API Urls, DB table names, DB syntax and etc.) in data layer, doesn't violates Clean Architecture Dependency rule? from Uncle Bob's manifest the last layer contains DB and if we consider that as raw DB and bring DBManager(ORM,...) to inner layer(adapters),It should know about outer details. Should we use Dependency Inversion again?
In your code, API's Url is hard coded in api.dart in data layer and if we consider API as an outer circle component,I think It violates Dependency Rule.
If you want to add DB as a source to this project, How you do that?
Thanks.

@orca741 orca741 changed the title using API specifications (url , ...) violates Dependency Rule using API specifications (url , ...) in data layer violates Dependency Rule?! Mar 28, 2024
@guilherme-v
Copy link
Owner

Hi @orca741

If you want to add DB as a source to this project, How you do that?

This is actually a good ideia, I'll try to do that as soon I manage to find some time.

But one approach would involve adapting the existing LocalStorageImpl class. Instead of relying on SharedPreferences, we could incorporate the ORM. Here's a suggested plan:

  1. Rename the directory from domain/entities to domain/models: Typically, the term "entity" is associated with ORM, so aligning it with the data layer makes sense.
  2. Create a data/entity directory: Within this directory, create a class like CharacterEntity that encompasses annotations related to ORM operations (e.g., ColumnName, PrimaryKey, etc.).
  3. Implement the CharacterEntity: Define all necessary ORM annotations within this class.
  4. Create a mapping mechanism: Determine a method to map between domain/models/Character and data/entities/CharacterEntity. This can be achieved through a dedicated mapper class or by utilizing inheritance. If opting for the mapper approach, ensure it resides within the data layer to adhere to dependency rule principles.
  5. Utilize the mapper within CharacterRepositoryImpl: Use the mapper to facilitate the mapping process between the domain's Character class and the CharacterEntity class.

This way, the domain layer remains agnostic to storage concerns, with all database-related operations encapsulated within the CharacterEntity class and enforce separation of concerns.

API's Url is hard coded in api.dart in data layer and if we consider API as an outer circle

Actually, this is debatable! 😄 In simple terms, the rule suggests that "inner layers shouldn't depend on outer layers." Despite the URL being hardcoded, it's exclusively utilized by that class and doesn't directly influence other parts of the app. However, it might complicate testing efforts. To address this, we could inject it through the constructor along with Dio. This adjustment would be a good improvement to this project.

@orca741
Copy link
Author

orca741 commented Apr 10, 2024

@guilherme-v

This is actually a good ideia, I'll try to do that as soon I manage to find some time.

Great. Sufficient and clear explanations. 👍

This adjustment would be a good improvement to this project.

What do you think about this schematic:

Untitled Diagram drawio (7)

This is my understanding from Reso coders tutorial, your explanation and of course Uncle Bob's article. As Uncle Bob said:

When any of the external parts of the system become obsolete, like the database, or the web framework, you can replace those obsolete elements with a minimum of fuss.

now we can replace fourth layer with any other framework without any customization in 3 inner layer.

This is nothing new at all, but I want to know your opinion about my vision of Clean Architecture and any suggestion or correction.

@guilherme-v
Copy link
Owner

Hi @orca741, great schematic!

Here are some thoughts

Domain:

  • Your use of plain Dart code, along with the implementation of UseCases and Entity/Models, is solid. It's great to see no dependencies on external layers.
  • I've also seen people creating services_interface besides the repositories_interface (like you did). Repositories would provide data from remote or local, and services would be used for "systems stuffs" in general, e.g.: asking form permissions. But this is something agreed between developers in each project
  • Another ideia is to use a base usecase that automatically logs things, retry operation when failing, or even logout user out of application if needed

Data:

I'm a bit uncertain about including Riverpod in this layer. I understand your point - Riverpod (and other state management libraries) can be utilized independently of Flutter. For instance, we have both Riverpod and flutter_riverpod, as well as bloc and flutter_bloc, among others. In a Flutter project, it's highly likely that you're utilizing the flutter_ dependency. Therefore, Riverpod's dependency on Flutter components might suggest relocating it to the Framework layer. Also, usually these packages serve two goals: managing state (for UI consumption) and acting as service locators (essentially functioning as dependency injection containers)

In my projects, I typically delegate dependency injection to a dedicated package like get_it to keep state management concerns separate. This allows for easier switching of state management libraries later. However, since this is an exploration project, using the built-in features of these libraries makes sense

Side note:

When any of the external parts of the system become obsolete, like the database, or the web framework, you can replace those obsolete elements with a minimum of fuss.

This is purely a personal opinion based on no evidence, but it seems to me that most projects gets to a point where it's good do not apply rules 100% in order to simplify maintenance. When Uncle Bob made the above statement, technologies like ORMs were new and likely subject to frequent changes. Today, in the Flutter ecosystem, we encounter a similar situation. While you might find yourself testing and replacing various state management libraries, but it's rare to replace foundational components like the library used for accessing shared_preferences, an ORM, or making network calls. Therefore, it's wise to make the project flexible where flexibility is likely needed - and to avoid over-engineering :)

@orca741
Copy link
Author

orca741 commented Apr 11, 2024

Hi @guilherme-v .
Thank you for your time and suggestions, which are very useful for me.

Another ideia is to use a base usecase that automatically logs things, retry operation when failing, or even logout user out of application if needed

the base usecase replaces services_interface or they have separate uses? So we have final implementation in base usecase class and there is not any other codes for this stuffs in outer layers, simple and straightforward, If I have understood correctly 🤔.

I'm a bit uncertain about including Riverpod in this layer.

I accept that. In this repo's ReadMe you demonstrate that the presentation layer interacts with domain layer directly , I understand better now. 👍

I thought that the purpose of what these libraries do, is to provide data. On the other hand, in Uncle Bob's documentation, this layer is named as interface adapters, and I think what State Management libs (like my favorite one Riverpod🥰) does is actually being an interface , and I think corresponds to this layer more. Thanks to the fact that these libraries are not dependent on Flutter.

This is purely a personal opinion based on no evidence, but it seems to me that most projects gets to a point where it's good do not apply rules 100% in order to simplify maintenance. When Uncle Bob made the above statement, technologies like ORMs were new and likely subject to frequent changes. Today, in the Flutter ecosystem, we encounter a similar situation. While you might find yourself testing and replacing various state management libraries, but it's rare to replace foundational components like the library used for accessing shared_preferences, an ORM, or making network calls. Therefore, it's wise to make the project flexible where flexibility is likely needed - and to avoid over-engineering :)

This is my biggest bug, over thinking and excessive adherence to rules.😑I agree that with real ORM, there is no need for boilerplate codes in 4th layer for DB data exchanges and It should be presentation layer not framework!
However lots of them do not remove dependencies on database stuffs (like syntax ).

All in all, I found the answers and thank you for being so responsive. I hope to be able to use your knowledge again in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants