Question

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 that abr 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?

Was it helpful?

Solution

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.

OTHER TIPS

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.

enter image description here

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:

enter image description here

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 the SNBL directly. If you like , you could place the SNBL part in a circle on its own, between the ring where you placed the services and the ring where OkHttp lives

  • or, 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.

Licensed under: CC-BY-SA with attribution
scroll top