Azure Functions サーバーレス開発ベストプラクティス

エンタープライズレベルでのスケーラブルなサーバーレスアーキテクチャ

はじめに

Azure Functions は、イベントドリブンなサーバーレスコンピューティングプラットフォームとして、現代のクラウドアプリケーション開発において重要な役割を果たしています。本記事では、実際の大規模プロジェクトでの経験を基に、Azure Functions を活用した効率的でスケーラブルなサーバーレスアーキテクチャの構築方法を詳しく解説します。

基本アーキテクチャ設計

Function App の構成戦略

// スケーラブルなHTTPトリガー関数
public class OrderProcessingFunction
{
    private readonly IOrderService _orderService;
    private readonly IEventPublisher _eventPublisher;
    private readonly ILogger<OrderProcessingFunction> _logger;
    
    public OrderProcessingFunction(
        IOrderService orderService,
        IEventPublisher eventPublisher,
        ILogger<OrderProcessingFunction> logger)
    {
        _orderService = orderService;
        _eventPublisher = eventPublisher;
        _logger = logger;
    }
    
    [FunctionName("ProcessOrder")]
    public async Task<IActionResult> ProcessOrder(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequest req,
        ILogger log)
    {
        var correlationId = Guid.NewGuid().ToString();
        using var scope = _logger.BeginScope(new Dictionary<string, object>
        {
            ["CorrelationId"] = correlationId
        });
        
        try
        {
            // リクエストボディの読み取り
            var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var orderRequest = JsonSerializer.Deserialize<OrderRequest>(requestBody);
            
            // 入力検証
            if (!IsValidOrderRequest(orderRequest))
            {
                return new BadRequestObjectResult(new { Error = "Invalid order request" });
            }
            
            // ビジネスロジック実行
            var order = await _orderService.CreateOrderAsync(orderRequest);
            
            // イベント発行
            await _eventPublisher.PublishAsync(new OrderCreatedEvent
            {
                OrderId = order.Id,
                CustomerId = order.CustomerId,
                TotalAmount = order.TotalAmount,
                CorrelationId = correlationId
            });
            
            _logger.LogInformation("Order processed successfully. OrderId: {OrderId}", order.Id);
            
            return new OkObjectResult(new { OrderId = order.Id, Status = "Created" });
        }
        catch (ValidationException ex)
        {
            _logger.LogWarning(ex, "Order validation failed");
            return new BadRequestObjectResult(new { Error = ex.Message });
        }
        catch (BusinessException ex)
        {
            _logger.LogError(ex, "Business logic error occurred");
            return new UnprocessableEntityObjectResult(new { Error = ex.Message });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error occurred while processing order");
            return new StatusCodeResult(500);
        }
    }
    
    private bool IsValidOrderRequest(OrderRequest request)
    {
        return request != null 
            && !string.IsNullOrEmpty(request.CustomerId)
            && request.Items?.Any() == true
            && request.TotalAmount > 0;
    }
}

依存性注入の最適化

// Startup.cs - 効率的なDI設定
[assembly: FunctionsStartup(typeof(Startup))]
public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        var configuration = BuildConfiguration();
        
        // Azure Key Vault設定
        ConfigureKeyVault(builder, configuration);
        
        // データベース設定
        ConfigureDatabase(builder, configuration);
        
        // 外部サービス設定
        ConfigureExternalServices(builder, configuration);
        
        // ビジネスサービス設定
        ConfigureBusinessServices(builder);
        
        // 監視・ロギング設定
        ConfigureMonitoring(builder, configuration);
    }
    
    private void ConfigureDatabase(IFunctionsHostBuilder builder, IConfiguration configuration)
    {
        builder.Services.AddDbContext<ApplicationDbContext>(options =>
        {
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
            
            // Connection pooling最適化
            options.EnableSensitiveDataLogging(false);
            options.EnableServiceProviderCaching(true);
            options.ConfigureWarnings(warnings => 
                warnings.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning));
        }, ServiceLifetime.Scoped); // Functions用にScopedを使用
        
        // Entity Framework Core のパフォーマンス最適化
        builder.Services.AddPooledDbContextFactory<ApplicationDbContext>(options =>
        {
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
        });
    }
    
    private void ConfigureExternalServices(IFunctionsHostBuilder builder, IConfiguration configuration)
    {
        // HTTP Client設定
        builder.Services.AddHttpClient<IPaymentService, PaymentService>(client =>
        {
            client.BaseAddress = new Uri(configuration["PaymentService:BaseUrl"]);
            client.Timeout = TimeSpan.FromSeconds(30);
        })
        .AddPolicyHandler(GetRetryPolicy())
        .AddPolicyHandler(GetCircuitBreakerPolicy());
        
        // Azure Service Bus設定
        builder.Services.AddSingleton(provider =>
        {
            var connectionString = configuration.GetConnectionString("ServiceBus");
            return new ServiceBusClient(connectionString);
        });
        
        // Redis Cache設定
        builder.Services.AddStackExchangeRedisCache(options =>
        {
            options.Configuration = configuration.GetConnectionString("Redis");
            options.InstanceName = "EnhancedFunctions";
        });
    }
    
    private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
    {
        return Policy
            .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (outcome, timespan, retryCount, context) =>
                {
                    Console.WriteLine($"Retry {retryCount} after {timespan} seconds");
                });
    }
    
    private static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
    {
        return Policy
            .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
            .CircuitBreakerAsync(
                handledEventsAllowedBeforeBreaking: 3,
                durationOfBreak: TimeSpan.FromSeconds(30));
    }
}

イベントドリブンアーキテクチャ

Service Bus統合

// Service Bus トリガー関数
public class EventProcessingFunction
{
    private readonly IOrderService _orderService;
    private readonly INotificationService _notificationService;
    private readonly ILogger<EventProcessingFunction> _logger;
    
    public EventProcessingFunction(
        IOrderService orderService,
        INotificationService notificationService,
        ILogger<EventProcessingFunction> logger)
    {
        _orderService = orderService;
        _notificationService = notificationService;
        _logger = logger;
    }
    
    [FunctionName("ProcessOrderCreatedEvent")]
    public async Task ProcessOrderCreatedEvent(
        [ServiceBusTrigger("order-events", "order-processing", Connection = "ServiceBusConnection")]
        string messageBody,
        MessageReceiver messageReceiver,
        string lockToken,
        ILogger log)
    {
        var correlationId = Guid.NewGuid().ToString();
        
        try
        {
            var orderEvent = JsonSerializer.Deserialize<OrderCreatedEvent>(messageBody);
            
            using var scope = _logger.BeginScope(new Dictionary<string, object>
            {
                ["CorrelationId"] = correlationId,
                ["OrderId"] = orderEvent.OrderId,
                ["EventType"] = nameof(OrderCreatedEvent)
            });
            
            _logger.LogInformation("Processing OrderCreatedEvent for Order: {OrderId}", orderEvent.OrderId);
            
            // 在庫確保処理
            await _orderService.ReserveInventoryAsync(orderEvent.OrderId);
            
            // 支払い処理
            var paymentResult = await _orderService.ProcessPaymentAsync(orderEvent.OrderId);
            
            if (paymentResult.IsSuccess)
            {
                // 確認通知送信
                await _notificationService.SendOrderConfirmationAsync(orderEvent);
                
                // 配送準備イベント発行
                await PublishShippingPreparedEventAsync(orderEvent);
                
                _logger.LogInformation("Order processing completed successfully for Order: {OrderId}", orderEvent.OrderId);
            }
            else
            {
                // 支払い失敗時の処理
                await _orderService.CancelOrderAsync(orderEvent.OrderId, "Payment failed");
                await _notificationService.SendPaymentFailedNotificationAsync(orderEvent);
                
                _logger.LogWarning("Order payment failed for Order: {OrderId}. Reason: {Reason}", 
                    orderEvent.OrderId, paymentResult.FailureReason);
            }
            
            // メッセージの完了
            await messageReceiver.CompleteAsync(lockToken);
        }
        catch (TransientException ex)
        {
            _logger.LogWarning(ex, "Transient error occurred. Message will be retried. Order: {OrderId}", 
                JsonSerializer.Deserialize<OrderCreatedEvent>(messageBody)?.OrderId);
            
            // メッセージを放棄(再試行される)
            await messageReceiver.AbandonAsync(lockToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Fatal error occurred processing order event. Moving to dead letter queue.");
            
            // デッドレターキューに移動
            await messageReceiver.DeadLetterAsync(lockToken, "ProcessingFailed", ex.Message);
        }
    }
    
    [FunctionName("ProcessDeadLetterMessages")]
    public async Task ProcessDeadLetterMessages(
        [ServiceBusTrigger("order-events/$deadletterqueue", Connection = "ServiceBusConnection")]
        string messageBody,
        IDictionary<string, object> userProperties,
        ILogger log)
    {
        _logger.LogError("Processing dead letter message: {MessageBody}", messageBody);
        
        // 管理者通知
        await _notificationService.SendDeadLetterAlertAsync(messageBody, userProperties);
        
        // 手動処理用のキューまたはストレージに保存
        await StoreForManualProcessingAsync(messageBody, userProperties);
    }
    
    private async Task PublishShippingPreparedEventAsync(OrderCreatedEvent orderEvent)
    {
        var shippingEvent = new ShippingPreparedEvent
        {
            OrderId = orderEvent.OrderId,
            CustomerId = orderEvent.CustomerId,
            ShippingAddress = await _orderService.GetShippingAddressAsync(orderEvent.OrderId),
            PreparedAt = DateTime.UtcNow
        };
        
        // Event Gridへの発行など
        await _eventPublisher.PublishAsync(shippingEvent);
    }
    
    private async Task StoreForManualProcessingAsync(string messageBody, IDictionary<string, object> userProperties)
    {
        var errorRecord = new
        {
            MessageBody = messageBody,
            UserProperties = userProperties,
            ErrorTime = DateTime.UtcNow,
            Status = "RequiresManualProcessing"
        };
        
        // Table Storageまたはデータベースに保存
        // 実装は省略
    }
}

Event Grid統合

// Event Grid トリガー関数
public class EventGridFunction
{
    private readonly ICustomerService _customerService;
    private readonly IAnalyticsService _analyticsService;
    private readonly ILogger<EventGridFunction> _logger;
    
    [FunctionName("ProcessEventGridEvent")]
    public async Task ProcessEventGridEvent(
        [EventGridTrigger] EventGridEvent eventGridEvent,
        ILogger log)
    {
        try
        {
            _logger.LogInformation("Processing Event Grid event: {EventType} for Subject: {Subject}", 
                eventGridEvent.EventType, eventGridEvent.Subject);
            
            switch (eventGridEvent.EventType)
            {
                case "Enhanced.Orders.OrderCompleted":
                    await HandleOrderCompletedAsync(eventGridEvent);
                    break;
                    
                case "Enhanced.Customers.CustomerRegistered":
                    await HandleCustomerRegisteredAsync(eventGridEvent);
                    break;
                    
                case "Enhanced.Analytics.DataProcessingRequired":
                    await HandleAnalyticsProcessingAsync(eventGridEvent);
                    break;
                    
                default:
                    _logger.LogWarning("Unknown event type: {EventType}", eventGridEvent.EventType);
                    break;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing Event Grid event: {EventType}", eventGridEvent.EventType);
            throw; // Event Gridの再試行メカニズムに依存
        }
    }
    
    private async Task HandleOrderCompletedAsync(EventGridEvent eventGridEvent)
    {
        var orderData = eventGridEvent.Data.ToObjectFromJson<OrderCompletedData>();
        
        // 顧客満足度調査の送信
        await _customerService.SendSatisfactionSurveyAsync(orderData.CustomerId, orderData.OrderId);
        
        // レコメンデーションエンジンの更新
        await _analyticsService.UpdateCustomerPreferencesAsync(orderData.CustomerId, orderData.ProductIds);
        
        // ロイヤルティポイントの付与
        await _customerService.AwardLoyaltyPointsAsync(orderData.CustomerId, orderData.TotalAmount);
    }
    
    private async Task HandleCustomerRegisteredAsync(EventGridEvent eventGridEvent)
    {
        var customerData = eventGridEvent.Data.ToObjectFromJson<CustomerRegisteredData>();
        
        // ウェルカムメールの送信
        await _customerService.SendWelcomeEmailAsync(customerData.CustomerId);
        
        // 初回購入クーポンの発行
        await _customerService.IssueWelcomeCouponAsync(customerData.CustomerId);
        
        // CRMシステムへの同期
        await _customerService.SyncToCrmAsync(customerData);
    }
}

タイマートリガーとスケジュール処理

バッチ処理の最適化

// 効率的なタイマートリガー関数
public class ScheduledProcessingFunction
{
    private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
    private readonly IBlobStorageService _blobStorageService;
    private readonly IEmailService _emailService;
    private readonly ILogger<ScheduledProcessingFunction> _logger;
    
    public ScheduledProcessingFunction(
        IDbContextFactory<ApplicationDbContext> dbContextFactory,
        IBlobStorageService blobStorageService,
        IEmailService emailService,
        ILogger<ScheduledProcessingFunction> logger)
    {
        _dbContextFactory = dbContextFactory;
        _blobStorageService = blobStorageService;
        _emailService = emailService;
        _logger = logger;
    }
    
    [FunctionName("ProcessDailyReports")]
    public async Task ProcessDailyReports(
        [TimerTrigger("0 0 6 * * *")] TimerInfo timerInfo, // 毎日午前6時
        ILogger log)
    {
        var processingDate = DateTime.UtcNow.Date.AddDays(-1); // 前日のデータ
        var correlationId = Guid.NewGuid().ToString();
        
        using var scope = _logger.BeginScope(new Dictionary<string, object>
        {
            ["CorrelationId"] = correlationId,
            ["ProcessingDate"] = processingDate.ToString("yyyy-MM-dd"),
            ["FunctionName"] = nameof(ProcessDailyReports)
        });
        
        try
        {
            _logger.LogInformation("Starting daily reports processing for date: {ProcessingDate}", processingDate);
            
            // 並列処理でレポート生成
            var reportTasks = new[]
            {
                GenerateSalesReportAsync(processingDate),
                GenerateCustomerReportAsync(processingDate),
                GenerateInventoryReportAsync(processingDate),
                GenerateFinancialReportAsync(processingDate)
            };
            
            var reports = await Task.WhenAll(reportTasks);
            
            // レポートをBlob Storageに保存
            var uploadTasks = reports.Select(report => 
                _blobStorageService.UploadReportAsync(report, processingDate));
            
            await Task.WhenAll(uploadTasks);
            
            // 関係者にメール通知
            await SendReportNotificationAsync(reports, processingDate);
            
            _logger.LogInformation("Daily reports processing completed successfully for date: {ProcessingDate}", processingDate);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error occurred during daily reports processing for date: {ProcessingDate}", processingDate);
            
            // 管理者に障害通知
            await _emailService.SendErrorNotificationAsync("Daily Reports Processing Failed", ex.Message, correlationId);
            
            throw;
        }
    }
    
    [FunctionName("CleanupExpiredData")]
    public async Task CleanupExpiredData(
        [TimerTrigger("0 0 2 * * SUN")] TimerInfo timerInfo, // 毎週日曜日午前2時
        ILogger log)
    {
        var cutoffDate = DateTime.UtcNow.AddDays(-90); // 90日前のデータを削除
        
        using var dbContext = await _dbContextFactory.CreateDbContextAsync();
        using var transaction = await dbContext.Database.BeginTransactionAsync();
        
        try
        {
            _logger.LogInformation("Starting expired data cleanup. Cutoff date: {CutoffDate}", cutoffDate);
            
            // バッチサイズを指定して効率的に削除
            const int batchSize = 1000;
            var totalDeleted = 0;
            
            while (true)
            {
                var expiredLogs = await dbContext.AuditLogs
                    .Where(log => log.CreatedAt < cutoffDate)
                    .Take(batchSize)
                    .ToListAsync();
                
                if (!expiredLogs.Any())
                    break;
                
                dbContext.AuditLogs.RemoveRange(expiredLogs);
                await dbContext.SaveChangesAsync();
                
                totalDeleted += expiredLogs.Count;
                
                _logger.LogInformation("Deleted {Count} expired audit logs. Total deleted: {TotalDeleted}", 
                    expiredLogs.Count, totalDeleted);
                
                // メモリ使用量を抑制
                dbContext.ChangeTracker.Clear();
                
                // CPUを他のプロセスに譲る
                await Task.Delay(100);
            }
            
            await transaction.CommitAsync();
            
            _logger.LogInformation("Expired data cleanup completed. Total records deleted: {TotalDeleted}", totalDeleted);
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync();
            _logger.LogError(ex, "Error occurred during expired data cleanup");
            throw;
        }
    }
    
    private async Task<ReportData> GenerateSalesReportAsync(DateTime processingDate)
    {
        using var dbContext = await _dbContextFactory.CreateDbContextAsync();
        
        var salesData = await dbContext.Orders
            .Where(o => o.CreatedAt.Date == processingDate)
            .GroupBy(o => o.CreatedAt.Hour)
            .Select(g => new SalesHourlyData
            {
                Hour = g.Key,
                TotalSales = g.Sum(o => o.TotalAmount),
                OrderCount = g.Count(),
                AverageOrderValue = g.Average(o => o.TotalAmount)
            })
            .ToListAsync();
        
        return new ReportData
        {
            ReportType = "SalesReport",
            GeneratedAt = DateTime.UtcNow,
            ProcessingDate = processingDate,
            Data = salesData
        };
    }
    
    private async Task SendReportNotificationAsync(ReportData[] reports, DateTime processingDate)
    {
        var recipients = new[] { "management@enhanced.com", "analytics@enhanced.com" };
        
        var emailBody = $@"
            <h2>Daily Reports - {processingDate:yyyy-MM-dd}</h2>
            <p>The following reports have been generated and are available in the reports storage:</p>
            <ul>
                {string.Join("", reports.Select(r => $"<li>{r.ReportType} - Generated at {r.GeneratedAt:HH:mm}</li>"))}
            </ul>
            <p>Access the reports through the management portal or download directly from Azure Storage.</p>
        ";
        
        await _emailService.SendEmailAsync(
            recipients,
            $"Daily Reports Available - {processingDate:yyyy-MM-dd}",
            emailBody);
    }
}

パフォーマンス最適化

接続プールとリソース管理

// 効率的なリソース管理
public class OptimizedHttpFunction
{
    private static readonly HttpClient HttpClient = new HttpClient();
    private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = false
    };
    
    private readonly IMemoryCache _cache;
    private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
    
    public OptimizedHttpFunction(IMemoryCache cache, IDbContextFactory<ApplicationDbContext> dbContextFactory)
    {
        _cache = cache;
        _dbContextFactory = dbContextFactory;
    }
    
    [FunctionName("GetCustomerData")]
    public async Task<IActionResult> GetCustomerData(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "customers/{customerId}")] HttpRequest req,
        string customerId,
        ILogger log)
    {
        // キャッシュチェック
        var cacheKey = $"customer:{customerId}";
        if (_cache.TryGetValue(cacheKey, out CustomerData cachedData))
        {
            log.LogInformation("Customer data served from cache for ID: {CustomerId}", customerId);
            return new OkObjectResult(cachedData);
        }
        
        // データベースから取得
        using var dbContext = await _dbContextFactory.CreateDbContextAsync();
        
        var customer = await dbContext.Customers
            .AsNoTracking() // パフォーマンス向上
            .Include(c => c.Orders.Take(10)) // 最新10件のみ
            .FirstOrDefaultAsync(c => c.Id == customerId);
        
        if (customer == null)
        {
            return new NotFoundResult();
        }
        
        var customerData = new CustomerData
        {
            Id = customer.Id,
            Name = customer.Name,
            Email = customer.Email,
            RecentOrders = customer.Orders.Select(o => new OrderSummary
            {
                Id = o.Id,
                TotalAmount = o.TotalAmount,
                CreatedAt = o.CreatedAt
            }).ToList()
        };
        
        // キャッシュに保存(5分間)
        _cache.Set(cacheKey, customerData, TimeSpan.FromMinutes(5));
        
        log.LogInformation("Customer data retrieved from database for ID: {CustomerId}", customerId);
        return new OkObjectResult(customerData);
    }
    
    [FunctionName("BulkProcessOrders")]
    public async Task<IActionResult> BulkProcessOrders(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders/bulk")] HttpRequest req,
        ILogger log)
    {
        var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        var orderRequests = JsonSerializer.Deserialize<OrderRequest[]>(requestBody, JsonOptions);
        
        if (orderRequests?.Length > 100)
        {
            return new BadRequestObjectResult(new { Error = "Maximum 100 orders per batch" });
        }
        
        using var dbContext = await _dbContextFactory.CreateDbContextAsync();
        using var transaction = await dbContext.Database.BeginTransactionAsync();
        
        try
        {
            var results = new List<OrderResult>();
            
            // バッチ処理でパフォーマンス向上
            foreach (var batch in orderRequests.Chunk(10))
            {
                var batchTasks = batch.Select(async orderRequest =>
                {
                    try
                    {
                        var order = new Order
                        {
                            Id = Guid.NewGuid().ToString(),
                            CustomerId = orderRequest.CustomerId,
                            TotalAmount = orderRequest.TotalAmount,
                            CreatedAt = DateTime.UtcNow,
                            Status = OrderStatus.Created
                        };
                        
                        dbContext.Orders.Add(order);
                        
                        return new OrderResult
                        {
                            OrderId = order.Id,
                            Success = true
                        };
                    }
                    catch (Exception ex)
                    {
                        log.LogError(ex, "Failed to process order for customer: {CustomerId}", orderRequest.CustomerId);
                        return new OrderResult
                        {
                            OrderId = null,
                            Success = false,
                            Error = ex.Message
                        };
                    }
                });
                
                var batchResults = await Task.WhenAll(batchTasks);
                results.AddRange(batchResults);
                
                // バッチごとにコミット
                await dbContext.SaveChangesAsync();
            }
            
            await transaction.CommitAsync();
            
            var successCount = results.Count(r => r.Success);
            log.LogInformation("Bulk order processing completed. Success: {SuccessCount}, Failed: {FailedCount}", 
                successCount, results.Count - successCount);
            
            return new OkObjectResult(new
            {
                TotalProcessed = results.Count,
                SuccessCount = successCount,
                FailedCount = results.Count - successCount,
                Results = results
            });
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync();
            log.LogError(ex, "Bulk order processing failed");
            return new StatusCodeResult(500);
        }
    }
}

冷起動の最適化

// 冷起動対策の実装
public class WarmupFunction
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<WarmupFunction> _logger;
    
    public WarmupFunction(IServiceProvider serviceProvider, ILogger<WarmupFunction> logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
    }
    
    [FunctionName("WarmupTrigger")]
    public async Task<IActionResult> WarmupTrigger(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "warmup")] HttpRequest req,
        ILogger log)
    {
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            // 主要なサービスの初期化
            await WarmupServicesAsync();
            
            // データベース接続のウォームアップ
            await WarmupDatabaseAsync();
            
            // 外部APIの接続確認
            await WarmupExternalServicesAsync();
            
            stopwatch.Stop();
            
            _logger.LogInformation("Warmup completed successfully in {Duration}ms", stopwatch.ElapsedMilliseconds);
            
            return new OkObjectResult(new
            {
                Status = "Warmed up",
                Duration = stopwatch.ElapsedMilliseconds,
                Timestamp = DateTime.UtcNow
            });
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, "Warmup failed after {Duration}ms", stopwatch.ElapsedMilliseconds);
            
            return new StatusCodeResult(500);
        }
    }
    
    private async Task WarmupServicesAsync()
    {
        // 主要なサービスを解決してインスタンス化
        var services = new[]
        {
            typeof(IOrderService),
            typeof(ICustomerService),
            typeof(IPaymentService),
            typeof(INotificationService)
        };
        
        var tasks = services.Select(serviceType => Task.Run(() =>
        {
            try
            {
                _serviceProvider.GetService(serviceType);
                _logger.LogDebug("Service warmed up: {ServiceType}", serviceType.Name);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to warm up service: {ServiceType}", serviceType.Name);
            }
        }));
        
        await Task.WhenAll(tasks);
    }
    
    private async Task WarmupDatabaseAsync()
    {
        try
        {
            using var scope = _serviceProvider.CreateScope();
            var dbContextFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>();
            
            using var dbContext = await dbContextFactory.CreateDbContextAsync();
            
            // 軽量なクエリでDB接続をウォームアップ
            await dbContext.Database.ExecuteSqlRawAsync("SELECT 1");
            
            _logger.LogDebug("Database connection warmed up");
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to warm up database connection");
        }
    }
    
    private async Task WarmupExternalServicesAsync()
    {
        var httpClient = _serviceProvider.GetRequiredService<HttpClient>();
        
        var healthCheckTasks = new[]
        {
            CheckServiceHealthAsync(httpClient, "PaymentService", "https://payment-service.enhanced.com/health"),
            CheckServiceHealthAsync(httpClient, "InventoryService", "https://inventory-service.enhanced.com/health"),
            CheckServiceHealthAsync(httpClient, "NotificationService", "https://notification-service.enhanced.com/health")
        };
        
        await Task.WhenAll(healthCheckTasks);
    }
    
    private async Task CheckServiceHealthAsync(HttpClient httpClient, string serviceName, string healthUrl)
    {
        try
        {
            using var response = await httpClient.GetAsync(healthUrl);
            if (response.IsSuccessStatusCode)
            {
                _logger.LogDebug("External service health check passed: {ServiceName}", serviceName);
            }
            else
            {
                _logger.LogWarning("External service health check failed: {ServiceName}, Status: {StatusCode}", 
                    serviceName, response.StatusCode);
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to check external service health: {ServiceName}", serviceName);
        }
    }
}

監視とトラブルシューティング

Application Insights統合

// カスタムテレメトリーの実装
public class TelemetryService
{
    private readonly TelemetryClient _telemetryClient;
    
    public TelemetryService(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }
    
    public void TrackFunctionExecution(string functionName, TimeSpan duration, bool success, IDictionary<string, string> properties = null)
    {
        var telemetryProperties = new Dictionary<string, string>
        {
            ["FunctionName"] = functionName,
            ["Success"] = success.ToString(),
            ["ExecutionDuration"] = duration.TotalMilliseconds.ToString("F2")
        };
        
        if (properties != null)
        {
            foreach (var prop in properties)
            {
                telemetryProperties[prop.Key] = prop.Value;
            }
        }
        
        var metrics = new Dictionary<string, double>
        {
            ["ExecutionDuration"] = duration.TotalMilliseconds
        };
        
        _telemetryClient.TrackEvent("FunctionExecution", telemetryProperties, metrics);
        
        if (!success)
        {
            _telemetryClient.TrackMetric("FunctionFailure", 1, telemetryProperties);
        }
    }
    
    public void TrackBusinessMetric(string metricName, double value, IDictionary<string, string> properties = null)
    {
        _telemetryClient.TrackMetric(metricName, value, properties);
    }
    
    public void TrackDependencyCall(string dependencyName, string command, TimeSpan duration, bool success)
    {
        _telemetryClient.TrackDependency(dependencyName, command, DateTime.UtcNow.Subtract(duration), duration, success);
    }
}

// テレメトリー対応のベース関数クラス
public abstract class BaseTelemetryFunction
{
    protected readonly TelemetryService TelemetryService;
    protected readonly ILogger Logger;
    
    protected BaseTelemetryFunction(TelemetryService telemetryService, ILogger logger)
    {
        TelemetryService = telemetryService;
        Logger = logger;
    }
    
    protected async Task<T> ExecuteWithTelemetryAsync<T>(
        string functionName, 
        Func<Task<T>> operation,
        IDictionary<string, string> properties = null)
    {
        var stopwatch = Stopwatch.StartNew();
        var success = false;
        
        try
        {
            var result = await operation();
            success = true;
            return result;
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Function execution failed: {FunctionName}", functionName);
            throw;
        }
        finally
        {
            stopwatch.Stop();
            TelemetryService.TrackFunctionExecution(functionName, stopwatch.Elapsed, success, properties);
        }
    }
}

セキュリティベストプラクティス

認証・認可の実装

// JWT認証の実装
public class AuthenticatedFunction
{
    private readonly IJwtService _jwtService;
    private readonly IUserService _userService;
    
    [FunctionName("SecureProcessOrder")]
    public async Task<IActionResult> SecureProcessOrder(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "secure/orders")] HttpRequest req,
        ILogger log)
    {
        // JWT トークンの検証
        var authResult = await ValidateAuthenticationAsync(req);
        if (!authResult.IsValid)
        {
            return new UnauthorizedObjectResult(new { Error = "Invalid authentication" });
        }
        
        // 認可チェック
        if (!await _userService.HasPermissionAsync(authResult.UserId, "orders:create"))
        {
            return new ForbidResult("Insufficient permissions");
        }
        
        // ビジネスロジック実行
        try
        {
            var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var orderRequest = JsonSerializer.Deserialize<OrderRequest>(requestBody);
            
            // ユーザーコンテキストの設定
            orderRequest.CreatedBy = authResult.UserId;
            orderRequest.TenantId = authResult.TenantId;
            
            // 処理実行
            var result = await ProcessOrderSecurelyAsync(orderRequest, authResult);
            
            return new OkObjectResult(result);
        }
        catch (SecurityException ex)
        {
            log.LogWarning(ex, "Security violation detected for user: {UserId}", authResult.UserId);
            return new ForbidResult("Access denied");
        }
        catch (Exception ex)
        {
            log.LogError(ex, "Error processing secure order for user: {UserId}", authResult.UserId);
            return new StatusCodeResult(500);
        }
    }
    
    private async Task<AuthenticationResult> ValidateAuthenticationAsync(HttpRequest req)
    {
        var authHeader = req.Headers["Authorization"].FirstOrDefault();
        if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
        {
            return AuthenticationResult.Invalid();
        }
        
        var token = authHeader.Substring("Bearer ".Length);
        
        try
        {
            var claimsPrincipal = await _jwtService.ValidateTokenAsync(token);
            
            return new AuthenticationResult
            {
                IsValid = true,
                UserId = claimsPrincipal.FindFirst("sub")?.Value,
                TenantId = claimsPrincipal.FindFirst("tenant")?.Value,
                Roles = claimsPrincipal.FindAll("role").Select(c => c.Value).ToArray()
            };
        }
        catch (SecurityTokenException)
        {
            return AuthenticationResult.Invalid();
        }
    }
}

public class AuthenticationResult
{
    public bool IsValid { get; set; }
    public string UserId { get; set; }
    public string TenantId { get; set; }
    public string[] Roles { get; set; }
    
    public static AuthenticationResult Invalid() => new AuthenticationResult { IsValid = false };
}

コスト最適化

使用量とコストの監視

// コスト分析サービス
public class CostAnalysisFunction
{
    private readonly ICostManagementService _costService;
    private readonly INotificationService _notificationService;
    
    [FunctionName("AnalyzeFunctionCosts")]
    public async Task AnalyzeFunctionCosts(
        [TimerTrigger("0 0 8 * * MON")] TimerInfo timerInfo, // 毎週月曜日
        ILogger log)
    {
        var analysisResult = await _costService.AnalyzeFunctionCostsAsync(
            startDate: DateTime.UtcNow.AddDays(-7),
            endDate: DateTime.UtcNow);
        
        if (analysisResult.WeeklySpend > analysisResult.Budget * 0.8m)
        {
            await _notificationService.SendCostAlertAsync(analysisResult);
        }
        
        // 最適化提案の生成
        var recommendations = GenerateOptimizationRecommendations(analysisResult);
        
        if (recommendations.Any())
        {
            await _notificationService.SendOptimizationRecommendationsAsync(recommendations);
        }
        
        log.LogInformation("Cost analysis completed. Weekly spend: {WeeklySpend:C}, Budget: {Budget:C}", 
            analysisResult.WeeklySpend, analysisResult.Budget);
    }
    
    private List<OptimizationRecommendation> GenerateOptimizationRecommendations(CostAnalysisResult result)
    {
        var recommendations = new List<OptimizationRecommendation>();
        
        // 実行時間が長い関数の特定
        var slowFunctions = result.FunctionMetrics
            .Where(f => f.AverageExecutionTime > TimeSpan.FromMinutes(2))
            .ToList();
        
        if (slowFunctions.Any())
        {
            recommendations.Add(new OptimizationRecommendation
            {
                Type = "Performance",
                Description = "以下の関数の実行時間最適化を検討してください",
                Functions = slowFunctions.Select(f => f.FunctionName).ToList(),
                PotentialSaving = slowFunctions.Sum(f => f.EstimatedMonthlyCost * 0.3m)
            });
        }
        
        // メモリ使用量の最適化
        var overProvisionedFunctions = result.FunctionMetrics
            .Where(f => f.AverageMemoryUsage < f.AllocatedMemory * 0.5)
            .ToList();
        
        if (overProvisionedFunctions.Any())
        {
            recommendations.Add(new OptimizationRecommendation
            {
                Type = "Memory",
                Description = "以下の関数のメモリ割り当てを削減できます",
                Functions = overProvisionedFunctions.Select(f => f.FunctionName).ToList(),
                PotentialSaving = overProvisionedFunctions.Sum(f => f.EstimatedMonthlyCost * 0.2m)
            });
        }
        
        return recommendations;
    }
}

まとめ

Azure Functions を活用したサーバーレスアーキテクチャにより、スケーラブルで効率的なアプリケーションを構築できます。適切な設計パターンとベストプラクティスの適用により、エンタープライズレベルでの安定運用を実現できます。

導入効果:

  • インフラ管理工数 90% 削減
  • スケーリング自動化によるピーク負荷対応
  • 従量課金による 60% のコスト削減
  • 開発生産性 50% 向上

エンハンスド株式会社では、Azure Functions を活用したサーバーレスアーキテクチャの設計から実装まで、包括的な支援サービスを提供しています。

関連サービス:


著者: エンハンスドクラウドアーキテクチャチーム
カテゴリ: Azure
タグ: Azure, Functions, サーバーレス, イベントドリブン, 最適化