Domain Driven Design with Web API revisited Part 5: starting with the domain in code

Introduction

In the previous post we discussed the concept of ubiquitous language in DDD. It describes the problem domain in clear terms that are well-known for each stakeholder in the project. Each bounded context has its own well defined ubiquitous language that all parties stick to in every form of communication. We also defined the most important terms and rules for load testing bounded context of our demo application.

In this post we’ll start coding the domain objects. My intention is to be as detailed as possible as the domain layer is the most important on in a DDD project. Therefore this section will span two blog posts.

Surrounding entities

Let’s start with the relatively easier cases. I called them the surrounding entities as they will be part of a central entity. The central entity of the load testing bounded context is the load test itself in this object graph. In fact there is an entity – the timetable – which lies higher above, but the main object cluster is centred around the load test. As we said a load test can have the following dependencies:

  • A load test agent
  • A load test engineer
  • A project
  • A customer
  • A load test scenario

An engineer is optional and the rest is mandatory. A load test will own one of each of these objects.

Open the WebSuiteDDD.Demo project we started working on previously. Right-click the solution, select Add, then New Solution Folder and call it BoundedContexts. Right-click the new folder and again select to add a new solution folder, then call it LoadTesting. Add a new C# class library called WebSuiteDemo.Loadtesting.Domain and add a reference to the WebSuiteDDD.SharedKernel project. Remove Class1.cs and we’ll start with a simple entity called Project. We’ll also get our hands wet with our first value object. Each project will have a field called Description which consists of a short and a long description. Add the following class to the WebSuiteDemo.Loadtesting.Domain project:

public class Description : ValueObjectBase<Description>
{
	public string ShortDescription { get; private set; }
	public string LongDescription { get; private set; }

	public Description(string shortDescription, string longDescription)
	{
		if (string.IsNullOrEmpty(shortDescription)) throw new ArgumentNullException("Short description");
		if (string.IsNullOrEmpty(longDescription)) throw new ArgumentNullException("Long description");
		ShortDescription = shortDescription;
		LongDescription = longDescription;
	}

	public Description WithShortDescription(string shortDescription)
	{
		return new Description(shortDescription, this.LongDescription);
	}

	public Description WithLongDescription(string longDescription)
	{
		return new Description(this.ShortDescription, longDescription);
	}

	public override bool Equals(Description other)
	{
		return this.ShortDescription.Equals(other.ShortDescription, StringComparison.InvariantCultureIgnoreCase)
			&& this.LongDescription.Equals(other.LongDescription, StringComparison.InvariantCultureIgnoreCase);
	}

	public override bool Equals(object obj)
	{
		if (obj == null) return false;
		if (!(obj is Description)) return false;
		return this.Equals((Description)obj);
	}

	public override int GetHashCode()
	{
		return this.LongDescription.GetHashCode() + this.ShortDescription.GetHashCode();
	}
}

Let’s see what we have here. We inherit from the ValueObjectBase class as expected. Note that the long and short descriptions have public getters but no public setters. This is in line with the requirement that external classes shouldn’t just manipulate the internal state of an object. We validate the short and long descriptions in the constructor, they cannot be null or empty.

Then come two methods whose names start with “With” that show you how to keep value objects immutable in code. I got this idea from various Java projects provided by Amazon that we used for a Big Data project based on Amazon Cloud. We don’t directly modify the properties of the Description object but rather return a new one based on the existing and incoming values. Finally we override the equality methods.

We’re now ready to add our first entity called Project:

public class Project : EntityBase<Guid>
{
	public Description Description { get; private set; }

	public Project(Guid id, string shortDescription, string longDescription) : base(id)
	{			
		Description = new Description(shortDescription, longDescription);
	}
}

We derive from the EntityBase class as expected. We let the Description object be validated by its constructor but the Project class itself won’t be aware of that logic.

A load test agent will also have a value object. The following Location class follows the same pattern as Description:

public class Location : ValueObjectBase<Location>
{
	public string City { get; private set; }
	public string Country { get; private set; }

	public Location(string city, string country)
	{
		if (string.IsNullOrEmpty(city)) throw new ArgumentNullException("City");
		if (string.IsNullOrEmpty(country)) throw new ArgumentNullException("Country");
		City = city;
		Country = country;
	}

	public Location WithCity(string city)
	{
		return new Location(city, this.Country);
	}

	public Location WithCountry(string country)
	{
		return new Location(this.City, country);
	}

	public override bool Equals(Location other)
	{
		return this.City.Equals(other.City, StringComparison.InvariantCultureIgnoreCase)
			&& this.Country.Equals(other.Country, StringComparison.InvariantCultureIgnoreCase);
	}

	public override bool Equals(object obj)
	{
		if (obj == null) return false;
		if (!(obj is Location)) return false;
		return this.Equals((Location)obj);
	}

	public override int GetHashCode()
	{
		return this.City.GetHashCode() + this.Country.GetHashCode();
	}
}

Here’s the Agent class, you can add it to the Domain layer:

public class Agent : EntityBase<Guid>
{
	public Location Location { get; private set; }

	public Agent(Guid id, string city, string country)
		: base(id)
	{
		Location = new Location(city, country);
	}
}

The other surrounding classes are quite simple. Add these classes to the Domain layer:

Customer.cs:

public class Customer : EntityBase<Guid>
{
	public string Name { get; private set; }

	public Customer(Guid guid, string name) : base(guid)
	{
		if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("Customer name");
                Name = name;
	}
}

Engineer.cs:

public class Engineer : EntityBase<Guid>
{
	public string Name { get; private set; }

	public Engineer(Guid guid, string name) : base(guid)
	{
		if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("Engineer name");
	        Name = name;
	}
}

Scenario.cs:

public class Scenario : EntityBase<Guid>
{
	public string UriOne { get; private set; }
	public string UriTwo { get; private set; }
	public string UriThree { get; private set; }	

		public Scenario(Guid guid, IEnumerable<Uri> loadtestSteps) : base(guid)
	{
		if (loadtestSteps == null || loadtestSteps.Count() == 0)
		{
			throw new ArgumentException("Loadtest scenario must have at least one valid URI.");
		}

		Uri uriOne = loadtestSteps.ElementAt(0);
		if (uriOne == null) throw new ArgumentException("Loadtest scenario must have at least one valid URI.");
		UriOne = uriOne.AbsoluteUri;

		if (loadtestSteps.Count() == 2 && loadtestSteps.ElementAt(1) != null)
		{
			Uri uriTwo = loadtestSteps.ElementAt(1);
			UriTwo = uriTwo.AbsoluteUri;
		}

		if (loadtestSteps.Count() >= 3 && loadtestSteps.ElementAt(1) != null
			&& loadtestSteps.ElementAt(2) != null)
		{
			Uri uriTwo = loadtestSteps.ElementAt(1);
			UriTwo = uriTwo.AbsoluteUri;

			Uri uriThree = loadtestSteps.ElementAt(2);
			UriThree = uriThree.AbsoluteUri;
		}			
	}
}

In Scenario we accept a list of URIs but only care about the first 3 elements, the others are ignored.

Note that all of these domains are deliberately kept simple so that the demo stays manageable. In reality all of these classes will probably me more complicated with more validation rules and intricate cross-relationships. Also, it’s probably not acceptable that things like the engineer name or the agent location cannot be modified from another caller. We could add method-based modifiers such as this one in Customer.cs:

public void ModifyName(string newName)
{
	if (string.IsNullOrEmpty(newName)) throw new ArgumentNullException("Customer name");
	Name = newName;
}

An alternative is to extend the Name property:

private string _name;

public string Name 
{
	get
	{
		return _name;
	}
	set
	{
		if (string.IsNullOrEmpty(value)) throw new ArgumentNullException("Customer name");
		_name = value;
	}
}

However, that doesn’t really matter for the load test bounded context. In this context we want to insert, update, modify and view the load tests in the time table. We won’t be dealing with e.g. the address of the customer or how to change the engineer name. The load test bounded context is centred around load testing, not administration. The administration bounded context may well need to concern itself with updating the properties of agents, customers, engineers etc. However, that’s not the point of this exercise.

OK, so we have sorted out some of our entities and value objects. We have at least started with them. Keep in mind that the domain objects are probably never final in a DDD project. They are constantly refactored as we get new insights. A large part of Eric Evans’ DDD book is dedicated to refactoring evidenced by the chapter “Refactoring toward deeper insight”.

Just to recap we have the following folders and classes in our DDD demo currently:

State of application after initial entities added

The next step is to build the class that will reference all of the above entities, i.e. the Loadtest class. However, it is actually not at straightforward as you might think at first. We’ll need to discuss a couple of other concepts from DDD: associations and standalone classes.

We’ll do that in the next post.

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 5: starting with the domain in code

  1. Jan Hansen says:

    I know it is not very important for this article but you might want to correct the Scenario-constructor in regard to initialization of URIs. If there are more than three loadtestSteps then uriTwo and uriThree won’t be initialized and if there are three the uriTwo won’t be initialized.

    Other than that good stuff! 🙂

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: