maandag 18 juni 2007

Dependency Injection versus Service Locators

When I was driving home from work, I have been thinking: when an object (entity) needs a reference to a repository for instance, to get some work done, how do you get a reference to that dependency (in this case, the repository) ?
Do you use dependency injection, or do you use a service locator ? When to choose what ? What are the advantages / disadvantages over both approaches ?

For example, suppose you have a 'Customer' class; this class has a property 'Status' which says whether the Customer is a 'Gold Customer', a 'Badly Paying Customer' or a 'Normal Customer'.
In order to determine to which 'state' a specific Customer belongs to, it might be necessary to retrieve some information out of a datastore. In this case, it could be necessary for instance to retrieve some invoice information regarding this customer.
(A Gold Customer for instance, is a Customer who has ordered for a specific amount of goods in a certain period, while a Bad Payer might be some-one who has overdue invoices).
So, a reference to an instance of IInvoiceRepository must be given to the Customer object.
The question now is, how do you give this dependency to the Customer object ?

Do you use 'Dependency Injection', and give this dependency via a constructor or a property to the Customer instance, like this:

Customer c = customerRepository.GetCustomer (5);
c.InvoiceRepository = repositoryFactory.CreateInvoiceRepository();

Or, should you leave this responsability to the Customer object ? In other words: let the Customer instance search for the correct IInvoiceRepository using a service locator. This could then be done in the Status property of the Customer class:

public CustomerStatus Status
{
get
{
IInvoiceRepository ir = DomainSettings.Instance.RepositoryLocator.GetInvoiceRepository();
...
return status;
}
}

In this case, the 'RepositoryLocator' can decide which implementation of IInvoiceRepository it has to create and return based on a value in a config file for instance.

Anyway, when do you choose for which option ? What are the advantages /disadvantages of both concepts ? In which situation do you favor the one over the other ?

I hope that some smart people can enlighten me about this matter. :)

11 opmerkingen:

Yves Goeleven zei

Usually I prefer the second, as I don't believe the client code should be messing with the internals of the Customer.

I do like to combine the two as well, getting the best of both: Provide two constructors, one that accepts the repository as a parameter and another that provides a looked up version of the repository: This allows you to overrule the repository in special testing cases without having to create a new service locator configuration.

public class Customer
{
public Customer() : this (Dependency.Resolve<IInvoiceRepository>()) {}
public Customer(IInvoiceRepository repository)
{
// store the repository for later use.
}
}

Frederik Gheysels zei

Hmm, is injecting a dependency really messing with the internals of an entity ?
In my opinion, that's not really true. The client just needs to give the dependency; whatever the entity does with it, is abstracted from the client.

I am also thinking: what if the injected / located dependency needs a dependency to another object. In this case, for instance, an NHibernate ISession ...
In this case, it would be more convenient to use dependency injection, since the client knows which ISession context the repository needs (Session mgment shouldn't be the responsability of repositories).

Yves Goeleven zei

I agree with that as well, but determining the correct repository, nhibernate session or linq datacontext is not the responsibility of the client code either. The infrastructure should take care of that.

I rather create an additional infrastructure class, like the service locator or an ioc container, that takes this decision than allowing client code (which is supposed to cover the domain logic) to concern itself about what infrastructure must be provided.

Frederik Gheysels zei

How can the infrastructure determine the correct NHibernate session for instance ?
Isn't it the client who knows the 'context'/'use case', and knows when to start a session, begin and commit/rollback a transaction?
How can a service locator / IoC know if he has to start a new session, or use an existing one ?

Yves Goeleven zei

I allways expose business functionality as a task oriented method on a service endpoint. The client sends a message to the service endpoint when it wishes to perform that specific task.

Per message that arrives at the service, a new thread is started and the infrastructure provides on nhibernate session per request/thread via the session per request pattern. The session is stored in thread local storage for use during the execution of the request.

So for example a user tells it's client that it wishes to make a request to order some product. The client doesn't really do anything besides sending the message to the server for RequestOrder. This will cause the service to fire up, infrastructure detects the new request and sets up a session for it to use, simple and effective.

Yet the approach is not so feasible if you want to let your client be in control.

Frederik Gheysels zei

Indeed, if you make use of a service layer, this is an approach which can be considered.
In a rich client scenario, it is less obvious to work like this.

(When using a service layer and Hibernate, this also means that, when updating an entity to the DB, you first re-load this entity ?
For instance, suppose your service-layer has a method 'UpdateCustomer( Customer c )', this method looks like this (pseudo-code):
public void UpdateCustomer( Customer c )
{
ISession s = GetNHibernateSession();

Customer updatedCustomer = customerRepository.GetCustomer (c.Id);
...
customerRepository.Save (updatedCustomer);

s.StartTransaction();
s.CommitTransaction();

s.Close();
}

Yves Goeleven zei

Indeed,

I believe it's good practice to load the customer, and apply the changes to it that correspond to the task at hand.

But I would never provide an UpdateCustomer method, but a method that's more intention revealing and that represents the task at hand.

If for example you want to change the customer's financial details, I would expose a method ChangeFinancialDetails(int customerID, string newBancAccount) or something similar. If you look at that signature, it's obvious that the customer will change by first retrieving it from persistence and than having the details changed.

Just try task oriented thinking and you will see that many difficulties that we all struggle with will just disappear

Frederik Gheysels zei

UpdateCustomer was merely a simple example. :)
Offcourse, it is better to create methods that correspond more to the 'use cases'/

Anoniem zei

I would prever encapsulation, to adher to the "Law of demeter". Check this article: http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf

Frederik Gheysels zei

I do not see how the 'law of demeter' fits into this, or why dependency injection would violate this law ?
With dependency injection, the object is given a dependency which conforms to a specific interface, so that the object can do something with that dependency.
You do not have to expose this dependency to the 'outside', so the law of demeter is not violated.

Frederik Gheysels zei

Scott Bellware has written two very interesting articles regarding dependency patterns.