Consuming a Domain Model: 10 Years of .Net Compressed into Weeks #10
on
This post is part of a blog series ASP.Net 10 Years On. Even though this is part of a series I have tried to make each post standalone. This post looks at how a domain model can be consumed via a service layer.
We have the following domain model that needs to be consumed by components of the application such as UIs and data storage methods:
We will focus on how we can get a list of competitions returned to our application as a starting point. The Competition class is below:
The architecture will be comprised of the following components:
The Application layer that sits between the website and API acts as a barrier that hides away the complexities of the domain logic and interacts with the data storage. Think of this as a line in the sand between any implementation detail of the application and anything that consumes it e.g. a web front end, desktop UI, web service etc.
In this application we are supporting an MVC website and we also need to expose some form of web service over HTTP so that 3rd party apps can be built to interact with the application.
To avoid any duplication of code our website and API layer must contain minimal logic and only implementation details that are directly related to its own implementation.
Creating a Service Layer
Let's get started on our application by creating a new project called Services. We are using the terminology of Service for the application layer as this article titled Service Layer by Martin Fowler presents this as a convention. I see the name Service used more regularly so I'm going to follow that convention. Another name for this style of architecture is called Onion Architecture.
To get started let's begin with a test:
[TestMethod]
public void GetCompetitionsReturnsAListOfOpenCompetitions()
{
// Arrange
CompetitionService service = new CompetitionService();
// Act
GetCompetitionsResponse response = service.GetCompetitions();
// Assert
Assert.AreEqual(10, response.Competitions.Count());
}
I write just enough code to make the test pass:
public class CompetitionService
{
public GetCompetitionsResponse GetCompetitions()
{
GetCompetitionsResponse response = new GetCompetitionsResponse();
List<CompetitionItem> items = new List<CompetitionItem>();
for (int i = 0; i < 10; i++)
{
items.Add(new CompetitionItem());
}
response.Competitions = items;
return response;
}
}
The CompetitionItem class is a Plain Old CLR Object (POCO) I want this class to have no logic and only property setters and getters.:
public class CompetitionItem
{
public string Question { get; set; }
}
To take this example closer to a working implementation I need some method of a data store. I don't really know what that's going to be yet, so for now I will use an interface to represent this dependency as we've done in previous posts. We will cover this later, but for now all we need to think about is the below interface:
public interface ICompetitionRepository
{
IEnumerable<Competition> GetCompetitions();
}
I can now revisit the service class to wire in the repository:
public class CompetitionService
{
ICompetitionRepository _repository = null;
public CompetitionService(ICompetitionRepository repository)
{
_repository = repository;
}
public GetCompetitionsResponse GetCompetitions()
{
GetCompetitionsResponse response = new GetCompetitionsResponse();
IEnumerable<Competition> competitions = _repository.GetCompetitions();
// mapping code
List<CompetitionItem> items = new List<CompetitionItem>();
for (int i = 0; i < competitions.Count(); i++)
{
items.Add(ConvertToItem(competitions.ElementAt(i)));
}
response.Competitions = items;
return response;
}
private static CompetitionItem ConvertToItem(Competition competition)
{
CompetitionItem item = new CompetitionItem();
item.Question = competition.Question;
return item;
}
}
Since I've added a parameter to the constructor of the service class I now need to revisit my test:
[TestClass]
public class CompetitionServiceTests
{
[TestMethod]
public void GetCompetitionsReturnsAListOfOpenCompetitions()
{
// Arrange
CompetitionService service = new CompetitionService(new StubCompetitionRepository());
// Act
GetCompetitionsResponse response = service.GetCompetitions();
// Assert
Assert.AreEqual(10, response.Competitions.Count());
Assert.AreEqual("Question #1", response.Competitions.ElementAt(0).Question);
Assert.AreEqual("Question #10", response.Competitions.ElementAt(9).Question);
}
private class StubCompetitionRepository : ICompetitionRepository
{
public IEnumerable<Competition> GetCompetitions()
{
List<Competition> list = new List<Competition>();
for (int i = 1; i < 11; i++)
{
list.Add(new Competition() { Question = "Question #" + i.ToString() });
}
return list;
}
}
}
Note the usage of the stub class that represents the repository.
Creating the API Layer
Here is a controller class for the HTTP REST Api. We're going to use the ASP.Net Web Api as a lightweight HTTP layer that will use JSON for messaging:
public class CompetitionController : ApiController
{
ICompetitionService _competitionService;
public CompetitionController(ICompetitionService competitionService)
{
_competitionService = competitionService;
}
// GET api/competition
public IEnumerable<CompetitionItem> Get()
{
return _competitionService.GetCompetitions().Competitions;
}
}
Here is a test for this controller:
[TestClass]
public class CompetitionControllerTests
{
[TestMethod]
public void GetCompetitionReturnsItems()
{
// Arrange
CompetitionController controller = new CompetitionController(new StubCompetitionService());
// Act
IEnumerable<CompetitionItem> response = controller.Get();
// Assert
Assert.AreEqual(10, response.Count());
}
private class StubCompetitionService : ICompetitionService
{
public GetCompetitionsResponse GetCompetitions()
{
GetCompetitionsResponse response = new GetCompetitionsResponse();
List<CompetitionItem> list = new List<CompetitionItem>();
for (int i = 1; i < 11; i++)
{
list.Add(new CompetitionItem() { Question = "Question #" + i.ToString() });
}
response.Competitions = list;
return response;
}
}
}
Creating the Web Layer
We also need to create a desktop browser version of this application using ASP.Net MVC which will result in another project:
public class CompetitionController : Controller
{
ICompetitionService _competitionService;
public CompetitionController(ICompetitionService competitionService)
{
_competitionService = competitionService;
}
//
// GET: /Competition/
public ActionResult Index()
{
CompetitionViewModel viewModel = new CompetitionViewModel();
viewModel.Competitions = _competitionService.GetCompetitions().Competitions;
return View("Index", viewModel);
}
}
And of course, a test:
[TestClass]
public class ComeptitionControllerTests
{
[TestMethod]
public void IndexReturnsViewModelWithCompetitions()
{
// Arrange
CompetitionController controller = new CompetitionController(new StubCompetitionService());
// Act
var result = controller.Index() as ViewResult;
// Assert
Assert.AreEqual("Index", result.ViewName);
Assert.IsInstanceOfType(result.Model, typeof(CompetitionViewModel));
Assert.AreEqual(10, ((CompetitionViewModel)result.Model).Competitions.Count());
}
private class StubCompetitionService : ICompetitionService
{
public GetCompetitionsResponse GetCompetitions()
{
GetCompetitionsResponse response = new GetCompetitionsResponse();
List<CompetitionItem> list = new List<CompetitionItem>();
for (int i = 1; i < 11; i++)
{
list.Add(new CompetitionItem() { Question = "Question #" + i.ToString() });
}
response.Competitions = list;
return response;
}
}
}
Mapping Entities
In the service method to get the competitions, you may have noticed some ugly code:
// mapping code
List<CompetitionItem> items = new List<CompetitionItem>();
for (int i = 0; i < competitions.Count(); i++)
{
items.Add(ConvertToItem(competitions.ElementAt(i)));
}
This code takes our competition domain entity and converts into a simple POCO. This is necessary since we don't want to send our complex domain objects beyond the service layer. The reason for only dealing in POCOs is that we are likely to encounter scenarios where we wish to serialise objects into some kind of format for transportation e.g. JSON, XML or Binary. Trying to serialise objects with potentially complex inheritance structures and interfaces can cause problems.
An important design feature is that I extracted these POCOS into their own class to be shared across multiple projects. Not doing so would mean I would have to duplicate these types of classes for each web or API layer resulting in shotgun surgery style changes when adding a new property to the main domain entity as I would have to replicate the change N number of times across projects.
Here is a class diagram of this structure that indicates the dependencies:
The key point is that there is no dependency between our controller classes and the domain model. There is the dependency of the class CompetitionItem between the API, web, and services projects. I've added these poco items into their own project called ReadModel, which indicates they are simple read only objects. Not doing this would mean I would need to duplicate such POCOs in my API and web projects as well as the mapping code for view models etc. NB I'm using the name suffix of Item as a convention so that I can differentiate classes when I'm working e.g. if it was called Competition I would have two classes called Competition!
Now that we know why we have this extra class, let's go back to the ugly code. What I really want is for the service code to read something along the lines of:
public GetCompetitionsResponse GetCompetitions()
{
GetCompetitionsResponse response = new GetCompetitionsResponse();
IEnumerable<Competition> competitions = _repository.GetCompetitions();
response.Competitions = competitions.MapToItems();
return response;
}
There is a very handy tool for taking care of this ugly code and saving us the headache of changing this every time we add a new property, AutoMapper introduces itself as:
AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?
After fetching AutoMapper from Nuget my extension method now reads as:
public static class CompetitionMapper
{
public static IEnumerable<CompetitionItem> MapToItems(this IEnumerable<Competition> competitions)
{
return Mapper.Map<IEnumerable<Competition>, IEnumerable<CompetitionItem>>(competitions);
}
}
NB there is some configuration required for this to work, please see the documentation.
Much improved but we have introduced a barrier for our unit tests. As the saying goes, never test objects you don't own, how do we get around this?
This post outlines a nice solution to this problem by creating an IMapper interface which we pass into the constructor of the service class. I like this solution so we are going to implement this:
response.Competitions = _mapper.Map<IEnumerable<Competition>, IEnumerable<CompetitionItem>>(competitions);
The first test we created for the service layer has to be changed a little from before, rather than repeat the whole test I will just show the line that has been changed:
CompetitionService service = new CompetitionService(new StubCompetitionRepository(), new StubMapper());
Note we had to create another stub class called StubMapper(). Here is the code for that:
private class StubMapper : IMapper
{
public TItem Map<TDomain, TItem>(TDomain domain) where TItem : class
{
List<CompetitionItem> list = new List<CompetitionItem>();
for (int i = 1; i < 11; i++)
{
list.Add(new CompetitionItem() { Question = "Question #" + i.ToString() });
}
return list as TItem;
}
}
Using a Mocking Framework
The stub methods we have been creating are starting to accumulate. I've had to write very similar and potentially repetitive code that could quite easily turn into a maintenance headache. We need to find a better way. Mocking frameworks are that better way.
We're going to make use of Moq in these examples which is an open source framework. The source code published as part of this series cannot rely on any commercial products, however there are paid mocking frameworks such as JustMock.
Such frameworks allow us to dynamically create stub implementations without having to create a private calss that that implements the interface as I've been doing in the code examples. Let's see some code:
[TestClass]
public class CompetitionServiceTests
{
[TestMethod]
public void GetCompetitionsReturnsAListOfOpenCompetitions()
{
// Arrange
Mock<ICompetitionRepository> respository = new Mock<ICompetitionRepository>();
respository.Setup(r => r.GetCompetitions()).Returns(GetStubCompetitions());
Mock<IMapper> mapper = new Mock<IMapper>();
mapper.Setup(m => m.Map<IEnumerable<Competition>, IEnumerable<CompetitionItem>>(It.IsAny<IEnumerable<Competition>>()))
.Returns(GetStubCompetitionItems);
CompetitionService service = new CompetitionService(respository.Object, mapper.Object);
GetCompetitionsResponse response = null;
// Act
response = service.GetCompetitions();
// Assert
Assert.AreEqual(10, response.Competitions.Count());
Assert.AreEqual("Question #1", response.Competitions.ElementAt(0).Question);
Assert.AreEqual("Question #10", response.Competitions.ElementAt(9).Question);
}
private static List<Competition> GetStubCompetitions()
{
List<Competition> list = new List<Competition>();
for (int i = 1; i < 11; i++)
{
list.Add(new Competition() { Question = "Question #" + i.ToString() });
}
return list;
}
private static List<CompetitionItem> GetStubCompetitionItems()
{
List<CompetitionItem> list = new List<CompetitionItem>();
for (int i = 1; i < 11; i++)
{
list.Add(new CompetitionItem() { Question = "Question #" + i.ToString() });
}
return list;
}
}
You will see I still have some stub methods that return the right number of items. The key benefit when using mocking frameworks is that I don't have to implement the class and I don't have to update my test stubs in the event I change an interface.
Here's another example of a stub class we used in a previous test:
private class StubPossibleAnswers : IPossibleAnswers
{
public IEnumerable<PossibleAnswer> Answers
{
get { throw new NotImplementedException(); }
}
public PossibleAnswer CorrectAnswer
{
get { throw new NotImplementedException(); }
}
public void Add(PossibleAnswer possibleAnswer)
{
throw new NotImplementedException();
}
public ValidationErrors ValidationErrors
{
get
{
if (IsValid)
{
return new ValidationErrors();
}
else
{
// add one row to put this in an invalid state
ValidationErrors errors = new ValidationErrors();
errors.Add(new ValidationError("property", "required"));
return errors;
}
}
}
public bool IsValid { get; private set; }
public void SetValidState(bool isValid)
{
this.IsValid = isValid;
}
}
Here is the original test. Note we had to implement the full class when all we are concerned with this test is setting the outcome of the call to IsValid.
[TestMethod]
public void CompetitionIsValid()
{
// Arrange
StubPossibleAnswers answers = new StubPossibleAnswers();
answers.SetValidState(true);
Competition competition = new Competition(answers, null);
competition.Question = "Who is Luke Skywalker's father?";
competition.ClosingDate = DateTime.Now.AddMonths(1);
competition.CompetitionKey = "WINPRIZE";
// Act
bool isValid = competition.IsValid;
// Assert
Assert.IsTrue(isValid);
}
By using a mocking framework we can delete this stub class and make a small change to our test:
[TestMethod]
public void CompetitionIsValid()
{
// Arrange
Mock<IPossibleAnswers> answers = new Mock<IPossibleAnswers>();
answers.Setup(a => a.IsValid).Returns(true);
Competition competition = new Competition(answers.Object, null);
competition.Question = "Who is Luke Skywalker's father?";
competition.ClosingDate = DateTime.Now.AddMonths(1);
competition.CompetitionKey = "WINPRIZE";
// Act
bool isValid = competition.IsValid;
// Assert
Assert.IsTrue(isValid);
}
This is much improved. We can get more done with less code. Granted we've introduced a little complexity with the mocking framework, but as we've just demonstrated that the payoff is worth the investment in learning how to use such a framework.
You can see the source code for the group of changes at this Github branch.
In the next post we will start working on our data repository. See the RSS subscription links at the bottom of the page to follow this series.
Further reading: Service Layer Guidelines, MSDN.