Skip to content

Getting Started

Akash Kava edited this page Mar 22, 2023 · 10 revisions

Configure Workflow Service

// You must inject EternityClock before resolving WorkflowContext.

services.AddSingleton<EternityClock>();

// This context will be used by any machine who will create workflow
// , raise an event or execute the workflows
public class WorkflowContext: EternityContext {

   public WorkflowContext(IServiceProvider services):
        base(
            new EternitySqlStorage(
                 "Connection String ... ",
                  services.GetRequiredService<EternityClock>()), 
            services, 
            services.GetRequiredService<EternityClock>()) {
   }
}

// register this as background service only on machine
// which will actually process the messages
public class WorkflowBackgroundService : BackgroundService {
    private readonly WorkflowContext workflowContext;
    private readonly TelemetryClient telemetryClient;

    public WorkflowBackgroundService(WorkflowContext workflowContext, TelemetryClient telemetryClient)
    {
        this.workflowContext = workflowContext;
        this.telemetryClient = telemetryClient;
    }

    protected async override Task ExecuteAsync(CancellationToken stoppingToken) {

        // optinal... to run workflows that are configured to execute daily
        workflowService.RunDailyWorkflows(stoppingToken, "Default");

        while (!stoppingToken.IsCancellationRequested) {
            try {
                await workflowService.ProcessMessagesAsync(cancellationToken: stoppingToken);
            } catch (Exception ex) {
                telemetryClient.TrackException(ex);
            }
        }
    }
}

You can have multiple machines to process the message queue and any machine can create workflow and raise an event.

Register Service Scope

To enable Microsoft.DependencyInjection.Extensions Scope, add following in configure method.

services.AddEternityServiceScope();

This will make every activity execute in separate service scope, you can inject scoped services in Activities.

Create new workflow

Each workflow is identified by unique alpha numeric ID, it can be anything but it has to be unique. If you use Azure Storage as Eternity Storage then ID must be escaped. If you do not specify ID, a new Guid will be created. CreateAsync has an overload which allows you to specify ID and input. Input does not have to be unique, but ID has to be unique per workflow.

// create new workflow and execute now
var id = await SignupWorkflow.CreateAsync(context, "sample@gmail.com");

Workflow Example

Lets assume we want to verify email address of user before signup, we want to set max timeout to 45 minutes and maximum 3 retries.

Activities are methods of the same class marked with [Activity] attribute and methods must be public and virtual.

Activities can also be scheduled in future by passing a parameter marked with [Schedule] attribute as shown below.

public class SignupWorkflow : Workflow<SignupWorkflow, string, string> {

    // name of external event
    public const string Resend = nameof(Resend);

    // name of external event
    public const string Verify = nameof(Verify);

    public override async Task<string> RunAsync(string input)
    {
        var maxWait = TimeSpan.FromMinutes(15);
        var code = (this.CurrentUtc.Ticks & 0xF).ToString();
        await SendEmailAsync(input, code);
        for (int i = 0; i < 3; i++)
        {
            // upon timeout, name is null
            var (name, result) = await WaitForExternalEventsAsync(maxWait, Resend, Verify);
            switch(name)
            {
                case Verify:
                    if(result == code)
                    {
                        return "Verified";
                    }
                    break;
                case Resend:
                    await SendEmailAsync(input, code, i);
                    break;
            }
        }
        return "NotVerified";
    }

    [Activity]
    public virtual async Task<string> SendEmailAsync(
        string emailAddress, 
        string code, 
        int attempt = -1,
        [Inject] MockEmailService emailService = null) {
        await Task.Delay(100);
        emailService.Emails.Add((emailAddress, code, CurrentUtc));
        return $"{emailService.Emails.Count-1}";
    }
}

Raise an Event

// raise an event...
await context.RaiseEventAsync(id, SignupWorkflow.Verify, verificationCode);

You can call context.RaiseEventAsync from anywhere, it accepts ID of the workflow, name of event and a result you want to pass on. Event can be raised by any machine as long as it has same underlying storage.

Daily Workflows

To execute one workflow daily, you can create workflow as shown below.

[ScheduleDaily("Default")]
public class SendDailyReminder: DailyWorkflow {

    public async Task<string> RunAsync(string input) {
          // ... do something....
        return "Ok";
    }

}

Register in services. WorkflowService can find DailyWorkflows only if you register the assembly where it is defined. Please add following code in Startup.cs

    services.RegisterDailyWorkflows(this.GetType().Assembly);