Domain Decomposition¶
Domain Decomposition, monolitik yapıyı doğru sınırlarla microservice’lere ayırmayı sağlar; yanlış ayrıştırma veri tutarsızlığına ve servisler arası aşırı bağımlılığa yol açar.
1. Teknik Katmana Göre Ayrıştırma¶
❌ Yanlış Kullanım: Servisleri teknik katmanlara göre bölmek.
// API Service - tüm controller'lar burada
// Business Service - tüm iş mantığı burada
// Data Service - tüm veri erişimi burada
// Her özellik değişikliği 3 servisi birden etkiler
✅ İdeal Kullanım: İş alanına (bounded context) göre ayrıştırın.
// Order Service - sipariş ile ilgili her şey
public class OrderService
{
// Controller, Business Logic, Data Access - tek servis içinde
[HttpPost("api/orders")]
public async Task<IActionResult> Create(CreateOrderDto dto) { /* ... */ }
}
// Payment Service - ödeme ile ilgili her şey
// Inventory Service - stok ile ilgili her şey
// Shipping Service - kargo ile ilgili her şey
2. Shared Database Kullanmak¶
❌ Yanlış Kullanım: Birden fazla servisin aynı veritabanını paylaşması.
// Order Service
public class OrderRepository
{
public async Task CreateAsync(Order order)
{
await _sharedDb.Orders.AddAsync(order);
var product = await _sharedDb.Products.FindAsync(order.ProductId); // Products tablosu Inventory'nin!
product.Stock -= order.Quantity;
await _sharedDb.SaveChangesAsync();
}
}
✅ İdeal Kullanım: Her servisin kendi veritabanı olsun, event ile senkronize edilsin.
// Order Service - kendi DB'si
public class OrderRepository
{
private readonly OrderDbContext _context; // Sadece Orders tablosu
public async Task CreateAsync(Order order)
{
await _context.Orders.AddAsync(order);
await _context.SaveChangesAsync();
}
}
// Inventory Service - kendi DB'si, event ile dinler
public class OrderCreatedHandler : INotificationHandler<OrderCreatedEvent>
{
private readonly InventoryDbContext _context;
public async Task Handle(OrderCreatedEvent notification, CancellationToken ct)
{
var product = await _context.Products.FindAsync(notification.ProductId);
product.ReserveStock(notification.Quantity);
await _context.SaveChangesAsync(ct);
}
}
3. Bounded Context Sınırlarını Yanlış Belirlemek¶
❌ Yanlış Kullanım: Çok ince granülaritede servisler oluşturmak.
// Ayrı servis: AddressService
// Ayrı servis: PhoneService
// Ayrı servis: EmailService
// Ayrı servis: CustomerProfileService
// Bir müşteri bilgisi güncellemek için 4 servise çağrı gerekir
✅ İdeal Kullanım: İlişkili kavramları aynı bounded context’te tutun.
// Customer Service - müşteri ile ilgili tüm kavramlar burada
public class Customer
{
public int Id { get; private set; }
public string Name { get; private set; }
public Address Address { get; private set; } // Value Object
public PhoneNumber Phone { get; private set; } // Value Object
public Email Email { get; private set; } // Value Object
public void UpdateProfile(string name, Address address, PhoneNumber phone, Email email)
{
Name = name;
Address = address;
Phone = phone;
Email = email;
AddDomainEvent(new CustomerProfileUpdated(Id));
}
}
4. Shared Kernel’ı Yanlış Kullanmak¶
❌ Yanlış Kullanım: Büyük shared library ile servisler arası sıkı bağlılık.
// SharedLibrary.dll - tüm servisler buna bağımlı
public class Order { /* ... */ }
public class Customer { /* ... */ }
public class Product { /* ... */ }
public class Payment { /* ... */ }
public static class Utils { /* ... */ }
public static class Constants { /* ... */ }
// Bir değişiklik tüm servislerin deploy edilmesini gerektirir
✅ İdeal Kullanım: Shared Kernel’ı minimal tutun, sadece ortak kontratları paylaşın.
// SharedKernel NuGet paketi - minimal
public interface IDomainEvent
{
Guid EventId { get; }
DateTime OccurredAt { get; }
}
public abstract class Entity
{
public int Id { get; protected set; }
}
public abstract class ValueObject
{
protected abstract IEnumerable<object> GetEqualityComponents();
}
// Integration Events - servisler arası kontratlar
public record OrderCreatedIntegrationEvent(int OrderId, int CustomerId, decimal Total) : IDomainEvent
{
public Guid EventId { get; } = Guid.NewGuid();
public DateTime OccurredAt { get; } = DateTime.UtcNow;
}
5. Anti-Corruption Layer Kullanmamak¶
❌ Yanlış Kullanım: Harici servisin modelini doğrudan kullanmak.
public class OrderService
{
public async Task CreateAsync(ExternalOrderDto externalOrder)
{
// Harici sistemin modeli doğrudan kullanılıyor
var order = new Order
{
Id = externalOrder.order_id, // snake_case
Total = externalOrder.total_amount, // Farklı alan adı
Status = MapStatus(externalOrder.sts) // Kısaltılmış alan
};
}
}
✅ İdeal Kullanım: Anti-Corruption Layer ile harici modeli kendi modeline dönüştürün.
public interface IExternalOrderAdapter
{
Task<Order> TranslateAsync(ExternalOrderDto externalOrder);
}
public class ExternalOrderAdapter : IExternalOrderAdapter
{
public Task<Order> TranslateAsync(ExternalOrderDto externalOrder)
{
var order = Order.Create(
customerId: externalOrder.customer_ref,
total: new Money(externalOrder.total_amount, "TRY"),
status: MapStatus(externalOrder.sts)
);
return Task.FromResult(order);
}
private OrderStatus MapStatus(string externalStatus) => externalStatus switch
{
"NEW" => OrderStatus.Pending,
"PAID" => OrderStatus.Confirmed,
"SENT" => OrderStatus.Shipped,
_ => OrderStatus.Unknown
};
}
public class OrderService
{
private readonly IExternalOrderAdapter _adapter;
public async Task ImportAsync(ExternalOrderDto externalOrder)
{
var order = await _adapter.TranslateAsync(externalOrder);
await _repository.AddAsync(order);
}
}