Azure Functions サーバーレス開発ベストプラクティス - エンタープライズレベルでのスケーラブルなサーバーレスアーキテクチャ
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, サーバーレス, イベントドリブン, 最適化