@@sawyermade5469 oh, so for example authentication module having clean architecture in it? source, repository and usecase package? and the same for all the other feature modules?
@@walrider7374 In my case, I have login as a module, then I have a data module which has all my API stuff (ktor), DataStore, models, and Firebase etc. Then I have a home screen module, job management module, etc where each module after login and data is only coupled with login and data, there's no coupling between the rest of the features. Im also working on a consistent UI that I will probably put in each individual module once I get there. Theres some redundant code but I can work on the features individually without breaking anything else.
@@walrider7374Actually according to this approach, you'll have at least 3 modules. Let's say for example: user:ui, user:domain and user: datasource You'll also need to have 3 common modules: ui, domain and datasource Your feature modules will depend on their parents. So user:ui will depend on ui, user:domain will depend on domain and so on So from a top-down view, you'll have this setup: user:ui ⬇️ user:domain ⬇️ user:datasource You'll create packages like this: datasource⤵️ datasource(module) user:datasource The root datasource package will be a simple folder Inside of it you'll have 2 GRADLE Modules :datasource (Base module, common to all other modules) :user:datasource (This is your specific feature module, in this case, the feature is called user, hence the name. It's important to apply meaningful names to your modules so you'll look at it and will already know what it does) Hope I could help you man If you have any other ideas, leave them here. I don't know everything, far from it actually, so it's always great to see different approaches 👍
0:25 Modularisierung Strategien 0:40 - 1:30 strategy: encapsulate each architectual layer e a Module; recommentation with 3 architectual layers: UI, Domain, Data. Der datenlayer enthält: repositories und Datenquellen. Domain: Business Logic und usecases UI: User Interface and stakeholders 2:50 granularität als Beurteilungskriterium
In my experience, when scaling to a large number of developers is better to avoid sharing business logic and data between feature modules. This may involve some duplication, but it can help prevent coupling and simplify maintenance in the long term.
@victormartinezlegaz1871 Hey Victor. How would you achieve that? Could provide some examples on how to do that or any real world scenarios where you struggled with this approach. I'm curious to know more about it. Thanks for sharing 👍
I think the best way to explain the last part or at least the safest strategy is to use the app/main module to stitch each feature module, the presentation layer of each feature module should ideally contains only ViewModels so it is up to the consumer which is the app module on how to present it in UI via View XML or Compose.
In a large codebase, additions to a feature data module cascades into changes to changes to other modules as well and in effect causes teams to block each other
This seems to be the official decision by Google in their MAD series. The domain module there only consists of use cases rather than domain models. Since this diverges from common definitions (like DDD and clean architecture) I'm sure Google discussed this internally. I'd love to hear their thoughts and pros and cons of the different approaches. Maybe they consider this approach more beginner friendly because it avoids IoC.
Was actually thinking the same like using DIP to have public interfaces for the data layer in the domain n have the data sources in the data modules implementing the interfaces in the domain.
@@peterfischer2748 I don't think it necessarily avoids IOC, but I think it is just a more sensible description of the relationship between domain and data. Using an interface IMO doesn't mean you can say domain doesn't depend on data layer. It still calls functions that direct to the data layer
I think it makes sense when you modularize by both Feature and modules because you'll have specific feature modules inside common modules such as datasource or domain. In this case, you can either have an interface to communicate with lower modules but in this approach you'll have to depend on specific feature modules for example: user:ui DEPENDS ON user:domain THAT DEPENDS ON user: datasource that only depends on its parent :datasource Your parent :domain module should not depend on :datasource though. Only its children should depend on :datasource children and on :datasource by association
I really don't see how the combined architecture solves the redundancy/code duplication problem. The domain:books still has no access to authors (Even though it can access the data:authors, it cannot obtain them with use cases/ re-used business logic) and suddenly the ui layer uses both domains? Does that mean that the GetBooksWithAuthorUseCase is implemented in the ui layer now? Really confusing
GetBooksWithAuthorUseCase can be in a module which can see both the other two domain modules. No need for it to be implemented in the UI module of course.
I don't think so. I don't see any problem with the domain module depending on the datasource module. The domain module needs to process data, business logic. So it needs to have Access to repositories and other files in order to inject them into Usecases so they will be able to process that data and provide it to the UI layer Maybe it's not the best approach, but it sure is better than having no organization in your projects whatsoever The worst case scenario is that if you need to change this arch setup, it will be easier than migrating from messy projects in regards to modularization
It's not clear to me what is meant by "loose coupling". I think this is often a vague or even faux argument that just looks good on powerpoint slides. A better, more practical approach in determining the efficacy of a chosen modularization is what I call "The checkbox test". Plan to add a new checkbox to a screen and see what modules and files are affected. You'll often find that the impact goes vertical through the layers from UI to database. Ideally, this dependency line goes straight down and doesn't affect horizontal neighbours. In the worst case, the module graph shows that the whole codebase will get recompiled. Time to split up things in vertical columns. That's proper decoupling. If however you find that you are duplicating code across different columns (features) you may opt to recouple a certain layer horizontally, like in a domain layer. However, as suggested in Andriod, by using small UseCases, you should keep this layer well fragmented to avoid unnecessary horizontal coupling. If your newly added checkbox changes a usecase that triggers a full recompilation, then it's probably time to refactor. A 2nd test is the "Cheap lasagne" test. If you have lots of layers where a class is just a proxy (boilerplate) that passes events or data from the previous layer to the next, then consider removing the boilerplate from that layer. Especially when it may have a runtime impact with member functions just forwarding to other members in the next layer. Even if the element in the layer is just an interface with zero runtime cost - if the interface only ever has one implementation, consider not having such an interface. The compiler will thank you and the code maintainer will thank you. Bigger companies may opt to keep these functionally empty elements for consistency, but personally I'm not a fan this.
This is perfect, explains everything concisely while simultaneously being terse. Cheers.
I mean, it explains the theory, but how do you achieve multi module layer in real world??
@@walrider7374 I mean, you'd have to design a feature level architecture where each feature is a module, right?
@@sawyermade5469 oh, so for example authentication module having clean architecture in it? source, repository and usecase package?
and the same for all the other feature modules?
@@walrider7374 In my case, I have login as a module, then I have a data module which has all my API stuff (ktor), DataStore, models, and Firebase etc. Then I have a home screen module, job management module, etc where each module after login and data is only coupled with login and data, there's no coupling between the rest of the features. Im also working on a consistent UI that I will probably put in each individual module once I get there. Theres some redundant code but I can work on the features individually without breaking anything else.
@@walrider7374Actually according to this approach, you'll have at least 3 modules. Let's say for example:
user:ui, user:domain and user: datasource
You'll also need to have 3 common modules:
ui, domain and datasource
Your feature modules will depend on their parents. So user:ui will depend on ui, user:domain will depend on domain and so on
So from a top-down view, you'll have this setup:
user:ui
⬇️
user:domain
⬇️
user:datasource
You'll create packages like this:
datasource⤵️
datasource(module)
user:datasource
The root datasource package will be a simple folder
Inside of it you'll have 2 GRADLE Modules
:datasource (Base module, common to all other modules)
:user:datasource (This is your specific feature module, in this case, the feature is called user, hence the name. It's important to apply meaningful names to your modules so you'll look at it and will already know what it does)
Hope I could help you man
If you have any other ideas, leave them here. I don't know everything, far from it actually, so it's always great to see different approaches 👍
0:25 Modularisierung Strategien
0:40 - 1:30 strategy: encapsulate each architectual layer e a Module; recommentation with 3 architectual layers: UI, Domain, Data.
Der datenlayer enthält: repositories und Datenquellen.
Domain: Business Logic und usecases
UI: User Interface and stakeholders
2:50 granularität als Beurteilungskriterium
In my experience, when scaling to a large number of developers is better to avoid sharing business logic and data between feature modules. This may involve some duplication, but it can help prevent coupling and simplify maintenance in the long term.
@victormartinezlegaz1871
Hey Victor. How would you achieve that? Could provide some examples on how to do that or any real world scenarios where you struggled with this approach. I'm curious to know more about it. Thanks for sharing 👍
I think the best way to explain the last part or at least the safest strategy is to use the app/main module to stitch each feature module, the presentation layer of each feature module should ideally contains only ViewModels so it is up to the consumer which is the app module on how to present it in UI via View XML or Compose.
In a large codebase, additions to a feature data module cascades into changes to changes to other modules as well and in effect causes teams to block each other
Estoy entrando en este tema y quedo muy bien explicado.
How can i use firebase auth or firestore in separated modules instead of the app module ??
great overview :)
Are you sure that the domain module depends on the data module?
This seems to be the official decision by Google in their MAD series.
The domain module there only consists of use cases rather than domain models. Since this diverges from common definitions (like DDD and clean architecture) I'm sure Google discussed this internally. I'd love to hear their thoughts and pros and cons of the different approaches.
Maybe they consider this approach more beginner friendly because it avoids IoC.
Was actually thinking the same like using DIP to have public interfaces for the data layer in the domain n have the data sources in the data modules implementing the interfaces in the domain.
@@peterfischer2748 I don't think it necessarily avoids IOC, but I think it is just a more sensible description of the relationship between domain and data. Using an interface IMO doesn't mean you can say domain doesn't depend on data layer. It still calls functions that direct to the data layer
I think it makes sense when you modularize by both Feature and modules because you'll have specific feature modules inside common modules such as datasource or domain. In this case, you can either have an interface to communicate with lower modules but in this approach you'll have to depend on specific feature modules for example:
user:ui DEPENDS ON user:domain THAT DEPENDS ON user: datasource that only depends on its parent :datasource
Your parent :domain module should not depend on :datasource though. Only its children should depend on :datasource children and on :datasource by association
Nice explanation.
I really don't see how the combined architecture solves the redundancy/code duplication problem. The domain:books still has no access to authors (Even though it can access the data:authors, it cannot obtain them with use cases/ re-used business logic) and suddenly the ui layer uses both domains? Does that mean that the GetBooksWithAuthorUseCase is implemented in the ui layer now? Really confusing
GetBooksWithAuthorUseCase can be in a module which can see both the other two domain modules. No need for it to be implemented in the UI module of course.
Data module depend on domain module not reverse I sure
It depends what you mean by depends on. IMO IOC doesn't mean "the data layer depends on domain module"
I don't think so. I don't see any problem with the domain module depending on the datasource module. The domain module needs to process data, business logic. So it needs to have Access to repositories and other files in order to inject them into Usecases so they will be able to process that data and provide it to the UI layer
Maybe it's not the best approach, but it sure is better than having no organization in your projects whatsoever
The worst case scenario is that if you need to change this arch setup, it will be easier than migrating from messy projects in regards to modularization
Separate codebase by Feature module ending up with Clean Architecture
It's not clear to me what is meant by "loose coupling". I think this is often a vague or even faux argument that just looks good on powerpoint slides.
A better, more practical approach in determining the efficacy of a chosen modularization is what I call "The checkbox test".
Plan to add a new checkbox to a screen and see what modules and files are affected.
You'll often find that the impact goes vertical through the layers from UI to database. Ideally, this dependency line goes straight down and doesn't affect horizontal neighbours. In the worst case, the module graph shows that the whole codebase will get recompiled. Time to split up things in vertical columns. That's proper decoupling. If however you find that you are duplicating code across different columns (features) you may opt to recouple a certain layer horizontally, like in a domain layer. However, as suggested in Andriod, by using small UseCases, you should keep this layer well fragmented to avoid unnecessary horizontal coupling.
If your newly added checkbox changes a usecase that triggers a full recompilation, then it's probably time to refactor.
A 2nd test is the "Cheap lasagne" test.
If you have lots of layers where a class is just a proxy (boilerplate) that passes events or data from the previous layer to the next, then consider removing the boilerplate from that layer. Especially when it may have a runtime impact with member functions just forwarding to other members in the next layer. Even if the element in the layer is just an interface with zero runtime cost - if the interface only ever has one implementation, consider not having such an interface. The compiler will thank you and the code maintainer will thank you.
Bigger companies may opt to keep these functionally empty elements for consistency, but personally I'm not a fan this.
amazing!
🤩🤩🤩🤩
Ok
bruh, none of your repositories follow this architecture
Do as I say but not as I do. I like this approach though
This was so poorly explained and I bet when the example comes out it will never compile just like all their other repos