依赖服务中的事务管理



我对下一刻的架构解决方案感兴趣。

我有:

public class GenericRepository<T> : IDisposable {
public GenericRepository(ISession session){
_session = session;
};
public T InsertAsync(T entity){...};
public IQueryable<T> Read(){...};
public T UpateAsync(T entity){...};
public void DeleteAsync(T entity){...};
public Task Commit(){
return _session.Transaction.Commit();
};
public void Dispose(){
if(_session.Transaction.IsActive){
_session.Transaction.Rollback();
}
};
}
public class UserService{
public UserService(GenericRepository<User> repository){...}
public long CreateUser(string userName){
...
_repository.Commit(); // [1]
};
}
public class OrganizationService{
public OrganizationService(GenericRepository<Organization> repository){...}
public int CreateOrganization(string code){
...
_repository.Commit(); // [2]
};
}

使用以下注册:

services.AddScoped<ISession>(x => x.GetRequiredService<NHSessionProvider>().OpenSession());
services.AddScoped(typeof(GenericRepository<>));
services.AddScoped<UserService>();
services.AddScoped<OrganizationService>();

这些CreateOrganizationCreateUser可以在代码的任何部分独立使用:

public IActionResult Post([FromServices] OrganizationService service, [FromBody] string code){
service.CreateOrganization(code);
return Ok();
}
public IActionResult Post([FromServices] UserService service, [FromBody] string userName){
service.CreateUser(userName);
return Ok();
}

但是,现在我有一项新服务:

public class MyBillingService{
public MyBillingService(GenericRepository<Contractor> repository, OrganizationService organizationService, UserService userService){...}
public int CreateNewContractor(string organizationCode, string userName){
...
_organizationService.CreateOrganization(organizationCode);
...
_userService.CreateUser(userName);// [3]
...     
_repository.Commit(); // [4]
}
}

在此实现中,CreateOrganizationCreateUser都有自己的事务,如果 [3] 引发异常,则无论如何都会创建组织。 好的,因为ISession已注册为 Scoped,所以我可以从CreateOrganizationCreateUser中删除_repository.Commit([1] 和 [2](。在这种情况下,[4] 将负责提交所有更改。

但是,当OrganizationServiceUserService独立使用时该怎么办?毕竟,现在它们已经成为非独立的服务,如果不将更改的提交委托给其他服务,就无法保存数据:

public IActionResult Post([FromServices] UserService service, [FromServices] TransactionService transaction, [FromBody] string userName){
service.CreateUser(userName);   
transaction.Commit();
return Ok();
}

至于这个决定是一个好决定

事务需要一个工作单元。没有其他方法可以协调存储库。你在这里遇到问题的原因是你的整个设计是错误的。

首先,您根本不应该拥有这些存储库。你使用的是 EF Core,它是一个 ORM,并且已经实现了存储库和工作单元模式。使用 ORM 就是选择为您的 DAL 使用第三方库。将您自己的 DAL 层包裹起来毫无意义,并且会给应用程序带来不必要的维护和测试成本,而没有任何好处。您的服务应直接依赖于您的上下文。

然后,服务应该是独立的功能单元。如果他们依赖其他服务,你就做错了。该服务应与应用程序的特定子域相对应。如果需要以事务方式一起管理用户和组织,则应使用一个包含两者的服务。

或者,如果你想/需要将两者分开,那么你就需要结合传奇的概念。

所以我开始更多地转向Chris在他的回答中提到的内容,并直接使用ISession,但我过去使用过通用存储库。 存储库无法正确处理已启动的事务。

所以我的通用存储库有几个方法

protected virtual TResult Transact<TResult>(Func<TResult> func)
{
if (_session.Transaction.IsActive)
return func.Invoke();
TResult result;
using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted))
{
result = func.Invoke();
tx.Commit();
}
return result;
}
protected virtual void Transact(System.Action action)
{
Transact(() =>
{
action.Invoke();
return false;
});
}

然后实现存储库功能的方法如下所示

public bool Remove(T item)
{
Transact(() => _session.Delete(item));
return true;
}

这允许该方法使用现有事务(如果它已启动(,否则为这项工作创建事务。

存储库中也不应包含"处置",因为您不拥有对 ISession 的引用。 它的生命周期应该由创建该实例的人处理。

通用存储库也不应具有提交功能,除非它显式启动新事务。 所以现在你需要有一些东西来处理启动和提交所述事务。 在 Web 方案中,您通常处于每个请求的会话方案中。 这意味着您将在 BeginRequest 中创建会话并在 EndRequest 中释放它。 然后,我使用事务属性来管理在执行控制器操作之前创建事务,并在执行控制器方法之后提交/回滚。

最新更新