在工作单元中同时使用数据库操作和外部方法

Using both database operation and external method in Unit of Work

关于Unit Of Work Pattern

Unit of Work design pattern does two important things: first it maintains in-memory updates and second it sends these in-memory updates as one transaction to the database

假设我需要在同一工作单元中使用 Web 服务并更新数据库 table。按照惯例,我执行以下步骤:

  1. 打开一个连接。
  2. 调用DBInsert方法(执行了没有风险,但在UOW中此时不是FLUSHED)
  3. 调用外部网络服务(来自其他公司的任何服务)。
  4. 如果 Web 服务结果为 200(正常),则调用提交,否则回滚。

    using (var unitOfWork = _unitOfWorkManager.Begin())
    {
        _personRepository.Insert(person);
        externalWebService.IncrementPeopleCount();
    
        unitOfWork.Complete();
    }
    

unitOfWork 有一种方法:Complete()。如果 web 服务出错,那不是 problem.Because 我可以抛出异常,所以 complete() 方法不会执行。但是我在 complete() 中出错,我必须反转 Web 服务吗?

如果在数据库中执行事务性 Insert() 方法(尚未提交),它会很好地工作。

如何在工作单元模式中执行此场景?或者绝对有必要有一个反向网络方法DecrementPeopleCount?

Unit of Work 中,您管理 transactional 操作。在 Unit of Work 范围内,您 运行 一些业务逻辑,当所有操作准备就绪时,将调用 CompleteSaveChanges 操作。此操作为您提供了一个机会,您的所有操作都成功完成或所有操作都被取消。在这种情况下,代码不能作为一个单元工作。有两个单独的操作,插入操作和 Web 服务增量操作。

DecrementPeopleCount操作不是解决办法。想象一下这样的事情:IncrementPeopleCount 操作 return 成功,但是 Complete 操作 return 错误。在此之后,您尝试调用 DecrementPeopleCount 但 web 服务太忙或出现网络问题。这样,您的代码仍然不能作为一个单元工作。

作为解决方案,您考虑改变您的方法。

1) WebService 调用操作可以包装并转换为事务操作。我建议使用一个名为 Hangfire 的工具。它在数据库中保存操作名称和参数,事务完成它读取数据库并触发注册函数。您可以将 entitycall webservice operation execution 作为一个操作保存在数据库中。

2) 您可以保存entity并发布user-created eventincrement-user-count command。您的 observer/consumer 消耗了那个 event/command 并执行网络 api 调用。

两种解决方案最终都给出了一致性。

externalWebService 调用在做什么?它是否正在同步另一个数据仓库?还是更新 ui 元素?

如果是UI,那么我会把两者分开,因为你的数据完整性不应该关心,更不用说依赖,ui元素更新成功了。

关于同步两个数据集,我可以提出类似的论点。

如果更新调用失败,应该不会影响源代码库中的数据完整性。相反,通过编写第二个同步方法来补偿失败,该方法将从人员回购中检索实际计数,并在增量调用失败时使用该值更新外部源。事实上,我会推荐这个更新而不是递增的更新。

在这种特殊情况下,我看不出为什么你的个人回购的数据完整性应该取决于第二次调用的成功。

来自 Unit Of Work 上的文档:

If the method throws an exception, the transaction is rolled back, and the connection is disposed. In this way, a unit of work method is atomic (a unit of work).

You don't have to do anything, but sometimes you may want to save changes to the database in the middle of a unit of work operation.

You can use the SaveChanges or SaveChangesAsync method of the current unit of work.

Note that if the current unit of work is transactional, all changes in the transaction are rolled back if an exception occurs. Even the saved changes!

所以,在调用Complete回滚之前抛出异常:

try
{
    using (var unitOfWork = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
    {
        _personRepository.Insert(person);

        // Save changes
        _unitOfWorkManager.Current.SaveChanges();

        var result = externalWebService.IncrementPeopleCount();
        if (result != 200)
        {
            // Rollback
            throw new MyExternalWebServiceException("Unable to increment people count!");
        }

        // Commit
        unitOfWork.Complete();
    }
}
catch (MyExternalWebServiceException)
{
    // Transaction rolled back, propagate exception?
    throw;
}

MyExternalWebServiceException可以继承UserFriendlyException显示给用户:

public class MyExternalWebServiceException : UserFriendlyException
{
    public MyExternalWebServiceException(string message)
        : base(message)
    {
    }
}