走向ASP.NET架构设计——第七章:阶段总结,“.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 上海企业网站制作"color: #000000;"> 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上海网站建设an>>();
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.NextRes上海闵行企业网站制作ult())
{
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.ReservedT上海企业网站设计与制作ickets)
{
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());
co上海闵行企业网站设计与制作mmand.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
深入解析微服务架构的设计与实践
在软件工程领域,"分而治之"的策略一直是解决复杂问题的有效方法。微服务架构作为这一策略的现代体现,它通过将大型应用程序分解为一组小的、独立的服务来简化开发与部署。本文将带你了解微服务的核心概念,探讨设计时的关键考虑因素,并分享实践中的一些经验教训,旨在帮助开发者更好地构建和维护可扩展的系统。
|
1天前
|
监控 Cloud Native 持续交付
云原生时代的微服务架构实践
【9月更文挑战第5天】随着云计算技术的飞速发展,云原生已成为现代软件开发的重要趋势。本文将深入探讨在云原生环境下,如何有效实施微服务架构,包括服务拆分、容器化部署、持续集成与交付等关键环节。通过具体案例,我们将展示如何在云平台上构建弹性、可扩展的微服务应用,并讨论在此过程中可能遇到的挑战及解决策略。
|
2天前
|
前端开发 安全 JavaScript
构建高效Web应用:前后端分离架构的实践
【9月更文挑战第4天】在数字时代,Web应用已成为企业与用户互动的主要平台。本文将介绍如何通过前后端分离的架构设计来构建高效的Web应用,探讨该架构的优势,并分享实现过程中的关键步骤和注意事项。文章旨在为开发者提供一种清晰、高效的开发模式,帮助其在快速变化的市场环境中保持竞争力。
|
2天前
|
消息中间件 监控 API
深入浅出微服务架构:从理论到实践
在软件开发领域,“微服务”这一概念已如日中天,它改变了我们构建、部署和扩展应用的方式。本文将带你走进微服务的世界,不仅探讨其核心理念,还将通过实际案例,展示如何将一个传统单体应用拆分为微服务架构。我们将一步步分析微服务的优势与挑战,并讨论如何在现实世界中实现和维护微服务架构,让你对微服务有一个全面而深入的理解。
|
7天前
|
Java Spring UED
Spring框架的异常处理秘籍:打造不败之身的应用!
【8月更文挑战第31天】在软件开发中,异常处理对应用的稳定性和健壮性至关重要。Spring框架提供了一套完善的异常处理机制,包括使用`@ExceptionHandler`注解和配置`@ControllerAdvice`。本文将详细介绍这两种方式,并通过示例代码展示其具体应用。`@ExceptionHandler`可用于控制器类中的方法,处理特定异常;而`@ControllerAdvice`则允许定义全局异常处理器,捕获多个控制器中的异常。
18 0
|
7天前
|
前端开发 大数据 数据库
🔥大数据洪流下的决战:JSF 表格组件如何做到毫秒级响应?揭秘背后的性能魔法!💪
【8月更文挑战第31天】在 Web 应用中,表格组件常用于展示和操作数据,但在大数据量下性能会成瓶颈。本文介绍在 JavaServer Faces(JSF)中优化表格组件的方法,包括数据处理、分页及懒加载等技术。通过后端分页或懒加载按需加载数据,减少不必要的数据加载和优化数据库查询,并利用缓存机制减少数据库访问次数,从而提高表格组件的响应速度和整体性能。掌握这些最佳实践对开发高性能 JSF 应用至关重要。
20 0
|
7天前
|
存储 设计模式 运维
Angular遇上Azure Functions:探索无服务器架构下的开发实践——从在线投票系统案例深入分析前端与后端的协同工作
【8月更文挑战第31天】在现代软件开发中,无服务器架构因可扩展性和成本效益而备受青睐。本文通过构建一个在线投票应用,介绍如何结合Angular前端框架与Azure Functions后端服务,快速搭建高效、可扩展的应用系统。Angular提供响应式编程和组件化能力,适合构建动态用户界面;Azure Functions则简化了后端逻辑处理与数据存储。通过具体示例代码,详细展示了从设置Azure Functions到整合Angular前端的全过程,帮助开发者轻松上手无服务器应用开发。
|
7天前
|
运维 Cloud Native Java
云原生时代的微服务架构实践
【8月更文挑战第31天】在这篇文章中,我们将一起探索云原生技术如何革新现代软件架构。通过深入浅出地讲解微服务的核心概念、优势及其在云平台上的实现方式,本文旨在为读者提供一个关于如何在云原生环境下设计、部署和管理微服务的全面指南。我们还将通过一个简易的代码示例来直观展示微服务的实践过程。无论你是软件开发者还是架构师,这篇文章都将为你打开一扇了解和进入云原生世界的大门。
|
7天前
|
Kubernetes Cloud Native Docker
云原生时代的微服务架构实践
【8月更文挑战第31天】在云原生的浪潮下,微服务架构成为企业数字化转型的利器。本文将深入探讨如何在云平台上部署和管理微服务,通过一个简易订单服务实例,展示从代码编写到容器化部署的全过程,并解析云原生技术栈如何助力服务的弹性伸缩和容错性。读者将获得构建和维护高可用、易扩展系统的实用知识,为打造现代化应用架构提供指导。
|
7天前
|
Kubernetes Cloud Native Docker
云原生时代的微服务架构与容器化实践
【8月更文挑战第31天】本文将带你走进云原生的世界,探索微服务架构的设计哲学和容器化技术的实用技巧。我们将从微服务的基本原则出发,逐步深入到如何在Kubernetes集群中部署和管理微服务。通过具体的代码示例和操作指南,你将学会如何将传统应用转化为云原生应用,并利用Docker和Kubernetes实现自动化部署、扩展和管理。无论你是初学者还是有经验的开发人员,这篇文章都将为你在云原生旅程上提供宝贵的知识和技能。

热门文章

最新文章

下一篇
DDNS