.NET应用架构设计—面向查询服务的参数化查询设计(分解业务点,单独配置各自的数据查询契约)
阅读目录:
- 1.背景介绍
- 2.对业务功能点进行逻辑划分(如:A、B、C分别三个业务点)
- 2.1.配置映射关系,对业务点配置查询契约(构造VS插件方便生成查询契约)
- 2.2.将配置好的映射策略文件放在调用端,与服务不耦合
- 3.Dynamic、Dom动态构造服务端对象(Dynamic、DOM实现动态DOM)
1.背景介绍
现在越来越多的公司都在尝试SOA架构的实践,本人最近也在尝试学习这方面的技术,但是在实践过程中遇到一个问题,我想这个问题也是我们普遍实践者都应该会遇到的问题,问题描述如下:
我们有一个SOA商品(Item)查询接口,这个接口很通用,主要用来支撑日常很多其他系统的大量关于Item的查询,尤其是在高峰期间该服务的压力是很大的;我们站在SOA的角度看这个接口,这个通用的接口解决了众多的查询业务,确实不错,但是我们切换一下角度,站在每一个调用接口的访问端看似乎并不是很满意或者说牺牲了部分性能上的代价,因为我们无法干净利落的只获取当前这个业务点需要的数据项;这个Item服务接口所返回的数据项必须同时满足所有调用它的业务点,哪怕这次调用我只需要用到Item的三分之一的数据字段都不行,每次都会把不需要的字段都查询出来,不管是返回的性能、查询的性能,其实都是可以通过调整设计来避免的;
以往我们的思路都是集中在服务端,常规做法都是提供了一个能够容纳所有查询客户端需求的数据实体,客户端可选择的余地很有限,无法只获取自己所需要的几个数据项,甚至各个业务点在不同的情况下都有可能需要两到三个数据返回实体;总而言之,面向数据查询的服务接口如果要向着SOA方向发展那就必须包含SOA设计上的相关原则,如这里的面向查询为主的服务设计其实就是缺少SOA原则中的”服务应具有策略性“一原则;
为什么以往一直没有暴露出这个问题呢,是因为以往都是在本地直接调用“查询引擎”,如:SQLSERVER,在“查询引擎”的最后一层就是应用程序,而应用程序中可以编写很多彼此类似的查询方法,每个方法可能只有一两个字段的差异性,或者通过“企业应用架构模式—查询对象模式”来将不同的方法合在一起通过一个可以调整查询字段的对象来配置本次需要的查询字段;由于现在我们已将查询服务化,就不太可能再去为了所有客户端在去适应性的去扩充类似没有太大价值的接口,但是客户端又需要将自己所需要的查询字段让服务知道,所以这里的解决方案可以称为面向SOA的”企业应用架构模式—查询对象模式“;
本文将通过运用”关注点分离“通用设计思想来对查询服务在服务端的强耦合进行分解,将强耦合从服务端迁移出来通过策略性的配置将关注点放入各自的客户端,从而有效的解决服务不再臃肿的问题,如果理解上有困难可以尝试使用面向SOA的”企业应用架构模式—查询对象模式“概念来理解;
2.对业务功能点进行逻辑划分(如:A、B、C分别三个业务点)
首先我们需要将相对于服务来说的客户端中所有业务点进行逻辑划分,将原本一个高耦合的庞大数据实体分解成各自所需要的一个精简的数据实体;业务点的划分目地在于可以将数据实体能与之对应起来,这个数据实体是针对于查询服务而言的,对于客户端来说没有任何的依赖和约束,也就是说本次业务点发起的查询将把这个数据实体转化成一组查询策略中的设置带到服务端中,然后服务端在根据这组策略信息进行组合最终的查询语句;
注:这里的数据实体并不是服务端定义的DTO,也不是客户端定义的DTO,而是一个只跟本次业务查询相关的数据查询实体,该实体不是一个定义的类,而是一个策略,类似:
A.Business{Query field{ItemNumber、Description、PromationPrice}}
这样一组配置信息;客户端用来反序列化的DTO可以是一个庞大的共用的数据实体,也可以是跟业务点绑定的精简实体,对于查询没有任何影响,我们要解决的是“只查询我所需要的数据项,只返回我所需要的数据项”,而跟你在服务端、客户端定义的用来辅助序列化的实体没有任何关系;
将查询的字段、返回的字段通过查询策略带入到服务端,我们就能够知道本次业务点查询的是需要什么样的字段,然后就可以在构造查询引擎参数时将返回的字段直接加上或者过滤不需要的;
2.1.配置映射关系,对业务点配置查询契约(构造VS插件方便生成查询契约)
将系统中需要调用服务接口的所有功能点进行业务点逻辑划分设计后,每个业务点都需要在自己发起调用服务的时候能够带上在之前某个时间点设计好的查询契约,这个用来生成查询契约的工具最好是集成在VisualStudio中的自定义插件,在设计时用来动态构造一个对应的契约配置文件,如果可以的话可以采用动态代码方案,将配置文件的静态文件通过动态生成代码的方式嵌入到生成的代码中去,减少不需要的配置文件,也减少查询框架的性能开销,一次生成后就可以直接使用;
2.2.将配置好的映射策略文件放在调用端,与服务不耦合
本篇文章的解决方案最大的突破点就是将关注点从服务端转移到所有客户端上,将原本都集中在服务上的所有客户端的需求分离出去,每个需要调用服务的客户端维护好自己的一份查询契约,在每次调用服务的时候带上那份契约;如果处于性能考虑每次带上契约会增加开销,那么可以在第一次身份验证的时候在服务上确认,以后调用都忽略;
这样一来服务将精简很多,通过同样的设计方法可以用来设计很多类似的服务接口,将关注点从服务上转移到客户端上,会是一个很好的设计思路;
3.Dynamic、Dom动态构造服务端对象(Dynamic、DOM实现动态DOM)
借助C#新特性Dynamic,我们可以在.NET平台上进行动态编程,这里可以解决我们预先定义服务端实体的好处;以往我们需要在服务上定义一个至少能容纳所有客户端查询契约中的所有数据项的实体,但是当我们运用动态编程时,我们无需事先定义一个类,而可以在运行时动态获取对象属性,当然这得益于.NETDLR的实现;再适当的结合DOM思想,我们就可以实现一个动态DOM效果,对于DOM的某个Element的访问也无需定义映射实体然后在通过属性获取,中间既增加了序列化的开销还增加了开发工作量;
1 using System; 2 using System.Collections.Generic; 3 4 namespace ConsoleApplication1.DynamicDom 5 { 6 public class ItemEntity : System.Dynamic.DynamicObject 7 { 8 /// <summary> 9 /// 可以使用LINQ TO XML或者将XML数据构造在一个指定的容器中,用来判断是否存在 10 /// </summary> 11 private static Dictionary<string, object> itemPropery = new Dictionary<string, object>(); 12 static ItemEntity() 13 { 14 itemPropery.Add("ItemNumber", 1029394); 15 itemPropery.Add("PromationPrice", 100); 16 } 17 public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result) 18 { 19 if (itemPropery.ContainsKey(binder.Name)) 20 { 21 result = itemPropery[binder.Name]; 22 return true; 23 } 24 result = null; 25 return false; 26 } 27 } 28 }
1 dynamic itemEntity = new DynamicDom.ItemEntity(); 2 Console.WriteLine("ItemNumber:" + itemEntity.ItemNumber); 3 Console.WriteLine("PromationPrice:" + itemEntity.PromationPrice); 4 Console.ReadLine();
这里只是一个简单的示例,目的是想让并不太了解Dynamic的朋友有了直观上的认识;通过使用Dynamic、Dom可以在服务端上无需定义任何的实体,根据各个客户端传过来的配置直接进行动态访问,可以借助LIQN TO XML;
全文仅仅是一个设计上的介绍,要想完全实现上面这些效果需要还是需要开发些东西的,这里只是抛砖引玉,希望对正在设计相关内容的朋友提供一个思路;