【问题标题】:What is the best way to handle exceptions in this scenario在这种情况下处理异常的最佳方法是什么
【发布时间】:2020-07-17 18:06:17
【问题描述】:

我正在编写一个 C# WEB API,它采用多部分形式的数据在 DB 中创建记录并将文件上传到某个位置。作为创建的一部分,我还将创建一个 Jira 问题并使用 JIRA 详细信息更新创建的记录。

  [Route("api/request/create")]
  [Consumes("multipart/form-data")]
  public async Task<HttpResponseMessage> CreateRequest()
  {
     try
     {
        var multipartMemoryStream = await Request.Content.ReadAsMultipartAsync();
        var newRequestData = await multipartMemoryStream.Contents[0].ReadAsStringAsync();

       CreateRequestCommand createRequestCommand = JsonConvert.DeserializeObject<CreateRequestCommand>(newRequestData);

       var requestId = CreateRequestInDB(createRequestCommand)
       var jira = await CreateJira(createContentRequestCommand);
       await WriteSourceFiles(multipartMemoryStream.Contents);
       await UpdateRequestWithJiraDetails(requestId, jira);
       return new SuccessResponse(requestId)
    }
   catch (Exception ex)
   {
      throw ex;
   }
}

这在大多数情况下都可以正常工作。如何以最佳方式处理异常,以便如果任何方法失败,系统不应保留 DB 中的记录并删除 JIRA 问题。 如果任何步骤失败,使系统保持一致状态的最佳策略是什么。

【问题讨论】:

  • 欢迎来到 StackOverflow。不幸的是,这是一个非常复杂的话题。您已进入分布式事务领域。没有万能的解决方案,但我建议检查Sagas pattern
  • @PeterCsala 你为什么不写一个答案而不是评论所有现有的帖子?

标签: c# asp.net-core exception design-patterns jira-rest-api


【解决方案1】:

如果您查看 WikiPedia 以了解 Distributed transaction 的定义,那么它会显示以下内容:

分布式事务是涉及两个或多个网络主机的数据库事务。通常,主机提供事务资源,而事务管理器负责创建和管理包含针对此类资源的所有操作的全局事务。与任何其他事务一样,分布式事务必须具有所有四个 ACID(原子性、一致性、隔离性、持久性)属性,其中原子性保证了工作单元(操作包)的全有或全无结果。

即使定义侧重于数据库相关性,示例也适用,即使您想将数据库和文件编写为工作单元。如果其中任何一个失败,那么它们都应该回滚。

在 .NET 只能在 Windows 操作系统上运行的那些日子里,.NET 可以利用Transaction Manager。你所要做的就是implementIEnlistmentNotification 接口用于两阶段提交。该接口暴露了4 methods

  • Prepare投票
  • Commit幸福之路
  • Rollback 不开心的路
  • InDoubt 第二阶段

最后,您必须通过以下调用将该实现注册到 TransactionManager:Transaction.Current.EnlistVolatile

.NET Core does not support 分布式事务。也许 .NET 5...

2 Phase Commit(和 3PC)协议存在阻塞问题(123)可能会出现卡住无法继续的情况。 Martin Kleppmann's Designing Data-Intensive Applications book 以一种非常容易理解的方式详细说明了这个问题。


就微服务而言,推荐的方法是Sagas pattern。你有本地交易,你有所有的 ACID 保证。需要的是参与者之间的某种协调。可以有协调者来指挥整个流程,而参与者彼此不了解。或者参与者可以形成一个编排,每个参与者都知道如何与前一个参与者和下一个参与者进行交流。

如果失败,您可以回滚或应用补偿操作来撤消先前操作的效果。有几个非常好的资源,所有这些资源都得到了深入的详细说明。我推荐Chris Richardson's Microservices Patterns 书。在线开始阅读this article

如果你想通过一个真实世界的例子,那么我建议阅读Jimmy Bogard's excellent blog series

【讨论】:

  • 相关SO主题#1, #2
【解决方案2】:

如果您有多个操作并且您希望它们全部成功或全部失败,那么您需要一个称为Transaction 的概念。

在 .NET 中,这是在 System.Transactions 命名空间中实现的,而 TransactionScope 类可能是您最终将使用的。

抱歉,这含糊不清且没有代码示例,但由于您只发布了自己的方法名称,因此您必须自己弄清楚在这些方法中实际执行的代码是做什么的,以及它与微软课程。如果您的数据库是实体框架或普通的SqlConnection,它可能开箱即用。但这取决于你自己想办法。

【讨论】:

  • TransactionTransactionScope 是为数据库操作而设计的,不是通用的。例如,如果您想以跨国方式修改文件,那么您应该考虑使用Transaction NTFS。由于 OP 的示例代码包含数据库写入、文件写入甚至 jira 票证创建等操作,这就是您提出的解决方案不适合的原因。两阶段提交可能是 DTC 的解决方案,但它的保证很差。改为检查 Saga。
  • @PeterCsala 你错了。 TransactionScope 通用的。您可以编写自己的提供程序来使用它。只是互联网上的每个人都使用MSSql来演示这个,因为MSSql Provider已经带有.NET Framework。如果您有更好的选择,请使用它提供答案。评论并不意味着永远存在。
猜你喜欢
  • 2016-02-15
  • 1970-01-01
  • 1970-01-01
  • 2020-12-16
  • 2019-02-19
  • 1970-01-01
  • 1970-01-01
  • 2012-05-04
  • 1970-01-01
相关资源
最近更新 更多