マイクロサービス設計パターン実践ガイド - エンタープライズレベルでの実装戦略とベストプラクティス
マイクロサービス設計パターン実践ガイド
エンタープライズレベルでの実装戦略とベストプラクティス
はじめに
マイクロサービスアーキテクチャは、大規模なエンタープライズアプリケーションの開発・運用において、柔軟性と拡張性を提供する重要な設計手法です。本記事では、実際の大規模プロジェクトでの経験を基に、マイクロサービス設計の核となるパターンと実装方法を詳しく解説します。
核となる設計パターン
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, 分散システム