博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一起谈.NET技术,走向ASP.NET架构设计——第七章:阶段总结,实践篇(中篇)...
阅读量:7081 次
发布时间:2019-06-28

本文共 11190 字,大约阅读时间需要 37 分钟。

  服务层(中篇)

  上一篇文章中,我们已经讲述了业务逻辑层和数据访问层层的设计和编码,下面我们就来讲述服务层的设计。如我们之前所讨论的:服务层想客户端暴露简单易用的API.

  如下图所示:

  在上图中:

1. ASPPatterns.Chap6.EventTickets.Contract: 这个类库中定义了服务层的接口契约。

2. ASPPatterns.Chap6.EventTickets.Service:这个类库中包含了上面接口契约的实现类以及业务逻辑的协调和数据的持久化和返回数据

3. ASPPatterns.Chap6.EventTickets.DataContract:这个类库中包含了客户端和服务端的数据契约对象;而且客户端 服务端之前采用”文档消息模式”来交换数据。

4. ASPPatterns.Chap6.EventTickets.HTTPHost:这个类库中host了WCF的服务。

  下面就从数据契约开始讲述,其实这也是在定义服务的时候一个很重要的思想:契约优先(服务契约和数据契约)。

  数据契约

  在设计服务层的时候,首先就要定义出客户端和服务端的数据交换的结构和格式,要定出数据的scheme.

  因为我们用WCF为例子,那么我们在数据契约的类库中引入:

 
System.Runtime.Serialization
System.ServiceModel

  我们之前说过:在服务层设计中,我们准备采用”文档消息模式”和”请求-响应模式”。而且所有的响应对象都有一些共性,那么我们就首先定义一个响应类的基类,然后其他的响应都从继承它:

 
[DataContract]
public
abstract
class
Response
{
[DataMember]
public
bool
Success {
get
;
set
; }
[DataMember]
public
string
Message {
get
;
set
; }
}

  相信大家在之前一些文章中,已经见过很多这样的代码了。下面就来定义两个具体的响应类:PurchaseTicketResponse和ReserveTicketResponse..其中PurchaseTicketResponse就代表了:客户端向服务端发起购买票的请求后,服务端发送给客户端的响应的数据结构。而ReserveTicketResponse就代表了:客户端向服务端发送请求后,服务端发送给客户端的一个标识对象的数据结构,这个标识对象可以说是用来对这个客户端本次的交易进行唯一的识别的。

  PurchaseTicketResponse和ReserveTicketResponse代码:

 
[DataContract]
public
class
PurchaseTicketResponse : Response
{
[DataMember]
public
string
TicketId {
get
;
set
; }
[DataMember]
public
String EventName {
get
;
set
; }
[DataMember]
public
String EventId {
get
;
set
; }
[DataMember]
public
int
NoOfTickets {
get
;
set
; }
}
[DataContract]
public
class
ReserveTicketResponse : Response
{
[DataMember]
public
string
ReservationNumber {
get
;
set
;}
[DataMember]
public
DateTime ExpirationDate {
get
;
set
; }
[DataMember]
public
String EventName {
get
;
set
; }
[DataMember]
public
String EventId {
get
;
set
; }
[DataMember]
public
int
NoOfTickets {
get
;
set
; }
}

  同上,下面添加两个请求的对象,代表了客户端向服务端发送请求的数据结构:

 
[DataContract]
public
class
ReserveTicketRequest
{
[DataMember]
public
string
EventId {
get
;
set
; }
[DataMember]
public
int
TicketQuantity {
get
;
set
; }
}
[DataContract]
public
class
PurchaseTicketRequest
{
[DataMember]
public
string
CorrelationId {
get
;
set
; }
[DataMember]
public
string
ReservationId {
get
;
set
; }
[DataMember]
public
string
EventId {
get
;
set
; }
}

  服务契约

  定义完了数据契约之后,我们接下来定义服务接口契约ITicketService:

 
[ServiceContract(Namespace
=
"
http://ASPPatterns.Chap6.EventTickets/
"
)]
public
interface
ITicketService
{
[OperationContract()]
ReserveTicketResponse ReserveTicket(ReserveTicketRequest reserveTicketRequest);
[OperationContract()]
PurchaseTicketResponse PurchaseTicket(PurchaseTicketRequest purchaseTicketRequest);
}

  这个服务接口主要暴露两个功能给客户端:

  ReserveTicket方法:服务端创建一个标识,并且返回响应给客户端。

  PurchaseTicket:购买真实的票,并且把结果发送给客户端。

  下面,我们来添加两个扩展方法类的辅助类:TicketPurchaseExtensionMethods和TicketReservationExtensionMethods.这两个扩展方法类负责把TicketReservation业务类和TicketPurchase业务类转换为文档消息的数据格式。如下:

 
public
static
class
TicketPurchaseExtensionMethods
{
public
static
PurchaseTicketResponse ConvertToPurchaseTicketResponse(
this
TicketPurchase ticketPurchase)
{
PurchaseTicketResponse response
=
new
PurchaseTicketResponse();
response.TicketId
=
ticketPurchase.Id.ToString();
response.EventName
=
ticketPurchase.Event.Name;
response.EventId
=
ticketPurchase.Event.Id.ToString();
response.NoOfTickets
=
ticketPurchase.TicketQuantity;
return
response;
}
}
public
static
class
TicketReservationExtensionMethods
{
public
static
ReserveTicketResponse ConvertToReserveTicketResponse(
this
TicketReservation ticketReservation)
{
ReserveTicketResponse response
=
new
ReserveTicketResponse();
response.EventId
=
ticketReservation.Event.Id.ToString();
response.EventName
=
ticketReservation.Event.Name;
response.NoOfTickets
=
ticketReservation.TicketQuantity;
response.ExpirationDate
=
ticketReservation.ExpiryTime;
response.ReservationNumber
=
ticketReservation.Id.ToString();
return
response;
}
}

  之前提到过:为了避免客户端因重复提交而导致服务端数据不一致,采用Idempotent Messaging模式(具体讲述,请参见走向ASP.NET架构设计-第六章-服务层设计(中篇)),代码实现如下:

 
public
class
MessageResponseHistory
<
T
>
{
private
Dictionary
<
string
, T
>
_responseHistory;
public
MessageResponseHistory()
{
_responseHistory
=
new
Dictionary
<
string
, T
>
();
}
public
bool
IsAUniqueRequest(
string
correlationId)
{
return
!
_responseHistory.ContainsKey(correlationId);
}
public
void
LogResponse(
string
correlationId, T response)
{
if
(_responseHistory.ContainsKey(correlationId))
_responseHistory[correlationId]
=
response;
else
_responseHistory.Add(correlationId, response);
}
public
T RetrievePreviousResponseFor(
string
correlationId)
{
return
_responseHistory[correlationId];
}
}

  这个类在内存中保存了一个请求-响应的记录字典。每次客户端发送一个请求,服务端就会去这个内存的字典中检查这个请求是否已经被处理(检查这个请求的预约标识是否在字典中存在),如果这个请求已经被处理了,那么服务端直接就不用在处理。当然,我们可以把”请求-响应”的处理记录保存在其他的存储介质中。

  服务实现

  下面就实现之前的服务契约实现代码(代码可能有点多,我们下面会做详细的讲解):

 
[AspNetCompatibilityRequirements(RequirementsMode
=
AspNetCompatibilityRequirementsMode.Allowed)]
public
class
TicketService : ITicketService
{
private
IEventRepository _eventRepository;
private
static
MessageResponseHistory
<
PurchaseTicketResponse
>
_reservationResponse
=
new
MessageResponseHistory
<
PurchaseTicketResponse
>
();
public
TicketService(IEventRepository eventRepository)
{
_eventRepository
=
eventRepository;
}
public
TicketService() :
this
(
new
EventRepository())
//
Poor mans DI
{ }
public
ReserveTicketResponse ReserveTicket(ReserveTicketRequest reserveTicketRequest)
{
ReserveTicketResponse response
=
new
ReserveTicketResponse();
try
{
Event Event
=
_eventRepository.FindBy(
new
Guid(reserveTicketRequest.EventId));
TicketReservation reservation;
if
(Event.CanReserveTicket(reserveTicketRequest.TicketQuantity) )
{
reservation
=
Event.ReserveTicket(reserveTicketRequest.TicketQuantity);
_eventRepository.Save(Event);
response
=
reservation.ConvertToReserveTicketResponse();
response.Success
=
true
;
}
else
{
response.Success
=
false
;
response.Message
=
String.Format(
"
There are {0} ticket(s) available.
"
, Event.AvailableAllocation());
}
}
catch
(Exception ex)
{
//
Shield Exceptions
response.Message
=
ErrorLog.GenerateErrorRefMessageAndLog(ex);
response.Success
=
false
;
}
return
response;
}
public
PurchaseTicketResponse PurchaseTicket(PurchaseTicketRequest PurchaseTicketRequest)
{
PurchaseTicketResponse response
=
new
PurchaseTicketResponse();
try
{
//
Check for a duplicate transaction using the Idempotent Pattern,
//
the Domain Logic could cope but we can't be sure.
if
(_reservationResponse.IsAUniqueRequest(PurchaseTicketRequest.CorrelationId))
{
TicketPurchase ticket;
Event Event
=
_eventRepository.FindBy(
new
Guid(PurchaseTicketRequest.EventId));
if
(Event.CanPurchaseTicketWith(
new
Guid(PurchaseTicketRequest.ReservationId)))
{
ticket
=
Event.PurchaseTicketWith(
new
Guid(PurchaseTicketRequest.ReservationId));
_eventRepository.Save(Event);
response
=
ticket.ConvertToPurchaseTicketResponse();
response.Success
=
true
;
}
else
{
response.Message
=
Event.DetermineWhyATicketCannotbePurchasedWith(
new
Guid(PurchaseTicketRequest.ReservationId));
response.Success
=
false
;
}
_reservationResponse.LogResponse(PurchaseTicketRequest.CorrelationId, response);
}
else
{
//
Retrieve last response
response
=
_reservationResponse.RetrievePreviousResponseFor(PurchaseTicketRequest.CorrelationId);
}
}
catch
(Exception ex)
{
//
Shield Exceptions
response.Message
=
ErrorLog.GenerateErrorRefMessageAndLog(ex);
response.Success
=
false
;
}
return
response;
}
}

  1、TicketService类包含了一个MessageResponseHistory对象的引用,这样就确保了所有调用这个服务的请求的响应都被记录下来。当一个请求发送到了服务端之后,在对应的服务方法里面就检查这个请求是否已经被处理,如下PurchaseTicket方法:

 
public
PurchaseTicketResponse PurchaseTicket(PurchaseTicketRequest PurchaseTicketRequest)
{
PurchaseTicketResponse response
=
new
PurchaseTicketResponse();
try
{
//
Check for a duplicate transaction using the Idempotent Pattern,
//
the Domain Logic could cope but we can't be sure.
if
(_reservationResponse.IsAUniqueRequest(PurchaseTicketRequest.CorrelationId))
{
TicketPurchase ticket;
Event Event
=
_eventRepository.FindBy(
new
Guid(PurchaseTicketRequest.EventId));
if
(Event.CanPurchaseTicketWith(
new
Guid(PurchaseTicketRequest.ReservationId)))
{
ticket
=
Event.PurchaseTicketWith(
new
Guid(PurchaseTicketRequest.ReservationId));
_eventRepository.Save(Event);
response
=
ticket.ConvertToPurchaseTicketResponse();
response.Success
=
true
;
}
else
{
response.Message
=
Event.DetermineWhyATicketCannotbePurchasedWith(
new
Guid(PurchaseTicketRequest.ReservationId));
response.Success
=
false
;
}
_reservationResponse.LogResponse(PurchaseTicketRequest.CorrelationId, response);
}
else
{
//
Retrieve last response
response
=
_reservationResponse.RetrievePreviousResponseFor(PurchaseTicketRequest.CorrelationId);
}
}
catch
(Exception ex)
{
//
Shield Exceptions
response.Message
=
ErrorLog.GenerateErrorRefMessageAndLog(ex);
response.Success
=
false
;
}
return
response;
}

  2、这个服务类有两个构造函数:第一个是无参,第二个需要传入一个实现了IEventRepository接口的类:

 
public
TicketService(IEventRepository eventRepository)
{
_eventRepository
=
eventRepository;
}
public
TicketService() :
this
(
new
EventRepository())
//
Poor mans DI
{ }

  在上面的代码中,为了示例的简单,我们直接把EventRepository Hard Code传入,当然了,可以采用IoC的方式来做,这里就暂不演示,大家可以参看之前的系列文章中的一些例子。

  3、对于服务类的ReserveTicket方法,这个方法只有唯一的一个参数:ReserveTicketRequest,没有像以前那样传入N多个参数。这个方法的作用就是标识客户端的这个请求,为这个请求生成唯一的一个标识。因为可能接下来的一些客户端的操作都属于一个业务事务,一个流程可能很复杂,需要客户端和服务端交互多次,但是这些多次交互都必须被看成是一个单元,所以,余下的请求都要带上这个唯一的标识,表示它们是一体的。当然,我们这里的例子很简单,没有反应出来。

  ReserveTicket方法检查服务端是否还有足够的票来满足这个请求,并且把结果转换为响应格式返回,并且通过标识Success来告诉客户端请求的处理状况。

 
public
ReserveTicketResponse ReserveTicket(ReserveTicketRequest reserveTicketRequest)
{
ReserveTicketResponse response
=
new
ReserveTicketResponse();
try
{
Event Event
=
_eventRepository.FindBy(
new
Guid(reserveTicketRequest.EventId));
TicketReservation reservation;
if
(Event.CanReserveTicket(reserveTicketRequest.TicketQuantity) )
{
reservation
=
Event.ReserveTicket(reserveTicketRequest.TicketQuantity);
_eventRepository.Save(Event);
response
=
reservation.ConvertToReserveTicketResponse();
response.Success
=
true
;
}
else
{
response.Success
=
false
;
response.Message
=
String.Format(
"
There are {0} ticket(s) available.
"
, Event.AvailableAllocation());
}
}
catch
(Exception ex)
{
//
Shield Exceptions
response.Message
=
ErrorLog.GenerateErrorRefMessageAndLog(ex);
response.Success
=
false
;
}
return
response;
}

  4、PurchaseTicket方法和之前的ReserveTicket方法不同,这个方法就是真正的用来订票的,之前的ReserveTicket只是验证和产生请求标识的。PurchaseTicket方法首先检查请求中的标识是否存在了响应了,即是否被处理过了,如果没有处理,就开始购票的流程,最后产生响应结果,并且保存响应。如果已经处理过了,直接返回。

  宿主程序

  下面一步就是把这个服务运行起来,方式有很多,这里我们就把整个服务host在IIS中。

  首先在ASPPatterns.Chap6.EventTicket.HTTPHost类库中添加一个TicketService.svc.,并且修改这个文件的代码:如下:

  然后在web.config中添加wcf的一些配置:就是我们常常说的”ABC”(Address, Binding, Contract)

 
<
system.serviceModel
>
<
services
>
<
service name
=
"
ASPPatterns.Chap6.EventTickets.Service.TicketService
"
behaviorConfiguration
=
"
metadataBehavior
"
>
<
endpoint address
=
""
binding
=
"
wsHttpBinding
"
contract
=
"
ASPPatterns.Chap6.EventTickets.Contracts.ITicketService
"
/>
</
service
>
</
services
>
<
behaviors
>
<
serviceBehaviors
>
<
behavior name
=
"
metadataBehavior
"
>
<
serviceMetadata httpGetEnabled
=
"
true
"
/>
</
behavior
>
</
serviceBehaviors
>
</
behaviors
>
<
serviceHostingEnvironment aspNetCompatibilityEnabled
=
"
true
"
/>
</
system.serviceModel
>

  今天就介绍到这里,下一篇文章接着介绍。

转载于:https://www.cnblogs.com/waw/archive/2011/09/02/2163110.html

你可能感兴趣的文章
使用 Spring 3 MVC HttpMessageConverter 功能构建 RESTful web 服务
查看>>
滚动页面
查看>>
Android日志打印类LogUtils,能够定位到类名,方法名以及出现错误的行数并保存日志文件...
查看>>
Android 监听 WiFi 开关状态
查看>>
Win7系统中哪些服务可以关闭?
查看>>
linux环境中设置jacoco覆盖率
查看>>
使用 Google Cloud 上的 tf.Transform 对 TensorFlow 管道模式进行预处理
查看>>
跳表在手天下我有之ConcurrentSkipListMap
查看>>
一篇文章,从源码深入详解ThreadLocal内存泄漏问题
查看>>
PHP算法:一个数字平分为N份,并且总值相等
查看>>
linux/unix编程手册-1_5
查看>>
Mac OS 解决 /usr/bin/sudo must be owned by uid 0 问题
查看>>
第5条:避免创建不必要的对象
查看>>
使用UltraISO制作U盘启动盘
查看>>
过滤器第二篇【编码、敏感词、压缩、转义过滤器】
查看>>
半小时轻松玩转WebGL滤镜技术系列(一)
查看>>
实现一个可管理、增发、兑换、冻结等高级功能的代币
查看>>
【vue源码篇】filter源码详解
查看>>
React Native 报错
查看>>
【android精选】图片涂鸦下拉加载地图大集合五子棋游戏自定义上下文菜单源码...
查看>>