Domain Driven Design with Web API revisited Part 10: the load test database context
September 7, 2015 Leave a comment
Introduction
In the previous post we started building our EntityFramework repository layer. In particular we worked on the overall WebSuite database context which is meant to be an overall DB context object for all bounded contexts in the WebSuite application. It can be beneficial for each bounded context to have its own matching DB context so that it can concentrate on its DB-related tasks without interfering with other bounded contexts.
In this post we’ll build the load test DB context. This is also where we’ll implement the abstract repository interface we added earlier to the Domain layer.
The load testing context
Open the WebSuite application we’ve been working on so far. We currently have the following project structure in the demo app:
Add a C# project library to the LoadTesting folder. Call it WebSuiteDemo.Loadtesting.Repository.EF. Remove Class1 and add the following EntityFramework NuGet package to the project:
Also, add references to the WebSuiteDemo.Loadtesting.Domain and WebSuiteDDD.SharedKernel projects.
We’ll do something similar to what we did in the previous post. However, instead of creating DB-specific objects we’ll directly use our load testing domain objects to create the DB sets.
Add the following DbContext object to the new repository layer:
public class LoadTestingContext : DbContext { public LoadTestingContext() : base("WebSuiteContext") { } public DbSet<Agent> Agents { get; set; } public DbSet<Customer> Customers { get; set; } public DbSet<Engineer> Engineers { get; set; } public DbSet<Loadtest> Loadtests { get; set; } public DbSet<LoadtestType> LoadtestTypes { get; set; } public DbSet<Project> Projects { get; set; } public DbSet<Scenario> Scenarios { get; set; } }
You’ll see that this is almost identical to WebSuiteContext.cs from the previous post. The main difference is that the objects of the DB sets, like Agent, Customer etc. refer to the domain objects in the WebSuiteDemo.Loadtesting.Domain layer.
We can implement the abstract time table repository here. Insert a folder called Repositories and add the following class calledTimetableRepository to it:
public class TimetableRepository : ITimetableRepository { public IList<Loadtest> GetLoadtestsForTimePeriod(DateTime searchStartDateUtc, DateTime searchEndDateUtc) { LoadTestingContext context = new LoadTestingContext(); IList<Loadtest> loadtestsInSearchPeriod = (from l in context.Loadtests where (l.Parameters.StartDateUtc <= searchStartDateUtc && SqlFunctions.DateAdd("s", l.Parameters.DurationSec, l.Parameters.StartDateUtc) >= searchStartDateUtc) || (l.Parameters.StartDateUtc <= searchEndDateUtc && SqlFunctions.DateAdd("s", l.Parameters.DurationSec, l.Parameters.StartDateUtc) >= searchEndDateUtc) || (l.Parameters.StartDateUtc <= searchStartDateUtc && SqlFunctions.DateAdd("s", l.Parameters.DurationSec, l.Parameters.StartDateUtc) >= searchEndDateUtc) || (l.Parameters.StartDateUtc >= searchStartDateUtc && SqlFunctions.DateAdd("s", l.Parameters.DurationSec, l.Parameters.StartDateUtc) <= searchEndDateUtc) select l).ToList(); return loadtestsInSearchPeriod; } public void AddOrUpdateLoadtests(AddOrUpdateLoadtestsValidationResult addOrUpdateLoadtestsValidationResult) { LoadTestingContext context = new LoadTestingContext(); if (addOrUpdateLoadtestsValidationResult.ValidationComplete) { if (addOrUpdateLoadtestsValidationResult.ToBeInserted.Any()) { foreach (Loadtest toBeInserted in addOrUpdateLoadtestsValidationResult.ToBeInserted) { context.Entry<Loadtest>(toBeInserted).State = System.Data.Entity.EntityState.Added; } } if (addOrUpdateLoadtestsValidationResult.ToBeUpdated.Any()) { foreach (Loadtest toBeUpdated in addOrUpdateLoadtestsValidationResult.ToBeUpdated) { context.Entry<Loadtest>(toBeUpdated).State = System.Data.Entity.EntityState.Modified; } } } else { throw new InvalidOperationException("Validation is not complete. You have to call the AddOrUpdateLoadtests method of the Timetable class first."); } context.SaveChanges(); } public void DeleteById(Guid guid) { LoadTestingContext context = new LoadTestingContext(); Loadtest loadtest = (from l in context.Loadtests where l.Id == guid select l).FirstOrDefault(); if (loadtest == null) throw new ArgumentException(string.Format("There's no load test by ID {0}", guid)); context.Entry<Loadtest>(loadtest).State = System.Data.Entity.EntityState.Deleted; context.SaveChanges(); } }
The implemented GetLoadtestsForTimePeriod will find all overlapping load tests for two dates. As the Add, AddMinutes, AddSeconds etc. methods of the DateTime object is not supported in Linq to SQL we have to revert to the SqlFunctions object which has a specialised DateAdd function that can be embedded into Linq to SQL statements. In order to find the end date of a load test we add the duration to the “second” field of the start date parameter.
The AddOrUpdateLoadtests method first checks if the validation has been performed before calling the repository. If not, then an exception is raised with a hint how to proceed correctly. Otherwise the method loops through the load tests to be inserted or updated and marks each entity as Added or Modified. EntityFramework will proceed accordingly when the SaveChanges method is called at the end.
In the next post we’ll create the database and perform some initial tests.
View the list of posts on Architecture and Patterns here.