Durable Functions で一定時間待機して続きを実行

Durable Functions では、IDurableOrchestrationContext .CreateTimer を使うことで、関数の実行中に任意の時間待機できる。

docs.microsoft.com

大人の事情により、処理の途中で 2 時間待機する必要があったけど、Durable Functions のおかげでシンプルに実装できた。

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;

namespace SampleFunctions
{
    // 処理途中で 2 時間待機する Durable Function
    public sealed class SampleDurableFunction
    {
        [FunctionName(nameof(SampleDurableFunction) + "_" + nameof(HttpStart))]
        public async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Function, methods: "delete", Route = "tenants/{tenantId}")] HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            string tenantId)
        {
            var instanceId = await starter.StartNewAsync(
                nameof(SampleDurableFunction),
                input: tenantId);

            return starter.CreateCheckStatusResponse(req, instanceId);
        }

        [FunctionName(nameof(SampleDurableFunction))]
        public async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var tenantId = context.GetInput<string>();
            var retryOptions = new RetryOptions(
                firstRetryInterval: TimeSpan.FromSeconds(5),
                maxNumberOfAttempts: 3);

            // テナントのデータベースの一覧を取得
            var databaseNames = await context.CallActivityWithRetryAsync<string[]>(
                functionName: nameof(SampleDurableFunction) + "_" + nameof(GetTenantDatabaseNames),
                retryOptions: retryOptions,
                input: tenantId);

            // テナント情報を削除
            await context.CallActivityWithRetryAsync<string>(
                functionName: nameof(SampleDurableFunction) + "_" + nameof(DeleteTenant),
                retryOptions: retryOptions,
                input: tenantId);

            // 大人の事情で 2 時間待機
            var deadline = context.CurrentUtcDateTime.AddHours(2);
            await context.CreateTimer(deadline, System.Threading.CancellationToken.None);

            // 2 時間待機した後、データベースを消す
            foreach (var databaseName in databaseNames)
            {
                await context.CallActivityWithRetryAsync(
                    functionName: nameof(SampleDurableFunction) + "_" + nameof(DropTenantDatabase),
                    retryOptions: retryOptions,
                    input: databaseName);
            }
        }

        // テナントのデータベース名一覧を取得
        [FunctionName(nameof(SampleDurableFunction) + "_" + nameof(GetTenantDatabaseNames))]
        public async Task<string[]> GetTenantDatabaseNames([ActivityTrigger] string tenantId)
        {
            // テナントのデータベースの一覧を取得する処理(省略)
        }

        // テナントを削除
        [FunctionName(nameof(SampleDurableFunction) + "_" + nameof(DeleteTenant))]
        public async Task DeleteTenant([ActivityTrigger] string tenantId)
        {
            // データベースからテナントの情報を削除する処理(省略)
        }

        // テナントのデータベースを削除
        [FunctionName(nameof(SampleDurableFunction) + "_" + nameof(DropTenantDatabase))]
        public async Task DropTenantDatabase([ActivityTrigger] string databaseName)
        {
            // データベースを削除する処理(省略)
        }
    }
}