一起谈.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 >

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

目录
相关文章
|
8月前
|
数据采集 监控 API
移动端性能监控探索:iOS RUM SDK 技术架构与实践
阿里云 RUM SDK 作为一款性能体验监控采集工具,可以作为辅助 App 运维的强有力助手,提升您的问题排查效率。
459 69
|
8月前
|
存储 运维 分布式计算
零售数据湖的进化之路:滔搏从Lambda架构到阿里云Flink+Paimon统一架构的实战实践
在数字化浪潮席卷全球的今天,传统零售企业面临着前所未有的技术挑战和转型压力。本文整理自 Flink Forward Asia 2025 城市巡回上海站,滔搏技术负责人分享了滔搏从传统 Lambda 架构向阿里云实时计算 Flink 版+Paimon 统一架构转型的完整实战历程。这不仅是一次技术架构的重大升级,更是中国零售企业拥抱实时数据湖仓一体化的典型案例。
594 0
|
9月前
|
数据采集 存储 运维
MyEMS:技术架构深度剖析与用户实践支持体系
MyEMS 是一款开源能源管理系统,采用分层架构设计,涵盖数据采集、传输、处理与应用全流程,支持多协议设备接入与多样化能源场景。系统具备高扩展性与易用性,结合完善的文档、社区、培训与定制服务,助力不同技术背景用户高效实现能源数字化管理,降低使用门槛与运维成本,广泛适用于工业、商业及公共机构等场景。
385 0
|
9月前
|
数据采集 运维 数据可视化
AR 运维系统与 MES、EMA、IoT 系统的融合架构与实践
AR运维系统融合IoT、EMA、MES数据,构建“感知-分析-决策-执行”闭环。通过AR终端实现设备数据可视化,实时呈现温度、工单等信息,提升运维效率与生产可靠性。(238字)
|
8月前
|
存储 SQL 消息中间件
从 ClickHouse 到 StarRocks 存算分离: 携程 UBT 架构升级实践
查询性能实现从秒级到毫秒级的跨越式提升
|
8月前
|
监控 Cloud Native 测试技术
.NET技术深度解析:现代企业级开发指南
每日激励:“不要一直责怪过去的自己,他曾经站在雾里也很迷茫”。我是蒋星熠Jaxonic,一名在代码宇宙中探索的极客旅人。从.NET Framework到.NET 8,我深耕跨平台、高性能、云原生开发,践行领域驱动设计与微服务架构,用代码书写技术诗篇。分享架构演进、性能优化与AI融合前沿,助力开发者在二进制星河中逐光前行。关注我,共探技术无限可能!
.NET技术深度解析:现代企业级开发指南
|
9月前
|
消息中间件 缓存 监控
中间件架构设计与实践:构建高性能分布式系统的核心基石
摘要 本文系统探讨了中间件技术及其在分布式系统中的核心价值。作者首先定义了中间件作为连接系统组件的&quot;神经网络&quot;,强调其在数据传输、系统稳定性和扩展性中的关键作用。随后详细分类了中间件体系,包括通信中间件(如RabbitMQ/Kafka)、数据中间件(如Redis/MyCAT)等类型。文章重点剖析了消息中间件的实现机制,通过Spring Boot代码示例展示了消息生产者的完整实现,涵盖消息ID生成、持久化、批量发送及重试机制等关键技术点。最后,作者指出中间件架构设计对系统性能的决定性影响,
|
9月前
|
前端开发 Java 开发者
MVC 架构模式技术详解与实践
本文档旨在全面解析软件工程中经典且至关重要的 MVC(Model-View-Controller) 架构模式。内容将深入探讨 MVC 的核心思想、三大组件的职责与交互关系、其优势与劣势,并重点分析其在现代 Web 开发中的具体实现,特别是以 Spring MVC 框架为例,详解其请求处理流程、核心组件及基本开发实践。通过本文档,读者将能够深刻理解 MVC 的设计哲学,并掌握基于该模式进行 Web 应用开发的能力。
1814 1
|
边缘计算 Kubernetes 物联网
Kubernetes 赋能边缘计算:架构解析、挑战突破与实践方案
在物联网和工业互联网快速发展的背景下,边缘计算凭借就近处理数据的优势,成为解决云计算延迟高、带宽成本高的关键技术。而 Kubernetes 凭借统一管理、容器化适配和强大生态扩展性,正逐步成为边缘计算的核心编排平台。本文系统解析 Kubernetes 适配边缘环境的架构分层、核心挑战与新兴解决方案,为企业落地边缘项目提供实践参考。
809 0