与多个系统交互时进行类似交易的行为
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]
序列示例
- 从
A
获取数据
- 在
X
上设置数据(使用 A
数据)(并获得响应 Z
)
- 从
B
获取数据
- 在
Y
上设置数据(使用从 B
和 Z
获取的数据)
场景
现在让我说 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
.
免责声明
在下面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]
序列示例
- 从
A
获取数据
- 在
X
上设置数据(使用A
数据)(并获得响应Z
) - 从
B
获取数据
- 在
Y
上设置数据(使用从B
和Z
获取的数据)
场景
现在让我说 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
.