trnelson
trnelson

Reputation: 2763

Basic DI/IoC Questions - Architecture, SimpleInjector

I have a simple hangup when setting up an IoC container in regards to the following type of architecture.

In my application, I have layers like this (bottom to top):

When a controller in the MicroServices layer is requested (GET, POST, etc.) SimpleInjector uses property injection to add an instance of an ILogger to the BaseController. Thus, any controllers in the MicroServices project have access to it. This works well so far.

Here's my problem:

I'd like to make logging accessible to the entire architecture, even in the domain layer, without breaking any cross cutting rules and keeping things decoupled. My idea was perhaps an event published from the domain layer that the Logging layer could subscribe to. But, I digress. What I don't understand is how to make SimpleInjector pass the Logger instance to the domain (if I should, thoughts?) Does it happen on app startup, or only on web requests? Should it be a singleton instance, or transient? And how would this best fit with Log4Net (It's possible that I'm over- or under-complicating my architecture).

I realize that registration happens on startup, but what about getting instances? From what I understand, I should use automatic constructor injection and not use Container.GetInstance() to get instances, but despite my extensive reading, I still can't quite figure out how that works outside of a Web API request and SimpleInjector's Web API Extensions.

Also, somewhat unrelated. I am abstracting out the Log4Net functionality, but I recently learned that I probably shouldn't. Would love some recommendations on how this all ties together considering that.

Upvotes: 4

Views: 277

Answers (1)

Steven
Steven

Reputation: 172606

Your post is a bit vague and full of questions that aren't easy to answer without more context, but I'll do my best.

I'd like to make logging accessible to the entire architecture, even in the domain layer

If you have a clear abstraction for logging, you can make that abstraction available to the application and do logging throughout the application.

I am abstracting out the Log4Net functionality, but I recently learned that I probably shouldn't.

I disagree. I think you should. Prevent making the core of your application dependent on 3rd party components if possible, and with logging, that's really easy to do. It's easy to define an abstraction that conforms to the SOLID principles. And don't forget, according to Dependency Inversion Principle, the abstraction should be defined by the client. A logging framework can't define the abstraction you need.

But be sure not to fall in the trap of logging too much as explained here. In most cases you shouldn't log that often, but rather fail fast, and have a few generic pieces of code that do the logging for you, instead of having calls to your ILogger.Log littered throughout the code base.

And if you are publishing events, I think that drastically lowers the amount of time you need to log, since events are the perfect logging system. Just append those events to table or on disk and you know what happened at exactly which moment in time. There's probably little reason to write more information to the log.

What I don't understand is how to make SimpleInjector pass the Logger instance to the domain (if I should, thoughts?)

There are many opinions about this. I and Mark Seemann favor anemic domain models with commands and events on top (or I'm I allowed to say that my commands and events become my domain model?). Others have more business logic in their domain objects. Those objects need some services such as an ILoanCalculator, and you probably want dependency injection, since letting those domain objects request services through Service Location is a very bad idea.

But don't forget that there's more than constructor injection. For your domain objects you can use method injection where the services some domain method needs are exposed as method arguments:

public class Loan
{
    public void PayLoan(LoanPeriod periodToPay, ILoanCalculator calculator)
    {
        // ...
    }
}

Since you'll have service classes (can I say command handlers?) that call those domain methods, you can apply constructor injection on those service classes and pass the dependencies on to the domain method.

I realize that registration happens on startup, but what about getting instances?

Instances are retrieved (object graphs are built) at the beginning of each request.

From what I understand, I should use automatic constructor injection and not use Container.GetInstance() to get instances

That is correct. Use constructor injection as much as possible on any service that is resolved from your container.

but despite my extensive reading, I still can't quite figure out how that works outside of a Web API request and SimpleInjector's Web API Extensions.

I'm not sure if I understand this. Almost any application is request based, whether this is a web application, windows service or command line tool. With the web application the requests come in from the internet as web requests. With a windows service you have a timer that goes off periodically and each pulse can be seen as a new request (or perhaps you are using a SqlDependency in which case the raised event is the start of a new request). A console application will probably just have a single request and dies shortly after that. For web applications, Simple Injector will implicitly control the lifetime of some services, while windows services and background processes, you will have to control this explicitly. With Simple Injector the LifetimeScope and ExecutionContextScope lifestyles allow you to define an explicit scope that controls the lifetime of objects for such request. The WebApiRequestLifestyle in fact uses the ExecutionContextScopeLifestyle in the background and begins and ends a scope per Web API request.

Upvotes: 4

Related Questions