This article describes how we can use Generics in C# to make our software more resilient to data-related changes, thereby improving its maintainability.
This article is published at the .NET Curry magazine, click here to read the article.
This article describes how we can use Generics in C# to make our software more resilient to data-related changes, thereby improving its maintainability.
This article is published at the .NET Curry magazine, click here to read the article.
This article discusses the treatment of data in large C# software applications. More specifically, it discusses runtime data encapsulation, behavior-only encapsulation, and treatment of state.
This article is published at the .NET Curry magazine, click here to read the article.
This article describes how to do unit and integration testing using the Composition Root as the source of Systems Under Test (SUTs).
This article is published at the .NET Curry magazine, click here to read the article.
C# examples on how we can use the Text Template Transformation Toolkit (T4) to create aspects.
This article is published at the .NET Curry magazine, click here to read the article.
Aspect Oriented Programming (AOP) in C# using SOLID principles. We will discuss challenges posed by context-independence in SOLID code bases, and provide a solution for them.
This article is published at the .NET Curry magazine, click here to read the article.
This article demonstrates (with an example published on GitHub) how Object Composition with SOLID helps an application evolve.
This article is published at the .NET Curry magazine, click here to read the article.
This article shows how we can use Pure DI and the single level of abstraction per function rule to create Composition Roots that we can easily understand and navigate.
This article is published at the .NET Curry magazine, click here to read the article.
This article presents a perspective of Liskov Substitution Principle (LSP) and presents an argument against some interpretation and practices of LSP that some people have/follow.
This article is published at the .NET Curry magazine, click here to read the article.
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:
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); var order_processor2 = 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.
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:
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( 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:
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):
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 |
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.
Notice how we use the index “i” to create unique names.
In the end, we need to register the AggregatedDocumentSource class. 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.