DDD:管理“工作单元实例”的两种模式
概念介绍
类图如下:
在常见的用例场景下,类图的对象图如下:
问题
在一个用例执行过程中,如何保证同一个界限上下文内的所有仓储实例可以共享同一个工作单元实例?
解决方案1
仓储采用依赖注入模式 + 使用IOC管理工作单元的生命周期(PerRequest或其它)。
代码示例
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using Autofac; 8 9 namespace AutoFacStudy 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 var buider = new ContainerBuilder(); 16 buider.RegisterType<服务>(); 17 buider.RegisterType<仓储A>(); 18 buider.RegisterType<仓储B>(); 19 buider.RegisterType<工作单元>().InstancePerLifetimeScope(); 20 21 var container = buider.Build(); 22 23 dynamic 服务 = container.Resolve<服务>(); 24 25 //下边两行代码输出一样 26 Console.WriteLine(服务.仓储A.工作单元.GetHashCode()); 27 Console.WriteLine(服务.仓储B.工作单元.GetHashCode()); 28 } 29 } 30 31 public class 服务 32 { 33 private readonly 仓储A _仓储A; 34 private readonly 仓储B _仓储B; 35 36 public 服务(仓储A 仓储A, 仓储B 仓储B) 37 { 38 _仓储A = 仓储A; 39 _仓储B = 仓储B; 40 } 41 42 public 仓储A 仓储A 43 { 44 get { return _仓储A; } 45 } 46 47 public 仓储B 仓储B 48 { 49 get { return _仓储B; } 50 } 51 } 52 53 public class 工作单元 { } 54 55 public class 仓储A 56 { 57 private readonly 工作单元 _工作单元; 58 59 public 仓储A(工作单元 工作单元) 60 { 61 _工作单元 = 工作单元; 62 } 63 64 public 工作单元 工作单元 65 { 66 get { return _工作单元; } 67 } 68 } 69 70 public class 仓储B 71 { 72 private readonly 工作单元 _工作单元; 73 74 public 仓储B(工作单元 工作单元) 75 { 76 _工作单元 = 工作单元; 77 } 78 79 public 工作单元 工作单元 80 { 81 get { return _工作单元; } 82 } 83 } 84 }
解决方案2
仓储采用服务定位器模式 + 使用服务定位器或简单工厂管理工作单元的生命周期(PerRequest或其它)。
代码示例
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using Autofac; 8 9 namespace AutoFacStudy 10 { 11 class Program 12 { 13 public static IContainer 服务定位器; 14 15 static void Main(string[] args) 16 { 17 var buider = new ContainerBuilder(); 18 buider.RegisterType<服务>(); 19 buider.RegisterType<仓储A>(); 20 buider.RegisterType<仓储B>(); 21 buider.RegisterType<工作单元>().InstancePerLifetimeScope(); 22 23 服务定位器 = buider.Build(); 24 25 dynamic 服务 = 服务定位器.Resolve<服务>(); 26 27 //下边两行代码输出一样 28 Console.WriteLine(服务.仓储A.工作单元.GetHashCode()); 29 Console.WriteLine(服务.仓储B.工作单元.GetHashCode()); 30 } 31 } 32 33 public class 服务 34 { 35 private readonly 仓储A _仓储A; 36 private readonly 仓储B _仓储B; 37 38 public 服务(仓储A 仓储A, 仓储B 仓储B) 39 { 40 _仓储A = 仓储A; 41 _仓储B = 仓储B; 42 } 43 44 public 仓储A 仓储A 45 { 46 get { return _仓储A; } 47 } 48 49 public 仓储B 仓储B 50 { 51 get { return _仓储B; } 52 } 53 } 54 55 public class 工作单元 { } 56 57 public class 仓储A 58 { 59 private readonly 工作单元 _工作单元; 60 61 public 仓储A() 62 { 63 _工作单元 = Program.服务定位器.Resolve<工作单元>(); 64 } 65 66 public 工作单元 工作单元 67 { 68 get { return _工作单元; } 69 } 70 } 71 72 public class 仓储B 73 { 74 private readonly 工作单元 _工作单元; 75 76 public 仓储B() 77 { 78 _工作单元 = Program.服务定位器.Resolve<工作单元>(); 79 } 80 81 public 工作单元 工作单元 82 { 83 get { return _工作单元; } 84 } 85 } 86 }
由此示例可以看出,服务定位器和依赖注入可以混合在一起使用。这个例子我为了简单,服务定位器和IOC容器是同一个实例。
有些系统将服务定位器的实现换成简单工厂模式,他们本质上是一样的(服务定位器是一个万能工厂)。
代码示例
1 public class 工作单元工厂 2 { 3 public static 工作单元 创建() 4 { 5 var 工作单元 = (工作单元)CallContext.GetData("工作单元"); 6 7 if (工作单元 == null) 8 { 9 工作单元 = new 工作单元(); 10 CallContext.SetData("工作单元", 工作单元); 11 } 12 13 return 工作单元; 14 } 15 }