一起谈.NET技术,走向ASP.NET架构设计——第七章:阶段总结,实践篇(中篇)

简介:   服务层(中篇)  上一篇文章中,我们已经讲述了业务逻辑层和数据访问层层的设计和编码,下面我们就来讲述服务层的设计。如我们之前所讨论的:服务层想客户端暴露简单易用的API.  如下图所示:  在上图中:1. ASPPatterns.Chap6.EventTickets.Contract: 这个类库中定义了服务层的接口契约。

  服务层(中篇)

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

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

目录
相关文章
|
14天前
|
存储 开发框架 前端开发
前端框架EXT.NET Dotnet 3.5开发的实验室信息管理系统(LIMS)成品源码 B/S架构
发展历史:实验室信息管理系统(LIMS),就是指通过计算机网络技术对实验的各种信息进行管理的计算机软、硬件系统。也就是将计算机网络技术与现代的管理思想有机结合,利用数据处理技术、海量数据存储技术、宽带传输网络技术、自动化仪器分析技术,来对实验室的信息管理和质量控制等进行全方位管理的计算机软、硬件系统,以满足实验室管理上的各种目标(计划、控制、执行)。
16 1
|
1天前
|
开发框架 JSON .NET
.Net4.0 Web.config 配置实践
.Net4.0 Web.config 配置实践
|
3天前
|
负载均衡 API 数据库
构建高效微服务架构的五大关键技术
【5月更文挑战第4天】 随着云计算和容器化技术的成熟,微服务架构已成为软件开发的主流模式。本文将详细探讨实现高效微服务架构的五个关键技术点:服务拆分策略、API网关设计、服务发现与注册、熔断机制以及分布式事务管理。这些技术点是确保微服务系统可扩展性、灵活性及稳定性的基石,对于后端开发者而言,掌握它们至关重要。文章将提供具体的实施建议和最佳实践,帮助读者构建和维护高性能的微服务系统。
|
6天前
|
设计模式 Cloud Native 算法
拥抱变化:我的技术适应之旅构建未来:云原生架构在企业数字化转型中的关键角色
【4月更文挑战第30天】 在技术的浪潮中,我学会了不仅仅是编码,还有如何与时俱进。本文记录了我从一名初出茅庐的开发者成长为一个能够适应不断变化技术环境的工程师的心路历程。从最初的困惑与挑战到后来的接纳与创新,我意识到,技术能力的提升和心态的转变同样重要。
|
6天前
|
前端开发 JavaScript 安全
【TypeScript技术专栏】TypeScript在微前端架构中的应用
【4月更文挑战第30天】微前端架构通过拆分应用提升开发效率和降低维护成本,TypeScript作为静态类型语言,以其类型安全、代码智能提示和重构支持强化这一架构。在实践中,TypeScript定义公共接口确保跨微前端通信一致性,用于编写微前端以保证代码质量,且能无缝集成到构建流程中。在微前端架构中,TypeScript是保障正确性和可维护性的有力工具。
|
8天前
|
设计模式 供应链 安全
如何在短频快的节奏中做好技术?业务开发必会的架构思维
本文提供一种业务架构设计模式:从业务&技术两个角度提炼出一个基础思维框架,供业务线开发同学参考。
如何在短频快的节奏中做好技术?业务开发必会的架构思维
|
13天前
|
Cloud Native Devops 持续交付
构建未来:云原生技术在现代IT架构中的演进与实践
【4月更文挑战第24天】 随着企业数字化转型的深入,云原生技术正成为推动创新和敏捷性的关键技术。本文聚焦于云原生技术的发展历程及其在现代IT架构中的应用,探讨了如何利用容器化、微服务、DevOps和持续集成/持续部署(CI/CD)等核心概念来优化资源利用率,提高系统弹性,并加速产品上市时间。通过分析多个行业案例,文章揭示了云原生实践对企业竞争力的显著影响,并提出了面向未来的IT架构战略建议。
|
4月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
46 0
|
12天前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
19 0