引言
服务层是在交互的两个层中间又定义了另外一个层,典型的是在表现层和业务逻辑层之间。这个中间层只是实现应用的用例的类集合。
服务和面向服务的出现,使得整个解决方案更有价值、更加成功。与表现层相比,服务层提供了松散的耦合,服务层提供商定的协议,可重用性,跨平台的部署。服务向其他类一样,允许你调整你需要的抽象总数。
真实世界的表现层,主要是用户前端。用户做的每一件事都通过表现层和用户界面。
企业级的应用,可能会有多种数据表现接口。一个接口可能就是一个用户界面,也可能是每一个支持的平台,例如:移动应用、Web、WPF、Windows、Silverlight,或者是其他软件平台。另一个接口可能是后端应用,传入数据或者是获取数据,并且转变他。可能还有一个接口是一个使用应用的连接者代理-系统整合方案中的内部处理逻辑。
服务层响应来自表现层的输入。相应的,表现层不关心另外一端的操作和模块。重要的是模块声明了它能做什么。
表现层和服务层都不包含业务逻辑。表现层只知道服务层提供的粗粒度接口,服务层只知道一系列可行的相互作用的协议,处理本质的细节:事务,资源管理,协调,数据消息。
服务层在现实生活中的例子
SOA的出现,与服务层的出现一致,加强了服务层的概念,使他更吸引人。一些人争论说在多层架构中使用SOA是有创造力的。争论这些是毫无意义的,就好像争论是先有鸡,还是先有蛋。
在实际的使用中,使用服务层的目的,背后的理由,很多程序员和架构师还未能理解。下面,让我们分析一个现实生活中的例子。
我们中的很多人都有做初级程序员的经历。在某些时候,我们还会碰到一个目中无人的老板。老板可能会说:嗨,我们需要马上为客户定制一个系统。
你听到了吗?老板就是表现层。老板关心的是向经理人发送一个简单的命令。在他的眼里,经理人暴露了一个任务和责任的列表。老板不关心经理人实际如何完成任务,但是他知道公司和经理人之间的协议中规定的经理人应该做什么(协议中也会提到,如果经理人不满足要求,就会被替换掉)。经理人就是服务层。最终,经理人协调其他资源来完成这个任务。
如果你左右看看,在现实生活中你会找到很多服务层模式的例子。例如:孩子向父母要零花钱,编辑要求修改文稿,你从ATM中取现金,等等。
什么时候使用服务层
在任何有点复杂的应用中都应该使用服务层。如果在一个简单的文档管理系统,或者是一个快速建立的网站,可能只是存在几个星期的网站中建立服务层很可能会没有回报。
在一个分层系统中,没有理由不使用服务层。一个可能的例外就是简单的前端和一系列只是满足用例的应用服务。在这种情况下,服务层很可能只是一个发报机,没有任务组合工作。简单的服务层还不如直接调用业务逻辑层。
相反的,在你拥有多个前端,而且是大量的应用逻辑,将应用逻辑存放在一个地方,而不是在每个应用接口中都保留副本会更加好。
服务层的优点
服务层增加抽象,解耦两个交互的层。在任何你想获得一个更好的系统的时候,你都应该构建一个服务层。服务层使用粗粒度的远程接口最小化表现层和业务层之间的通信次数。
通过服务(例如:WCF)来实现服务层的时候,你能感觉到其他的好处,例如:通过配置来改变绑定信息。
服务层的缺点
因为抽象是服务层的主要优点,对于简单的系统可能有点过头了。
服务层不是必须使用例如WCF这样的服务技术。在ASP.NET中的表现层,你可以把code-behind类叫做服务层。这时候使用WCF代替普通的类,可能有点过头了,很可能会降低性能。考虑在你的系统中使用WCF,需要考虑性能,如果性能下降的无法忍受,请选择其他服务层技术。
服务层适用于什么样的场景
表现层调用服务层。是一个远程调用,还是一个本地调用?
Martin Flower关于分布式对象设计的推荐是:不要分散你的对象。我们可以理解为:“除非是必须的,或者是有好处”。就想你所知道的,必要性和好处实际容易变化的,难以量化,但是在一些特殊的方案中,他们很容易识别。
因此,在什么场景适合服务层呢?通常来说,如果你有一个服务层,可以很容易的跨层移动,那是一件好事。在这点上,例如WCF这样的服务技术是一个正确的工具。
如果客户端是Web网页,服务层最好是位于本地的Web服务器上。如果站点成功了,你可以将服务层分离到独立的应用服务器,来增加扩展性。
如果客户端是桌面应用,服务层会部署到一个独立的物理层,并且通过远程来访问。这个方法类似于Software+Services的架构,客户端除了GUI什么都没有,全部的应用逻辑都在远端。如果客户端使用Silverlight,服务层会发布在Internet上,你可以建立一个完美的RIA(Rich Internet Application)应用。
实战服务层模式
实现服务层依赖于两个技术选择。第一个选择就是那什么方法或者是调用来作为服务层的基础。使用普通的类还是服务?如果选择服务,又该选择哪种服务的实现技术?在Windows或者是.NET平台,你的选择比较少。你可以选择WCF service、asp.net xml web service,或者是类似于REST之类的服务。
如果你对于.NET框架有一些了解,你应该知道创建一个WCF service或者是web service,就好像创建普通的类,然后添加一些attribute。当然,还有很多细节需要考虑,例如:web service的WSDL(web service description language)web服务描述语言,WCF的配置和数据协议。服务最终是一个包含其他内容的类。
设计服务层的类
服务层使用的类应该暴露一个协议,无论是WCF的协议还是实现接口。实现接口是一个比较好的做法,因为它更清晰的描述了一个类可以做什么,将会做什么。接口使用DTO接收和返回数据,推荐粗粒度的方法,以便它可以最小化网络传输,最大化网络吞吐量。
如何将需要的方法映射为接口和类呢?在用例的基础上,列出一系列所需的方法,然后将他们分为逻辑组。每个组建立自己的服务或者是类。
大多数情况,你的结果是问题域的每个实体建立一个服务类,OrderService,CustomerServcie等等。这些都是应用需要的。但是,如果用户的行为相对比较小,行为比较相似,这时候一个服务类可能就够用了。否则,一个单一的服务类会迅速变大,会很难以维护和变化。
通常来说,我们认为没有严格定义的的准则,例如:每个实体都要有自己的service,或者是一个service需要满足用户所有实体。服务层在系统的表现层和其他部分之间进行调节。服务层包括了粗粒度的服务(也是用例驱动的),在他们的编程接口中,实现了用例。
服务层和系统的其他部分相比,是独立的,对于表现层来说只是一个调用内部处理流程的接口。如果用例有变化,你很可能只是修改服务层而不用修改业务逻辑。在一个相对较大的应用中,对于服务层的编程接口来说,你应该先看一下你的用例,然后使用通用的方法组织类中的方法。
实现一个服务层的类
我们推荐每个类应该实现一个接口。如果你选择WCF,这是严格要求的,而且从整体上来讲是一个好方法。
public interface IOrderService
{
[OperationContract]
bool Submit(BeautyCode.Entity.Order order,BeautyCode.Entity.CommunUser user, out BeautyCode.Entity.CException exception);
[OperationContract]
List < BeautyCode.Entity.Order > FindOrders(BeautyCode.Entity.OrderFind find, BeautyCode.Entity.CommunUser user, out BeautyCode.Entity.CException exception);
[OperationContract]
BeautyCode.Entity.Order GetByOrderSeqNo( string orderSeqNo, BeautyCode.Entity.CommunUser user, out BeautyCode.Entity.CException exception);
}
[AspNetCompatibilityRequirements (RequirementsMode = AspNetCompatibilityRequirementsMode .Allowed )]
public class OrderService:ServiceBaseImpl , IOrderService
{
private void ThrowException(BeautyCode.Entity.CommunUser user)
{
}
public bool Submit(BeautyCode.Entity.Order order, BeautyCode.Entity.CommunUser user, out BeautyCode.Entity.CException exception)
{
throw new NotImplementedException();
}
public List < BeautyCode.Entity.Order > FindOrders(BeautyCode.Entity.OrderFind find, BeautyCode.Entity.CommunUser user, out BeautyCode.Entity.CException exception)
{
throw new NotImplementedException();
}
public BeautyCode.Entity.Order GetByOrderSeqNo( string orderSeqNo, BeautyCode.Entity.CommunUser user, out BeautyCode.Entity.CException exception)
{
throw new NotImplementedException();
}
}
首先假设接口直接使用领域模型对象。在上面的例子中的Order类,代表我们在领域模型中建立的order实体。如果我们使用实际的领域模型对象,我们假设在业务逻辑中使用领域模型的模式。如果你使用数据表模型的模式,上面代码中的order类应该替换为DataTable。我们一会在回到DTO的讨论上来。
submit方法需要整合应用内部的服务,检查用户的账户状态,检查订单中商品的有效性,同步厂商的商品信息。submit方法是一个典型的服务层方法,它对表现层提供了单一的协议,与不同的领域模型和业务逻辑进行多个步骤的操作。
FindOrders方法返回一个order集合,GetByOrderSeqNo返回一个特定的order。此外,假定我们在业务逻辑层使用领域模型的模式,没有专门的数据传输对象。很多的架构师推荐在服务层不出现Create、Read、Update、Delete(CRUD)方法。FindOrders和GetByOrderSeqNo方法本质上来说就是CRUD中的Read方法。
而且,方法依赖于你的用例。如果用例中有用户点击一个地方显示订单列表,或者是单个订单的详细信息的需要,那么这些方法就必须要有。
处理角色和安全
FindOrders方法应该只是返回当前用户可以看到的订单。
如果你把安全当回事来考虑的话,就应该在服务层的每一个方法中检查调用者的身份,对未授权的用户拒绝方法的调用。
如果你不想在每个方法中重复验证用户的身份,那就需要在服务层的方法上面添加attribute来实现身份验证。
服务层起到一个看门人的作用,通常不需要将role信息传输到业务逻辑层中去验证,除非有好的理由。但是,如果有这么一个好的理由,使得你不得不将role信息传到业务逻辑层中,也是不错的。