【问题标题】:Transaction management in dependent services依赖服务中的事务管理
【发布时间】:2019-09-24 00:14:34
【问题描述】:

我对接下来的架构解决方案很感兴趣。

我有:

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([1]和[2])中删除_repository.Commit。在这种情况下,[4] 将负责提交所有更改。

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

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

就这个决定而言是一个好的决定

【问题讨论】:

标签: c# asp.net-core dependency-injection nhibernate architecture


【解决方案1】:

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

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

那么,服务应该是独立的功能单元。如果他们依赖于其他服务,那你就错了。该服务应与您的应用程序的特定子域相对应。如果用户和组织需要以事务方式共同管理,那么您应该拥有一个包含两者的一个服务。

或者,如果您希望/需要将两者分开,则需要合并sagas 的概念。

【讨论】:

  • 谢谢!是的,我已经考虑过 UoW,很可能这就是我需要的。在之前的项目中,我总是使用 Repository over NHibernate 模式;这让我对 ORM 有了一些抽象。
  • 我有点不明白为什么服务不能依赖于其他服务。事实上,在这种情况下,我将不得不复制功能。是不是?示例中的 MyBillingService 的实现是否是错误的实现?
  • 首先,repo 模式不能像 EF 那样抽象依赖。您仍然有一个依赖项,您仍然需要注入它,并且对您的 ORM 的更改将仍然泄漏到您的应用程序代码中。抽象喜欢 CQRS 或微服务等模式。没有什么是值得您花时间的,尽管服务类至少比存储库更好。至于服务,没有技术上的理由它们不能相互依赖,但从哲学上讲它们不应该。服务应该封装您的子域,因此依赖于另一个服务的服务表明设计不佳。
【解决方案2】:

所以我开始更倾向于 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;
        });
    }

那么实现repo功能的方法是这样的

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

这允许该方法使用已经启动的现有事务,否则为这项工作创建您的事务。

你也不应该在你的 repo 中有 Dispose,因为你不拥有对 ISession 的引用。它的生命周期应该由创建该实例的人来处理。

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

【讨论】:

    猜你喜欢
    • 2019-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 2020-02-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多