C# 在与多个系统交互时做出类似事务的行为
免责声明 在以下职位: action=action/Func 我有一个执行多个操作的长方法。每个操作都包装在一个try-catch中。如果特定操作失败,在catch中,我必须对所有以前的操作和当前操作执行一个清除操作。 我不知道如何停止在catch中复制代码,并使用设计模式或其他方式来聚合它们 我目前拥有的 在上面的方法中,我不想在所有catch块中写入所有清除操作,直到该点。 我希望每个成功的操作都设置一个类似于检查点的东西,然后当检查点失败时,检查状态并执行所有必需的清除,直到该点 我想要什么 我在想,是否有一种设计模式来包装这些可能具有不同返回类型的操作,并将其管道化。无论哪个操作失败,它都会触发Clearaction1…ClearactionN,其中N是失败的操作 另外,它可能有点像单子 并购->并购->并购->并购->并购->并购->并购->并购->并购->并购->并购->并购->并购->并购 其中a、b、c、d是类型,区别在于我需要汇总所有失效处理 更新 在这个论坛上回答之后,我觉得我需要做一些补充: 这是ASP网络控制器内的端点。我从多个系统检索数据,并使用获取的数据设置其他系统。 我希望它看起来像一个分布式系统事务: 获取输入系统[A,B] 到输出系统[X,Y] 序列示例 从数据库中获取数据 使用数据在X上设置数据并获得响应Z 从B获取数据 使用从B和Z获取的数据在Y上设置数据 塞塞纳里奥 现在假设从B获取数据失败,我想: -仅从X清除数据 我不想试图清除Y上的数据,因为它会产生不可撤销的损害C# 在与多个系统交互时做出类似事务的行为,c#,design-patterns,sequence,monads,C#,Design Patterns,Sequence,Monads,免责声明 在以下职位: action=action/Func 我有一个执行多个操作的长方法。每个操作都包装在一个try-catch中。如果特定操作失败,在catch中,我必须对所有以前的操作和当前操作执行一个清除操作。 我不知道如何停止在catch中复制代码,并使用设计模式或其他方式来聚合它们 我目前拥有的 在上面的方法中,我不想在所有catch块中写入所有清除操作,直到该点。 我希望每个成功的操作都设置一个类似于检查点的东西,然后当检查点失败时,检查状态并执行所有必需的清除,直到该点 我想要什
我只关心设置数据的I/O操作。如果您的代码对性能/分配不是很关键,您可以创建一个反向操作列表和布尔变量来跟踪成功:
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();
}
}
}
}
如果您的代码对性能/分配不是很关键,您可以创建一个反向操作列表和布尔变量来跟踪成功:
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
此外,还可以使用单子进行补偿,例如Scala CAT库。似乎您可以简单地构建一个动作对列表和相应的补偿动作。然后从1迭代到N,应用每个操作。如果在步骤I中捕获到异常,则通过补偿操作列表从I向后迭代到1
此外,还可以使用单子进行补偿,例如Scala猫的库如果您能够拥有一个状态对象,该对象具有所有副作用,您可以执行以下操作:
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的评论,我想提出以下备选方案:
也许与你的问题无关,但是你是否考虑过如果你的流程在这个交易的中间终止会发生什么?
如果进程在上述代码中终止,则会有一个一致的状态:未更改的状态 缺点是:只有当您能够隔离状态并且不依赖于过程中触发的事件时,它才真正起作用更清楚地说:如果在这个过程中,假设action5对API XYZ执行HTTP PUT,那么这个解决方案是不够的,因为您必须主动地反转该PUT。如果您能够有一个状态对象,它会产生所有副作用,那么您可以这样做:
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的评论,我想提出以下备选方案:
也许与你的问题无关,但是你是否考虑过如果你的流程在这个交易的中间终止会发生什么?
如果进程在上述代码中终止,则会有一个一致的状态:未更改的状态 缺点是:只有当您能够隔离状态并且不依赖于过程中触发的事件时,它才真正起作用 更清楚地说:如果在这个过程中,假设action5对API XYZ执行HTTP PUT,那么这个解决方案是不够的,因为您必须主动反转该PUT。一个操作总是返回void类型。否则它将是一个函数。我在考虑做一些实际上是交易性的事情。但这将取决于这些行动的作用。你有状态对象或类似的东西吗?这些行动有副作用吗在改变该状态之外?请原谅,我对它们的命名不正确,你是对的。我所说的操作是指Func和Action。它们中的一些确实有返回类型,而另一些则没有。它们中的一些相互依赖。序列可以是Func>>Action>>Func>>Action。这些方法确实有副作用。也许与你的问题无关,但是你是否考虑过如果你的进程在这个交易的中间终止会发生什么?我知道它不是很好,但是它们都需要发生,所以我现在有了一个大的捕捉,我正在运行所有清晰的动作。其中包含检查是否需要执行的逻辑。通过相互依赖,您的意思是,例如,在没有调用Action1之前,不能调用Action2?一个操作总是返回void类型。否则它将是一个函数。我在考虑做一些实际上是交易性的事情。但这将取决于这些行动的作用。你有状态对象或类似的东西吗?这些操作在改变该状态之外是否有副作用?请原谅,我对它们的命名不正确,你是对的。我所说的操作是指Func和Action。它们中的一些确实有返回类型,而另一些则没有。它们中的一些相互依赖。一个序列可能是Func>>Action>>Func>>Action。这些方法确实有副作用。也许与你的问题无关,但是你是否考虑过如果你的进程在这个交易的中间终止会发生什么?我知道它不是很好,但是它们都需要发生,所以我现在有了一个大的捕捉,我正在运行所有清晰的动作。它们内部有逻辑,可以检查是否需要执行。通过相互依赖,您的意思是,例如,在没有调用Action1之前,不能调用Action2?这就是我在最后将所有这些操作包含在monad中的意思。因此,操作的结果可以是一个对象,它可以处于两种状态之一:异常/结果可能是结果。所以我可以有一个类:class Monad{Exception ex;T result;}由一个具有所有副作用的状态对象生成,你是说一个可以触发所有i/O调用的对象,对吗?我可能有,是的,你的实现实际上就是我想要在一个对象上应用计算,而这个对象在每一步都会失败。我的问题是,你会把每次计算的ClearAction逻辑放在哪里?我不想仅仅抛出一个异常。异常处理也会引发副作用。@Bercovicadrian在这种情况下,状态对象仅仅是一个数据库,一个属性集合,如果您愿意的话。它在计算过程中按特定时间点保存数据。上述解决方案的要点是:您不需要ClearActions,因为一旦发生错误,您只需一次性放弃所有更改。但正如在回答中已经指出的那样:只有当你不改变其他地方的状态时,这才能起作用,也必须改变回去。你说的其他地方是什么意思?创建更多上下文:这是ASP NET Web API控制器中的一个方法。这是一种初始化方法,我尝试从一些系统A、B获取数据i/O调用,并将其他数据设置到一些其他系统X、Y。序列可以是get A->set X,get B->set Y。然而,我确实关心序列到达了哪里。我不想尝试清除尚未查询的系统中的数据。示例:get A->set X,get B在get B时失败。在这种情况下,我不想尝试清除Y。好的,在这种情况下,您不能使用它。其他地方可能意味着系统x和y。只读IO并不重要。但在SystemX中设置某些内容是一种外部状态更改。因此,如果您需要在出现错误的情况下逆转这一点,那么这个解决方案是不够的。这就是我在最后将所有这些操作包含在monad中的意思。因此,操作的结果可以是一个对象,它可能处于两种状态之一:异常/结果可能结果。所以我可以有一个类:class Monad{Exception ex;T result;}由一个具有所有副作用的状态对象生成,你是说一个可以触发所有i/O调用的对象,对吗?我可能有,是的,你的实现实际上就是我想要在一个对象上应用计算,而这个对象在每一步都会失败。我的问题是,你会把每次计算的ClearAction逻辑放在哪里?我不想仅仅抛出一个异常。异常处理也会引发副作用。@Bercovicadrian在这种情况下,状态对象仅仅是一个数据库,一个属性集合,如果您愿意的话。它在计算过程中按特定时间点保存数据。上述解决方案的要点是:您不需要ClearActions,因为一旦发生错误,您只需一次性放弃所有更改。但正如在回答中已经指出的那样:只有当你不改变其他地方的状态时,这才能起作用,也必须改变回去。你说的其他地方是什么意思?创建更多上下文:这是ASP NET Web API控制器中的一个方法。这是一种初始化方法,我尝试在其中获取数据i/O调用
从一些系统A、B和set其他数据到一些其他系统X、Y。序列可以是get A->set X,get B->set Y。然而,我确实关心序列到达了哪里。我不想尝试清除尚未查询的系统中的数据。示例:get A->set X,get B在get B时失败。在这种情况下,我不想尝试清除Y。好的,在这种情况下,您不能使用它。其他地方可能意味着系统x和y。只读IO并不重要。但在SystemX中设置某些内容是一种外部状态更改。因此,如果您需要在发生错误的情况下反转,那么这个解决方案是不够的。