How to handle common functionality that is not business logic in clean architecture
https://softwareengineering.stackexchange.com/questions/356080
-
18-01-2021 - |
Frage
The dependency rule in Clean Architecture states that:
... that source code dependencies can only point inwards.
I now came across a problem that I cannot figure out myself.
Lets assume I have a module with application business rules abr
and 2 modules for services that implement interfaces from abr
.
+---------+
| abr | Business Layer (defines interfaces
+---------+ 'Service1' and 'Service2')
+----^ ^---+
- - -|- - - - - - - - - |- - - - -
| |
+-----+----+ +-----+----+
| service1 | | service2 | Dependencies (implement Interfaces
+----------+ +----------+ define in abr)
Now the 2 services use the networking library OkHttp
and should use the same user agent string for their requests.
Unfortunately I do not how to achieve this nicely.
Possibilities are:
- Create a module
sharedokhttpconfig
that both service depend on
This would violate the depdency rule - Put the code in abr
This would violate thatabr
should not know about networking or even that the services use okhttp - Duplicate the code to set the user agent in both services
Code duplication is bad
Another example would be that the project has 2 different UI modules that are fundamentally different but some logic in them would be the same.
Like formatting the address of a user.
Any suggestions on how to handle this?
To clarify why I think it would violate the dependency rule.
The abr
is the middle of my application, it should not know about UI or anything else, so all dependencies must point int its direction.
But when I add a module that the 2 modules depend on it seems to me that they are pointing outwards.
+---------+
| abr |
+---------+
+----^ Inwards ^---+
| |
+-----+----+ +-----+----+
| service1 | | service2 |
+----------+ +----------+
| Outwards? |
+----v v---+
+---------+
| snbl | snbl = share non-business logic
+---------+
Or is doing it that way ok because from the perspective of abr
nothing has changed?
Lösung
After a discussion with the OP, we came to the following mutually shared conclusion:
The interfaces of both services are declared as part of business logic, while it's implementations are not part of it. Therefore, it makes sense if these implementations depend on configuration data for OkHttp
.
The most promising solution for moving forward is to declare the module snbl
, which includes the configuration data, in the same circle the service as implementations. Then a dependency between these and the configuration data for OkHttp
is no violation of the dependency rule.
Andere Tipps
I'd just like to add something to the accepted answer, as well as to expand upon the insightful answer by Doc Brown. What's implicit in Uncle Bob's Clean Architecture is that "underneath" everything there is some common infrastructure that every circle depends on (e.g. the standard libraries provided with the language, and possibly some other components that you can choose to treat as standard).
So, this comes down to making a decision about what level of isolation you want for the OkHttp
library. Let's assume for a moment that your "Dependencies" layer more or less corresponds to Uncle Bob's "Interface Adapters" circle; if you don't mind this layer/circle depending on OkHttp
directly, then you can place snbl
there and have it directly reference OkHttp
.
If you would like, on the other hand, to avoid depending on OkHttp
, then you can apply the exact same dependency inversion trick you used for your Business layer. This is actually what the insert at the bottom right depicts; but here's another image I found on the Internet that shows how the inversion happens:
Basically, what you do is you declare an interface that your "Dependencies" layer requires (in that same layer), and then you implement it in another layer that's "further out". This outer layer would roughly correspond to the "Frameworks & Drivers" circle; your snbl
component would be implemented there, where it would, again, reference OkHttp
directly.
In the end, I would like to acknowledge that this is (unless I'm terribly mistaken) pretty much what Doc Brown said much more concisely in his own answer (at least when it comes to key points).
You have basically two options:
either handle it the same way you do it with third party libs and frameworks: by using Dependency Inversion. You need to arrange some interfaces for "docking" the
SNBL
part of the application to the different services, so the services only depend on the interfaces, but not on theSNBL
directly. If you like , you could place theSNBL
part in a circle on its own, between the ring where you placed the services and the ring whereOkHttp
livesor, you declare the
SNBL
to be part of the same circle where the services live. It may not be "business logic" in the strict sense, but it could be support functionality not needed somewhere else, so the "best" place may still be the business logic layer. That gives you the option to omit DI.