如果我们使用了不止一个数据层操作,为了使这些操作具有原子性,那么事务是必须的。也就是说,我们想要所有的数据层调用都成功(提交),要么都失败(回滚)。假设,我们可以将事务放到业务逻辑层。
假设底层的数据层会使用和.NET内置的事务类TransactionScope 兼容的技术,结合try/catch 块,我们可以给Accrue 方法添加事务代码:
public void Accrue(RentalAgreement agreement)
{
//防御性编程
if (agreement==null)
{
throw new Exception("agreement为null!");
}
//日志
Console.WriteLine("Accrue:{0}",DateTime.Now);
Console.WriteLine("Customer:{0}",agreement.Customer.Id);
Console.WriteLine("Vehicle:{0}",agreement.Vehicle.Id);
using (var ts=new TransactionScope())//开始一个新事务
{
try
{
var rentalTimeSpan = agreement.EndDate.Subtract(agreement.StartDate);
var numberOfDays = (int)rentalTimeSpan.TotalDays;
var pointsPerDay = 1;
if (agreement.Vehicle.Size >= Size.Luxury)
{
pointsPerDay = 2;
}
var points = numberOfDays * pointsPerDay;
//调用数据服务存储客户获得的积分
_loyaltyDataService.AddPoints(agreement.Customer.Id, points);
ts.Complete();//调用Complete方法表明事务成功提交
}
catch (Exception ex)
{
throw;//没有调用Complete方法,事务会回滚
}
}
Console.WriteLine("Accrue Complete:{0}",DateTime.Now);
}
记住,只有调用了事务的Complete 方法,事务才会提交,否则就会回滚。如果抛出了异常,这里我们只是重新抛出,相似地,也可以在Redeem 方法中使用TransactionScope ,这里不再贴了,请自行看源码。
上面的代码开始变长、变丑了,原始的业务逻辑代码周围包了很多和横切关注点有关的代码块:logging,防御性编程和事务代码。
但是我们还没做完,假设底层的数据持久层偶尔会出现高流量,可能就会导致某些请求失败(比如,抛出超时异常)。如果是那种情况,执行几次重试会保持程序平滑运行(尽管在高流量期间有点慢)。通过在事务中放一个循环,每次事务回滚时,我们就增加重试次数,一旦重试次数达到限制值,我们就不管了,如下:
public void Accrue(RentalAgreement agreement)
{
//防御性编程
if (agreement==null)
{
throw new Exception("agreement为null!");
}
//日志
Console.WriteLine("Accrue:{0}",DateTime.Now);
Console.WriteLine("Customer:{0}",agreement.Customer.Id);
Console.WriteLine("Vehicle:{0}",agreement.Vehicle.Id);
using (var ts=new TransactionScope())//开始一个新事务
{
var retries = 3;//重试事务3次
var succeeded = false;
while (!succeeded)//一直循环,直到成功
{
try
{
var rentalTimeSpan = agreement.EndDate.Subtract(agreement.StartDate);
var numberOfDays = (int)rentalTimeSpan.TotalDays;
var pointsPerDay = 1;
if (agreement.Vehicle.Size >= Size.Luxury)
{
pointsPerDay = 2;
}
var points = numberOfDays * pointsPerDay;
//调用数据服务存储客户获得的积分
_loyaltyDataService.AddPoints(agreement.Customer.Id, points);
ts.Complete();//调用Complete方法表明事务成功提交
succeeded = true;//成功后设置为true,确保最后一次循环迭代
Console.WriteLine("Accrue Complete:{0}", DateTime.Now);//这句移入try里
}
catch
{
if (retries>=0)
{
retries--;//直到尝试完次数时才重抛异常
}
else
{
throw;//没有调用Complete方法,事务会回滚
}
}
}
}
}
相似地,我们也要在Redeem 方法中添加,这里不做了,省略。问题越来越明显了,横切关注点基本上占据了这个方法的一半代码。但是我们还没有做完,我们需要讨论一下异常处理。
处理异常
前面不是添加了try/catch 了么?难道还不够?也许!比如,服务器离线了,重试次数到达限制了,异常还是会重抛出去,如果是这种情况,我们就需要在程序崩溃前处理这个异常。
因此我们需要在防御性编程后再添加一个try/catch 块包裹其他所有的代码,如下:
public void Accrue(RentalAgreement agreement)
{
//防御性编程
if (agreement==null)
{
throw new Exception("agreement为null!");
}
//日志
Console.WriteLine("Accrue:{0}",DateTime.Now);
Console.WriteLine("Customer:{0}",agreement.Customer.Id);
Console.WriteLine("Vehicle:{0}",agreement.Vehicle.Id);
try
{
using (var ts = new TransactionScope())//开始一个新事务
{
var retries = 3;//重试事务3次
var succeeded = false;
while (!succeeded)//一直循环,直到成功
{
try
{
var rentalTimeSpan = agreement.EndDate.Subtract(agreement.StartDate);
var numberOfDays = (int)rentalTimeSpan.TotalDays;
var pointsPerDay = 1;
if (agreement.Vehicle.Size >= Size.Luxury)
{
pointsPerDay = 2;
}
var points = numberOfDays * pointsPerDay;
//调用数据服务存储客户获得的积分
_loyaltyDataService.AddPoints(agreement.Customer.Id, points);
ts.Complete();//调用Complete方法表明事务成功提交
succeeded = true;//成功后设置为true,确保最后一次循环迭代
Console.WriteLine("Accrue Complete:{0}", DateTime.Now);//这句移入try里
}
catch
{
if (retries >= 0)
{
retries--;//直到尝试完次数时才重抛异常
}
else
{
throw;//没有调用Complete方法,事务会回滚
}
}
}
}
}
catch (Exception ex)
{
if (!ExceptionHelper.Handle(ex))//如果没有处理异常,继续重抛
{
throw ex;
}
}
}
(编辑:成都站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|