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

简介:   示例说明  本篇的例子的是一个在线订票的服务系统。这个系统向外界暴露了一些可以通过Http协议访问的API,在这个订票服务下面允许任意多个隶属机构来使用服务API进行真正的售票活动。如下图所示:  就好比银行外面的那些自动取款机(对应图中的Affiliate A, B, C),可以把它们看成是银行系统的隶属机构,我们就是通过这些取款机来进行存取活动的,其实这些取款机是调用了银行系统的一些服务来进行数据操作,当然我们也可以直接到银行柜台(对应图中的Ticket Shop)去进行存取款操作。

  示例说明

  本篇的例子的是一个在线订票的服务系统。这个系统向外界暴露了一些可以通过Http协议访问的API,在这个订票服务下面允许任意多个隶属机构来使用服务API进行真正的售票活动。如下图所示:

  就好比银行外面的那些自动取款机(对应图中的Affiliate A, B, C),可以把它们看成是银行系统的隶属机构,我们就是通过这些取款机来进行存取活动的,其实这些取款机是调用了银行系统的一些服务来进行数据操作,当然我们也可以直接到银行柜台(对应图中的Ticket Shop)去进行存取款操作。本例中的售票例子和这个有点类似。

  在本例中,在我们将会在上图中的Application和Internal Client之间采用Reservation模式来约定票,通过采用Idempotent模式来确保订票的每个交易只进行一次。

  下面就开始进入实战:

  解决方案建立如下:

  为了演示的方便,上面的Solution把客户端和服务端程序建立在了一起。

  Domain Model

  首先,我们来建立这个系统中所涉及到的一些业务类和一些辅助的类。

  其中:

  Event类代表了一次购票的活动。

  Event类包含了两个集合:一个TicketPurchase集合代表了真实的要购买的票;另外一个TicketReservation集合代表了Reservation模式中的预约票据,或者大家理解为标识,或者令牌,概念类似于ASP.NET中的验证票据 。

  另外两个工厂类提供了一些简单的接口来创建TicketPurchase和TicketReservation。

  下面我们就来看看上面提及的一些类的具体的定义:

  
  
public class TicketReservation
{
public Guid Id { get ; set ; }
public Event Event { get ; set ; }
public DateTime ExpiryTime { get ; set ; }
public int TicketQuantity { get ; set ; }
public bool HasBeenRedeemed { get ; set ; }

public bool HasExpired()
{
return DateTime.Now > ExpiryTime;
}

public bool StillActive()
{
return ! HasBeenRedeemed && ! HasExpired();
}
}
public class TicketPurchase
{
public Guid Id { get ; set ; }
public Event Event { get ; set ; }
public int TicketQuantity { get ; set ; }
}

  为了简化创建票据类的方面,我们添加两个工厂类如下:

  
  
public class TicketReservationFactory
{
public static TicketReservation CreateReservation(Event Event, int tktQty)
{
TicketReservation reservation
= new TicketReservation();

reservation.Id
= Guid.NewGuid();
reservation.Event
= Event;
reservation.ExpiryTime
= DateTime.Now.AddMinutes( 1 );
reservation.TicketQuantity
= tktQty;

return reservation;
}
}

public
class TicketPurchaseFactory
{
public static TicketPurchase CreateTicket(Event Event, int tktQty)
{
TicketPurchase ticket
= new TicketPurchase();

ticket.Id
= Guid.NewGuid();
ticket.Event
= Event;
ticket.TicketQuantity
= tktQty;

return ticket;
}
}

  上面两个工厂的方法都是很直观,简单。在TicketReservationFactory中创建ReservationTicket类的时候,设置这个标识票据的默认过期时间是一分钟。也就是说,整个订票的交易要在一分钟之内完成,当然一分钟只是例子而已,便于例子的测试。可能时间太长了一是耗费太多的资源,二是在安全方面也存在一些隐患。

  下面就来一起看看比较核心的Event类:(下面的代码有点多,在代码的后面我会详细讲述类中每个方法的意思)

  
  
public class Event
{
public Event()
{
ReservedTickets
= new List < TicketReservation > ();
PurchasedTickets
= new List < TicketPurchase > ();
}

public Guid Id { get ; set ; }
public string Name { get ; set ; }
public int Allocation { get ; set ; }
public List < TicketReservation > ReservedTickets { get ; set ; }
public List < TicketPurchase > PurchasedTickets { get ; set ; }

public int AvailableAllocation()
{
int salesAndReservations = 0 ;

PurchasedTickets.ForEach(t
=> salesAndReservations += t.TicketQuantity);

ReservedTickets.FindAll(r
=> r.StillActive()).ForEach(r => salesAndReservations += r.TicketQuantity);

return Allocation - salesAndReservations;
}

public bool CanPurchaseTicketWith(Guid reservationId)
{
if (HasReservationWith(reservationId))
return GetReservationWith(reservationId).StillActive();

return false ;
}

public TicketPurchase PurchaseTicketWith(Guid reservationId)
{
if ( ! CanPurchaseTicketWith(reservationId))
throw new ApplicationException(DetermineWhyATicketCannotbePurchasedWith(reservationId));

TicketReservation reservation
= GetReservationWith(reservationId);

TicketPurchase ticket
= TicketPurchaseFactory.CreateTicket( this , reservation.TicketQuantity);

reservation.HasBeenRedeemed
= true ;

PurchasedTickets.Add(ticket);

return ticket;
}

public TicketReservation GetReservationWith(Guid reservationId)
{
if ( ! HasReservationWith(reservationId))
throw new ApplicationException(String.Format( " No reservation ticket with matching id of '{0}' " , reservationId.ToString()));

return ReservedTickets.FirstOrDefault(t => t.Id == reservationId);
}

private bool HasReservationWith(Guid reservationId)
{
return ReservedTickets.Exists(t => t.Id == reservationId);
}

public string DetermineWhyATicketCannotbePurchasedWith(Guid reservationId)
{
string reservationIssue = "" ;
if (HasReservationWith(reservationId))
{
TicketReservation reservation
= GetReservationWith(reservationId);
if (reservation.HasExpired())
reservationIssue
= String.Format( " Ticket reservation '{0}' has expired " , reservationId.ToString());
else if (reservation.HasBeenRedeemed )
reservationIssue
= String.Format( " Ticket reservation '{0}' has already been redeemed " , reservationId.ToString());
}
else
reservationIssue
= String.Format( " There is no ticket reservation with the Id '{0}' " , reservationId.ToString());

return reservationIssue;
}

private void ThrowExceptionWithDetailsOnWhyTicketsCannotBeReserved()
{
throw new ApplicationException( " There are no tickets available to reserve. " );
}

public bool CanReserveTicket( int qty)
{
return AvailableAllocation() >= qty;
}

public TicketReservation ReserveTicket( int tktQty)
{
if ( ! CanReserveTicket(tktQty))
ThrowExceptionWithDetailsOnWhyTicketsCannotBeReserved();

TicketReservation reservation
= TicketReservationFactory.CreateReservation( this , tktQty);

ReservedTickets.Add(reservation);

return reservation;
}
}

  下面,我们就来看看每个方法的作用:

  AvailableAllocation():这个方法计算现有还有多少票可以卖;用总的票数减去已经卖出的票数和已经预定了的票数。

  CanReserveTicket(int qty):这个检查是否还有足够数量的票供预定。

  ReserveTicket(int qty):这个方法创建一个新的TicketReservation,并且指定在这个标识票据中有多少张真实的票要购买的,并且将标识票据添加到集合中

  HasReservationWith(Guid reservationId):这个方法判断给定Id的TicketReservation是否存在。

  GetReservationWith(Guid reservationId):通过Id标识,获取一个TicketReservation。

  CanPurchaseTicketWith(Guid reservationId):这个方法判断可以基于给定的标识Id来购买真实的票。

  PurchaseTicketWith(Guid reservationId):基于给的预约标识来创建一个新的真实的票TicketPurchase.

  DetermineWhyTicketCannotBePurchase(Guid reservationId):这个方法返回一个字符串结果,说明一下为什么不能基于给定的预约标识来购买票,可以因为标识过期或者我们规定一个标识所代表的一次交易最多只能买指定数量的真实票。

  业务类建立完成之后,下面我们就来创建一个类来进行存取这些业务类所需要的数据。

  Repository

  添加一个IEventReposistory接口,如下:

  
  
public interface IEventRepository
{
Event FindBy(Guid id);
void Save(Event eventEntity);
}

  为了演示的简洁,这个接口定义的很简单。下面就用ADO.NET的方式来实现一个EventRepository.(当然,大家可以采用自己喜欢的数据访问技术)

  下面的代码很多,但是很容易理解:

  
  
public class EventRepository : IEventRepository
{
private string connectionString = @" Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\EventTickets.mdf;Integrated Security=True;User Instance=True " ;

public Event FindBy(Guid id)
{
Event Event
= default (Event);

string queryString = " SELECT * FROM dbo.Events WHERE Id = @EventId " +
" SELECT * FROM dbo.PurchasedTickets WHERE EventId = @EventId " +
" SELECT * FROM dbo.ReservedTickets WHERE EventId = @EventId; " ;

using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= queryString;

SqlParameter Idparam
= new SqlParameter( " @EventId " , id.ToString());
command.Parameters.Add(Idparam);

connection.Open();

using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
reader.Read();
Event
= new Event();
Event.PurchasedTickets
= new List < TicketPurchase > ();
Event.ReservedTickets
= new List < TicketReservation > ();
Event.Allocation
= int .Parse(reader[ " Allocation " ].ToString());
Event.Id
= new Guid(reader[ " Id " ].ToString());
Event.Name
= reader[ " Name " ].ToString();

if (reader.NextResult())
{
if (reader.HasRows)
{
while (reader.Read())
{
TicketPurchase ticketPurchase
= new TicketPurchase();
ticketPurchase.Id
= new Guid(reader[ " Id " ].ToString());
ticketPurchase.Event
= Event;
ticketPurchase.TicketQuantity
= int .Parse(reader[ " TicketQuantity " ].ToString());
Event.PurchasedTickets.Add(ticketPurchase);
}
}
}

if (reader.NextResult())
{
if (reader.HasRows)
{
while (reader.Read())
{
TicketReservation ticketReservation
= new TicketReservation();
ticketReservation.Id
= new Guid(reader[ " Id " ].ToString());
ticketReservation.Event
= Event;
ticketReservation.ExpiryTime
= DateTime.Parse(reader[ " ExpiryTime " ].ToString());
ticketReservation.TicketQuantity
= int .Parse(reader[ " TicketQuantity " ].ToString());
ticketReservation.HasBeenRedeemed
= bool .Parse(reader[ " HasBeenRedeemed " ].ToString());
Event.ReservedTickets.Add(ticketReservation);
}
}
}
}
}
}

return Event;
}

public void Save(Event Event)
{
// Code to save the Event entity
// is not required in this senario

RemovePurchasedAndReservedTicketsFrom(Event);

InsertPurchasedTicketsFrom(Event);
InsertReservedTicketsFrom(Event);

}

public void InsertReservedTicketsFrom(Event Event)
{
string insertSQL = " INSERT INTO ReservedTickets " +
" (Id, EventId, TicketQuantity, ExpiryTime, HasBeenRedeemed) " +
" VALUES " +
" (@Id, @EventId, @TicketQuantity, @ExpiryTime, @HasBeenRedeemed); " ;

foreach (TicketReservation ticket in Event.ReservedTickets)
{
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= insertSQL;

SqlParameter Idparam
= new SqlParameter( " @Id " , ticket.Id.ToString());
command.Parameters.Add(Idparam);

SqlParameter EventIdparam
= new SqlParameter( " @EventId " , ticket.Event.Id.ToString());
command.Parameters.Add(EventIdparam);

SqlParameter TktQtyparam
= new SqlParameter( " @TicketQuantity " , ticket.TicketQuantity);
command.Parameters.Add(TktQtyparam);

SqlParameter Expiryparam
= new SqlParameter( " @ExpiryTime " , ticket.ExpiryTime);
command.Parameters.Add(Expiryparam);

SqlParameter HasBeenRedeemedparam
= new SqlParameter( " @HasBeenRedeemed " , ticket.HasBeenRedeemed);
command.Parameters.Add(HasBeenRedeemedparam);

connection.Open();
command.ExecuteNonQuery();
}
}

}

public void InsertPurchasedTicketsFrom(Event Event)
{
string insertSQL = " INSERT INTO PurchasedTickets " +
" (Id, EventId, TicketQuantity) " +
" VALUES " +
" (@Id, @EventId, @TicketQuantity); " ;

foreach (TicketPurchase ticket in Event.PurchasedTickets)
{
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= insertSQL;

SqlParameter Idparam
= new SqlParameter( " @Id " , ticket.Id.ToString());
command.Parameters.Add(Idparam);

SqlParameter EventIdparam
= new SqlParameter( " @EventId " , ticket.Event.Id.ToString());
command.Parameters.Add(EventIdparam);

SqlParameter TktQtyparam
= new SqlParameter( " @TicketQuantity " , ticket.TicketQuantity);
command.Parameters.Add(TktQtyparam);

connection.Open();
command.ExecuteNonQuery();
}
}
}

public void RemovePurchasedAndReservedTicketsFrom(Event Event)
{
string deleteSQL = " DELETE PurchasedTickets WHERE EventId = @EventId; " +
" DELETE ReservedTickets WHERE EventId = @EventId; " ;

using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command
= connection.CreateCommand();
command.CommandText
= deleteSQL;

SqlParameter Idparam
= new SqlParameter( " @EventId " , Event.Id.ToString());
command.Parameters.Add(Idparam);

connection.Open();
command.ExecuteNonQuery();
}
}
}

  今天就到这里。下一篇接着讲述!

目录
相关文章
|
7天前
|
运维 持续交付 API
从零构建微服务架构:一次深度技术探索之旅####
【10月更文挑战第28天】 本文记录了作者在从零开始构建微服务架构过程中的深刻技术感悟,通过实战案例详细剖析了微服务设计、开发、部署及运维中的关键要点与挑战。文章首先概述了微服务架构的核心理念及其对企业IT架构转型的重要性,随后深入探讨了服务拆分策略、API网关选型、服务间通信协议选择、容器化部署(Docker+Kubernetes)、以及持续集成/持续部署(CI/CD)流程的设计与优化。最后,分享了在高并发场景下的性能调优经验与故障排查心得,旨在为读者提供一套可借鉴的微服务架构实施路径。 ####
41 3
|
18天前
|
边缘计算 Cloud Native 安全
构建灵活高效的下一代应用架构 随着企业数字化转型的加速,云原生技术正逐渐成为构建现代化应用程序的关键支柱。
随着企业数字化转型加速,云原生技术逐渐成为构建现代化应用的关键。本文探讨了云原生的核心概念(如容器化、微服务、DevOps)、主要应用场景(如金融、电商、IoT)及未来发展趋势(如无服务器计算、边缘计算、多云架构),并分析了面临的挑战,如架构复杂性和安全问题。云原生技术为企业提供了更灵活、高效的应用架构,助力数字化转型。
42 4
|
15天前
|
Kubernetes Cloud Native 持续交付
云端新纪元:云原生技术重塑IT架构####
【10月更文挑战第20天】 本文深入探讨了云原生技术的兴起背景、核心理念、关键技术组件以及它如何引领现代IT架构迈向更高效、灵活与可扩展的新阶段。通过剖析Kubernetes、微服务、Docker等核心技术,本文揭示了云原生架构如何优化资源利用、加速应用开发与部署流程,并促进企业数字化转型的深度实践。 ####
|
1天前
|
Kubernetes Cloud Native 云计算
云原生技术深度解析:重塑企业IT架构的未来####
本文深入探讨了云原生技术的核心理念、关键技术组件及其对企业IT架构转型的深远影响。通过剖析Kubernetes、微服务、容器化等核心技术,本文揭示了云原生如何提升应用的灵活性、可扩展性和可维护性,助力企业在数字化转型中保持领先地位。 ####
|
3天前
|
存储 分布式计算 分布式数据库
风险数据集市整体架构及技术实现
【11月更文挑战第11天】在当今大数据时代,风险数据集市作为金融机构的核心基础设施之一,扮演着至关重要的角色。它不仅为银行、保险等金融机构提供了全面、准确的风险数据支持,还帮助这些机构实现了风险管理的精细化和智能化。本文将深入探讨一种基于大数据Lambda架构设计的风险数据集市整体架构,并详细介绍其底层实现原理及实现方式。
15 3
|
6天前
|
机器学习/深度学习 人工智能 自然语言处理
医疗行业的语音识别技术解析:AI多模态能力平台的应用与架构
AI多模态能力平台通过语音识别技术,实现实时转录医患对话,自动生成结构化数据,提高医疗效率。平台具备强大的环境降噪、语音分离及自然语言处理能力,支持与医院系统无缝集成,广泛应用于门诊记录、多学科会诊和急诊场景,显著提升工作效率和数据准确性。
|
12天前
|
监控 安全 Serverless
"揭秘D2终端大会热点技术:Serverless架构最佳实践全解析,让你的开发效率翻倍,迈向技术新高峰!"
【10月更文挑战第23天】D2终端大会汇聚了众多前沿技术,其中Serverless架构备受瞩目。它让开发者无需关注服务器管理,专注于业务逻辑,提高开发效率。本文介绍了选择合适平台、设计合理函数架构、优化性能及安全监控的最佳实践,助力开发者充分挖掘Serverless潜力,推动技术发展。
32 1
|
2月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
38 7
|
2月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
53 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
46 0
下一篇
无影云桌面