1

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.

Tom
  • 679
  • 1
  • 6
  • 15

3 Answers3

1

Logging is more like infrastructure and so will be shared by all your components. Why does it have to exist in one of the currently defined projects like Domain? Unless you are writing a logging library I wouldn't expect it to be in the Domain project.

Will your logging be static or will you be injecting some logging interface? It sounds like you want to use a logging interface, so this is what I would do.

Create a library which holds your logging interface and reference that in the Domain and DAL (and any other projects you want to log in). Then create an implementation library for your logging and reference this in your console app. Use DI to inject you implementation into your Domain/DAL/Whatever classes that want to log.

Honestly though, I'd just use either the built in TraceSource classes, or ETW or one of the many existing logging libraries statically across everything and be done with it. Pragmatism for the win!

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
  • To be clear, I am not putting my logging implementation in the domain, I am putting it in a stand-alone library. I was intending to inject loggers into the constructor of any class needing it (both in the domain and the DAL). – Tom Jun 17 '16 at 15:34
  • As for the suggestion of creating an interface library and referencing it from domain and DAL, that would have the benefit of not being tightly coupled with a concrete implementation. Are there any downsides? Tight coupling to the interface library? – Tom Jun 17 '16 at 15:40
  • It sounds like you are on the right track then. Downsides of having a separate interface library are that its probably unnecessary unless you are going to have multiple logger implementations, but to be honest unless you have good reason I'd just use what the framework has built in. – Sam Holder Jun 17 '16 at 15:51
1

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.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    Hi Steven, thanks for your well thought out response. I am already familiar with everything you wrote, and indeed coded up a test app exactly how you described due to the use of a third party logger, before reading this post. I was however, still left with a feeling of consternation due to the logging being shared between the domain and the DAL. It took you mentioning LSP for me to finally understand the source of this dissonance. Your post helped clarify my thoughts so I'm going to mark it as the accepted answer. Thanks. – Tom Jun 19 '16 at 13:30
0

How far do you want to go with the Dependency Injection pattern and abstracting implementations? Would you also inject basic features like sorting, memory allocation and creation of threads?

With Domain Driven Design and Dependency Injection patterns, you're giving the implementor the flexibility to provide one or more implementations that fit the model and isolate the responsibilities of each component. Logging is a standard operation for any program, but if you don't standardise on one logging mechanism, component implementors might end up choosing different logging frameworks.

I recommend that you establish a standard logging mechanism (even if it's stdout + stderr) for the project and document this decision in e.g. root README. There are many languages and frameworks that have a standard logging mechanism built in that is sufficiently configurable that you can rely on that. For example, it allows for some kind of contextual-awareness so that logs within that context could be configured as ignore, information, error, etc. Since, at this time, the language isn't specified in the question, I can't suggest anything specific.

JBRWilkinson
  • 4,821
  • 1
  • 24
  • 36