与多个系统交互时进行类似交易的行为

Making a transaction-like behaviour when interacting with multiple systems

免责声明
在下面post:
action=Action/Func

我有一个执行多个操作的长方法。每个操作都包含在 try-catch 中。如果特定操作失败,在 catch 中我必须执行 clear 对所有以前的和当前的动作。 我不知道如何停止在 catch 中复制代码并使用设计模式或其他方式聚合它们。

我现在有什么

public void LongMethod()
{
   try
   {
      try
      {
        action1();
      }catch(Exception ex)
      {
        ClearAction1();
      }
      try{
        action2();
      }catch(Exception ex){
         Clearaction1();
         Clearaction2();
      }
      try
      {
         action3();

      }
      catch(Exception ex)
      {
         Clearaction1();
         Clearaction2();
         Clearaction3();
      }
   catch(Exception ex)
   {
       //how should i aggregate the action clearance here
   }

}

在上述方法中,我不想在所有 catch 块中写入所有 clear 操作,直到那个点为止。 我希望为每个成功的操作设置检查点之类的东西,然后当它失败时,检查 state 并执行所有需要的清理直到那个点。

我想要什么

public void LongMethod()
{
   try
   {
      int checkpoint=0;
      action1();
      checkpoint=1;
      action2();
      checkpoint=2;
      action3();
      checkpoint=3; 
      action4();   //fails here
   }
   catch(Exception ex)
   {   
        switch(checkpoint)
        {
            3//does Clearaction1()+Clearaction2()+Clearaction3()+Clearaction4();
        }
    }
}

我在想是否有类似设计模式的东西来包装每个 actions 可能有不同的 return 类型和管道 them.Whichever actionN 失败了触发器 Clearaction1()...ClearactionN() 其中 N 是确实失败的 Action

P.S 可能类似于 monad.

m a ->(a->m b) -> m b -> (m b -> (b -> m c) -> m c) -> (m c -> ( c -> m d) -> m d) 其中 a ,b,c,d 是类型,区别在于我需要汇总所有失败处理。

更新

在这个论坛上回答后,我觉得我需要做一些补充:

这是 ASP NET Controller 内的端点。我从多个系统检索数据,并使用获取的数据设置其他系统。 我想让它看起来像一个分布式系统事务:

获取输入系统
[A,B]
到输出系统
[X,Y]

序列示例

场景 现在让我说 fetch data from B 失败了:
- 仅清除 X 中的数据

我不想尝试清除 Y 上的数据,因为它会造成不可挽回的损失。

我只关心 set 数据的 I/O 操作。

如果您的代码不是非常非常 performance/allocation 关键,您可以创建一个 "reverse actions" 列表和布尔变量来跟踪成功:

public void LongMethod()
{
   var reverseActions = new List<Action>();
   var success = false;
   try
   {
      int checkpoint=0;
      Action1();
      reverseActions.Add(ClearAction1);
      Action2();
      reverseActions.Add(ClearAction2);
      ...
      success = true;
   }
   finally // or can be catch if you can/want to handle/swallow exception
   {   
         if(!success)
         {
            foreach(var a in reverseActions)
            {
             a();
            }
         }
    }
}

看来您可以简单地构建一个动作对列表和相应的补偿动作。然后从 1 迭代到 N,应用每个动作。如果在步骤 I 中捕获到异常,则通过补偿操作列表从 I 向后迭代到 1。

此外,可以使用 monad 进行补偿,例如cats-saga Scala 猫库

如果你能够拥有一个状态对象,它会占用所有 "side-effects",你可以这样做:

public State LongMethod( State originalState ) // assuming, `State` is your state object type
{
    //                     vv Copy-CTOR == "Begin Transaction"
    State localState = new State(originalState); 
    try{
       // mutate _the local copy_
       action1(localState);
       var intermediateResult = func2(localState);
       action3(localState, intermediateResult);
       // ...
       return localState; // return mutated state == "Commit"
    }
    catch(Exception ex)
    {
        // return unchanged state == "Rollback"
        return originalState;
    }
}

为什么在已经接受了不同的答案后我还要添加这个?

关于 Martin 的评论,我想提出这个替代方案:

Perhaps not relevant to your question, but have you considered what will happen if your process terminates in the middle of this "transaction"?

如果进程在上述代码中终止,则您有一个一致的状态:未更改的状态。

缺点 是:只有当您可以隔离状态并且不依赖于过程中触发的事件时,它才真正起作用。

为了更清楚:如果在这个过程中,假设 action5 做了一个 HTTP PUT 到 API XYZ,那么这个解决方案是不够的,因为你必须主动反转 PUT.