Skip to content

Samples

Akash Kava edited this page Nov 9, 2021 · 8 revisions

Configure Workflow Service

public class WorkflowContext: EternityContext {

   public WorkflowContext(IServiceProvider services):
        base(new EternityAzureStorage(("ET", "azure storage connection string...")), services, new EternityClock()) {
   }
}

// register this as background service
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) {
        while (!stoppingToken.IsCancellationRequested) {
            try {
                await workflowService.ProcessMessagesAsync(cancellationToken: stoppingToken);
            } catch (Exception ex) {
                telemetryClient.TrackException(ex);
            }
        }
    }
}

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

// create new workflow and execute now
var id = await SignupWorkflow.CreateAsync(context, "[email protected]");

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

Signup 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++)
        {
            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}";
    }
}

Mobile Sample

For mobile, on iOS, there is no way to generate the code, so you can use Schedule method by importing .Mobile namespace as shown below.

var maxWait = TimeSpan.FromMinutes(15);
var code = (this.CurrentUtc.Ticks & 0xF).ToString();
await SendEmailAsync(input, code);
for (int i = 0; i < 3; i++)
{
    var (name, result) = await WaitForExternalEventsAsync(maxWait, Resend, Verify);
    switch(name)
    {
        case Verify:
            if(result == code)
            {
                return "Verified";
            }
            break;
        case Resend:
            // note this will use method delegate and will ensure that we are passing
            // same type of parameters, the only problem is you will have to supply all
            // default parameters as well
            await this.ScheduleAsync( SendEmailAsync, input, code, i, null);
            break;
    }
}
return "NotVerified";

Renew Membership

In the following example, we are creating Renew Membership Workflow when user registers for one year. In the following example, we will renew the membership after 364 days from the current utc date. The workflow will be suspended immediately and it will not occupy any memory. After 364 days, workflow will restart and will continue to renew. However, if user decides to cancel, you can raise an event with context that will cause workflow to cancel.

public class RenewMembershipWorkflow: Workflow<RenewMembershipWorkflow,long,string> {

    public const string Cancel = nameof(Cancel);
    
    public async Task<string> RunAsync(long id) {

        var till = TimeSpan.FromDays(364);
        var (name, result) = await this.WaitForExtenralEvents(till, Cancel);
        if (name == Cancel) {
            // user has cancelled the membership, exit..
            return "Cancelled";
        }
        // at this time, this workflow will be suspended and removed from the execution
        // internally it will throw `ActivitySuspendedException` and it will start
        // just before the given timespan

        for(int i = 0; i<3; i++) {
            var success = await RewewAsync(id, at);
            if(success) {

                // restart the same workflow
                await RenewMembershipWorkflow.CreateAsync(this.Context, id);

                return "Done";
            }

            // try after 3 days again...
            at = TimeSpan.FromDays(3);
        }

        // renewal failed...
        return "Failed";

    }

    [Activity]
    public virtual async Task<bool> RenewAsync(
        long id, 
        [Schedule] TimeSpan at, 
        [Inject] IPaymentService paymentService = null,
        [Inject] IEmailService emailService = null
        ) {

        var result = await paymentService.ChargeAsync(id);
        if(result.Success) {
            return true;
        }
        await emailService.SendFailedRenewalAsync(id);
        return false;
    }   
}
Clone this wiki locally