图解“管道过滤器模式”应用实例:SOD框架的命令执行管道
管道和过滤器
管道和过滤器是八种体系结构模式之一,这八种体系结构模式是:层、管道和过滤器、黑板、代理者、模型-视图-控制器(MVC) 表示-抽象-控制(PAC)、微核、映像。
管道和过滤器适用于需要渐增式处理数据流的领域,而常见的“层”模式它 能够被分解成子任务组,其中每个子任务组处于一个特定的抽象层次上。
按照《POSA(面向模式的软件架构)》里的说法,管道过滤器(Pipe-And-Filter)应该属于架构模式,因为它通常决定了一个系统的基本架构。管道过滤器和生产流水线类似,在生产流水线上,原材料在流水线上经一道一道的工序,最后形成某种有用的产品。在管道过滤器中,数据经过一个一个的过滤器,最后得到需要的数据。
管道&过滤器模型的基本部件都有一套输入输出接口。每个部件从输入接口中读取数据,经过处理,将结果数据置于输出接口中,这样的部件称为“过滤器”。这种模型的连接者将一个过滤器的输出传送到另一个过滤器的输入,
我们把这种连接者称为“管道”。在这种模型中,过滤器必须是独立的实体,每一个过滤器的状态不受其它过滤器的影响,并且,虽然人们对过滤器的输入输出有一定的规定,但过滤器并不需要知道向它提供数据流的过滤器和
它要提供数据流的过滤器的内部细节。任何两个过滤器,只要它们之间传送的数据遵守共同的规约就可以相连接。
每个过滤器都有自己独立的输入输出接口,如果过滤器间传输的数据遵守其规约,只要用管道将它们连接就可以正常工作。
查询的关注点
基于以上管道和过滤器特点,它为处理数据流的系统提供了一种良好的结构,每一个处理步骤封装在一个过滤器组件中,数据通过相邻的过滤器之间的管道传输。在程序处理中,也有类似的这种数据流,最常见的就是命令处理的数据流,它从最开始的查询命令,到最后的结果输出,会经过多个步骤,以ADO.NET来说,执行一个查询会经过以下过程:
查询命令:
- 获取数据集:
- 打开数据库连接 IDbConnection
- 创建命令对象 IDbCommand
- 创建数据适配器 IDataAdapter
- 填充数据集 IDataAdapter.Fill(DataSet)
- 关闭数据库连接
- 返回数据集 DataSet
- 获取数据阅读器
- 打开数据库连接 IDbConnection
- 创建命令对象 IDbCommand
- 执行数据阅读器查询 IDbCommand.ExecuteReader
- 返回数据阅读器 IDataReader
- 关闭数据库连接
非查询命令:
- 打开数据库连接 IDbConnection
- 创建命令对象 IDbCommand
- 执行查询 IDbCommand.ExecuteNonQuery()
- 关闭数据库连接
- 查询前
- 查询中
- 查询后
- 查询异常
SOD框架的命令处理管道
命令处理接口
/// <summary>
/// 查询命令处理器接口
/// </summary>
public interface ICommandHandle
{
/// <summary>
/// 获取当前适用的数据库类型,如果通用,请设置为 UNKNOWN
/// </summary>
DBMSType ApplayDBMSType { get; }
/// <summary>
/// 执行前处理,比如预处理SQL,补充设定参数类型,返回是否继续进行查询执行
/// </summary>
/// <param name="db">数据库访问对象</param>
/// <param name="SQL"></param>
/// <param name="commandType"></param>
/// <param name="parameters"></param>
/// <returns>返回真,以便最终执行查询,否则将终止查询</returns>
bool OnExecuting(CommonDB db, ref string SQL, CommandType commandType, IDataParameter[] parameters); /// <summary>
/// 执行过程中出错情况处理
/// </summary>
/// <param name="cmd"></param>
/// <param name="errorMessage"></param>
void OnExecuteError(IDbCommand cmd, string errorMessage);
/// <summary>
/// 查询执行完成后的处理,不管是否执行出错都会进行的处理
/// </summary>
/// <param name="cmd"></param>
/// <param name="recordAffected">命令执行的受影响记录行数</param>
long OnExecuted(IDbCommand cmd, int recordAffected);
}
一图胜千言,先看下面的“SOD框架命令处理管道”图:
由前面接口的定义并结合这个图,可以看到查询命令在“数据访问”这个管道里面流动过程:
- 首先,它在 OnExecuting 这个过滤插口位置改变命令的行为特征,比如SQL预处理,终止查询等,发起异步操作等;
- 接着,查询命令由Ado.Net进行处理,而此时是很有可能发生查询错误的情况的,那么提供一个OnExecuteError 过滤插口,让错误信息可以被一些过滤器使用,比如查询操作日志组件;
- 最后,不论前面命令执行是否成功,命令执行完了还需要进行一些其它的处理,那么提供一个OnExecuteError 过滤插口,比如观察命令执行的结果行/影响行,命令的执行时间,返回异步通知等。
根据这里定义的命令执行管道接口,最典型的实现就是可以用来记录查询日志,比如下面的 CommandExecuteLogHandle 类:
/// <summary>
/// 命令执行日志处理器,可以记录SQL和参数,执行时间等信息
/// </summary>
public class CommandExecuteLogHandle :ICommandHandle
{
/// <summary>
/// 初始化一个命令执行日志处理器
/// </summary>
public CommandExecuteLogHandle()
{
this.CurrCommandLog = new CommandLog(true);
//这里需要进行一些初始化检查,设置日志路径等
if (CommandLog.DataLogFile == null)
CommandLog.DataLogFile = "~/sql.log";
CommandLog.SaveCommandLog = true;
} public CommandLog CurrCommandLog { get; private set; } public bool OnExecuting(CommonDB db, ref string SQL, CommandType commandType, IDataParameter[] parameters)
{
this.CurrCommandLog.ReSet();
return true;
} public void OnExecuteError(IDbCommand cmd, string errorMessage)
{
CurrCommandLog.WriteErrLog(cmd, "AdoHelper:" + errorMessage);
} public long OnExecuted(IDbCommand cmd, int recordAffected)
{
long elapsedMilliseconds;
CurrCommandLog.WriteLog(cmd, "AdoHelper", out elapsedMilliseconds);
CurrCommandLog.WriteLog("RecordAffected:"+recordAffected , "AdoHelper");
return elapsedMilliseconds;
} public DBMSType ApplayDBMSType
{
get { return DBMSType.UNKNOWN; }
}
}
注意,这里 ApplayDBMSType 返回 UNKNOW,表示当前接口实现类性适合于任意数据库查询的情况。
另外,日志过滤器内部使用了框架内置的 CommandLog 类,它可以异步的记录SQL执行情况,并能记录查询时间大于某个值的查询,详细请看《PDF.NET的SQL日志》。
再看下面,我们实现一个用于处理Oracle查询的“过滤器”组件,它会在查询开始前,对SQL进行一些预处理,比如将本来使用于SQLSERVER的SQL语句格式,处理成Oracle特有的格式:
/// <summary>
/// 自定义的Oracle命令处理器,用于处理特殊的字段名大写问题
/// </summary>
public class OracleCommandHandle : ICommandHandle
{ public bool OnExecuting(CommonDB db, ref string sql, System.Data.CommandType commandType, System.Data.IDataParameter[] parameters)
{
sql= sql.Replace("[", "").Replace("]", "").Replace("@", ":").ToUpper();
//设置SQLSERVER兼容性为假,避免命令对象真正执行的时候再进行Oracle的查询语句的预处理。
db.SqlServerCompatible = false;
//返回真,以便最终执行查询,否则将终止查询
return true;
} public void OnExecuteError(System.Data.IDbCommand cmd, string errorMessage)
{ } public long OnExecuted(System.Data.IDbCommand cmd, int recordAffected)
{
return ;
} public PWMIS.Common.DBMSType ApplayDBMSType
{
get { return PWMIS.Common.DBMSType.Oracle; }
}
}
注意:上面这个实现类,指明了当前命令执行过滤器组件,仅使用于Oracle数据库,当前如果是其它数据库类型,会忽略该过滤器组件。
除此之外,是不是还可以写一个过滤器组件,监视下当前查询是否执行成功,如果成功,将查询的SQL和参数发送到消息队列,进行异步更新其它数据库?
开闭原则
所以,SOD框架的“命令执行管道”给予了最终用户在不改变原有数据访问组件的内部实现的情况下,一个监视和处理命令执行过程的“窗口”,一个或者多个对查询命令的“过滤器”组件,这正是面向对象原则之一的开闭原则。
我们来看下百度百科对开闭原则的解释:
开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。 遵循开闭原则设计出的模块具有两个主要特征: (1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。 (2)对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。
既然命令执行管道如此有用,我们该如何使用呢?还是直接看示例代码比较简单:
/// <summary>
/// 用来测试的本地 数据库上下文类
/// </summary>
public class MyOracleDbContext : DbContext
{
public MyOracleDbContext()
: base("local")
{
//local 是连接字符串名字
//注册日志处理器和Oracle命令处理器
base.CurrentDataBase.RegisterCommandHandle(new CommandExecuteLogHandle());
base.CurrentDataBase.RegisterCommandHandle(new OracleCommandHandle());
} #region 父类抽象方法的实现 protected override bool CheckAllTableExists()
{
//创建用户表
CheckTableExists<User>();
return true;
} #endregion
}
在这个 MyOracleDbContext 类中,我们注册了2个过滤器组件:日志过滤器和Oracle命令过滤器。
如果当前连接配置名 local 对应的数据库访问提供程序不是Oracle了怎么办?
不用担心,前面说过, Oracle命令过滤器仅对Oracle数据访问有效,其它数据库访问会忽略,而日志过滤器组件它是适用于任何数据库访问的。
上面的示例代码中,CurrentDataBase 对象其实就是 SOD框架的 AdoHelper对象,所以,只要你使用SOD框架,那么不管你使用的是框架的ORM,SQL-MAP,Data Controls功能,甚至是最简单的“SqlHelper”类应用,你都可以享受到SOD框架的“命令执行管道”带给你d便利!
与“观察者模式”的区别
.NET框架中,对观察者模式最常见的实现就是“事件”,事件可以实现监视某个对象的改变情况然后发起事件通知,最后由事件处理程序完成处理。在本文描述的查询处理场景中,也可以在查询处理前,处理后,发生异常这3个“观察点”发起事件,并且,事件也可以实现“多播”,一个事件可以由多个事件处理程序来处理。所以,从这个意义上来说,“管道-过滤器”模式跟“观察者”模式功能上很相似的,但为何SOD框架不选择后者来实现呢?
我认为,主要区别有以下几个方面:
在架构层面上,
“管道-过滤器”模式通常用于架构设计层面,是一种“架构模式”,比如分层架构;而观察者模式一种面向对象编程的模式,运用的领域不一样。
“管道-过滤器”模式让架构实现松耦合;而观察者模式的观察者和被观察者之间,往往是紧密耦合的关系。
在具体使用形式上,
“架构模式”可以通过配置文件来提供附件的一种功能实现,比如ASP.NET的HttpHandle,ASP.NET MVC的Controller上的Filter等,所以它的实现是松耦合的;
而观察者模式往往体现在编写的代码中,用事件来处理代码来实现,所以它往往是紧耦合的。
在业务语义上,
“管道-过滤器”是用于处理流动的载体的,比如数据,信息或其它具有流动特性的物体,方便进行多环节,多层次的拦截或者加工处理,并且每个处理环节都有序的,流动和有序,这是这类业务最重要的特征;
“事件”处理的客体范围更广,事件的客体没有固定的形态,事件的发生和处理可能都是无序的。
其它方面的考虑,事件使用前总是需要声明事件挂钩,会多增加一些代码量,并且使用完成之后,往往还需要解除挂钩,否则可能发生内存泄漏,请参见 我另外一篇文章《Release编译模式下,事件是否会引起内存泄漏问题初步研究》。
总结
所以,在当前这个数据查询的场景中,对于查询命令的处理,采用“管道-过滤器”模式来实现一个命令执行管道,是最合适的,它让人在业务语义上更加明确,并且使用上更加灵活,代码实现量也最小,而且不需要修改原有的代码实现,符合开闭原则。
到目前为止,我还没有看到其它 数据处理框架/ORM框架 比较明确的提供了关注和干预组件内部查询执行过程的功能,都只能进行外部的拦截,如果你有这样的需求,来试试SOD框架带给你的灵活和自由吧!
附注:
SOD不仅仅是一个ORM,它还有SQL-MAP和DataControl,具体可以看框架官网 http://www.pwmis.com/sqlmap ,9年历史铸就的成果,坚固可靠。
非常感谢你看到这里,相信你初步了解了SOD框架的基本功能,如果您还有其它问题,欢迎你在项目的开源网站 http://pwmis.codeplex.com的讨论区发帖,或者去官方博客相关文章回帖也可。
图解“管道过滤器模式”应用实例:SOD框架的命令执行管道的更多相关文章
- Dynamics 365中的事件框架与事件执行管道(Event execution pipeline)
本文介绍了Microsoft Dynamics 365(以下简称D365)中的两个概念,事件框架(Event Framework)与事件执行管道(Event execution pipeline). ...
- MVC框架模式技术实例(用到隐藏帧、json、仿Ajax、Dom4j、jstl、el等)
前言: 刚刚学完了MVC,根据自己的感悟和理解写了一个小项目. 完全按照MVC模式,后面有一个MVC的理解示意图. 用MVC模式重新完成了联系人的管理系统: 用户需求: 多用户系统,提供用户注册.登录 ...
- 将复杂查询写到SQL配置文件--SOD框架的SQL-MAP技术简介
引言 今天看到一片热门的博客, .NET高级工程师面试题之SQL篇 ,要求找出每一个系的最高分,并且按系编号,学生编号升序排列.这个查询比较复杂,也比较典型,自从用了ORM后,很久没有写过SQL语句了 ...
- Oracle 免费的数据库--Database 快捷版 11g 安装使用与"SOD框架"对Oracle的CodeFirst支持
一.Oracle XE 数据库与连接工具安装使用 Oracle数据库历来以价格昂贵出名,当然贵有贵的道理,成为一个Oracle DBA也是令人羡慕的事情,如果程序员熟悉Oracle使用也有机会接触到大 ...
- DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架
引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DDD使用产生的疑问: •没有正确的使用ORM, 导致数 ...
- SOD框架的数据容器,打造最适合DDD的ORM框架
SOD框架的数据容器,打造最适合DDD的ORM框架 引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DD ...
- MapReduce实例&YARN框架
MapReduce实例&YARN框架 一个wordcount程序 统计一个相当大的数据文件中,每个单词出现的个数. 一.分析map和reduce的工作 map: 切分单词 遍历单词数据输出 r ...
- 致敬平凡的程序员--《SOD框架“企业级”应用数据架构实战》自序
“简单就是美” “平凡即是伟大” 上面两句话不知道是哪位名人说的,又或者是广大劳动人民总结的,反正我很小的时候就常常听到这两句话,这两句话也成了我的人生格言,而且事实上我也是一个生活过得比较简单的平凡 ...
- Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77942585 前面的博客<Android平台dalvik模式下java Ho ...
随机推荐
- 开始使用MarkDown写博客
MarkDown 标题 #h1 ##h2 ###h3 h1 h2 h3 代码段 代码段缩进4个空格即可,如下: <div class="form-group"> < ...
- 快速入门系列--WCF--02消息、会话与服务寄宿
经过WCF基础的ABC学习,已经可以构建简单的WCF的服务,使用不同的服务地址和绑定类型,根据业务提供所需的服务契约.但不禁想问,服务所使用的消息报文是什么样的形式么?蕴含什么样内容呢?WCF服务是否 ...
- 开发高效的Tag标签系统数据库设计
需求背景 目前主流的博客系统.CMS都会有一个TAG标签系统,不仅可以让内容链接的结构化增强,而且可以让文章根据Tag来区分.相比传统老式的Keyword模式,这种Tag模式可以单独的设计一个Map的 ...
- php基础教程-语法
PHP 脚本可放置于文档中的任何位置.PHP 脚本以 <?php 开头,以 ?> 结尾: <?php // 此处是 PHP 代码 ?> PHP 文件的默认文件扩展名是 &quo ...
- Unity3D UNet网络组件详解
UNet常见概念简介 Spawn:简单来说,把服务器上的GameObject,根据上面的NetworkIdentity组件找到对应监视连接,在监视连接里生成相应的GameObject. Command ...
- tomcat架构之-----基本概念
一直都没有搞明白tomcat中server.service.Engine.Host.Context概念的意义,最近认真看了<Tomcat 6 Developer Guide>,有了进一步的 ...
- C++笔记(3):一些C++的基础知识点
前言: 找工作需要,最近看了下一些C++的基本概念,为范磊的<零起点学通C++>,以下是一些笔记. 内容: delete p;只是删除指针p指向内存区,并不是删除指针p,所以p还是可以用 ...
- LocationManager使用细节
在使用系统的LocationManager请求地理位置的时候,请特别注意一个很小的细节,调用 requestLocationUpdates 以后,请记得[自己]设置一个timeout值,否则在某些情况 ...
- Java实时读取日志文件
古怪的需求 在实习的公司碰到一个古怪的需求:在一台服务器上写日志文件,每当日志文件写到一定大小时,比如是1G,会将这个日志文件改名成另一个名字,并新建一个与原文件名相同的日志文件,再往这个新建的日志文 ...
- java.io.File中的pathSeparator与separator的差异
先总的说一下区别: File.pathSeparator指的是分隔连续多个路径字符串的分隔符,例如: java -cp test.jar;abc.jar HelloWorld 就是指";&q ...