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

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

目录
相关文章
|
9月前
|
存储 缓存 安全
某鱼电商接口架构深度剖析:从稳定性到高性能的技术密码
某鱼电商接口架构揭秘:分层解耦、安全加固、性能优化三维设计,实现200ms内响应、故障率低于0.1%。详解三层架构、多引擎存储、异步发布、WebSocket通信与全链路防护,助力开发者突破电商接口“三难”困境。
|
10月前
|
数据采集 监控 JavaScript
移动端性能监控探索:鸿蒙 NEXT 探针架构与技术实现
阿里云 ARMS 团队倾力打造的鸿蒙 NEXT SDK,为鸿蒙应用提供了业界领先的全链路监控解决方案。这不仅仅是一个 SDK,更是您洞察用户体验、优化应用性能的智能伙伴。
954 78
|
12月前
|
算法 物联网 定位技术
蓝牙室内定位技术解决方案:核心技术架构与优化实践
本文探讨了蓝牙iBeacon与Lora结合的室内定位技术,分析其在复杂室内环境中的优势与挑战。通过三层架构实现高精度定位,并提出硬件、算法与部署优化方向,助力智慧仓储、医疗等场景智能化升级。
602 0
蓝牙室内定位技术解决方案:核心技术架构与优化实践
|
9月前
|
人工智能 自然语言处理 安全
AI助教系统:基于大模型与智能体架构的新一代教育技术引擎
AI助教系统融合大语言模型、教育知识图谱、多模态交互与智能体架构,实现精准学情诊断、个性化辅导与主动教学。支持图文语音输入,本地化部署保障隐私,重构“教、学、评、辅”全链路,推动因材施教落地,助力教育数字化转型。(238字)
1597 23
|
9月前
|
Java Linux 虚拟化
【Docker】(1)Docker的概述与架构,手把手带你安装Docker,云原生路上不可缺少的一门技术!
1. Docker简介 1.1 Docker是什么 为什么docker会出现? 假定您在开发一款平台项目,您的开发环境具有特定的配置。其他开发人员身处的环境配置也各有不同。 您正在开发的应用依赖于您当前的配置且还要依赖于某些配置文件。 您的企业还拥有标准化的测试和生产环境,且具有自身的配置和一系列支持文件。 **要求:**希望尽可能多在本地模拟这些环境而不产生重新创建服务器环境的开销 问题: 要如何确保应用能够在这些环境中运行和通过质量检测? 在部署过程中不出现令人头疼的版本、配置问题 无需重新编写代码和进行故障修复
735 2
|
11月前
|
机器学习/深度学习 存储 人工智能
RAG系统文本检索优化:Cross-Encoder与Bi-Encoder架构技术对比与选择指南
本文将深入分析这两种编码架构的技术原理、数学基础、实现流程以及各自的优势与局限性,并探讨混合架构的应用策略。
825 10
RAG系统文本检索优化:Cross-Encoder与Bi-Encoder架构技术对比与选择指南
|
10月前
|
Cloud Native API 开发者
Gemini 2.5 Flash 技术拆解:从 MoE 架构到阿里云生态落地指南
2025年9月,谷歌Gemini 2.5 Flash发布,性能提升5%、成本降24%,引发行业关注。其MoE架构、百万上下文与“思考”范式,助力阿里云开发者高效构建云原生应用。本文解析技术内核,结合汽车、物流等案例,提供落地指南与避坑建议,展望大模型与流计算融合前景。
1074 6
|
10月前
|
JSON 供应链 监控
1688商品详情API技术深度解析:从接口架构到数据融合实战
1688商品详情API(item_get接口)可通过商品ID获取标题、价格、库存、SKU等核心数据,适用于价格监控、供应链管理等场景。支持JSON格式返回,需企业认证。Python示例展示如何调用接口获取商品信息。