65.9K
CodeProject 正在变化。 阅读更多。
Home

领域上下文模式

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2014年9月15日

CPOL

3分钟阅读

viewsIcon

15423

在您的领域模型中,您经常需要将相同重复的信息传递给您的领域实体或值对象。本文提出了一种我称之为“领域上下文”的解决方案。

问题

在您的领域模型中,您经常需要将相同重复的信息传递给您的领域实体或值对象。例如,在多租户领域中,租户 ID 会出现在许多地方。如果您需要跟踪对实体的更改的作者,并且这不是典型的审计日志场景,则可能需要用户名。

在这种情况下,您很快就会注意到许多构造函数和方法具有相同的参数,例如tenantIduserName。只设置一次并访问域类内部的值将是有益的。

可能的解决方案

首先想到的是使用static属性(或应用单例模式,但是它仍然不被推荐),如下所示

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

这是一个非常简单的解决方案,但是它在ASP.NET应用程序中无法正常工作。不同的请求可能由不同的租户和用户发出。另一个问题可能出现在单元测试的并行执行中——同时使用相同的static属性意味着共享状态,这是一个导致测试脆弱的不良实践。

环境上下文模式似乎是一个不错的解决方案。但是,必须进行一些修改。

领域上下文

注意:这与WCF RIA 服务中的DomainContext类没有任何关系。

我多次使用过的解决方案就是我所说的领域上下文。这是一种环境上下文变体,它为领域模型提供关于我们正在使用的上下文的信息。实现如下所示

[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();
	}
}

它与环境上下文模式的实现非常相似,但需要注意几点

  • 不支持子上下文。它们对于在领域对象之间共享值根本不需要。
  • DomainContextStorage类用于存储上下文数据。我们不限于每个线程一个上下文。这使我们能够将此解决方案与任何需要的技术一起使用:ASP.NET、WCF、Windows Forms等。

用法

在其最简单的形式中,领域上下文可以这样使用

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

领域类通过DomainContext.Current属性直接访问领域上下文

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

此示例适用于简单的场景,例如单线程应用程序和单元测试。要在其他环境中使用领域上下文,我们必须为该环境选择或创建正确的DomainContextStorage实现。

领域上下文存储

DomainContext类使用DomainContextStorage子类来保存其状态以便稍后检索。默认存储是CallContextStorage,这意味着上下文数据存储在内存中,并且仅对当前调用堆栈可用。它在后台使用ThreadStatic属性,因此每个线程都有自己的领域上下文。默认存储可用于单线程应用程序和单元测试

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

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

对于其他环境,我们需要更复杂的存储实现。以下是不同环境可以使用的一些可能的实现列表

  • ASP.NET – HttpContext.Session。会话是针对每个用户的,可以在用户登录时初始化领域上下文。
  • WCF – OperationContext。如果使用每个调用实例模式,则可以使用IInstanceContextInitializer初始化领域上下文。
  • Windows Forms 和 WPF – static变量存储可能就足够了。可以在应用程序启动时或用户登录时初始化领域上下文。

结论

我之前在多个项目中成功使用了领域上下文模式。它可以通过在全局提供通用数据来简化您的领域类,而不会影响可测试性。但是,由于DomainContext类与领域类紧密耦合,因此它也属于领域模型,因此请注意不要在那里放置任何基础架构问题。

演示领域上下文模式基本用法的源代码可以在GitHub上找到
领域上下文演示代码.

© . All rights reserved.