
Domain-Driven Design (DDD): İş Mantığı Etrafında Yazılım Geliştirme
Domain-Driven Design, genellikle DDD olarak bilinen, yazılım tasarımında iş alanını yani domain’i merkeze alan bir yaklaşımdır. Bu yaklaşımda yazılım geliştirmeye veritabanı tablolarından, route yapısından, controller dosyalarından veya framework klasörlerinden başlanmaz. Önce gerçek iş problemi anlaşılır: siparişler, ödemeler, faturalar, kullanıcılar, abonelikler, gönderiler, onay süreçleri, iş politikaları ve kurallar.
DDD’nin temel fikri basit ama çok güçlüdür: yazılım, işin konuştuğu dili konuşmalıdır. Kod, iş tarafının kullandığı kavramlarla aynı dili kullandığında sistem daha anlaşılır, daha sürdürülebilir ve zamanla değişime daha dayanıklı hale gelir.
Domain-Driven Design Nedir?
Domain-Driven Design, karmaşık iş mantığını doğrudan uygulama modelinin içine yerleştirmeye odaklanan bir yazılım tasarım yaklaşımıdır. Eric Evans tarafından popüler hale getirilmiştir ve özellikle basit veri saklama işlemlerinden çok iş kurallarının önemli olduğu sistemlerde büyük değer taşır.
DDD yalnızca bir klasör yapısı değildir. Sadece Domain, Application ve Infrastructure isimli klasörler oluşturmak, gerçek anlamda DDD uyguladığınız anlamına gelmez. DDD’nin asıl amacı, kodun iş gerçekliğini mümkün olduğunca açık ve doğru şekilde temsil etmesidir.
Örneğin basit bir CRUD sisteminde orders adında bir tablo ve sipariş durumunu güncelleyen bir controller metodu olabilir. Ancak iş odaklı bir sistemde sipariş durumunu değiştirmek birçok kurala bağlı olabilir:
Ödeme onaylanmadan sipariş gönderilemez.
Gönderilmiş bir sipariş iptal edilemez.
İade yalnızca ödeme tahsil edildiyse oluşturulabilir.
Premium müşterilerin iptal kuralları farklı olabilir.
Başarılı ödeme sonrasında fatura oluşturulmalıdır.
Bu kurallar controller’lara, job’lara, observer’lara, helper fonksiyonlara veya database trigger’larına dağılırsa sistem zamanla zor yönetilir hale gelir. DDD, bu kuralları anlamlı iş modellerinin içine yerleştirmeyi hedefler.
DDD Hangi Problemi Çözer?
Birçok uygulama başlangıçta temiz görünür fakat iş mantığı büyüdükçe bakım maliyeti artar. Geliştiriciler ilk aşamada mantığı controller içinde, model içinde, service sınıflarında veya bazen doğrudan view tarafında yazabilir. Küçük projelerde bu yaklaşım çalışabilir. Ancak sistem büyüdüğünde tehlikeli hale gelir.
Yaygın problem şudur: Kod teknik olarak düzenli görünür ama iş açısından anlaşılmaz hale gelir. Controller, model, migration, request, resource ve job dosyaları vardır; fakat gerçek iş kuralının nerede yaşadığını kimse net söyleyemez.
Örneğin:
public function cancel($id)
{
$order = Order::findOrFail($id);
if ($order->status === 'shipped') {
return response()->json(['message' => 'Cannot cancel shipped order'], 422);
}
if ($order->payment_status === 'captured') {
Refund::create([
'order_id' => $order->id,
'amount' => $order->total,
]);
}
$order->status = 'cancelled';
$order->save();
return response()->json(['message' => 'Order cancelled']);
}Bu kod çalışabilir, fakat iş kuralı controller içine hapsolmuştur. Aynı iptal mantığı admin panelinden, API endpoint’inden, command sınıfından, job’dan veya zamanlanmış bir görevden çağrılmak istenirse kural tekrar tekrar yazılabilir. Daha sonra iş tarafı iptal politikasını değiştirdiğinde bir yer güncellenir, başka bir yer unutulur.
DDD bu problemi iş kuralını domain modelinin içine taşıyarak çözer.
DDD İş Dili Üzerine Kurulur
DDD’nin en önemli kavramlarından biri Ubiquitous Language yani ortak iş dilidir. Bu, geliştiricilerin ve iş tarafındaki kişilerin sistemi konuşurken aynı kavramları kullanması anlamına gelir.
İş tarafı “müşteri” diyorsa, kod içinde aynı kavram için rastgele “user”, “client”, “account” ve “profile” isimleri kullanılmamalıdır. İş tarafı “abonelik yenileme” diyorsa, bu davranış updateStatus() gibi genel ve belirsiz bir metodun içine saklanmamalıdır.
İyi bir DDD kodu iş davranışlarını görünür hale getirir:
$order->cancel();
$invoice->markAsPaid();
$subscription->renew();
$shipment->dispatch();
$payment->capture();
$customer->upgradeToPremium();Bu tarz kod daha okunabilirdir çünkü teknik veri işlemlerinden önce gerçek iş aksiyonlarını anlatır.
Domain, Subdomain ve İş Bağlamı
Domain, yazılımın çözmek için inşa edildiği iş alanıdır. Bir e-ticaret uygulaması için domain, online ürün satışı olabilir. Bir hastane sistemi için domain; randevular, hastalar, doktorlar, reçeteler ve faturalandırma olabilir. Bir eğitim platformunda domain; kurslar, dersler, öğrenciler, sertifikalar ve abonelikler olabilir.
Büyük domain’ler genellikle daha küçük subdomain yapılarına ayrılır. Her subdomain işin belirli bir parçasını temsil eder.
E-ticaret sistemi için örnek:
Katalog: ürünler, kategoriler, fiyatlar, stok durumu.
Sipariş: sepetler, siparişler, sipariş kalemleri, sipariş durumu.
Ödeme: ödeme yetkilendirme, tahsilat, iade.
Kargo: gönderi oluşturma, takip, teslimat.
Müşteri Destek: destek talepleri, şikayetler, iadeler.
DDD, tüm bu kavramların her şeyi bilen tek bir dev model içine karışmasını engeller. Böylece her alan kendi iş anlamı içinde tasarlanır.
Bounded Context: DDD’nin En Kritik Stratejik Kavramı
Bounded Context, belirli bir iş modelinin geçerli olduğu açık sınırdır. Aynı kelime farklı iş bölümlerinde farklı anlamlara gelebilir.
Örneğin Customer kelimesi farklı bağlamlarda farklı anlamlar taşıyabilir:
Satış bağlamında müşteri, ürün satın alan kişidir.
Destek bağlamında müşteri, destek talebi açan kişidir.
Faturalandırma bağlamında müşteri, faturaları ve ödeme yöntemleri olan kişidir.
Tüm bağlamlar için tek ve evrensel bir Customer modeli oluşturmaya çalışmak genellikle şişmiş ve karmaşık bir nesne üretir. DDD, modellerin bağlama göre ayrılmasını önerir.
Bunun yerine:
App\Models\CustomerŞöyle bir ayrım yapılabilir:
App\Domain\Sales\Models\Customer
App\Domain\Support\Models\Customer
App\Domain\Billing\Models\CustomerBu her zaman ayrı veritabanı tabloları kullanmanız gerektiği anlamına gelmez. Asıl anlamı şudur: kod modeli hangi iş bağlamına ait olduğunu açıkça göstermelidir.
DDD’de Entity Nedir?
Entity, benzersiz bir kimliği olan ve özellikleri değişse bile aynı varlık olarak yaşamaya devam eden nesnedir.
Örneğin sipariş bir entity’dir. Durumu pending, paid veya shipped olabilir; fakat aynı kimliğe sahip olduğu sürece aynı sipariştir.
final class Order
{
private string $id;
private string $status;
private float $total;
public function __construct(string $id, float $total)
{
$this->id = $id;
$this->status = 'pending';
$this->total = $total;
}
public function pay(): void
{
if ($this->status !== 'pending') {
throw new DomainException('Only pending orders can be paid.');
}
$this->status = 'paid';
}
public function cancel(): void
{
if ($this->status === 'shipped') {
throw new DomainException('Shipped orders cannot be cancelled.');
}
$this->status = 'cancelled';
}
public function status(): string
{
return $this->status;
}
}Buradaki önemli nokta entity’nin kendi kurallarını korumasıdır. Sistemin herhangi bir yerinden status değerini doğrudan değiştirmek yerine, entity pay() ve cancel() gibi anlamlı iş metotları sunar.
DDD’de Value Object Nedir?
Value Object, kimliğiyle değil değeriyle tanımlanan nesnedir. ID’ye ihtiyaç duymaz. İki value object aynı değerlere sahipse aynı kabul edilir.
Value object örnekleri:
E-posta adresi
Para
Adres
Tarih aralığı
Yüzde
Koordinat
Ham string ve number değerleri her yere taşımak yerine value object kullanmak kodu daha güvenli ve anlaşılır hale getirir.
final class Money
{
public function __construct(
private readonly int $amountInCents,
private readonly string $currency
) {
if ($amountInCents < 0) {
throw new InvalidArgumentException('Money amount cannot be negative.');
}
if ($currency === '') {
throw new InvalidArgumentException('Currency is required.');
}
}
public function amountInCents(): int
{
return $this->amountInCents;
}
public function currency(): string
{
return $this->currency;
}
public function add(Money $money): Money
{
if ($this->currency !== $money->currency) {
throw new DomainException('Cannot add money with different currencies.');
}
return new Money(
$this->amountInCents + $money->amountInCents,
$this->currency
);
}
}Value object kullanılmadığında para bir yerde float, başka bir yerde integer, başka bir yerde string olarak temsil edilebilir. Bu da hatalara yol açar. Value object, kavrama net bir şekil verir.
Aggregate ve Aggregate Root
Aggregate, birlikte tutarlı kalması gereken ilişkili nesneler grubudur. Aggregate Root ise bu gruba dışarıdan erişimi kontrol eden ana nesnedir.
Örneğin bir sipariş, sipariş kalemlerinden oluşabilir. Sipariş aggregate root’tur, sipariş kalemleri ise sipariş aggregate’inin içinde yer alır.
Genellikle dışarıdan doğrudan sipariş kalemleri değiştirilmemelidir. Kalemlerin nasıl ekleneceğine, silineceğine veya değiştirileceğine sipariş karar vermelidir.
final class Order
{
private array $items = [];
public function addItem(string $productId, int $quantity, Money $price): void
{
if ($this->status !== 'pending') {
throw new DomainException('Cannot add items to a non-pending order.');
}
if ($quantity <= 0) {
throw new DomainException('Quantity must be greater than zero.');
}
$this->items[] = new OrderItem($productId, $quantity, $price);
}
public function total(): Money
{
$total = new Money(0, 'USD');
foreach ($this->items as $item) {
$total = $total->add($item->subtotal());
}
return $total;
}
}Bu yapı iş tutarlılığını korur. Sipariş, ürün eklenip eklenemeyeceğine karar verir. Toplam tutarı kendisi hesaplar. Kendi içindeki kuralları kendisi yönetir.
DDD’de Repository
Repository, domain nesnelerini getirmek ve kaydetmekten sorumludur. DDD’de repository, rastgele veritabanı sorgu yardımcıları gibi değil, domain nesnelerinin koleksiyonu gibi düşünülmelidir.
Domain layer, verinin MySQL’den mi, PostgreSQL’den mi, MongoDB’den mi, Redis’ten mi yoksa harici bir API’den mi geldiğini bilmemelidir. Bunlar altyapı detaylarıdır.
Bir repository interface’i şöyle olabilir:
interface OrderRepository
{
public function findById(string $id): ?Order;
public function save(Order $order): void;
}Gerçek implementasyon Laravel Eloquent kullanabilir, fakat domain doğrudan Eloquent’e bağımlı olmamalıdır.
final class EloquentOrderRepository implements OrderRepository
{
public function findById(string $id): ?Order
{
$record = OrderModel::with('items')->find($id);
if ($record === null) {
return null;
}
return OrderMapper::toDomain($record);
}
public function save(Order $order): void
{
$record = OrderMapper::toEloquent($order);
$record->save();
}
}Bu ayrım iş mantığını test etmeyi kolaylaştırır ve domain’i framework detaylarından uzak tutar.
Domain Service
Domain Service, doğal olarak tek bir entity veya value object içine ait olmayan iş mantığını taşır.
Örneğin kargo ücreti hesaplamak; sipariş ağırlığına, müşteri adresine, teslimat yöntemine ve kargo bölgelerine bağlı olabilir. Bu mantık tamamen Order veya Customer içine ait değilse domain service içinde yer alabilir.
final class ShippingCostCalculator
{
public function calculate(Order $order, Address $address): Money
{
if ($address->country() === 'TR') {
return new Money(5000, 'TRY');
}
if ($order->totalWeight() > 10000) {
return new Money(2500, 'USD');
}
return new Money(1500, 'USD');
}
}Domain service yine de iş mantığını temsil etmelidir. E-posta göndermek, log yazmak veya API çağırmak gibi teknik işlemler domain service’in görevi değildir. Bunlar infrastructure layer’a aittir.
Application Service
Application Service, bir use case’i koordine eder. Derin iş kurallarını içermez. Input alır, domain nesnelerini yükler, domain davranışlarını çağırır, değişiklikleri kaydeder ve gerekirse dış sistemleri tetikler.
Örnek:
final class CancelOrderService
{
public function __construct(
private readonly OrderRepository $orders
) {
}
public function handle(string $orderId): void
{
$order = $this->orders->findById($orderId);
if ($order === null) {
throw new RuntimeException('Order not found.');
}
$order->cancel();
$this->orders->save($order);
}
}Application service, siparişin iptal edilip edilemeyeceğine karar vermez. Bu kural domain modeline aittir. Service yalnızca süreci koordine eder.
Domain Events
Domain Event, iş açısından önemli bir şeyin gerçekleştiğini ifade eder.
Örnekler:
OrderWasPlaced
PaymentWasCaptured
InvoiceWasGenerated
SubscriptionWasRenewed
CustomerWasUpgraded
Domain event’ler sistemi daha anlaşılır hale getirir. Her yan etkiyi aynı metodun içine koymak yerine domain, önemli bir olayın gerçekleştiğini kaydedebilir. Sistemin başka parçaları bu olaya tepki verebilir.
final class OrderWasPlaced
{
public function __construct(
public readonly string $orderId,
public readonly string $customerId
) {
}
}Aggregate içinde:
final class Order
{
private array $events = [];
public function place(): void
{
if (empty($this->items)) {
throw new DomainException('Cannot place an empty order.');
}
$this->status = 'placed';
$this->events[] = new OrderWasPlaced(
$this->id,
$this->customerId
);
}
public function releaseEvents(): array
{
$events = $this->events;
$this->events = [];
return $events;
}
}Sipariş kaydedildikten sonra application layer bu event’leri yayınlayabilir. Bir listener e-posta gönderebilir, fatura oluşturabilir, depoyu bilgilendirebilir veya analytics sistemini güncelleyebilir.
DDD Katmanları
Yaygın DDD yapısı sistemi katmanlara ayırır. Klasör isimleri projeye göre değişebilir, fakat fikir aynıdır.
Domain Layer: entity, value object, aggregate, domain service ve domain event’leri içerir.
Application Layer: use case, command, handler ve application service sınıflarını içerir.
Infrastructure Layer: database implementasyonları, harici API’ler, queue, mail, storage ve framework detaylarını içerir.
Interface Layer: controller, API resource, request, CLI command ve UI adapter yapılarını içerir.
En önemli kural bağımlılık yönüdür. Domain; Laravel’e, Eloquent’e, HTTP request’lerine, queue sistemine veya veritabanına bağımlı olmamalıdır. Domain mümkün olduğunca saf iş mantığı içermelidir.
Laravel İçin Örnek DDD Klasör Yapısı
Laravel sizi DDD kullanmaya zorlamaz. Fakat Laravel uygulamasını DDD’ye uygun şekilde organize etmek mümkündür.
app/
├── Domain/
│ └── Orders/
│ ├── Entities/
│ │ ├── Order.php
│ │ └── OrderItem.php
│ ├── ValueObjects/
│ │ ├── Money.php
│ │ └── OrderStatus.php
│ ├── Events/
│ │ └── OrderWasPlaced.php
│ ├── Repositories/
│ │ └── OrderRepository.php
│ └── Services/
│ └── ShippingCostCalculator.php
│
├── Application/
│ └── Orders/
│ ├── Commands/
│ │ └── PlaceOrderCommand.php
│ └── Services/
│ └── PlaceOrderService.php
│
├── Infrastructure/
│ └── Persistence/
│ └── Eloquent/
│ ├── Models/
│ │ └── OrderModel.php
│ ├── Mappers/
│ │ └── OrderMapper.php
│ └── Repositories/
│ └── EloquentOrderRepository.php
│
└── Http/
└── Controllers/
└── OrderController.phpBu yapı ciddi iş kuralları olan projelerde faydalıdır. Çok küçük CRUD projelerinde fazla gelebilir. DDD karmaşıklığı çözmek için kullanılmalıdır, gereksiz tören üretmek için değil.
DDD ile Controller Örneği
DDD tarzı Laravel uygulamasında controller ince olmalıdır. Request alır, input’u doğrular, application service çağırır ve response döner.
final class OrderController
{
public function __construct(
private readonly PlaceOrderService $placeOrderService
) {
}
public function store(PlaceOrderRequest $request): JsonResponse
{
$command = new PlaceOrderCommand(
customerId: $request->input('customer_id'),
items: $request->input('items')
);
$orderId = $this->placeOrderService->handle($command);
return response()->json([
'order_id' => $orderId,
'message' => 'Order placed successfully.',
], 201);
}
}Controller sipariş toplamının nasıl hesaplandığını bilmez. Ödeme kurallarını bilmez. Sipariş durumunu doğrudan değiştirmez. Sadece use case’i çağırır.
Application Service Örneği
final class PlaceOrderService
{
public function __construct(
private readonly OrderRepository $orders,
private readonly ProductCatalog $products
) {
}
public function handle(PlaceOrderCommand $command): string
{
$order = Order::createForCustomer($command->customerId);
foreach ($command->items as $item) {
$product = $this->products->findById($item['product_id']);
if ($product === null) {
throw new RuntimeException('Product not found.');
}
$order->addItem(
productId: $product->id(),
quantity: $item['quantity'],
price: $product->price()
);
}
$order->place();
$this->orders->save($order);
return $order->id();
}
}Application service use case’i koordine eder. Sipariş entity’si ise önemli iş kurallarını korur: ürün eklenebilir mi, sipariş verilebilir mi, toplam nasıl hesaplanır.
DDD ve Geleneksel CRUD Arasındaki Fark
CRUD; kayıt oluşturma, okuma, güncelleme ve silme işlemlerine odaklanır. Basit admin panelleri, küçük dashboard’lar ve temel içerik yönetim sistemleri için yeterlidir.
DDD ise davranış, kural, workflow ve iş kararı olan sistemlerde kullanışlıdır. DDD’de soru yalnızca “Bu satırı nasıl güncelleriz?” değildir. Daha doğru soru şudur: “Hangi iş aksiyonu gerçekleşiyor ve hangi kurallar korunmalı?”
| CRUD Düşüncesi | DDD Düşüncesi |
|---|---|
| Sipariş durumunu güncelle | Siparişi iptal et, gönder, öde |
| Ödeme kaydı oluştur | Ödemeyi tahsil et veya iade et |
| Abonelik bilgisini düzenle | Aboneliği yenile, duraklat, yükselt veya iptal et |
| Fatura alanını değiştir | Fatura kes, ödendi olarak işaretle, iptal et |
DDD, iş aksiyonlarını açık şekilde modellemeyi teşvik eder.
DDD Ne Zaman Kullanılmalı?
DDD, iş mantığı basit CRUD yapısını aşacak kadar karmaşık olduğunda faydalıdır.
DDD kullanmayı düşünmeniz gereken durumlar:
Sistemde çok sayıda iş kuralı varsa.
Farklı ekipler aynı kavramları farklı anlamlarda kullanıyorsa.
İş mantığı controller, job ve service sınıflarına dağılmışsa.
Bir özelliği değiştirmek başka bir özelliği sık sık bozuyorsa.
Sistemde sipariş, ödeme, onay, fatura, abonelik veya lojistik gibi workflow’lar varsa.
İş davranışları için güçlü testler yazmak gerekiyorsa.
Projenin yıllarca büyümesi bekleniyorsa.
DDD her proje için gerekli değildir. Basit bir blog, landing page veya küçük CRUD admin paneli geliştiriyorsanız klasik Laravel yapısı yeterli olabilir.
DDD Ne Zaman Fazla Gelir?
DDD, gerçek iş karmaşıklığı olmadan mekanik şekilde uygulanırsa zararlı hale gelebilir. Çok fazla layer, interface, factory, command, handler ve mapper oluşturmak, proje buna ihtiyaç duymuyorsa geliştirmeyi yavaşlatır.
Kötü DDD genellikle şöyle görünür:
Her basit model için gereksiz interface yazılır.
Her işlem için çok fazla sınıf oluşturulur.
Domain layer içinde gerçek iş mantığı yoktur.
Klasör yapısı gelişmiş görünür ama kod hâlâ sadece CRUD’dur.
Geliştiriciler iş problemini çözmekten çok dosyalar arasında gezinir.
DDD iş karmaşıklığını azaltmalıdır. Sadece teknik karmaşıklık ekliyorsa yanlış uygulanıyor demektir.
DDD’de Yaygın Hatalar
En yaygın hatalardan biri DDD’yi yalnızca klasör mimarisi sanmaktır. Dosyaları Domain ve Application klasörlerine taşımak tek başına iyi domain tasarımı oluşturmaz. Önemli olan iş kurallarının nerede yaşadığı ve kodun işi ne kadar açık ifade ettiğidir.
Bir diğer hata entity’leri çok boş bırakmaktır. Entity yalnızca getter ve setter içeriyorsa iş davranışını korumuyordur.
// Zayıf model
$order->setStatus('cancelled');
// Daha iyi model
$order->cancel();İkinci versiyon daha iyidir çünkü siparişin iptal edilip edilemeyeceğini kontrol etmesine izin verir.
Üçüncü hata tüm mantığı service sınıflarına koymaktır. Bu, anemic domain model problemine yol açar. Entity’ler pasif veri taşıyıcılara dönüşür, service sınıfları ise büyük prosedürel script’ler haline gelir.
Dördüncü hata infrastructure detaylarını domain mantığıyla karıştırmaktır. Bir entity e-posta gönderiyorsa, log yazıyorsa, Laravel facade kullanıyorsa veya dış API çağırıyorsa domain teknik detaylara bağımlı hale gelir.
DDD ve Test Edilebilirlik
DDD’nin en büyük avantajlarından biri daha iyi test edilebilirliktir. Domain mantığı controller ve veritabanından ayrıldığı için iş kuralları doğrudan test edilebilir.
public function test_shipped_order_cannot_be_cancelled(): void
{
$order = Order::createForCustomer('customer-1');
$order->addItem('product-1', 1, new Money(1000, 'USD'));
$order->place();
$order->markAsShipped();
$this->expectException(DomainException::class);
$order->cancel();
}Bu test HTTP, migration, factory, middleware veya API request gerektirmez. Doğrudan iş kuralını test eder.
Gerçek Projelerde DDD
Gerçek projelerde DDD çoğu zaman kademeli olarak uygulanır. Tüm sistemi bir anda yeniden yazmanız gerekmez. Pratik yaklaşım, en karmaşık iş alanından başlamaktır.
Örneğin bir e-ticaret sisteminde ürün katalog kısmı basit CRUD olarak kalabilir. Sipariş ve ödeme modülleri ise daha fazla kural içerdiği için DDD ile tasarlanabilir.
Gerçekçi bir geçiş planı şöyle olabilir:
En karmaşık iş workflow’unu belirleyin.
Ekibin kullandığı gerçek iş dilini yazın.
Entity, value object, aggregate ve domain event kavramlarını çıkarın.
İş kurallarını controller dışına taşıyın.
Önemli use case’ler için application service oluşturun.
Persistence ayrımı gerçekten faydalı olduğunda repository kullanın.
Domain davranışları için test yazın.
Her şeyi yeniden yazmak yerine kademeli refactor yapın.
DDD ve Microservices
DDD çoğu zaman microservices ile birlikte anılır, fakat aynı şey değildir. DDD iş sınırlarını anlamanıza yardım eder. Microservices ise dağıtım ve mimari stilidir.
Bir bounded context microservice’e dönüşebilir, fakat dönüşmek zorunda değildir. Birçok sistemde önce modular monolith ile başlamak daha doğrudur. Bağlamları tek codebase içinde net şekilde ayırabilirsiniz. Daha sonra gerçek ihtiyaç oluşursa bazı context’ler ayrı servislere çıkarılabilir.
Net domain sınırları olan iyi tasarlanmış bir modular monolith, iş sınırları belirsiz dağınık bir microservices sisteminden çoğu zaman daha iyidir.
Anti-Corruption Layer
Anti-Corruption Layer, domain modelinizi dış sistemlerden veya legacy koddan korur. Dış sistemden gelen veriyi domain’in anlayacağı kavramlara çevirir.
Örneğin bir ödeme sağlayıcısı şu status değerlerini döndürebilir:
AUTH_OKCAPTURED_FULLREVERSAL_DONE
Domain tarafında ise daha anlaşılır terimler kullanılabilir:
authorizedcapturedrefunded
Anti-corruption layer, dış sistem terminolojisinin iş modelinize sızmasını engeller.
final class PaymentStatusTranslator
{
public function translate(string $providerStatus): PaymentStatus
{
return match ($providerStatus) {
'AUTH_OK' => PaymentStatus::authorized(),
'CAPTURED_FULL' => PaymentStatus::captured(),
'REVERSAL_DONE' => PaymentStatus::refunded(),
default => throw new DomainException('Unknown payment status.'),
};
}
}DDD Uygulama Kontrol Listesi
DDD uygulamadan önce şu soruları sorun:
Çekirdek iş problemi nedir?
İş tarafı hangi kelimeleri kullanıyor?
Hangi kavramların kimliği vardır?
Hangi kavramlar value object olabilir?
Hangi nesneler birlikte tutarlı kalmalıdır?
Her iş kuralı nerede yaşamalıdır?
Hangi aksiyonlar açık metotları hak eder?
İş açısından önemli olaylar nelerdir?
Hangi parçalar domain logic, hangileri infrastructure detayıdır?
İş kuralları HTTP ve database kurulumu olmadan test edilebiliyor mu?
Hatırlanması Gereken Basit Kural
Kod, işin ne demek istediğini söylüyorsa doğru yoldasınız.
Bunun yerine:
$order->status = 'cancelled';
$order->save();Bunu tercih edin:
$order->cancel();
$orderRepository->save($order);İkinci versiyon domain modeline kendi kurallarını koruma şansı verir. DDD’nin kalbi budur.
Sonuç
Domain-Driven Design yazılımı daha karmaşık göstermek için kullanılan bir yöntem değildir. Amaç, iş karmaşıklığını görünür, düzenli ve korunabilir hale getirmektir. Doğru kullanıldığında DDD, geliştiricilerin işin gerçek diliyle, kurallarıyla ve workflow’larıyla uyumlu sistemler kurmasına yardımcı olur.
En önemli ders şudur: iş mantığı controller’ların, doğrudan database güncellemelerinin veya dağınık service metodlarının içine gizlenmemelidir. Sistem ne yapıyorsa, bunu açıkça ifade eden domain modelleri içinde yaşamalıdır.
DDD özellikle sipariş, ödeme, abonelik, fatura, onay süreci, lojistik, kurumsal workflow veya basit veri saklamadan daha fazla kural içeren sistemlerde güçlüdür. Dikkatli kullanın, en karmaşık alanlardan başlayın ve yazılım mimarisini iş modelinin yönlendirmesine izin verin.

