Domain Driven Design with Web API revisited Part 18: tests and conclusions

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:

Add JsonNet NuGet package to console application

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:

Successful load test insertion via tester console application

Here’s an example showing a double-booking:

Example of failed load test insertion due to double booking of agents and engineers

Here comes a validation error with a too short test duration:

Validation error when trying to add load test with too short duration

Finally here’s an exception message about a wrong start date:

Wrong date time given in JSON when inserting new load test

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:

Failed to remove non-existent load test

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.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

2 Responses to Domain Driven Design with Web API revisited Part 18: tests and conclusions

  1. Nick says:

    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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: