マイクロサービス設計パターン実践ガイド

エンタープライズレベルでの実装戦略とベストプラクティス

はじめに

マイクロサービスアーキテクチャは、大規模なエンタープライズアプリケーションの開発・運用において、柔軟性と拡張性を提供する重要な設計手法です。本記事では、実際の大規模プロジェクトでの経験を基に、マイクロサービス設計の核となるパターンと実装方法を詳しく解説します。

核となる設計パターン

1. API Gateway パターン

API Gatewayは、クライアントと複数のマイクロサービス間の単一エントリポイントとして機能します。

// ASP.NET Core + Ocelot での API Gateway 実装
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOcelot()
                .AddConsul()
                .AddPolly();
                
        services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", options =>
                {
                    options.Authority = "https://identity-service.company.com";
                    options.RequireHttpsMetadata = true;
                    options.Audience = "api-gateway";
                });
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseOcelot().Wait();
    }
}
// ocelot.json 設定例
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/users/{id}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "user-service",
          "Port": 443
        }
      ],
      "UpstreamPathTemplate": "/api/users/{id}",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer"
      },
      "RateLimitOptions": {
        "ClientWhitelist": [],
        "EnableRateLimiting": true,
        "Period": "1m",
        "PeriodTimespan": 60,
        "Limit": 100
      }
    }
  ]
}

2. Circuit Breaker パターン

// Polly を使用した Circuit Breaker 実装
public class OrderService
{
    private readonly HttpClient _httpClient;
    private readonly IAsyncPolicy<HttpResponseMessage> _circuitBreakerPolicy;
    
    public OrderService(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _circuitBreakerPolicy = Policy
            .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
            .CircuitBreakerAsync(
                handledEventsAllowedBeforeBreaking: 3,
                durationOfBreak: TimeSpan.FromSeconds(30),
                onBreak: (exception, duration) =>
                {
                    Console.WriteLine($"Circuit breaker opened for {duration}");
                },
                onReset: () =>
                {
                    Console.WriteLine("Circuit breaker closed");
                });
    }
    
    public async Task<Order> GetOrderAsync(int orderId)
    {
        try
        {
            var response = await _circuitBreakerPolicy.ExecuteAsync(async () =>
            {
                return await _httpClient.GetAsync($"/api/orders/{orderId}");
            });
            
            if (response.IsSuccessStatusCode)
            {
                var json = await response.Content.ReadAsStringAsync();
                return JsonSerializer.Deserialize<Order>(json);
            }
            
            return null;
        }
        catch (CircuitBreakerOpenException)
        {
            // フォールバック処理
            return await GetOrderFromCacheAsync(orderId);
        }
    }
}

3. Saga パターン(分散トランザクション)

// オーケストレーションベースのSaga実装
public class OrderSagaOrchestrator
{
    private readonly IPaymentService _paymentService;
    private readonly IInventoryService _inventoryService;
    private readonly IShippingService _shippingService;
    private readonly IEventBus _eventBus;
    
    public async Task<SagaResult> ProcessOrderAsync(OrderRequest orderRequest)
    {
        var sagaId = Guid.NewGuid();
        var saga = new OrderSaga(sagaId, orderRequest);
        
        try
        {
            // ステップ1: 在庫確保
            var inventoryResult = await _inventoryService.ReserveInventoryAsync(
                orderRequest.ProductId, orderRequest.Quantity);
            saga.AddCompensation(() => _inventoryService.ReleaseInventoryAsync(inventoryResult.ReservationId));
            
            // ステップ2: 支払い処理
            var paymentResult = await _paymentService.ProcessPaymentAsync(
                orderRequest.PaymentInfo, orderRequest.TotalAmount);
            saga.AddCompensation(() => _paymentService.RefundPaymentAsync(paymentResult.TransactionId));
            
            // ステップ3: 配送手配
            var shippingResult = await _shippingService.CreateShipmentAsync(
                orderRequest.ShippingAddress, inventoryResult.Items);
            saga.AddCompensation(() => _shippingService.CancelShipmentAsync(shippingResult.ShipmentId));
            
            // 成功時の最終処理
            await _eventBus.PublishAsync(new OrderCompletedEvent(sagaId, orderRequest.OrderId));
            
            return SagaResult.Success(sagaId);
        }
        catch (Exception ex)
        {
            // 補償トランザクション実行
            await saga.CompensateAsync();
            await _eventBus.PublishAsync(new OrderFailedEvent(sagaId, ex.Message));
            
            return SagaResult.Failure(sagaId, ex.Message);
        }
    }
}

public class OrderSaga
{
    private readonly List<Func<Task>> _compensations = new();
    
    public Guid SagaId { get; }
    public OrderRequest OrderRequest { get; }
    
    public OrderSaga(Guid sagaId, OrderRequest orderRequest)
    {
        SagaId = sagaId;
        OrderRequest = orderRequest;
    }
    
    public void AddCompensation(Func<Task> compensation)
    {
        _compensations.Add(compensation);
    }
    
    public async Task CompensateAsync()
    {
        // 逆順で補償処理を実行
        _compensations.Reverse();
        
        foreach (var compensation in _compensations)
        {
            try
            {
                await compensation();
            }
            catch (Exception ex)
            {
                // 補償処理の失敗をログに記録
                Console.WriteLine($"Compensation failed: {ex.Message}");
            }
        }
    }
}

4. Event Sourcing パターン

// Event Sourcing の実装例
public abstract class EventSourcedAggregate
{
    private readonly List<IDomainEvent> _uncommittedEvents = new();
    
    public Guid Id { get; protected set; }
    public int Version { get; private set; }
    
    protected void RaiseEvent(IDomainEvent domainEvent)
    {
        _uncommittedEvents.Add(domainEvent);
        ApplyEvent(domainEvent);
        Version++;
    }
    
    public IEnumerable<IDomainEvent> GetUncommittedEvents()
    {
        return _uncommittedEvents.AsReadOnly();
    }
    
    public void MarkEventsAsCommitted()
    {
        _uncommittedEvents.Clear();
    }
    
    protected abstract void ApplyEvent(IDomainEvent domainEvent);
    
    public void LoadFromHistory(IEnumerable<IDomainEvent> history)
    {
        foreach (var domainEvent in history)
        {
            ApplyEvent(domainEvent);
            Version++;
        }
    }
}

public class Order : EventSourcedAggregate
{
    public string CustomerName { get; private set; }
    public decimal TotalAmount { get; private set; }
    public OrderStatus Status { get; private set; }
    
    public Order() { } // For reconstruction
    
    public Order(Guid orderId, string customerName, decimal totalAmount)
    {
        RaiseEvent(new OrderCreatedEvent(orderId, customerName, totalAmount));
    }
    
    public void UpdateStatus(OrderStatus newStatus)
    {
        if (Status != newStatus)
        {
            RaiseEvent(new OrderStatusUpdatedEvent(Id, Status, newStatus));
        }
    }
    
    protected override void ApplyEvent(IDomainEvent domainEvent)
    {
        switch (domainEvent)
        {
            case OrderCreatedEvent orderCreated:
                Id = orderCreated.OrderId;
                CustomerName = orderCreated.CustomerName;
                TotalAmount = orderCreated.TotalAmount;
                Status = OrderStatus.Created;
                break;
                
            case OrderStatusUpdatedEvent statusUpdated:
                Status = statusUpdated.NewStatus;
                break;
        }
    }
}

サービス間通信パターン

1. 同期通信(HTTP/REST)

// Typed HttpClient with Polly
public interface IUserServiceClient
{
    Task<User> GetUserAsync(int userId);
    Task<User> CreateUserAsync(CreateUserRequest request);
}

public class UserServiceClient : IUserServiceClient
{
    private readonly HttpClient _httpClient;
    
    public UserServiceClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    
    public async Task<User> GetUserAsync(int userId)
    {
        var response = await _httpClient.GetAsync($"/api/users/{userId}");
        response.EnsureSuccessStatusCode();
        
        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<User>(json);
    }
    
    public async Task<User> CreateUserAsync(CreateUserRequest request)
    {
        var json = JsonSerializer.Serialize(request);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        var response = await _httpClient.PostAsync("/api/users", content);
        response.EnsureSuccessStatusCode();
        
        var responseJson = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<User>(responseJson);
    }
}

// DI 設定
services.AddHttpClient<IUserServiceClient, UserServiceClient>(client =>
{
    client.BaseAddress = new Uri("https://user-service.company.com");
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());

2. 非同期通信(Message Queue)

// Azure Service Bus による非同期メッセージング
public class EventBusService : IEventBus
{
    private readonly ServiceBusSender _sender;
    
    public EventBusService(ServiceBusClient serviceBusClient, string topicName)
    {
        _sender = serviceBusClient.CreateSender(topicName);
    }
    
    public async Task PublishAsync<T>(T eventData) where T : IDomainEvent
    {
        var json = JsonSerializer.Serialize(eventData);
        var message = new ServiceBusMessage(json)
        {
            MessageId = Guid.NewGuid().ToString(),
            ContentType = "application/json",
            Subject = typeof(T).Name
        };
        
        await _sender.SendMessageAsync(message);
    }
}

// イベントハンドラー
public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
    private readonly IInventoryService _inventoryService;
    private readonly IEmailService _emailService;
    
    public async Task HandleAsync(OrderCreatedEvent orderCreated)
    {
        // 在庫更新
        await _inventoryService.UpdateInventoryAsync(orderCreated.ProductId, -orderCreated.Quantity);
        
        // 確認メール送信
        await _emailService.SendOrderConfirmationAsync(orderCreated.CustomerEmail, orderCreated.OrderId);
    }
}

運用・監視パターン

1. 分散トレーシング

// OpenTelemetry による分散トレーシング
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOpenTelemetryTracing(builder =>
        {
            builder
                .SetSampler(new AlwaysOnSampler())
                .AddHttpClientInstrumentation()
                .AddAspNetCoreInstrumentation()
                .AddSqlClientInstrumentation()
                .AddJaegerExporter(options =>
                {
                    options.AgentHost = "jaeger";
                    options.AgentPort = 6831;
                });
        });
    }
}

// カスタムトレース実装
public class OrderService
{
    private static readonly ActivitySource ActivitySource = new("OrderService");
    
    public async Task<Order> ProcessOrderAsync(OrderRequest request)
    {
        using var activity = ActivitySource.StartActivity("ProcessOrder");
        activity?.SetTag("order.id", request.OrderId);
        activity?.SetTag("customer.id", request.CustomerId);
        
        try
        {
            var order = await CreateOrderAsync(request);
            activity?.SetTag("order.status", "created");
            
            await ProcessPaymentAsync(order);
            activity?.SetTag("payment.status", "completed");
            
            return order;
        }
        catch (Exception ex)
        {
            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            throw;
        }
    }
}

2. ヘルスチェック

// 包括的なヘルスチェック実装
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHealthChecks()
                .AddSqlServer(connectionString, name: "database")
                .AddAzureServiceBusTopic(serviceBusConnection, topicName, name: "servicebus")
                .AddUrlGroup(new Uri("https://external-api.com/health"), name: "external-api")
                .AddCheck<CustomBusinessLogicHealthCheck>("business-logic");
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHealthChecks("/health", new HealthCheckOptions()
        {
            ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
        });
        
        app.UseHealthChecks("/health/ready", new HealthCheckOptions()
        {
            Predicate = check => check.Tags.Contains("ready")
        });
        
        app.UseHealthChecks("/health/live", new HealthCheckOptions()
        {
            Predicate = _ => false
        });
    }
}

public class CustomBusinessLogicHealthCheck : IHealthCheck
{
    private readonly IOrderRepository _orderRepository;
    
    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        try
        {
            // ビジネスロジック固有のチェック
            var pendingOrdersCount = await _orderRepository.GetPendingOrdersCountAsync();
            
            if (pendingOrdersCount > 1000)
            {
                return HealthCheckResult.Degraded($"High pending orders count: {pendingOrdersCount}");
            }
            
            return HealthCheckResult.Healthy("Business logic is healthy");
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy("Business logic check failed", ex);
        }
    }
}

セキュリティパターン

1. OAuth 2.0 / OpenID Connect

// JWT Token 検証
public class JwtSecurityService
{
    private readonly IConfiguration _configuration;
    
    public async Task<ClaimsPrincipal> ValidateTokenAsync(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var validationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = await GetSigningKeyAsync(),
            ValidateIssuer = true,
            ValidIssuer = _configuration["Jwt:Issuer"],
            ValidateAudience = true,
            ValidAudience = _configuration["Jwt:Audience"],
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };
        
        var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
        return principal;
    }
}

まとめ

マイクロサービスアーキテクチャの成功には、適切な設計パターンの選択と実装が不可欠です。本記事で紹介したパターンを組み合わせることで、拡張性が高く、保守しやすいシステムを構築できます。

エンハンスド株式会社では、マイクロサービス移行戦略の策定から実装、運用まで、包括的な支援を提供しています。

関連サービス:


著者: エンハンスドアーキテクチャチーム
カテゴリ: アーキテクチャ
タグ: マイクロサービス, 設計パターン, .NET, Azure, 分散システム