“You gotta know when to be lazy. Done correctly, it's an art form that benefits everyone.” Nicholas Sparks, The Choice
I admit this was taken out of context a bit - but we will see how this applies to dependency injection.
Identifying the problem
When dealing with dependency injection - and when I am saying dependency injection I am referring to the software design pattern - you are facing one problem pretty quickly. The first instance you create (or resolve using the container at the very root of your application) needs everything injected that you will ever need, either directly or something you inject takes that. This will result in tons of wire up code - and this is one reason why using a DI container might be a good idea for an application that you design with this pattern in mind - however a DI container might save you from writing tons of boilerplate code but it still does not solve the problem of instantiating the whole object graph at the start of the application on its own.
There are several ways on how not to do dependency injection - please read the linked article first as it will explain some important things that I felt would be redundant to repeat because they are very well explained (and the few C# code snippets are easy to understand).
...
Things to avoid
You are back? Good.
Let's talk about the service locator and the GlobalContainer - both things you might have seen in several Spring4D examples. If you are writing an application with DI in mind - don't use the service locator.
The service locator might be a nice way of introducing the DI container to an already existing application because you can plug it in at some place deep within the existing code and then kick off the container from there. But don't (ab)use it just to replace constructor calls with something else or to get around actually doing dependency injection. Using the service locator is not DI!
Another important thing to know about the use of the DI container - it does not serve to produce value objects - objects that only hold some values (think of TCustomer or TOrder) but for service objects - objects that actually do some work (like TCustomerValidator or TOrderProcessor). With that in mind you understand the possible problem of "I need to pass some data that is only known later in the application to the container" actually is not a problem. If you need to create customer or order objects then you either register a factory to the container that takes the necessary arguments and constructs an instance or you create that object in your code. No, this is not evil and there is no tight coupling you need to remove - if you follow some guidelines that Misko Hevery explains in one of his articles.
Now what about that GlobalContainer singleton we see in all the Spring4D examples? Actually if you want to make it 100% correct you should create your own TContainer instance - you remember you only need that at the start of your application to register all your types and then resolve the first instance and from there on it will never be seen again throughout your whole application.
If you ever have heard someone telling you that you should put your classes in your implementation part of the unit and then register them in the initialization part of that unit - never ever do that please!
First you are making these classes untestable (because not accessible from outside) without the DI container - always write your code in a way that it is testable without a container. Second you are tightly coupled to the GlobalContainer instance - what if you created your own one - you would be screwed.
Solving the problem
But now let's get back to our initial problem. Having things that might be needed later or even never throughout one run of the application. That is when we need lazy initialization.
Let's see how the example from the other article would look in Delphi:
container.RegisterType<IExampleService, TExampleService>('default').AsDefault; container.RegisterType<IExampleService, TAnotherExampleService>('another'); container.RegisterInstance<TFunc<string, IExampleService>>( function(name: string): IExampleService begin Result := container.Resolve<IExampleService>(name); end); container.Build; Assert(container.Resolve<IExampleService> is TExampleService); Assert(container.Resolve<TFunc<string, IExampleService>> .Invoke('another') is TAnotherExampleService);
So if we had another class that would take this factory as an argument on its constructor the container would inject this anonymous method there - keep in mind that we used RegisterInstance which returns the same anonymous method every time. In this example it is completely valid because the anonymous method has no state but pay attention when you use variable capturing.
You can imagine that this will be much code to write if you have many service types and you want to resolve many of them lazily. But just like other DI containers the Spring4D container has built-in support for that. Take a look at this code:
type THomeController = class private fService: IExampleService; fServiceFactory: TFunc<IExampleService>; function GetService: IExampleService; public constructor Create(const serviceFactory: TFunc<IExampleService>); property Service: IExampleService read GetService; end; constructor THomeController.Create(const serviceFactory: TFunc<IExampleService>); begin inherited Create; fServiceFactory := serviceFactory; end; function THomeController.GetService: IExampleService; begin if not Assigned(fService) then fService := fServiceFactory(); Result := fService; end;
container.RegisterType<IExampleService, TExampleService>('default').AsDefault; container.RegisterType<IExampleService, TAnotherExampleService>('another'); container.RegisterType<THomeController>; container.Build; Assert(container.Resolve<THomeController>.Service is TExampleService);
As you can see we did not register the factory method anywhere. The container took care of that and passed it to the constructor.
We now have the lazy initialization logic inside the getter because the container did just give us a factory method. Every time we would call it the container would run its logic and create a new service (unless we registered it as singleton of course). But Spring4D contains the lazy type - so we can reduce this code a bit and make use of that:
type THomeController = class private fService: Lazy<IExampleService>; function GetService: IExampleService; public constructor Create(const service: Lazy<IExampleService>); property Service: IExampleService read GetService; end; constructor THomeController.Create(const service: Lazy<IExampleService>); begin inherited Create; fservice := service; end; function THomeController.GetService: IExampleService; begin Result := fService; end;
The rest of the code is pretty much the same. The Spring4D container has built-in support to TFunc<T>, Lazy<T> and ILazy<T> where T needs to be a registered service type that can be resolved without a name - either by only having one implementor or having specified AsDefault on any.
So this was the first article on dependency injection best practices - more will follow. As always your feedback is very much appreciated.
By the way - in case you missed this - Spring4D is now hosted on Bitbucket.
If you use Lazy will you not get dependencies to Spring unit from all your units using it?
ReplyDeleteYes, and? As much as you get dependencies to SysUtils if you are using IntToStr or Classes when you use TStringList.
Delete