Introduction
Application models
like The Onion Architecture by Jeffrey Palermo are based on Inversion of
Control. Dependency injection is the usual way to realize it.
A classic example used
to present the technique is to inject an infrastructure repository to a
business class via the constructor:
public BusinessClassA
{
private readonly IRepositoryA _repositoryA;
public BusinessClassA(IRepositoryA repositoryA)
{
_repositoryA = repository;
}
[…]
} Colourised in 3ms
However, in real-life
projects, applying this way to proceed for each infrastructure service has some
limits:
More
infrastructure repository/proxy/service could be required by the business class
and the constructor could explode: ctor(IRepositoryA repositoryA, IRepositoryB repositoryB,
IProxyC proxyC, ….);
The
lifecycle of the instance of the infrastructure service should be independently
managed from the one of business class. This does not occur in the example,
since the repository is a private filed and initialized once;
This article proposes
to inject a factory instead of infrastructure services directly in order to
solve both these problems.
Background
The Abstract Factory pattern
“Provide an interface for creating families of related or dependent objects without specifying their concrete classes.” Inversion of Control
"Don't call us, we'll call you (Hollywood's principle)".
Usually, if a class of type A uses a class of type B (flow of functional dependency), B needs to be compiled before A.
But when we introduce an interface for class B, put it into the component of A and delegate the creation of the concrete implementation of B to an external Factory Container, A could be compiled before B.
In this way the flow of source code dependency is
opposed to the flow of functional dependency (this is the meaning of the term
inversion).
Inversion of Control
can be achieved in two way:
Dependency
Injection (constructor, parameter, setter or interface injection): using a
builder object to initialize objects and providing the required dependencies in
order to "inject" a dependency from outside the class.
Service
Locator: introducing a locator object that is used to "resolve" a
dependency within a class.
The proposed solution in an example
Let consider a simple
application modeled with the onion-architecture. The application manages a
school. Consider a scenario in which we want to perform a bulk notification to
all school member about Christmas Holiday.
The model of the demo application is composed by the actors of a school: students and trainers.
namespace Tiskali.Core.Model
{
public abstract class Person
{
public virtual Guid ID { get; set; }
public virtual string PIN { get; set; }
public virtual string Name { get; set; }
public virtual string Surname { get; set; }
public virtual string Email { get; set; }
}
} Colourised in 7ms
namespace Tiskali.Core.Model
{
public partial class Student : Person
{
}
} Colourised in 1ms
namespace Tiskali.Core.Model
{
public partial class Trainer : Person
{
}
} Colourised in 3ms
The scenario of the Use Case Module is incapsulatad in an Application Service as a transaction script. The service needs two infrastructure services: one repository (in order to retrieve all people registered in the system that are the target of the notification) and one notification engine.
Obtaining the notification engine on demand is a key point because its lifecycle become manageable. We can assume that the notification engine wrap System.Net.Mail.SmtpClient that is disposable. Obtaining the service on demand let us to dispose it with the using construct.
namespace Tiskali.Core.Services
{
public class NotificationService : INotificationService
{
#region Private fields
private readonly IServiceFactory _factory;
#endregion
public NotificationService(IServiceFactory factory)
{
_factory = factory; }
public void NotifySchoolClosure(ClosureInfo info)
{
var personRepository = _factory.CreatePersonRepository();
var people = personRepository.GetAllActivePeople();
NotifySchoolClosure(info, people);
}
private void NotifySchoolClosure(ClosureInfo info, IEnumerable<Person> people)
{
using (var notificator = _factory.CreateEmailNotificator())
{
foreach (var person in people)
{
notificator.SendNotification(person.Email, FormatMessage(person, info));
}
}
}
private string FormatMessage(Person person, ClosureInfo info)
{
var messageBuilder = new StringBuilder();
messageBuilder.Append(string.Format("Hi {0}. ", person.Name));
messageBuilder.Append(info.Reason);
messageBuilder.Append(string.Format(" The school will be close from {0} to {1}",
info.StartDate.Date, info.EndDate));
messageBuilder.Append("Best regards.");
return messageBuilder.ToString();
}
}
} Colourised in 62ms
Here below the interfaces of infrastructure services.
namespace Tiskali.Core.Repository
{
public interface IPersonRepository
{
IEnumerable<Person> GetAllActivePeople();
}
}
namespace Tiskali.Core.Notificators
{
public interface IEmailNotificator : IDisposable
{
void SendNotification(string toAddress, string message);
}
} Colourised in 22ms
The concrete implementation of both of them is empty in this simple demo
namespace Tiskali.Infrastructure.Repository
{
public class PersonRepository : IPersonRepository
{
private static ICollection<Person> _people = new List<Person>() {
new Student { ID = Guid.NewGuid(), Name = "Simona", Surname = "Bianchi", Email = "simona.bianchi@tiskali.it", PIN = "00001" },
new Student { ID = Guid.NewGuid(), Name = "Marco", Surname = "Rossi", Email = "marco.rossi@tiskali.it", PIN = "01001" },
new Student { ID = Guid.NewGuid(), Name = "Paolo", Surname = "Verdi", Email = "paolo.verdi@tiskali.it", PIN = "01002" },
};
public IEnumerable<Person> GetAllActivePeople()
{
return _people.AsEnumerable();
}
}
}
namespace Tiskali.Infrastructure.Notificators
{
public class EmailNotificator : IEmailNotificator
{
public void SendNotification(string toAddress, string message)
{
}
public void Dispose()
{
}
}
} Colourised in 40ms
The abstract factory is decomposed in more interfaces in order to take full advantage of Interface Segregation Principle.
namespace Tiskali.Core.Factories
{
public interface IEmailNotificatorFactory
{
IEmailNotificator CreateEmailNotificator();
}
}
namespace Tiskali.Core.Factories
{
public interface IRepositoryFactory
{
IPersonRepository CreatePersonRepository();
}
}
namespace Tiskali.Core.Factories
{
public interface IServiceFactory : IRepositoryFactory, IEmailNotificatorFactory
{
}
} Colourised in 11ms
The concrete factory is defined in a separated project.
namespace Tiskali.Factories.Concrete
{
public class ServiceFactory : IServiceFactory
{
public virtual IPersonRepository CreatePersonRepository()
{
return new PersonRepository();
}
public virtual IEmailNotificator CreateEmailNotificator()
{
return new EmailNotificator();
}
}
} Colourised in 5ms
The application is run by a simple console application.
namespace Tiskali.ConsoleApplication
{
class Program
{
public Program()
{
RootContainer.RegisterAll(); }
static void Main(string[] args)
{
NotifyChristmasVacation();
Console.WriteLine("Press any key to terminate the program...");
Console.ReadKey();
}
static void NotifyChristmasVacation()
{
INotificationService notificationService = RootContainer.Resolve<INotificationService>();
var closureInfo = new ClosureInfo
{
StartDate = new DateTime(DateTime.Now.Year, 12, 24),
EndDate = new DateTime(DateTime.Now.Year, 12, 26),
Reason = "Christmas vacation."
};
notificationService.NotifySchoolClosure(closureInfo);
}
}
} Colourised in 23ms
The composition root takes place in RootContainer class.
namespace Tiskali.ConsoleApplication
{
static class RootContainer
{
#region Private field
private readonly static UnityContainer _container;
#endregion
static RootContainer()
{
_container = new UnityContainer();
RegisterAll();
}
internal static void RegisterAll()
{
_container.RegisterType<IEmailNotificatorFactory, ServiceFactory>();
_container.RegisterType<IRepositoryFactory, ServiceFactory>();
_container.RegisterType<IServiceFactory, ServiceFactory>();
_container.RegisterType<INotificationService, NotificationService>();
}
internal static IofS Resolve<IofS>()
{
return _container.Resolve<IofS>();
}
}
} Colourised in 19ms
Using the code
The code is delivered a single solution (VS2013) containing four projects:
Tiskali.Core: It contains the business logic as transaction script, the anemic model; the definition of the abstract factory and the interfaces of the infrastructre services;
Tiskali.Infrastructure: It contains the concrete implementation of proxies, repositories, or any technology dependent class;
Tiskali.Factories: It contains the concrete implementations of the abstract factory;
Tiskali.ConsoleApplication: It runs the application;
History
2014-05-26: First Version.