Click here to Skip to main content
15,886,806 members
Articles / Domain

Domain Context Pattern

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
16 Sep 2014CPOL3 min read 14.8K   4   1
In your domain model, you often need to pass the same repetitive information to your domain entities or value objects. This article suggests a solution that I call "Domain Context".

The Problem

In your domain model, you often need to pass the same repetitive information to your domain entities or value objects. An example of this is a multi-tenant domain, where tenant ID appears in many places. A username might be needed if you need to track authors of changes to entities and it’s not a typical audit log scenario.

In such cases, you quickly notice that many constructors and methods have the same parameters, like tenantId or userName. It would be beneficial to set it only once and access the values inside of your domain classes.

Possible Solution

The very first thing that pops into the head is to use a static property (or apply Singleton pattern, but it’s discouraged anyway), something like this:

C#
public class DomainWideValues
{
    public static TenantId TenantId { get; set; }
    public static string UserName { get; set; }
}

This is a very simple solution, but it wouldn’t work in ASP.NET applications properly. Different requests might be made by different tenants and users. Another problem might appear in parallel execution of unit tests – using the same static properties simultaneously means sharing state and that is a bad practice that leads to fragile tests.

Ambient Context pattern seems like a good solution to the problem. However, some modifications must be made.

Domain Context

NOTE: This is NOT related in any way to DomainContext class in WCF RIA services.

The solution that I’ve used many times is what I call a Domain Context. This is an ambient context variation that provides values for the domain model about the context that we are using it in. The implementation looks like this:

C#
[Serializable]
public class DomainContext : IDisposable
{
	private static DomainContextStorage storage = new CallContextStorage();

	public static DomainContext Current
	{
		get { return Storage.Get(); }
	}

	public static DomainContextStorage Storage
	{
		get { return storage; }
		set { storage = value; }
	}

	public TenantId TenantId { get; private set; }
	public string UserName { get; private set; }

	public DomainContext(TenantId tenantId, string userName)
	{
		if(Storage.Get() != null)
			throw new InvalidOperationException("Only a single Domain Context can be created!");

		TenantId = tenantId;
		UserName = userName;

		Storage.Add(this);
	}

	public void Dispose()
	{
		Storage.Remove();
	}
}

It closely resembles the Ambient Context pattern implementation, but there are few things to note:

  • There is no support for sub-contexts. They are simply not needed for sharing values between domain objects.
  • DomainContextStorage class is used for storing context data. We are not tied to a single context per thread. This enables us to use this solution with any technology that we need: ASP.NET, WCF, Windows Forms, etc.

Usage

In its simplest form, Domain Context can be used like this:

C#
using(new DomainContext(new TenantId("Gedgei Inc."), "Gediminas"))
{
	var someEntity = new SomeEntity();
	Console.WriteLine(someEntity.TenantId);
	Console.WriteLine(someEntity.Author);
}

Domain classes accesses Domain Context directly through DomainContext.Current property:

C#
public SomeEntity()
{
	this.TenantId = DomainContext.Current.TenantId;
	this.Author = new Author(DomainContext.Current.UserName);
}

This example works in simple scenarios, like single-threaded applications and unit tests. To use Domain Context in other environments, we must pick or create the correct DomainContextStorage implementation for that environment.

Domain Context Storage

DomainContext class uses a DomainContextStorage subclass to save its state for later retrieval. The default storage is CallContextStorage, which means that context data is stored in memory and only available for the current call stack. It’s using ThreadStatic attribute under the hood, so every thread gets its own Domain Context. The default storage can be used in single threaded applications and unit tests:

C#
[SetUp]
public void FixtureSetup()
{
	new DomainContext(new TenantId("test"), "testuser");
}

[TearDown]
public void FixtureTearDown()
{
	DomainContext.Current.Dispose();
}

For other environments, we need more complex implementations of the storage. Here’s a list of what possible implementations can use for different environments:

  • ASP.NET – HttpContext.Session. Sessions are per-user and Domain Context can be initialized on user login.
  • WCF – OperationContext. Domain Context can be initialized using IInstanceContextInitializer if per-call-instance mode is used.
  • Windows Forms and WPF – A static variable storage would probably suffice. Domain Context could be initialized on application startup or when user logs in.

Conclusion

I have successfully used Domain Context pattern in multiple projects before. It can simplify your Domain classes by providing common data globally without any impact on testability. However, since DomainContext class is so tightly coupled to the domain classes, it belongs to the domain model too, so be careful not to put any infrastructure concerns there.

Source code demonstrating basic usage of Domain Context pattern can be found on GitHub:
Domain Context Demo code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Lithuania Lithuania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThis is just repository pattern but renamed Pin
OmidXavaX31-Aug-18 10:17
OmidXavaX31-Aug-18 10:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.