Domain Driven Design with Web API revisited Part 12: testing the load testing database context

Introduction

In the previous post we created our data store using EF code-first techniques. We also tested our overall database context by running a number of test queries to view and insert a couple of database objects.

We’re done with the data store but we’re not done with our load test context yet. Recall that the load test bounded context has its own well defined database context which is somewhat reduced compared to the full context. We need to test how it works.

Load test database context

In the previous post we inserted a C# console application called DemoDatabaseTester to our demo solution in order to test the overall web suite database context. In order to test the load testing context you can add project references to the following:

  • WebSuiteDemo.Loadtesting.Domain
  • WebSuiteDemoLoadtest.Repository.EF
  • WebSuiteDDD.SharedKernel

Add a new class to the tester app called LoadtestingContextService. Let’s first test whether we can populate the Agent domain objects from the database using the LoadTestingContext object. Remember that the Agent domain is not the same as the Agent database object. The database variant has a couple more properties which we don’t care about in the load testing bounded context.

Insert the following method into LoadtestingContextService.cs:

public void TestLoadtestingContext()
{
	LoadTestingContext loadTestingContext = new LoadTestingContext();
	List<Agent> domainAgents = loadTestingContext.Agents.ToList();
	foreach (Agent agent in domainAgents)
	{
		Debug.WriteLine(string.Format("Id: {0}, city: {1}, country: {2}", agent.Id, agent.Location.City, agent.Location.Country));
	}
}

Make sure that Agent refers to the domain agent, i.e. you’ll need the following using statements:

using WebSuiteDemo.Loadtesting.Domain;
using WebSuiteDemo.Loadtesting.Repository.EF;

Let’s call this from Main in Program.cs. You can either delete or comment out the code we produced in the previous post:

LoadtestingContextService domainService = new LoadtestingContextService();
domainService.TestLoadtestingContext();

Run the code and… …you should get an exception saying that the entity has no key defined. Hmm, what could that be? We do have an ID defined in the EntityBase class:

private readonly IdType _id;

public EntityBase(IdType id)
{
	_id = id;
}

public IdType Id
{
	get
	{
		return _id;
	}
}		

What’s wrong with that? It turns out that there are multiple problems with mapping the database object ID with the domain ID. We called the ID in the database object Agent “Id”, whereas it’s called _id in the above case. Another problem is that EntityFramework needs a setter so that EF can set its ID. It’s enough to set the access level to private for that purpose. Locate EntityBase.cs and replace the above code to this:

public IdType Id { get; private set; }

public EntityBase(IdType id)
{
	Id = id;
}			

Hang on a minute

We mentioned before in this series and the original one on DDD that domain objects should not concern themselves with data store related operations. This idea is called persistence ignorance (PI) However, you’ll realise that we have just modified the domain to accommodate a technology layer requirement. The change is not dramatic and does not really affect the accessibility rules set out by DDD, but still, it’s a data store related modification. EntityFramework will use reflection to populate the properties of the entity. As “Id” doesn’t match “_id” we got the exception about the undefined ID field for an entity.

Note that EntityFramework probably has a built-in mapping mechanism to declare that “Id” should be converted to “_id” but I’m not sure how to do that or if it’s at all possible. However, this is not a course on EF and I don’t want to bloat the series more than it’s necessary so we’ll just go with the simplest solution presented above. The domain model is still fine as it is.

Let’s go on

OK, let’s rerun the code in DemoDatabaseTester, and… …what now? We’ve got another exception of type System.Reflection.TargetInvocationException:

“Exception has been thrown by the target of an invocation.”

The inner exception states the following:

“The class ‘WebSuiteDemo.Loadtesting.Domain.Location’ has no parameterless constructor.”

There we go again, that’s a requirement by EntityFramework. Each entity must have a parameterless constructor otherwise the mapping will fail. This is yet another case where theory, i.e. Persistence Ignorance, is beaten by practice. If we cannot hold onto 100% PI then we should at least go for, say, 95% and minimise the “casualties”. We certainly don’t want to have a public parameterless constructor for every domain. On the other hand EF requires one. It turns out that it’s enough to have a private constructor which is an acceptable solution to our dilemma.

Let’s test this on our Agent object. Locate Agent.cs in the WebSuiteDemo.Loadtesting.Domain namespace and add the following constructor:

private Agent() : base(Guid.NewGuid()) { }

As Agent has a property of type Location, it will also need a parameterless constructor. Open Location.cs in the same namespace and add the following constructor:

private Location(){}

Run the same test code in LoadtestingContextService.cs. It should succeed this time and you should see the 3 agents printed in the Debug window. I got the following results:

Id: 751ec485-437d-4bae-9ff1-1923203a87b1, city: Seattle, country: USA
Id: 23b83ac5-c29f-420f-bc9a-48906b243693, city: Frankfurt, country: Germany
Id: 3e953948-8d0a-46df-8ffb-9beef9991a9b, city: Tokyo, country: Japan

I’ve double-checked the entries in the database to see whether the IDs are correct:

Double checking agent entries in the database

Everything looks fine.

We’ll need to insert a private parameterless constructor to all our domain entities and value objects. Insert one in each of the following classes:

  • Customer
  • Description
  • Engineer
  • Loadtest
  • LoadtestParameters
  • LoadtestType
  • Project
  • Scenario

Morale

We have definitely not succeeded to adhere to 100% PI in our domain classes. We had to give in to the pressure and let a technology layer permeate into our domain classes. The morale of the above exercise is that sometimes practice wins over theory. In theory PI is a desirable characteristic of a domain object. However, the selected data store mechanism forces us to bend the rules somewhat.

Are these additions and modifications to the domain objects acceptable? I think so. They are at least not harmful. No external caller will be able to misuse a private constructor very easily. At least it won’t be obvious to an outside class that a certain domain object has a parameterless constructor. I’m not entirely happy with the changes, but I’ll have to live with it. I tried hard to fit EF into the picture since I know that it is the ORM choice #1 for many .NET developers. If you have another way to solve the PI dilemma with EF then you’re welcome to describe it in the comment section.

Testing the concrete Timetable repository

We can also test the 3 methods that we currently have in TimetableRepository.cs. Here’s how we can retrieve the load tests for a time period:

ITimetableRepository timetableRepo = new TimetableRepository();
IList<Loadtest> loadtests = timetableRepo.GetLoadtestsForTimePeriod(DateTime.UtcNow.AddDays(-10), 
	DateTime.UtcNow.AddDays(10));

We can then construct a valid Timetable object:

Timetable tt = new Timetable(loadtests);

Next we can build a new Loadtest domain object:

Loadtest newLoadtest = new Loadtest(Guid.NewGuid(),
	new LoadtestParameters(DateTime.UtcNow.AddDays(3), 120, 900), Guid.Parse("751ec485-437d-4bae-9ff1-1923203a87b1")
	, Guid.Parse("99f4dc94-718c-450d-87b6-3153bb8db622"), Guid.Parse("471119e2-2b3c-4545-97a2-5f52d1fa7954")
	, Guid.Parse("a868a7c5-2f4a-43f7-9a8c-a597793fdc56"), Guid.Parse("96877388-ce4d-4ea8-ae93-438a696386b9")
	, Guid.Parse("73e25716-7622-4af6-99a0-0638efb1c8cc"));

…or build one to be updated, i.e. where the first parameter is an existing ID:

Loadtest newLoadtest = new Loadtest(Guid.Parse("8c928a5e-d038-44f3-a8ff-70f64a651155"),
	new LoadtestParameters(DateTime.UtcNow.AddDays(3), 120, 900), Guid.Parse("751ec485-437d-4bae-9ff1-1923203a87b1")
	, Guid.Parse("99f4dc94-718c-450d-87b6-3153bb8db622"), Guid.Parse("471119e2-2b3c-4545-97a2-5f52d1fa7954")
	, Guid.Parse("a868a7c5-2f4a-43f7-9a8c-a597793fdc56"), Guid.Parse("96877388-ce4d-4ea8-ae93-438a696386b9")
	, Guid.Parse("73e25716-7622-4af6-99a0-0638efb1c8cc"));

I copied all these GUID values from the data store. In your case the Agent, Project, Customer etc. IDs will be different. Go ahead and copy those values from your database.

Next we add the load test to a collection:

List<Loadtest> allChanges = new List<Loadtest>() { newLoadtest };

…then call AddOrUpdateLoadtests of Timetable and print the operation summary:

AddOrUpdateLoadtestsValidationResult res = tt.AddOrUpdateLoadtests(allChanges);
Debug.WriteLine(res.OperationResultSummary);

We can then call the repository AddOrUpdateLoadtests method:

timetableRepo.AddOrUpdateLoadtests(res);

It’s best if you set breakpoints in your code when testing so that you can follow exactly what happens.

We can also test the deletion:

timetableRepo.DeleteById(Guid.Parse("4e880392-5497-4c9e-a3de-38f66348fe8e"));

Again, I copied the GUID of a valid Loadtest entry from my database.

We’ll reuse most of what we presented here later on when we’re ready to build the application service layer of the demo.

In the next post look at view models.

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.

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: