用领域事件模拟AOP注入

12-04-10 gameboyLV

欢迎围观KylinORM系列文章:

第一篇:使用Tag网络维护实体关系

第二篇:基于业务驱动的领域服务

第三篇:使用领域事件模拟AOP注入

使用AOP织入领域对象可以方便地实现级联加载、数据校验、缓存、日志等功能,而不必将代码放在领域对象的基类或仓储对象的基类中。




使用AOP动态创建领域对象的Proxy实例,并按需要实现指定的接口。




可惜AOP(.NET)必须在运行时使用IL字节码动态创建类,或者让目标对象继承ContextBoundObject。有没有更简便的方法呢?看这段代码:

public class OrderInfo
{
    public IAspectMapper<OrderInfo> Mapper; //注册实体映射器
    public OrderInfo()
    {
        this.Mapper = AspectMapper.Inject(this, "OrderID") //注入实体并拦截领域事件
            .Before(MapperEvent.EntityCreate, Validator.Validate) //在实体创建之前调用权限控制模块
            .AfterReturning(MapperEvent.EntityCreate, Logger.Log); //在实体创建之后调用系统日志模块
    }
}
<p class="indent">


OrderInfo是一个普通的实体(POCO),在实体中声明一个映射器用于管理OrderInfo和其他实体的关联。然后使用Before拦截器拦截EntityCreate事件,使用AfterReturning拦截器拦截EntityCreate事件。

当对实体执行创建、更新、删除等操作时,数据层会首先检查实体映射器上注册的拦截器,调用相关的响应方法,根据方法的返回值再决定是否要执行真正的操作。




事件的响应Logger.Log可以指定任意数量的重载,事件源应根据实体和操作信息自动匹配最合适的方法。

public class Logger
{
    public static void Log(OrderInfo sender, AfterReturningEventArgs e)
    {
        //e.EventType 需要响应的实体操作
        if (e.EventType == MapperEvent.EntityCreate)
            Debug.WriteLine(string.Format("订单 {0} 已创建", sender.OrderNo));
    }
    public static void Log(object sender, AfterReturningEventArgs e)
    {
        Debug.WriteLine("测试");
    }
}
<p class="indent">


数据映射器可以代理任意对象的任意操作,只需要将操作TargetClass.DO更改为AspjectManager.Invoke(TargetClass.DO)即可。在Invoke之前AspjectManager会检查TargetClass.Mapper上注册的拦截器。
虽然需要在POCO对象中注册一个小小的属性,但是这点代价和动态类型、继承基类比起来就微不足道了。

相关示例和文档请参见 http://www.kylinorm.org/

[该贴被gameboyLV于2012-04-10 23:19修改过]

12
gameboyLV
2012-04-10 23:21

似乎一张帖子只能发3张图



banq
2012-04-11 15:53

非常不错,和JdonFramework区别在于,JF响应是基于内存中领域对象,而你的框架是基于持久层的实体。

各有优缺点,KylinORM框架非常类似开源内存数据库Java-Chronicle:

http://www.jdon.com/jivejdon/thread/43888

都是在持久层这里做文章,所不同的是KylinORM是框架,可以搭配很多数据库,而Java-Chronicle是一个最终技术产品。

都属于不同的细分市场。

gameboyLV
2012-04-12 07:52

感谢banq老师,每次回复都能给我带来灵感。

可能是对我领域模型的理解不够深入,在我想象中领域模型应该是纯业务的,和持久层没有任何关系。领域模型获取和更新数据都应该直接操作缓存层,缓存层就类似一个面向对象的数据库。

当缓存层接到查询请求时会直接从memory cache中返回数据,接到写入或更新请求时会直接将请求写入一个队列,然后根据需要批量将队列提交到持久层。

而创建kylinorm这个试验品的目的就是为了将领域模型中的持久化操作完全分离,领域通过消息机制和object pool打交道。object pool的管理者就是kylin orm,而存放object的地方是一个运行在IIS上的超轻量级数据库,比如SQL CE 4.0。

orm定期将pool中的数据同步到数据库服务器。这样就走上了一条与贫血模型 完全相反的道路,业务层只负责封装对外的API和调度业务,领域层只负责实现业务逻辑、发送事件到缓存层,缓存层负责管理查询和写入操作。(是缓存所有的数据库操作,不是简单的缓存数据)

[该贴被gameboyLV于2012-04-12 07:52修改过]

banq
2012-04-12 08:23

2012-04-12 07:52 "@gameboyLV"的内容
领域层只负责实现业务逻辑、发送事件到缓存层,缓存层负责管理查询和写入操作 ...


明白了,相当于Jdon框架 + Java-Chronicle。Jdon框架没有捆绑持久层方式,而你的框架采取事件驱动顺便解决了持久层,方便用户直接使用。

但是有一个问题也在困扰我,你可能发现我一直在鼓吹传统事务锁的消亡,比如数据库锁或JTA等锁,这些都是性能杀手,虽然Scala提出内存事务机制一说,但是我比它更激进,我倡导弱一致性弱一致性在现实世界中到处存在

那么对于你的整合了持久层的框架来说,你可能需要解决缓存层领域模型更新和持久层数据更新的事务一致性问题了,就如同国外另外一个CQRS框架Axon一样,也花大力气提供事务机制。


gameboyLV
2012-04-12 10:11

每次对实体的操作我都会解析成一个DataQuery对象,这个对象中封装了sql语句、参数、缓存时间等。

new UserInfo { [author]RealName[/author] = "李四" }.Mapper.Create();
<p class="indent">


new DataQuery
{
    CommandText = "INSERT INTO [User] ([[author]RealName[/author]]) VALUES (@[author]RealName[/author]);\nSELECT SCOPE_IDENTITY() AS NewID;",
    Parameters = new object[] { "李四" },
    CacheTime = 0
}
<p class="indent">


当请求到达缓存层时,由缓存层决定是直接提交还是写入请求队列。要实现事务很简单:

//ExecuteTransaction当前放在DataProvider.GetInstance()中,稍后会移动到TransactionManager
TransactionManager.ExecuteTransaction(dataQuerys);
<p class="indent">


只需要指定请求队列中的若干个DataQuery对象,ORM就会将这些对象包裹在数据库事务中一次性提交。

对于数据一致性要求不高的业务(比如统计点击量),就会等待CacheTime超时后批量提交。

对于缓存队列中请求的安全性,因为我是将队列存放在Sql CE或Sql Lite这样的文件型数据库中,即使断电也不会丢失。

数据查询请求也是同样的道理,如果请求队列中有相同的DataQuery,那么则返回上次的查询结果。

[该贴被gameboyLV于2012-04-12 10:13修改过]

dark
2012-04-13 23:13

楼主的作品不错,很前卫。

我以前在写orm的时候,也被 并发和事务的问题困扰,二者是死敌啊。
如果说,采用 对数据库的访问 全部队列,让读并发,让写只有单个线程进行,
看起来似乎可以,那么 我是不是要在 存在事务的时候,停住所有
领域的数据库请求呢?因为 那个事务内的代码,也可能会操作 其它领域的数据啊,,,,这样的话,一旦有一个事务,那么所有领域的查询,都要被停止了。

说明,我在写orm的时候,还没有学习DDD啦,最近买了《领域驱动设计:软件核心复杂应对之道》那本书,感觉也没怎么看懂,但是我只看了《.NET企业级框架设计》这本书的 一小段介绍 领域驱动设计的章节,就茅塞顿开了,不知道是《领域驱动设计:软件核心复杂应对之道》的翻译问题,还是作者表达问题,还是我自己理解有限,我就是看不大明白作者到底要说什么。。。