Domain Driven Design with Web API revisited Part 18: tests and conclusions
October 5, 2015 2 Comments
Introduction
In the previous post we continued working on our Web API 2 layer and added two new controller actions: POST which handles both updates and insertions and DELETE which is responsible for deleting load tests. Our first draft of the DDD load testing demo is actually finished at this point. All that’s left is testing the POST and DELETE functions of the Web API.
We’ll do that in this post. We’ll also write some conclusions.
Testing
There are various tools out there that can generate any type of HTTP calls for you where you can specify the JSON inputs, HTTP headers etc. However, we’re programmers, right? We don’t need any extra tools, we can write a simple one ourselves! Don’t worry, I only mean a GUI-less throw-away application that consists of a few lines of code, not a complete Fiddler.
Fire up Visual Studio and create a new Console application. I called mine PostDeleteActionTester but it doesn’t matter, it’s not part of the main DDD demo solution.
We’ll be sending the InsertUpdateLoadtestViewModel objects to the Web API 2 in JSON format. We’ll need a JSON library for that and we’ll go for the #1 JSON.NET library out there. Add the following NuGet package to the console app:
We’ll need to serialise InsertUpdateLoadtestViewModel objects from the console application. The easiest way to do that is to insert that same class into the console app as well but without the conversion function, we won’t need that. Add the following 2 objects to the console tester app:
public class InsertUpdateLoadtestViewModel { public Guid Id { get; set; } public string AgentCity { get; set; } public string AgentCountry { get; set; } public string CustomerName { get; set; } public string EngineerName { get; set; } public string LoadtestTypeShortDescription { get; set; } public string ProjectName { get; set; } public string ScenarioUriOne { get; set; } public string ScenarioUriTwo { get; set; } public string ScenarioUriThree { get; set; } public StartDate StartDate { get; set; } public int UserCount { get; set; } public int DurationSec { get; set; } }
public class StartDate { public int Year { get; set; } public int Month { get; set; } public int Day { get; set; } public int Hour { get; set; } public int Minute { get; set; } public string Timezone { get; set; } }
We’ll get the valid time zone strings using the list available on this Microsoft documentation page.
Next add a reference to the System.Net.Http library version 4.0.0.0. We’ll need it in order to send HTTP requests to the DDD web.
We’ll also need to take note of the exact URL of the local load testing demo project. Open the WebSuiteDDD.Demo solution and start the project. Take note of the exact localhost URL, such as “http://localhost:3522”. Then insert the following private variable to Program.cs in the console tester app:
private static Uri _serviceUri = new Uri("http://localhost:3522/loadtests");
Testing the POST action method
The following method will test the Post action method of the Web API:
private static void RunPostOperation() { HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, _serviceUri); requestMessage.Headers.ExpectContinue = false; List<InsertUpdateLoadtestViewModel> vms = new List<InsertUpdateLoadtestViewModel>(); InsertUpdateLoadtestViewModel first = new InsertUpdateLoadtestViewModel() { AgentCity = "Seattle", AgentCountry = "USA", CustomerName = "OK Customer", DurationSec = 600, EngineerName = "Jane", LoadtestTypeShortDescription = "Stress test", ProjectName = "Third project", ScenarioUriOne = "http://www.hello.com", StartDate = new StartDate() { Year = 2015, Month = 8, Day = 22, Hour = 15, Minute = 30, Timezone = "E. Europe Standard Time" }, UserCount = 30 }; InsertUpdateLoadtestViewModel second = new InsertUpdateLoadtestViewModel() { AgentCity = "Frankfurt", AgentCountry = "Germany", CustomerName = "Great customer", DurationSec = 20, EngineerName = "Fred", LoadtestTypeShortDescription = "Capacity test", ProjectName = "First project", ScenarioUriOne = "http://www.goodday.com", ScenarioUriTwo = "http://www.goodevening.com", StartDate = new StartDate() { Year = 2015, Month = 8, Day = 21, Hour = 16, Minute = 00, Timezone = "Nepal Standard Time" }, UserCount = 50 }; vms.Add(first); vms.Add(second); string jsonInput = JsonConvert.SerializeObject(vms); requestMessage.Content = new StringContent(jsonInput, Encoding.UTF8, "application/json"); HttpClient httpClient = new HttpClient(); httpClient.Timeout = new TimeSpan(0, 10, 0); Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None); HttpResponseMessage httpResponse = httpRequest.Result; HttpStatusCode statusCode = httpResponse.StatusCode; HttpContent responseContent = httpResponse.Content; if (responseContent != null) { Task<String> stringContentsTask = responseContent.ReadAsStringAsync(); String stringContents = stringContentsTask.Result; Console.WriteLine("Response from service: " + stringContents); } Console.ReadKey(); }
You’ll see that we attempt to add 2 load tests. You’ll also notice how we serialise the view models using Json.NET and add it to the payload of the request. We deliberately specify a long timeout of 10 minutes so that you can step through the code in the demo project without generating a timeout exception in the tester application. Feel free to adjust the properties of the test values above the way you like.
You can now start the DDD demo first. Then call RunPostOperation() from the Main function and run the tester application. It can be a good idea to set a breakpoint within the Post method of LoadtestsController so that you can go through the code step by step and see how the different projects and classes are connected.
Depending on what you send as test values you’ll get different responses. Here’s a successful insertion:
Here’s an example showing a double-booking:
Here comes a validation error with a too short test duration:
Finally here’s an exception message about a wrong start date:
Testing the DELETE action method
Testing the Delete action method is even easier. The following method in Program.cs will be enough:
private static void RunDeleteOperation() { HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Delete, string.Concat(_serviceUri, "/e3d4012c-50f6-4a58-af3a-5debfc40a01d")); requestMessage.Headers.ExpectContinue = false; HttpClient httpClient = new HttpClient(); httpClient.Timeout = new TimeSpan(0, 10, 0); Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None); HttpResponseMessage httpResponse = httpRequest.Result; HttpStatusCode statusCode = httpResponse.StatusCode; HttpContent responseContent = httpResponse.Content; if (responseContent != null) { Task<String> stringContentsTask = responseContent.ReadAsStringAsync(); String stringContents = stringContentsTask.Result; Console.WriteLine("Response from service: " + stringContents); } Console.ReadKey(); }
As you can see we simply attach the ID to the end of the loadtests URL. You can then call this method from Main, add a breakpoint to the Delete method of LoadtestsController and test the deletion chain. If you attach a valid load test ID to the URL then you should simply get a message saying “Deleted”. Otherwise you’ll be given an exception message:
Conclusions
That actually completes the revised DDD project. Most of the conclusions from the original DDD project are still valid. Most importantly we still have a solution with an independent domain layer. Also, the technology-driven EF layer is not referenced directly by any other layer in the solution.
If we start from the top then we see that the web layer talks to the service layer through the ITimetableService interface. The ITimetableService interface uses RequestResponse objects to communicate with the outside world. Any implementation of ITimetableService will communicate through those objects so their use within LoadtestsController is acceptable as well.
The application service layer has a reference to the SharedKernel layer – through the DDD-related abstractions – and the Domain layer. In a full-blown project there will be more links to the SharedKernel and possibly a separate common Infrastructure layer – logging, caching, authentication etc. – but as longs as you hide those concerns behind abstractions you’ll be fine. The Loadtest repository is only propagated in the form of interfaces – ITimetableRepository and ITimetableViewModelRepository. Otherwise the domain objects are allowed to bubble up to the Service layer as they are the central elements of the application.
The Domain layer has a dependency on the SharedKernel layer through abstractions such as EntityBase and IAggregateRoot. That’s all fine and good.
The repository layer has a reference to the SharedKernel layer – again through abstractions such as IAggregateRoot – and the Domain layer. Notice that the domain layer does not depend on the repository but the repository depends on the domain layer.
I think the most significant property of the demo is that no single layer is directly or indirectly dependent on the concrete repository layer. You can test and unload the project from the solution – right-click, select Unload Project. There will be a broken reference in the Web layer that only exists for the sake of StructureMap, but otherwise the solution survives this “amputation”. We have successfully hidden the most technology-driven layer behind abstractions. You can then instruct StructureMap to use a different data store implementation instead. You can even switch between two or more different implementations to test how the different technologies work before you go for a specific one in your project. Of course writing those implementations may not be a trivial task but switching from one technology to another will certainly be. I’m planning to extend this basic project with a different data store namely MongoDb.
The domain layer is still the central one in the solution. The services, Web API and data access layers directly reference it.
That’s all folks for now. It’s been a long journey of 18 posts and we’re still only scratching the surface of DDD. I hope you have learned new things and can use this solution in some way in your own project. I’m planning to get out a couple of extensions to this demo which I’ll provide the link to as they become available.
Here’s the first extensions: messaging.
View the list of posts on Architecture and Patterns here.
Thank you for this series. It’s been very helpful trying get a firmer understanding of domain driven design and how it plays together with a real system like WebAPI.
Is there a download somewhere for the complete Visual Studio solution? Seeing it all together in one place and how the dependencies work would be very helpful.
Thanks!
Hi Nick,
Thanks for your comment. You can always check out the Github page available from the top menu. Here’s the repository for the updated DDD project:
https://github.com/andras-nemes/ddd-updated-skeleton-project
//Andras