2

I am trying to wrap my head around the concepts of Clean Architecture, and specifically about the responsibility of the Infrastructure layer. Its purpose is often described as "to provide functionality to access external systems", which is a bit vague when you try to think deeply about it.

Consider this example. Let's say I'm developing an app that determines if the weather tomorrow is going to be good or bad. In order to do that, it tries to call 2 weather forecast providers via REST APIs: first it calls the main provider, and if their service is down, it calls the backup provider.

My current project structure is the following:

/Application

  IDataProvider // interface for weather forecast provider
     bool IsWeatherGood(Date date); // just definition, no implementation

  WeatherService // Main application logic
     IDataProvider[] providers; // Injected via DI
     bool IsWeatherGood(Date date) {
         foreach (var provider in providers)
             try { return provider.IsWeatherGood(date); } catch {}
         throw Error("None of the providers worked")
     }

/Infrastructure

  /WeatherForeacstProviders
    /ProviderOne
      /DataContracts
        ProviderOneApiRequest // API contract
        ProviderOneApiResponse // API contract
      /ApiClientImpl
        ProviderOneApiClient : IDataProvider
           bool IsWeatherGood(Date date) {
               var response = callApi("http://provider1.com/get-weather");
               if(response.IsSunny) return true;
               return false;
           }      
    /ProviderTwo
      /DataContracts
        ProviderTwoApiRequest // API contract
        ProviderTwoApiResponse // API contract
      /ApiClientImpl
        ProviderTwoApiClient : IDataProvider
           bool IsWeatherGood(Date date) {
               var response = callApi("http://provider2.com/forecast");
               if(response.SunshineLevel > 10) return true;
               return false;
         }     

My concern is that the infrastructure level contains part of the business logic: it converts API responses into the domain concept of "good/bad weather". Is this a proper responsibility for the infrastructure layer? Currently, our domain experts consider ApiTwo's sunshine level above 10 as "good". But this threshold may change in the future, and it it does, we'd need to make changes in the infrastructure level, which doesn't sound right, because this threshold looks more like business logic.

Another option would be to move this logic into the Application layer. But that would mean that the application layer would be aware of the 3rd-party APIs' data contracts, which doesn't sound right either according to the definition of the Infrastructure layer. Also, in this case the infrastructure layer would only be a thin abstraction over HTTP calls with one or two methods like PerformPostRequest(obj body), which is redundant in many modern programming languages / frameworks, as the frameworks themselves often provide such abstractions.

Which approach (if any) is "more correct" in the context of Clean Architecture?

3
  • By logic, do you mean business logic? If yes, then the answer is none! If by logic you mean how to map one model into another, then it's perfectly reasonable. That's what drivers do and that's what your "infrastructure" layer is.
    – Laiv
    Dec 14, 2022 at 8:58
  • @Laiv by "logic" I mean what I described in my example: the conversion of response.SunshineLevel into the domain concept of "good/bad". One API may provide SunshineLevel in the response, another may provide CloudLevel, etc. And those various responses need to be converted into bool IsWeatherGood according to some rules (eg SunshineLevel > 10, CloudLevel == "Low", etc). Would such conversion be considered business logic that should not live in the Infrastructure Layer?
    – lemerek759
    Dec 14, 2022 at 10:47
  • Implementing ports might involve transforming those CloudLevel and SunshineLevel into bool or whatever the application needs. If rules are not that simple and depend on a complex algorithm that only the application knows, then this "knowledge" is injected into the port. As objects (OOP approach) or as functions (functional approach). Ask yourself. What decides what good weather is? is my application? is my provider? Who dictates this? And Who could change this? The business experts? Provider's semantics or data?
    – Laiv
    Dec 14, 2022 at 11:58

1 Answer 1

2

First, in Uncle Bob's Clean Architecture scetch, there is no "infrastructure layer". There is the outer layer of "Frameworks & Drivers", and the intermediate layer of "Interface Adapters". One of the responsibilities of the latter is to "convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as the Database or the Web." (I would actually add "and vice versa" to this cite).

Since interface & adapters mediate between the core business logic of the application and the technical infrastructure, it is not very surprising you will find sometimes knowledge of both in it, technical knowledge as well as a little bit of business logic elements. When your use case just needs an IsWeatherGood operation, but your external API only provides a sunshine level, it is perfectly reasonable to let an interface adapter make the transition. Especially when this specific form transition is only required once, only for this specific service, and cannot be reused elsewhere, an adapter is a suitable place for it.

However, when you expect to have more than one weather service which all provide you with the same kind of "sunshine level", then you may want to move the logic out of the adapter into the application layer, to make it reusable for different services. One may design an interface in a way it can be asked if it supports IsWeatherGood directly, or provides only a sunshine level exclusively, and in the second case, let the application use the latter.

Of course, you said it bothers you that the threshold of 10 might change in the future. And indeed, hiding this magic value somewhere deep inside the adapter is probably not the best idea (even if you replace the 10 by some symbolic constant). You can externalize this by providing the threshold as a parameter through ProviderTwoApiClient`s constructor, make it configurable from outside and move the value to the place where your interfaces & adapters are configured, which might be sufficient in your case.

So in short, none of both approaches is "more correct", it is a trade-off which depends on the context. If there is business logic which might be reused for different services, keeping it in the application is probably more suitable, if the logic is too specific to be reused, keeping it in the adapter is probably more suitable.

3
  • That makes sense, but it still feels weird to have the SunshineLevel > 10 check inside the adapter, event if I make it configurable from outside. What if in the future we want to change the definition of "good" to account for more stuff, for example in ProviderOne we would have isGood = response.IsSunny && !response.IsHumid, and in ProviderTwo isGood = response.Sunshine > 10 && response.UvIndex < 50. These rules would be determined by our domain analysts, not by 3rd party API providers, so they feel more like business logic. And business logic should live in the application layer, right?
    – lemerek759
    Dec 14, 2022 at 14:16
  • @lemerek759: business logic of your application should live in the application layer. If you want your users (or domain experts, or some admin) change that behaviour through some configuration dialog in your application, then it belongs into your application. When it comes to things which need to be changed by a developer, and the dev who maintains the adapters is the very same dev who maintains the application, I would try to be pragmatic here. The goal is to keep your app evolvable, hence the important part is to have this logic easily "rediscoverable" at a later point in time....
    – Doc Brown
    Dec 14, 2022 at 19:49
  • ... and when something is required for just one service, and nowhere else, one of the first places to look for this logic is probably the service adapter.
    – Doc Brown
    Dec 14, 2022 at 19:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.