Skip to content

Actions

Actions are the building blocks for providing behavior using workflows. Actions are designed to be modular, and unit-testable, they can be thought of lego-pieces, used alone or in combination with other actions, to satisfy business requirements.

Actions can be given arguments. Arguments are primitive data types which can be bound either from the entity's properties, from invocation parameters, or can be constant values defined by the user at workflow configuration time. See here to learn more about action arguments.

Actions can also get access to the current workflow domain entity's instance to act on it. Actions can inject services through DI, and other specific dependencies such as MassTransit's IPublishEndpoint can be accessed through IActionHandlerContext, as well.

Action descriptors

Workflow actions are described by means of IWorkflowActionDescriptor instances. Suite framework provides WorkflowActionDescriptor<THandler> base class, from which you should use for describing your workflow actions.

Descriptors provide information to identify the action and the handler type, and any argument required by the action to get executed.

The action handler provides the workflow behavior by updating the entity's instance and/or by orchestrating other services through publishing messages.

Note

We recommend you to gather all the information describing the action inside a single class, meaning that the handler class would be defined as inner class inside the descriptor itself, in order to easily locate the descriptor's related handler.

Registering actions

Workflow actions must be added to the workflow module options before they are used by workflow templates. The module options instance requires the corresponding WorkflowActionDescriptor type argument when invoking the AddWorkflowAction method to do so:

C#
    public override void SetupModule(IModuleBuilder builder)
    {
        builder.DependsOn<WorkflowsClientModule, WorkflowsClientModuleOptions>(opts =>
        {
            opts.SetDbContext<ServiceOrdersDbContext>();

            opts.AddWorkflowAction<ServiceOrderAssignActionDescriptor>();
            opts.AddWorkflowAction<ServiceOrderAcknowledgeActionDescriptor>();
            opts.AddWorkflowAction<ServiceOrderCompleteActionDescriptor>();

            opts.AddWorkflow<ServiceOrder>(WorkflowInfo.DefaultWorkflowTemplateId, builder =>
            {
                // configure your workflow here
            });
        });

Action identifier

The Id property is intended to provide a unique id among the whole ecosystem. We encourage you to use a namespace-like notation, such as {your-bounded-context-name}Actions/{action-id}. For example, for ServiceOrders bounded context, the actions id should start with ServiceOrderActions.

Action handlers

You'll typically need access to entity's instance to provide workflow behavior. In that case, you'll need to implement IWorkflowActionHandler{TInstance}, which let you access to the entity's instance at runtime, to get or update it's properties. Take into account that any update on the instance properties will be persisted upon current unit of work completion.

In the other hand, IWorkflowActionHandler interface implementation is intended to provide behavior that do not require access to the entity's instance. You're unlikely to found yourself doing that. This is something rather done by framework developers.

Important

IWorkflowActionHandler implementations can be shared across workflows defined

for different entities, whereas IWorkflowActionHandler{TInstance} doesn't, because they are designed to work with workflows of TInstance type.

IWorkflowActionHandlerContext and IWorkflowActionHandlerContext{TInstance}

Action context has properties and methods to build the workflow logic:

  • Instance property: let you access to the entity's instance (only available for IWorkflowActionHandlerContext{TInstance}).
  • GetArgument<TArgType>(argName) method let you access argument values, using the binding type configured for the current workflow.
  • GetPublishEndpoint() extension method let you access the current publish endpoint, from the current scope.

Action descriptor example

Here below you can see a complete example of an action descriptor with its action handler implementation (inner class).

C#
public class ServiceOrderAssignActionDescriptor
    : WorkflowActionDescriptor<ServiceOrderAssignActionDescriptor.Handler>
{
    public static readonly string AssignedPositionId = "assignedPositionId";

    public ServiceOrderAssignActionDescriptor()
    {
        this.AddArgument<Guid>(AssignedPositionId);
    }

    public override string Id => "ServiceOrdersActions/ServiceOrderAssignAction";

    public class Handler : IWorkflowActionHandler<ServiceOrder>
    {
        private readonly IReadOnlyRepository<Guid, PositionMetadata> positionRepository;

        public Handler(
            IReadOnlyRepository<Guid, PositionMetadata> positionRepository)
        {
            this.positionRepository = positionRepository;
        }

        public async Task ExecuteAsync(IWorkflowActionHandlerContext<ServiceOrder> context)
        {
            var assignedPositionId = context.GetArgument<Guid>(AssignedPositionId);

            var assignedPosition = await this.positionRepository.FindByIdOrThrowAsync(assignedPositionId);

            context.Instance.Assign(assignedPosition);

            await context.GetPublishEndpoint().Publish<ServiceOrderAssigned>(new
            {
                CorrelationId = context.Instance.CorrelationId,
                AssignedPositionCorrelationId = assignedPositionId,
                IssueCorrelationId = context.Instance.Issue.CorrelationId,
                AssetCorrelationId = context.Instance.Asset.CorrelationId,
            });
        }
    }
}