Search code examples
loggingdependency-injectioninversion-of-control

IoC Architecture


I’ve been reading Mark Seemann’s book recently about Dependency Injection and it has raised some architectural questions around Inversion of Control. Let’s assume I have the following:

  • A very basic executable project that acts as the composition root, called CompositionRoot.exe.
  • A domain project that compiles to a library, called Domain.dll
  • A data access project that compiles to a library, called DAL.dll
  • A logging project that compiles to a library, called Logging.dll

Following IoC patterns from Seeman’s book, repository interfaces are defined in Domain. DAL references Domain and implements these interfaces. CompositionRoot is responsible for instantiating these repositories and injecting them into the Domain. So far, so uneventful.

Now the question; how does logging fit into this scenario?

I had envisioned the logging library being used by both the Domain and DAL. Some reading on StackOverflow shows some developers think logging only belongs in the Domain. There are times when logging in the DAL is useful for me, such as when benchmarking specific pieces of SQL, or logging Entity Framework exceptions without exposing Entity Framework specific exceptions to the Domain.

Let’s assume then that I want logging in both the Domain and the DAL (unless someone can convince me otherwise). Should the logging interface be defined in the Domain similar to the repository interfaces? If so, this would tie the DAL’s logging to Domains logging, which feels wrong. Alternatively, DAL could define its own logging interface which Logging also implements. This, however, leads to Logging having to implement a new interface for each new DLL that requires logging, which also feels wrong. Alternatively, should Domain and DAL reference Logging (seems to go against IoC)? Is there another way? I haven’t finished Seemann’s book yet but at a couple of points he alluded to using an interface library i.e. a DLL that consists of just interfaces. I can’t currently picture how this would work.


Solution

  • It all comes done to the Dependency Inversion Principle (DIP) (the principle that is driving Dependency Injection). The DIP states:

    the abstracts are owned by the upper/policy layers

    In other words: An abstraction should be defined by the layer that uses it. So it seems obvious to have the logging abstraction inside the Domain layer, if your domain layer requires logging.

    This doesn't mean however that your Domain layer should depend on your logging layer/library. If you use an external library (or make your logging library reusable) it's impossibly for it to depend on your Domain layer, since obviously it isn't reusable. The DIP however guides us towards applications that have core layers that don't have any dependencies on external libraries. Instead the dependency on external libraries should be moved all the way up to the Composition Root. The Composition Root takes a dependency on all the application assemblies. Because your Domain layer and logging library can't take a dependency on each other, the solution is to implement an adapter inside the composition root. This adapter will implement the Domain.ILogger abstraction and its Log method will call the logging functionality of your Logging library.

    Note that this advice isn't some obscure practice. This is actually the way to decouple your core layer from other parts and people like Robert C. Martin and Alister Cockburn are explaining for years. Robert C. Martin calls this type of architecture Screaming Architecture and Alister Cockburn uses the term Hexagonal Architecture. Mark Seemann explained a few years ago that both architectures are all the same.

    About your other question, should the DAL use the logging abstraction from the Domain layer?

    Since the DAL already is coupled to the Domain layer (because repository abstractions are defined in the domain), it wouldn't be strange to let the DAL layer depend on the logging abstraction as well. The only thing you have to think about hard is whether or not this would violate the Liskov Substitution Principle. In other words, do both consumers (the DAL and the Domain) have the same expectations of that abstraction? If that's not the case, it makes sense to let the DAL have its own logging abstraction and have (again) an adapter in the Composition root that forwards calls to the logging library. This makes it very easy to write the DAL logs to a different source, with a different verbosity level. The interface for the DAL logger might as well be different, since it seems you especially want performance measurements there. This all doesn't concern the Domain at all, since the dependency is from DAL to Domain and not the other way around.