I have decided to move the blog to a private domain. The new blog address is criticalsoftwareblog.com
Critical Software
Tuesday, March 22, 2016
Sunday, October 25, 2015
Object Composability: Another reason why the Service Locator is an anti-pattern
The Service Locator pattern (or anti-pattern) is considered to be one way to implement the Inversion of Control (IoC) principle. When this pattern is used, a class can find or locate some dependency by asking some entity (called the Service Locator) to provide such dependency. Here is an example:
....
Please note also that using names to locate different implementations of the dependency based on names (as a differentiating parameter) does not solve the issue. How can two instances of OrderProcessor (for example) use two different names to locate different implementations of the IEmailSender service? Should we use Dependency Injection to inject the service name? How can a name be enough to present the many different ways a service can be composed? Wouldn't this violate the IoC principle by having the client know much about its dependency?
public interface IEmailSender
{
void SendEmail(string to, string subject, string body);
}
public class OrderProcessor : IOrderProcessor
{
public OrderProcessor()
{
}
private void InformCustomer()
{
IEmailSender email_sender =
ServiceLocator.LocateService<IEmailSender>();
email_sender.SendEmail(
GetCustomerEmail(),
"Your order status",
"Your order has been
submitted");
}
}
Here we have a service contract called IEmailSender that allows for sending email messages. The OrderProcessor class depends on this service. To obtain such service, it invokes a static class, i.e., the ServiceLocator class, to locate the IEmailSender Service. It uses such service to send some email to the customer.
For this to work, the developer would have registered some implementation of IEmailSender with the Service Locator, probably at application startup.
In his blog, Mark Seemann has published two articles to provide reasons why the Service Locator pattern is actually an anti-pattern.
The first article speaks about the fact that this pattern creates hidden dependencies. In our example, it is not very clear from the constructor of OrderProcessor that it has a dependency on IEmailSender.
The second article speaks about the fact that the Service Locator entity provides its consumers access to more services than they need and thus it violates the Interface Segregation Principle. In our example, the OrderProcessor class can use the ServiceLocator to locate some IOtherService service that it does not need.
In this article, I provide another important reason why the Service Locator is an anti-pattern.
The Service Locator is an anti-pattern because it limits Object Composability.
Object Composability is a design principle that drives us to design classes in a such a way that they can be composed in different ways to satisfy current and new requirements.
Consider the previous example of OrderProcessor, but this time I will be using the Dependency Injection pattern instead of the Service Locator pattern. Here is how it would look like:
public class OrderProcessor : IOrderProcessor
{
private readonly IEmailSender m_EmailSender;
public OrderProcessor(IEmailSender email_sender)
{
m_EmailSender = email_sender;
}
private void InformCustomer()
{
m_EmailSender.SendEmail(
GetCustomerEmail(),
"Your order status",
"Your order has been
submitted");
}
...
}
The OrderProcessor class does not know which implementation of IEmailSender it will be using. However, this can also be achieved using the Service Locator.
What is different in this case, is that each OrderProcessor instance can be depending on a different implementation of IEmailSender or even an object graph that is composed in a totally different way.
Consider the case where we want to encrypt email messages. We can use the Decorator Pattern to create a decorator that encrypts the body of the message before sending it. Here is an example:
public class EmailSenderEncryptionDecorator : IEmailSender
{
private readonly ITextEncryptor m_TextEncryptor;
private readonly IEmailSender m_EmailSender;
public EmailSenderEncryptionDecorator(
IEmailSender email_sender,
ITextEncryptor text_encryptor)
{
m_EmailSender = email_sender;
m_TextEncryptor = text_encryptor;
}
public void SendEmail(string to, string subject, string body)
{
string encrypted_body = m_TextEncryptor.EncryptText(body);
m_EmailSender.SendEmail(to ,
subject, encrypted_body);
}
}
Please ignore the fact the email encryption does not work this way. I am just using this as an example.
This class implements the IEmailSender interface and also has a dependency on the IEmailSender interface. When it is invoked to send a message, it first encrypts the body and then invokes its IEmailSender dependency to actually send the message.
Using the Service Locator, we can register this EmailSenderEncryptionDecorator service with the service locator and the job is done. Right? What's the problem then?
The problem starts to appear when we want two instances of OrderProcessor to depend on two different implementations of IEmailSender. For example, we might want one instance of OrderProcessor to send encrypted messages, and another instance of OrderProcessor to send plain text messages.
Using dependency injection, we can easily do this. Actually, we can compose objects in any way that we want.
Consider the following sample Composition Root that uses Pure DI to construct the object graph:
var email_sender = new SmtpEmailSender("server.test.lab", 25);
var text_encryptor = new TextEncryptor();
var email_sender_encryption_decorator =
new EmailSenderEncryptionDecorator(email_sender, text_encryptor);
var order_processor1 =
new OrderProcessor(email_sender);
new OrderProcessor(email_sender);
var order_processor2 =
new OrderProcessor(email_sender_encryption_decorator);
new OrderProcessor(email_sender_encryption_decorator);
//Continue composing the object graph
Here we have very flexible Object Composability, i.e., we can compose the objects in any way that we like.
What happens when we use the service locator? The OrderProcessor will always locate and use the same service, or a different instance of the same service implementation type (or graph).
How can we use the Service Locator pattern to make one instance of OrderProcessor use EmailSenderEncryptionDecorator as its dependency, and another one to use SmtpEmailSender? I cannot think of any clean solution for this.
Please note that I gave a simple example. In real applications, we have more complex examples of object composition. We might have multiple decorators for a single interface, and for different consumers (who might be of the same type) we would need to inject a dependency that is composed in a totally different manner.
Please note also that using names to locate different implementations of the dependency based on names (as a differentiating parameter) does not solve the issue. How can two instances of OrderProcessor (for example) use two different names to locate different implementations of the IEmailSender service? Should we use Dependency Injection to inject the service name? How can a name be enough to present the many different ways a service can be composed? Wouldn't this violate the IoC principle by having the client know much about its dependency?
Summary:
Object Oriented design principles drive us in the direction of creating classes that have high composability. This gives us the power to reuse classes by composing them differently.
Using the Service Locator to achieve IoC will limit composability.
With the Service Locator, our options of specifying the dependency to use are much more limited than in the case of Dependency Injection.
Sunday, August 23, 2015
Why DI containers fail with "complex" object graphs
In object oriented programming, SOLID
is a set of principles that allows us to create flexible software that is easy
to maintain and extend in the future. If we use such principles, we end up with
a lot of small classes in our code base. Each of these classes will have only a
single responsibility, but can collaborate with other classes.
We use Dependency Injection
to create loosely coupled classes. Classes do not depend on other classes
directly, instead they depend on abstractions. A class declares that it depends
on specific abstractions, and a third party will inject concrete implementations of such abstractions when an object of such class is created.
This third party is usually some code that lives in the
application entry point and is called the composition root. This
code will create a graph of objects that formulate the application.
Two ways of creating such object graph are:
1) Using a Dependency
Injection (DI) container (also called Inversion of Control (IOC) container).
2) Pure DI.
In this article I talk about a problem of using DI
Containers and show how using Pure DI might be a better solution.
Let’s start by an example of creating an object graph using
a DI container. Consider the following UML class diagram:
Here, the OrderProcessor class depends on both
ICurrencyConverter and IEmailSender interfaces.
The EmailSender class requires an smtp server address to
function. Such information is provided to the class via its constructor.
Similarly the CurrencyConverter class requires a currency conversion web
service URL to function. Such URL is provided to the class via the constructor.
The OrderProcessor class constructor requires an
IEmailSender and an ICurrencyConverter.
We can use a DI container to map each interface with the
corresponding class that implements it. After such mapping, we can use the
container to create as many objects as we want. The following code demonstrates
this using Unity
DI Container (which is a DI container created by Microsoft):
UnityContainer
container = new UnityContainer();
container.RegisterType<IEmailSender, EmailSender>();
container.RegisterType<ICurrencyConverter, CurrencyConverter>();
container.RegisterType<IOrderProcessor, OrderProcessor>();
var processor =
container.Resolve<IOrderProcessor>(
new ParameterOverride("url", "http://service.currency.lab"),
new ParameterOverride("smtp_server",
"smtp.lab.lab"));
|
Here we map each interface to the class that implements it.
After doing that we ask the container to build us an order processor. We
provide any required settings such as the currency conversion web service URL
and the email server address. Other than that, the container knows how to
construct the object graph using something called auto-wiring.
The container starts by looking in the registered maps for
the IOrderProcessor interface. It will find that it is mapped to the
OrderProcessor class. Looking at such class, the container finds that the
constructor of such class requires an IEmailSender and an ICurrencyConverter.
Now the container will try to resolve such dependencies, it looks into its
registration map. It will find that the IEmailSender interface is mapped to
EmailSender and the ICurrencyConverter interface is mapped to the
CurrencyConverter class. The container will try to create an EmailSender class.
Looking at its constructor, it will see that it requires a string “url”. Since
we have provided such “url” when we invoked the Resolve method, the container
will be able to construct the EmailSender. The same thing goes with the
CurrencyConverter class. After creating these dependencies, the container is
now ready to create the OrderProcessor class. This same process will work
regardless of the size of the object graph or its depth.
In pure DI, we create the object graph manually. Here is how
we would create the same object graph using pure DI:
var processor = new OrderProcessor(
new EmailSender("smtp.lab.lab"),
new CurrencyConverter("http://service.currency.lab"));
|
Here comes the benefit of using the DI container: Imagine
that you have a bigger object graph. Let’s say that other than the email sender
service and the currency conversion service, you have an order storage service
(or an order repository), a SMS sending service, a credit card processing
service, an order queuing service, a customer loyalty management service, a
logging service, security related services and many other services. Imagine
that there are 20 such services and some of them depend on each other. And
let’s say that you have some 20 application-level classes (like ASP.Net
controllers) that depend on these services to work. Constructing such classes
manually requires a lot of code and thus costs more to maintain.
For the sake of the discussion I will define the following:
1)
Simple object graph: an
object graph of any size and any depth that has the following two attributes:
a. For any interface (or abstraction) at most one class that impalements such interface is used in the object graph.
b. For any class, at most one instance is used in the object graph (or multiple instances with the exact same construction parameter arguments). This single instance can be used multiple times in the graph.
a. For any interface (or abstraction) at most one class that impalements such interface is used in the object graph.
b. For any class, at most one instance is used in the object graph (or multiple instances with the exact same construction parameter arguments). This single instance can be used multiple times in the graph.
2)
Complex object graph: Any
other object graph.
The previous object graph is an example of a simple object
graph.
Please note that I am using the terms “simple” and “complex”
here with a special meaning. I chose these terms because I could not come up
with better ones. In different contexts, “complex object graph” means
something else.
My argument in this article is that once the object graph
starts to get “complex”, pure DI quickly becomes a better alternative than
using a DI container.
There are other reasons why pure DI might be a better
alternative, but in this article I will only speak about graph complexity.
Please note that here we can have a rough measure of
complexity depending on how many objects violate the two previous rules. Some
graphs are more complex than others.
Here are some reasons why we can have a complex object
graph:
For attribute (a):
1) We sometimes use the Decorator pattern to
add functionality to some object. For example, we might create a decorator for
the IEmailSender class to encrypt the email message before we send it.
2) We might abstract some
concept behind an interface and have two implementations that are going to be
used simultaneously in our application. For example, consider an
IDocumentSource interface for an application that processes documents. We might
want the application to pull documents from an FTP site, and in the same time
we also want it to pull documents form a database. We might have an
FTPDocumentSource class, and a DatabaseDocumentSource class. And then we might create a
façade to obtain documents from both sources.
For attribute (b):
Using the same IDocumentSource example, we might have different FTP servers that we want to pull documents from. So we create two or more instances of the FTPDocumentSource class. Using an ftp server address parameter at the constructor, we will be able to point one object to ftp server 1 and the other object to ftp server 2.
To make things complex, imagine that there is some
configuration file or database that contains the list of ftp servers that we
want to pull the documents from (such configuration file/database can be
updated by the application administrator). We will need to create FTPDocumentSource objects
at runtime when our application starts. Also, based on such configuration, we
might decide to decorate some but not all of our objects. For example, imagine
the following configuration table in the database (the same can be done inside
a configuration file):
FTP Server
|
Document types to include
|
ftp1.server1.lab
|
ALL
|
ftp2.server2.lab
|
pdf;docx
|
ftp3.server3.lab
|
pdf
|
So, we have our FTPDocumentSource class, and also we have a
decorator to filter documents based on document type. Such decorator might be named
DocumentSourceFilteringDecorator. Such decorator takes in an IDocumentSource
and a string filter in its constructor, and when it is invoked to pull
documents, it would filter the documents based on the filter string.
In the example configuration in the database, the first FTP
source does not require applying the decorator. While the other two sources do
require such decorator.
We also need another IDocumentSource implementation to make
multiple document sources look like a single document source for the document
source consumer. Such implementation can be called AggregatedDocumentSource.
Here is how this part of the object graph would look like:
Here is a UML class diagram for the involved types:
Here is how such object graph is configured and created using
Unity DI container:
//Assume this comes from configuration database or file
SourceSettings[]
settings =
{
new SourceSettings {
FTPServerAddress = "ftp1.server1.lab" , Filter= "ALL"},
new SourceSettings {
FTPServerAddress = "ftp2.server2.lab" , Filter= "pdf;docx"},
new SourceSettings {
FTPServerAddress = "ftp3.server3.lab" , Filter= "pdf"}
};
UnityContainer
container = new UnityContainer();
List<string> names = new List<string>();
for(int i = 0 ; i <
settings.Length ; i++)
{
string
ftp_document_source_name = "ftp_document_source" + i;
string filtering_document_source_name
= "filtering_document_source" + i;
container.RegisterType<IDocumentSource, FTPDocumentSource>(
ftp_document_source_name,
new InjectionConstructor(settings[i].FTPServerAddress));
if (settings[i].Filter != "ALL")
{
container.RegisterType<IDocumentSource, DocumentSourceFilteringDecorator>(
filtering_document_source_name,
new InjectionConstructor(
new ResolvedParameter<IDocumentSource>(ftp_document_source_name),
settings[i].Filter));
names.Add(filtering_document_source_name);
}
else
{
names.Add(ftp_document_source_name);
}
}
object[]
document_sources_resolved_parameters =
names.Select(x
=> new ResolvedParameter<IDocumentSource>(x)).Cast<object>().ToArray();
container.RegisterType<IDocumentSource, AggregatedDocumentSource>(
new InjectionConstructor(
new ResolvedArrayParameter<IDocumentSource>(document_sources_resolved_parameters)));
var
document_source = container.Resolve<IDocumentSource>();
|
You can see from this example how complex it has become to
create the object graph using a DI container.
Since we have multiple registrations for the same interface,
we use names
to distinguish between different registrations.
In the above code, we loop through the list of ftp servers
that we got from the configuration database or file, and for each one of them
we decide whether we want to have a filtering decorator or not based on the
corresponding filter.
1) If we decide that we do
want to filter such source, we make 2 registrations, one for the FTP
document source, and the other one for the filtering decorator. We link the FTP
document source and its corresponding filtering decorator using the name of
the FTP document source registration. We store the name of the filtering
decorator registration in the names list because we will need it when we create
the AggregatedDocumentSource.
2) If we decide not to filter
the document source, we simply make a single registration for the FTP document
source and we give it a name. Again, we store the name of the registration.
Notice how we use the index “i” to create unique names.
In the end, we need to register the
AggregatedDocumentSource. Such class accepts an array of IDocuemntSource objects in its
constructor. We use the names we collected in the loop to point this
registration to the document sources it needs to link to.
As you can see, we no longer use the auto-wiring feature.
It’s like we are manually constructing the graph, but with more verbosity.
Now, let’s take a look on how we can use pure DI to
construct such object graph.
SourceSettings[]
settings =
{
new SourceSettings {
FTPServerAddress = "ftp1.server1.lab" , Filter= "ALL"},
new SourceSettings {
FTPServerAddress = "ftp2.server2.lab" , Filter= "pdf;docx"},
new SourceSettings {
FTPServerAddress = "ftp3.server3.lab" , Filter= "pdf"}
};
List<IDocumentSource>
sources = new List<IDocumentSource>();
foreach (var source_settings in settings)
{
IDocumentSource
source = new FTPDocumentSource(source_settings.FTPServerAddress);
if (source_settings.Filter !=
"ALL")
source = new DocumentSourceFilteringDecorator(source, source_settings.Filter);
sources.Add(source);
}
var document_source = new AggregatedDocumentSource(sources.ToArray());
|
Here, it is very clear that using pure DI in this case
produces much more readable and maintainable code.
When we start having complex graphs, we lose the ability to
auto-wire the graph using the auto-wiring feature provided by DI containers.
Most of the registrations will have names (or other DI container features with
similar consequences), and we would be manually-wiring the objects ourselves,
but with more code and less compile-time validation support than pure DI.
Named registration isn’t required just for the types that
violate the simple graph attributes. Once you start having such named
registrations, some objects that depend on types that were registered with
names will also require names for registration, take a look at this example:
Here we have a DocumentsProcessor class that takes in an
IDocumentSource in its constructor, and will process documents from the source
when we call its Process method.
Let’s say that we want to create a DocumentProcessor for
each document source, i.e., we want to have the following graph (all 3 DocumentProcessor objects will be used in a single graph):
In this case, even if you don’t have multiple
implementations of IDocumentProcessor, you would still need to name each one of
the 3 DocumentProcessor registrations, because each one of them depends on a
different configuration of IDocumentSource.
Summary:
For simple object graphs, DI containers can be a good
choice. Although in my experience, graphs tend to become complex very fast in a
project lifetime. If you use DI containers, once the graph becomes complex you
would either have to switch to pure DI or live with a lot of named
registrations.
For complex graphs, pure DI is much better.
Subscribe to:
Posts (Atom)