Bootstrap Your App! : API
Alright so now that we have a basic domain set up and ready and a data-access layer waiting to spring into action we can finally open our app up to the world.
This is one of the primary goals of domain driven design, the idea that our business rules and logic are self contained and independent of the ways we will access and persist the data they work with and generate and the API layer specifies how we are allowed to interact with the domain from any given interface.
Bootstrapping
We will need to install dependecies from our API assembly and that will require a container installer:
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Classes.FromThisAssembly().Pick()
.WithService.DefaultInterfaces()
);
}
And this will need to be installed in the bootstrapper:
public static IWindsorContainer Init()
{
var container = Domain.DI.Bootstrapper.BootstrapContainer();
container.Install(FromAssembly.This());
container.Install(FromAssembly.Containing<DataAccessContainerInstaller>());
return container;
}
As you can see, although the core domain can be bootstrapped in isolation, in reality the entry point for our application will be the API bootstrapper. Here we will spin up the core, install any dependecies we need to support the API (in this case that includes the data-access project) and return the container to the caller.
We spin up the core first to allow it to register any dependencies it might need and to do its own bootstrapping and then pass it outwards through the layers. This allows us to register all the dependencies in the app in a single container while allowing each layer to register the things it requires internally.
The API
Here is a simple example to demonstrate how the API would be used, the interface:
public interface ITestApi
{
TestEntityModel LoadEntity(Guid id);
List<TestEntityModel> LoadAll();
TestEntityModel AddEntity(TestEntityModel newModel);
TestEntityModel SetEntityName(Guid id, string newName);
}
And the implementation:
public class TestApi : ITestApi
{
private readonly ITestModelMapper mapper;
private readonly IRepository<TestEntity, Guid> testRepository;
public TestApi(
ITestModelMapper mapper,
IRepository<TestEntity, Guid> testRepository
)
{
this.mapper = mapper;
this.testRepository = testRepository;
}
public TestEntityModel LoadEntity(Guid id)
{
var testEntity = this.testRepository.Get(id);
var model = this.mapper.MapToModel(testEntity);
return model;
}
public List<TestEntityModel> LoadAll()
{
var testEnities = this.testRepository.GetAll();
var models = this.mapper.MapToModel(testEnities);
return models;
}
public TestEntityModel AddEntity(TestEntityModel newModel)
{
TestEntity newEntity = new TestEntity();
var testEntity = this.mapper.MapToDomain(newEntity, newModel);
var savedEntity = this.testRepository.Save(testEntity);
return this.mapper.MapToModel(savedEntity);
}
public TestEntityModel SetEntityName(Guid id, string newName)
{
var entity = this.testRepository.Get(id);
entity.SetName(newName);
this.testRepository.Save(entity);
return this.mapper.MapToModel(entity);
}
}
As you can see we want to group logical sets of operations together into API interfaces. I like to group them by area's of the application, such as payments or user administration, and they can often represent operations on an aggregate root.
The API interfaces we provide will control the way any interaction layer works with our application and this allows us to centralize operations like validating and persisting data in a consistent way. If the accessor is a WPF UI, a website, a console app or a set of web services the data will be accessed the same way and all business rules will apply. If we need to make changes to the core application we will need to make them in only one place and the change will seemlessly apply to all callers.
As you can see in the API above the SetEntityName method represents the typical pattern we will follow for operations on our application. A call will come into the API requesting an operation to be performed and this will in turn load the entities from the data store, perform the operation and either return the changed entity or a service result which reports the result of the operation.
A Note On Mappers
As you can see in the code above, I have used a simple mapper to map my domain entities to models. Mapping your domain entities into some other models when they leave the API is essential. If you allow domain entities to escape the API you lose all control over them and thus defeat the purpose of the API layer and forfeit it's ability to provide a consistent interface to your application. Another possible unintended consequence of this is that most serializers will follow all links in objects which could lead to half of your domain being serialized and sent out over the wire.
The interface the mapper provides is very simple:
public interface ITestModelMapper
{
TestEntityModel MapToModel(TestEntity testEntity);
TestEntity MapToDomain(TestEntity domainEntity, TestEntityModel testEntity);
List<TestEntityModel> MapToModel(IEnumerable<TestEntity> testEnities);
}
And its implementation is also very simple:
public TestEntityModel MapToModel(TestEntity testEntity)
{
return new TestEntityModel
{
Id = testEntity.Id,
DisplayName = testEntity.DisplayName,
Name = testEntity.Name,
OtherValue = testEntity.OtherValue
};
}
public TestEntity MapToDomain(TestEntity domainEntity, TestEntityModel testEntity)
{
domainEntity.DisplayName = testEntity.DisplayName;
domainEntity.Name = testEntity.Name;
domainEntity.OtherValue = testEntity.OtherValue;
return domainEntity;
}
public List<TestEntityModel> MapToModel(IEnumerable<TestEntity> testEnities)
{
return testEnities.Select(this.MapToModel).ToList();
}
}
Since this is a very simple entity I could quite easily use automapper to do the mapping from domain entity to model but by the same token it takes very little effort to write some quick mapping code for this operation. When mapping from model to domain entity, however, it is best to be explicit with your mapping code as an automapper configuration for these types of operation is usually over complicated and fraught with pitfalls.
Now we have an API to work with our domain and to persist data into our chosen data-store. In the next post in the series we'll look at working with the application from different interaction projects so check back soon!