用AOP思想改造一个服务器的数据存储

背景是有一个游戏服务器一直以来都是写SQL的, 后来改过一段时间的redis, 用的是别的员工写的类orm方式将实体类型映射成各类key-value对进行写入, 可是仍有一个缺点就是须要在增\删\改的时候显式调用API, 更糟糕的是要注明删\改的字段名, 否则就会整个实体重写入. 实际使用中常常会出现写错字段名, 或是重命名字段以后忘记修改旧的字段名字符串参数, 甚至忘记调用update API致使数据没有保存...(一样的状况也发生在写SQL的时候)git

其实增/删/改用到的SQL或是reids api都很是简单, 均可以自动生成, 免去反复书写的麻烦也容易写错的问题. github

因而乎, 我计划经过监视/拦截增/删/改 方法以在数据修改的同时自动生成SQL/Redis调用, 来对改属性值进行保存. 其实增删比较简单, 只要自定义一个集合类型继承下IEnumerable<T>接口, 而后实现增删方法便可, 对原代码的修改量也是极小的. redis

可是改就有点麻烦了, 须要拦截属性的set方法, 这是之前没有怎么用过的AOP领域.api

一些调查以后, 以为有两种方式比较适合.服务器

一是静态织入, 就是用相似代码模板的方式, 先写实体类型模板, 而后生成真正的实体类型类库, 在生成的时候进行修改在属性的set方法中注入PropertyChangedNotify事件, 服务中使用的是生成后的实体类库. PostSharp貌似不错, 可是不想引入更多的开销就罢了. 用T4或者上Emit或许不错.ide

二是动态, 大体上就是生成一个代理类型, 拥有原类型的全部接口, 可是从新给实现了一遍. 能够是组合的方式, 将原类型实例做为私有成员保存, Emit生成全部原类型的开放方法; 也能够是继承的方式, 重载. 考虑到对原代码的修改量, 我以为继承重载不错. 函数

好比原实体类型以下:工具

public class Person
{
    public string Id{get;set;}
    public string Name{get;set;}
}

对这个类实现修改保存的托管, 那么直接使用这个类型是不行的了(除非方案1静态修改注入), 只有使用动态生成的代理实体类, 而若是使用组合方式的代理类型, 在OO的状况下很别扭, 因此我选择用继承重载的方式. 这就须要对这个实体类型进行改造, 将全部须要监视的属性修改为virutal声明 ctrl+H搞定. 学习

而后, 由于是增删改, 因此Key是必不可少的, 那么实体须要用attribute标记出Key属性, 支持多属性组合Key. 由于原代码用过一段时间的entityframework, 因此这个改造很是顺利. this

最后的类型定义以下:

[DataContract]
public class Person
{
    [Key]  
    public string Id{get;set;}
    public virtual string Name{get;set;}
}

首先用attribute标记Person为须要代理的类型, 便于在实际使用中纠错和预加载, 而后标记Id属性为Key, 而且不须要重载, 其余属性声明为virtual表示须要重载进行监视.

而后手写一个代理类型:

public class PersonProxy : Person
{
    public override string Name
    {
        set
        {
            base.Name= value;
            Interceptor.Invoke(this, "Name", value);
        }
    }  
}

public static class Interceptor
{
    public static void Invoke(object instance, string name, object value)
    {
        //log
    }
}

再定义个Factory的静态类, 实现静态泛型函数Create<T>()来建立Person的代理类型PersonProxy. 其实后面调用的都是代理类型, 只是使用方法和Person一致, 兼容旧代码.

只是这样的话, 还不如使用静态织入了. 因此上面手写的这个PersonProxy须要动态生成, 这样经过泛型来支持全部自定义class, 减小频繁修改. 因而Emit登场了.

不过我对IL和Emit认识浅薄, 个人办法是使用VS自带工具 -- ildasm, 用它打开刚才生成的PersonProxy查看IL代码, 反复对比总结以后终于将这个PersonProxy类型的动态生成用Emit实现成功并修改为泛型通用. 这套特殊的IL学习方法论真是屡试不爽. 方法大体如此抛砖引玉, 因此这里也就不贴emit的代码了. Invoke织入也能够作各类进化, 以达成更高级的功能, 好比invoke在赋值前, 写入失败回退throw exception等等...

这样ProxyFactory.Create<T>()函数就写好了.

在Invoke里生成须要SQL或是redis set方法就能够了. 

最后再实现增删代理的集合类型, 在Add的时候直接将插入的对象经过ProxyFactory.Create<T>()转成proxy子类对象, 为了不Add以后继续使用传入的原对象而丢失对属性set的监控, 修改Add的声明为Add(ref T obj)强行修改引用为代理对象. 从入口处根绝, 完美. 

理论Ok, 因而封装成类库. 

最后放上代码地址 : https://github.com/Roytin/SmartDb

相关文章
相关标签/搜索