-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Try-catch clause #525
base: master
Are you sure you want to change the base?
Try-catch clause #525
Conversation
It is needed to exclude cases where try is present and catch is not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a significant and impressive contribution 👍
Left a few comments... I'd also like to pull this branch and play with it a bit before merging.
|
||
namespace WorkflowCore.Interface | ||
{ | ||
public interface ICatchStepBuilder<TData, TStepBody> : IStepBuilder<TData, TStepBody>, ITryStepBuilder<TData, TStepBody> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need an empty interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This interface gives a builder full functionality of IStepBuilder
and also allows to Catch()
if we want to have multiple catches after one Try()
. It is used as a return type for builder after the first Catch()
, whereas after Try()
we want to allow only to Catch()
.
{ | ||
public interface IWorkflowDefinitionValidator | ||
{ | ||
bool IsDefinitionValid(WorkflowDefinition definition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this part of the try/catch feature? or something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now this only checks that every Try()
has corresponding Catch()
.
@@ -45,6 +45,8 @@ public class ExecutionPointer | |||
public object Outcome { get; set; } | |||
|
|||
public PointerStatus Status { get; set; } = PointerStatus.Legacy; | |||
|
|||
public Exception CurrentException { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class needs to be serializable... not sure how different sub-classes of Exception
will behave in that case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understood, error handling happens in the same persistence round (not sure if this is how it's called tho) in which the exception occurs. So do we really need to serialize this field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this field on the ExecutionPointer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danielgerlag We need this field to build StepExecutionContext
so that we can retrieve a thrown exception in Catch()
step. An example:
builder.Try(b => b.StartWith(_ => throw new ArgumentException("I am Exception1")))
.Catch(new[] {typeof(Exception)}, ctx =>
{
Console.WriteLine($"Caught an exception: Type: '{ctx.CurrentException.GetType().Name}', Message: '{ctx.CurrentException.Message}'");
return ExecutionResult.Next();
})
Does it really need to be serializable though, or do you have any counter-arguments to what I have said above?
UPD: just used here the same SerializableException
class as in WorkflowTerminated
|
||
namespace WorkflowCore.Models.LifeCycleEvents | ||
{ | ||
public class WorkflowTerminated : LifeCycleEvent | ||
{ | ||
public Exception Exception { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class also needs to be serializable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be fine if we would put ExecutionError
in here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There already is WorkflowError
lifecycle event. Also, you could terminate a workflow without an error... not sure this field makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danielgerlag
When a workflow doesn't catch an exception thrown in Try
clause, it seems logical to me that the workflow has to be terminated, because that means something has gone pretty badly. This can be the case if there is no matching exception type in corresponding Catch
clauses. It means WorkflowTerminated
event has to be published when this happens. I publish WorkflowError
event when an exception happens inside of a Try
clause, please, correct me, if I'm doing it wrong. If we want to retrieve information about why workflow has been terminated, we have to preserve an exception in some way. If a workflow was terminated without an error, this field just equals to null
.
Nevertheless, if this field needs to be serializable, I'm going to make it of a custom class with fields like Type, Message and StackTrace, because I feel like people might make a good use of the additional information on an unexpected workflow termination.
public class StepBuilder<TData, TStepBody> : IStepBuilder<TData, TStepBody>, IContainerStepBuilder<TData, TStepBody, TStepBody> | ||
public class StepBuilder<TData, TStepBody> | ||
: IContainerStepBuilder<TData, TStepBody, TStepBody>, | ||
ICatchStepBuilder<TData, TStepBody> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand why the class would implement ICatchStepBuilder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we need to return ICatchStepBuilder
after Catch()
.
public class StepBuilder<TData, TStepBody>
: IContainerStepBuilder<TData, TStepBody, TStepBody>,
ICatchStepBuilder<TData, TStepBody>,
IStepBuilder<TData, TStepBody>,
ITryStepBuilder<TData, TStepBody>
This would be more verbose, but two last lines are redundant in that case.
|
||
namespace WorkflowCore.Services.ErrorHandlers | ||
{ | ||
public class CatchHandler : IWorkflowErrorHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you implement a set of unit tests for this class that make it's behavior explicit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will do it soon. By the way I have a question concerning behaviour of CompensateHandler
. I don't understand what lines 41-45 of CatchHandler
do as I copypasted them from lines 56-60 of CompensateHandler
, hence I don't understand if they are needed at all. Could you please elaborate on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that was for bubbling up the call stack of nested containers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I clearly have troubles understanding this part of bubbling up the call stack. Could you please go into deeper details? And please tell me if it's needed or not in case of Catch
clause.
Going to be travelling for a few weeks but I'll try take a deeper look soon. |
Just wanted to say I didn't forget about PR, going to fix the issues you noticed some time soon. |
@kirsanium I haven't forgotten about this PR. I will try carve out some time in the next week or so to dive deeper into it but on the surface it looks really good! |
@danielgerlag @kirsanium Any news on this? Would be very handy to have this in the product. |
@knoxi waiting for the review from @danielgerlag |
@danielgerlag any news on that? It's now open since a year. |
@kirsanium |
Sorry I have no time and interest in this one since too much time have passed - I haven't even coded in c# for some time now...but please please finish this one, it was goddamn ready two years ago |
Feature introducing complex error handling via new StepBuilder's methods Try() and Catch().
This works pretty similar to CompensateWith() and is based off of that, but with no reversion and it allows to catch exceptions by type.
Also introducing WorkflowDefinitionValidator to ensure that there are corresponding catches for every try.