思考一种好的架构(五)
工作单元(Unit Of Work)
是什么?
一个事务控制装置,在一个请求进入时,由它来开启事务,结束时,如果没有错误则由他来提交事务,如有异常则回滚事务,他来申明,提交和回滚事务,一个请求中所有的服务操作数据库都会被他记录下来并选择是否提交工作,所以它叫工作单元,最早我是拿它来解决嵌套事务的问题。
为什么?
它解决了不同业务服务都需要事务操作的问题,比如用户下单这个流程,有账户服务(Account),有库存服务(Stock),入口则是订单服务(Order),账户服务和库存服务都需要事务去回滚,于是就需要一个大的事务把他们都笼罩住,如果把事务放在Order中则Account和Stock的接口不能复用,因为他们不从Order中调用的话则没有事务去回滚他们,而且这样做会照成非常大的耦合,
如果把
services.AddScoped<IDbContext>(x=> new MySqlContext(x.GetService<IDCScoped>()));
改成
services.AddTransient<IDbContext>(x=> new MySqlContext(x.GetService<IDCScoped>()));
可以避免事务嵌套的问题,但是不觉得很蠢吗?同一个数据库同一个请求中,居然分了不同的DB上下文,而且对数据库也是一种压力
怎么做:
说了这么多,其实实现它很简单,而且我命名弄反了😓,因为我是百度翻译滴
public interface IUnitOfWork { /// <summary> /// 提交 /// </summary> void Commit(); /// <summary> /// 回滚 /// </summary> void Rollback(); } /// <summary> /// 工作单元 /// </summary> public class UnitOfWork: IUnitOfWork { IDbContext DC { get; set; } ITransientTransaction tran { get; set; } IDCScoped dcScoped { get; set; } public UnitOfWork(IDbContext DC, IDCScoped dcScoped) { this.DC = DC; this.dcScoped = dcScoped; if (dcScoped.OperationType== DCOperationType.Command) { tran = DC.BeginTransaction(); } } public void Commit() { if (dcScoped.OperationType == DCOperationType.Command) { tran.Commit(); } } public void Rollback() { if (dcScoped.OperationType == DCOperationType.Command) { tran.Rollback(); } } }
每次创建一个工作单元我们就获取这次请求的数据库上下文对象
IDbContext 数据库上下文
IDCScoped 本次请求修改的数据库上下文配置,具体请在ORM那一章节查看,CQRS也有提及(保存了操作类型和数据库连接串)
并且通过判断本次操作类型去选择是否开启事务
public class ChloeUnitOfWorkMiddleware { private readonly RequestDelegate _next; public ChloeUnitOfWorkMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { var unitWork = httpContext.RequestServices.GetService<IUnitOfWork>(); try { await _next(httpContext); unitWork.Commit(); } catch (Exception e) { unitWork.Rollback(); throw e; } } } // Extension method used to add the middleware to the HTTP request pipeline. public static class ChloeUnitOfWorkMiddlewareExtensions { public static IApplicationBuilder UseChloeUnitOfWorkMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<ChloeUnitOfWorkMiddleware>(); } }
在这个中间件中去获取工作单元 并在通过它的时候提交,如有异常则回滚
public static class Startup { public static void ConfigureServices(IServiceCollection services) { services.AddScoped<IUnitOfWork, UnitOfWork>(); } public static void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseChloeUnitOfWorkMiddleware(); } }
这一节介绍了工作单元和它的操作流程