I’m currently working on an application built with the ports and adapters pattern in mind, sometimes known as Hexagonal or Onion architecture. The application has both data side adapters and client side adapters.
If you’ve not done so already, please read Alistair Cockburn’s excellent article on the pattern at:
http://alistair.cockburn.us/Hexagonal+architecture
If you want to read it later and keep reading this now then here’s a quote from the article which succinctly conveys the spirit of the pattern:
“Create your application to work without either a UI or a database so you can run automated regression-tests against the application, work when the database becomes unavailable, and link applications together without any user involvement.”
The data side adapters take the form of interfaces defined by the application to enable it to use data services without needing to know about the underlying implementation. There are a few implementations of these interfaces in the application at present including an in memory implementation, an implementation which connects to a legacy database and a version which connects to a Mongo instance. Additionally I can inject mocks for the relevant interfaces and decouple from any of these implementations. This allow me to write tests which focus on the application logic.
The client side adapters are used to expose application features to different types of consumer. There is a client side adapter which provides a web user interface using asp.net MVC and another which exposes the same features via asp.net WebApi. Within the development team it has been discussed that the core application and the WepApi are one and the same thing. Whilst this is probably true it is useful to be aware of the boundary between application logic and how that is exposed over Http. Theoretically we could write a WPF user interface that talks directly to the core application assemblies or even use Xamarin tools to create iOS or Android applications that provide a rich offline experience on mobile devices. More importantly we can write tests against application features which do not need to know or care about Http.
In many instances you’ll only have one adapter implementation that your application ever uses though you’ll probably substitute this for a mock or a stub in your tests. For my current project we envisage using multiple adapters in production. The application is multi-tenanted in the sense that somewhere between 80-100 web sites from the corporate estate could eventually use its services and each site would need to work with data from one of three data stores.
Another way to think about this is that we’re actually creating an abstraction layer that supports the features the business needs. Once we’ve abstracted the key features it shouldn’t matter which implementation provides the data storage. This gives us a nice migration path. If the adapter interfaces expose methods for exporting and importing data we should in theory be able to migrate from one data store to another. For me this approach is reminiscent of branch by abstraction (http://martinfowler.com/bliki/BranchByAbstraction.html) and strangler application (http://martinfowler.com/bliki/StranglerApplication.html).
On day zero we’ll most likely support adapters for two legacy systems for web sites which depend on data held there and we’ll also have at least one web site as a pilot using an adapter for a new vendor system. The upshot of this is that we will need to select the appropriate data side adapter based on the web site calling into our application services and pages. Enter the adapter selector:
public interface IIdmAdapterSelector { IIdmAdapter GetIdmAdapter(); }
This interface provides a single method to get an Idm (identity management) adapter.
public interface IIdmAdapter { IUserRepository GetUserRepository(); IEmailBlackList GetEmailBlackList(); IAddressRepository GetAddressRepository(); ICountryRespository GetCountryRepository(); ISubscriptionRepository GetSubscriptiontRepository(); }
And this is the adapter interface. It’s very similar in intent to an abstract factory. The interface provides methods for getting a number of interfaces used by our application for identity management. Each concrete adapter will provide adapter specific implementations of the various interfaces.
We manage the mapping of adapters to web sites through a configuration class:
public class Configuration { public void RegisterIdmAdapter<T>(string siteId) where T : IIdmAdapter { } public Type AdapterFor(Context context) { } }
The configuration class provides a method for registering adapters against a site id and another method for retrieving an adapter by passing in an application defined context class. Notice that we register the type of the adapter rather than an instance. Also notice that whilst we use site id to register an adapter type we pass in a context instance to retrieve the type. As we’ll see, the context contains the site id so we can resolve the adapter type based on that but we can also take into account any additional contextual information which might be needed to configure the adapter specific implementations. But for now the context is based on the site id only:
public class Context { public string SiteId { get; set; } }
The configuration class can be set up by any mechanism we choose – perhaps from a web.config, a database, whatever is most appropriate. Initially we’ve decided that our web applicaton is responsible for setting up the configuration. As we’re using OWIN the entry point is the Startup class. We’ve added a method to get the configuration which we’ll be using and for simplicity it is hardcoded:
protected virtual Configuration GetConfiguration() { var Configuration = new Configuration(); Configuration.RegisterIdmAdapter<InMemoryIdmAdapter>("siteA"); Configuration.RegisterIdmAdapter<SqlIdmAdapter>("siteB"); return Configuration; }
Next we need to consider how the application will get the site id. We’ve seen how this will be part of a context object but how does it get resolved? Let’s take a look:
public interface IEstablishContext { Context GetContext(); }
This interface provides a method to get a context object. We’ve got a couple of implementations. The web application will use a WebContextResolver. This simply implements GetContext by looking for the site id in the HttpContext perhaps in a custom header or maybe the querystring. This then gets set on an instance of the application defined context object. The other implementation is an NUnitTestContextResolver. This works in a very similar way but instead of looking in the HttpContext it looks in the Nunit TestContext for user defined properties (such as our site id) which can be set per test.
We specify which implementation should be used in our Startup class:
protected virtual Type GetContextResolverType() { return typeof(WebContextResolver); }
We’ve opted to keep the adapter selection out of our controller classes preferring them to work only with the interfaces they require. Instead we’ll rely on our container configuration to determine which adapter to use and wire things up accordingly. We’ve defined a method to configure our container (autofac but it could be any other DI container). The adapter selector which we discussed earlier is registered with the container. It has three constructor dependencies:
public IdmAdapterSelector(IEstablishContext contextResolver, Configuration Configuration, IEnumerable<IIdmAdapter> adapters) { this.contextResolver = contextResolver; this.Configuration = Configuration; this.adapters = adapters; }
It is very simple to resolve the correct adapter for the context:
public IIdmAdapter GetIdmAdapter() { var context = contextResolver.GetContext(); var adapterType = Configuration.AdapterFor(context); var adapter = adapters.FirstOrDefault(x => x.GetType() == adapterType); return adapter; }
The controllers depend on the interfaces provided by the adapter so we need to register those with the container too:
private static void RegisterAdapters(ContainerBuilder builder) { var adapter = new Func<IComponentContext, IIdmAdapter> (c => c.Resolve<IIdmAdapterSelector>().GetIdmAdapter()); builder.Register(c => adapter(c).GetUserRepository()); builder.Register(c => adapter(c).GetEmailBlackList()); builder.Register(c => adapter(c).GetAddressRepository()); builder.Register(c => adapter(c).GetCountryRepository()); builder.Register(c => adapter(c).GetSubscriptiontRepository()); }
So what we’ve done here is register each method of the adapter as the way to resolve the various interfaces it provides. When we set up our container from our Startup class we pass it the configuration and context like this:
var dependancyContainer = appBuilder.UseAutofac(config, BindingOverrides);
The binding overrides property uses the virtual methods we described earlier to populate the binding overrides instance:
protected BindingOverrides BindingOverrides { get { var BindingOverrides = new BindingOverrides(); BindingOverrides.ContextResolverType = GetContextResolverType(); BindingOverrides.Configuration = GetConfiguration(); return BindingOverrides; } }
This in turn means we can subclass our OWIN Startup to create a TestStartup which swaps out the WebContextResolver for the NUnitTestContextResolver and provides an alternate test configuration class:
public class TestStartup : Startup { protected override Type GetContextResolverType() { return typeof(NUnitTestContextResolver); } protected override Configuration GetConfiguration() { var Configuration = new Configuration(); Configuration.RegisterIdmAdapter<InMemoryIdmAdapter>("testId"); return Configuration; } }
This allows us to write tests that exercise our web app in memory using test settings:
WebApp.Start<TestStartup>(apiAddress);
We can set our context through attributes on our unit test classes:
[TestFixture] [Property("siteId", "testId")] public class UserControllerVerifyTests : UserControllerInMemoryTests { … }
We can set these properties at test level too allowing us to vary the context for each test.
We need to make use of some marker interfaces to ensure that the container doesn’t get conflicting registrations. If we take the example of the user repository consider the following:
public class UserRepository : IUserRepository { … }
And:
builder.Register(c => adapter(c).GetUserRepository());
The UserRepository class will get registered as a type which can be provided when the container is asked to resolve IUserRepository. But we’ve also registered IUserRepository with the GetUserRepository method of the adapter. The implementation of that method will need to provide an adapter specific instance. In order to ensure that we can resolve the correct instance we can have it implement a marker interface like this:
public class UserRepository : IInMemoryUserRepository { … }
And:
public interface IInMemoryUserRepository : IUserRepository { }
This way the adapter’s GetUserRepository method is registered as the way to resolve IUserRepository and the adapter’s constructor requests an IInMemoryUserRepository which can be auto registered with the container by scanning the assemblies. This interface does nothing other than inherit from IUserRepository and serves purely as a marker to help us make effective use of the container.
This last part however is very important. If we implement interfaces from the application core directly in our adapter assemblies we will likely encounter unpredictable results. But hopefully as we add new dependencies to our constructors we’ll notice the use of marker interfaces and remember to follow that convention.
In summary, we’ve discussed the ports and adapters approach also known as Hexagonal or Onion architecture. We’ve discussed how it helps achieve a clean separation of concerns and enables easy testing and inter-application communication. We then looked at how to select the correct adapter for a multi-tenanted scenario by making use of an application context and an application configuration. We also saw how we were able to swap out context resolvers and configurations by overriding our OWIN Startup class which enabled us to more easily test our web application. Additionally we explored how to force our container to resolve the correct adapter specific implementations rather than require consuming classes to have an awareness of the mechanism. Finally we made use of marker interfaces to control how the container resolves interfaces with adapter specific implementations.