惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
博客园 - 三生石上(FineUI控件)
WordPress大学
WordPress大学
博客园_首页
宝玉的分享
宝玉的分享
S
SegmentFault 最新的问题
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
V
Visual Studio Blog
美团技术团队
IT之家
IT之家
罗磊的独立博客
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
月光博客
月光博客
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Last Week in AI
Last Week in AI
博客园 - 叶小钗
M
MIT News - Artificial intelligence
B
Blog RSS Feed
有赞技术团队
有赞技术团队
Y
Y Combinator Blog

博客园 - 小水坝

搞定thrift双向消息 读《软件需求最佳实践》有感 【thrift】vc中使用thrift中文字符串乱码问题解决 __declspec(dllimport)的小秘密(转) 跨线程send message 【boost】使用serialization库序列化子类 【boost】ptree 读写中文的问题 动态创建TeeChart的简便方法 【MFC】动态创建CMFCToolbar图标不显示问题 【boost】使用装饰者模式改造boost::thread_group 【VC】VC工具栏图标合并工具(非tbcreator和visual toolbar) 【boost】使用lambda表达式和generate_n生成顺序序列 【boost】BOOST_LOCAL_FUNCTION体验 【boost】MFC dll中使用boost thread的问题 【转帖】C++编译原理 资料 IE6,7下password框长度变短问题 dwz局部表格分页 dwz中combox的value问题 dwz中使用flot,js报表等js插件
使用双分派解决领域实体和外部机制通信问题
小水坝 · 2015-09-17 · via 博客园 - 小水坝

使用双分派解决领域实体和外部机制通信问题

经典DDD分层中领域层领域实体自身高内聚,在领域层内通过聚合,实体,事件和仓储接口完成领域业务逻辑。
最近在实际项目中遇到一种场景,有如下所示实体Device:

public class Device
{
	void ExecuteCmd(string cmd)
	{
		//Do something "Domian" related before sending..
		//Send command to real device via network
	}
}

其中ExecuteCmd方法需要首先将指令通过网络发送到实际设备。由于领域实体自身不能完成诸如Email,网络操作等实际业务功能,故该功能只能通过某种服务完成。但是执行这个动作和其结果,流程却属于领域部分,故不能全部交由应用层来实现,否则将导致部分业务逻辑从领域层泄露。
为了解决这个问题,先后尝试了三种方式:

  • 使用ServiceLocate模式向领域实体中注入服务接口
  • 使用领域事件,在事件处理器重注入服务接口
  • 使用双分派模式,传入执行接口

1实体中注入服务接口

public Interface IDeviceService
{
	void Execute(string cmd);
}
public class Device
{
	private IDeviceAgent _agent = ServiceLocator.GetInstance<IDeviceService>();
	void ExecuteCmd(string cmd)
	{
		//Do something "Domian" related before sending..
		//invoke agent
		_agent.Execute(cmd);
	}
}

该模式最简单,但却是DDD中的反模式,造成的后果是使得实体不再“纯净”,需要依赖一个服务接口,这样将给单元测试带来麻烦,并且容忍了这种方式之后,极有可能造成后续将Repository接口等注入实体,使得领域实体进一步腐化。

2使用领域事件

该方式下在需要定义领域事件,而在事件处理器中注入服务接口。此方法符合DDD要求,但在经典DDD分层项目中显得比较繁琐,需要频繁定义细粒度事件及事件处理器。并且调用依赖领域服务总线实现,总线消息实现为延迟传递则会带来一定问题。同样的,在这种方式下,单元测试也有一定的麻烦。

3使用双分派模式传入领域服务接口

Chassaging在这篇文章

If I have mail message entity and I want a Send method on it ?
Tiding your entity to the service that will use it is not conceptually satisfying. The
server >that will send the message is a medium and is not part of the entity itself.
It’s seems better to call server.Send(message).
The problem is that you can end breaking the tell don’t ask principle because the Send
method will ask the message for each property value. And you can be tempted to put
computations that should be in the message entity inside the Send method.

Let’s call Double Dispatch to the rescue !

  • Design a server interface presenting a Send method that take the parameters it need (title, recipients, message body⋯)
  • Add a SendThrough method to your entity that takes the server interface as parameter
  • Implement SendTrhough so that it computes values to pass to the Send method.

That’s it.

简单来说,就是定义一个服务接口及其所需的参数,然后在实体中添加一个ExecuteThrough方法,将参数和接口传入,在基础层等实现该接口:

public class Device
{
	public void ExecuteCmdThrough(string cmd, IDeviceService service)
	{
		//Do something "Domian" related before sending..
		//Dispatch behaviors to interface
		service.Execute(cmd);
	}
}