Diyarbakır Yazılım LogoDİYARBAKIR
>Ana Sayfa>Projeler>Atölye>Yazılar
>Ana Sayfa>Projeler>Atölye>Yazılar
durum: inşa ediliyor
Hexagonal Mimari: İş Mantığını Çerçevelerden İzole Etmek
  1. Anasayfa
  2. Yazılar
  3. Hexagonal Mimari: İş Mantığını Çerçevelerden İzole Etmek

Hexagonal Mimari: İş Mantığını Çerçevelerden İzole Etmek

Diyarbakır Yazılım
Diyarbakır Yazılım
16.08.2026
#Yazılım#Teknoloji#Topluluk

Bir backend projesinde Spring Boot, ASP.NET Core, NestJS, PostgreSQL veya Kafka kullanmak kolaydır. Zor olan, birkaç yıl sonra bu teknolojilerden biri değiştiğinde iş kurallarının ne kadarının değişmek zorunda kalacağını kontrol etmektir. On yıllık yazılım geliştirme deneyimimde bakım maliyetini yükselten problemlerin önemli bir bölümünün, framework ve altyapı detaylarının business logic içine erken girmesinden kaynaklandığını gördüm. Hexagonal Mimari: İş Mantığını Çerçevelerden İzole Etmek yaklaşımı, tam olarak bu bağımlılığı azaltmayı hedefler. Bu rehberde Hexagonal Architecture nedir ve nasıl uygulanır sorusundan başlayarak port, adapter, dependency inversion, test edilebilirlik, persistence, messaging, dış servis entegrasyonu ve kurumsal uygulama tasarımını adım adım ele alacağız.

Hexagonal Mimari Nedir?

Hexagonal Architecture, uygulamanın iş mantığını dış dünyadaki teknik detaylardan ayırmayı amaçlayan bir yazılım mimarisi yaklaşımıdır. Alistair Cockburn tarafından Ports and Adapters adıyla ortaya konan yaklaşımda uygulamanın çekirdeği database, HTTP framework, message broker veya üçüncü taraf servis gibi bileşenlerin varlığını doğrudan bilmez. Dış dünya uygulamaya portlar üzerinden ulaşır ve teknik entegrasyonlar adapter bileşenleriyle gerçekleştirilir. Böylece framework değişiklikleri sistemin merkezindeki business rule'ları mümkün olduğunca etkilemez. Hexagonal mimari ile iş mantığı frameworklerden nasıl izole edilir sorusunun temel cevabı da bağımlılık yönünü merkeze doğru çevirmek ve teknik detayları sınırların dışında tutmaktır.

Ports and Adapters Nedir?

Ports and Adapters yaklaşımında port, uygulama çekirdeğinin dış dünya ile konuşmak için tanımladığı sözleşmedir. Adapter ise bu sözleşmeyi belirli bir teknolojiyle gerçekleştiren bileşendir. Örneğin sipariş kaydetme ihtiyacı için uygulama bir OrderRepository port'u tanımlayabilir ve PostgreSQL adapter bu port'u uygulayabilir. Aynı port ileride farklı bir persistence adapter tarafından da gerçekleştirilebilir. Bu ayrım sayesinde business logic PostgreSQL driver'ı, ORM API'si veya database bağlantı detaylarıyla doğrudan ilişki kurmaz.

Hexagonal Architecture'ın Temel Amacı

Yaklaşımın temel amacı değişmesi muhtemel teknik detayları iş kurallarından uzak tutmaktır. Framework, database, message broker ve harici servislerin yaşam döngüsü çoğu iş kuralından daha kısadır. Siparişin hangi koşullarda iptal edilebileceği yıllarca aynı kalabilirken kullanılan ORM veya HTTP framework birkaç kez değişebilir. Mimari bu iki değişim hızını aynı kod alanına bağlamamaya çalışır. Sonuç olarak uygulamanın çekirdeği daha kolay test edilir, anlaşılır ve farklı teknik ortamlara taşınabilir.

Neden “Hexagonal” Denir?

Hexagon şekli mimarinin grafiksel anlatımında uygulama çekirdeğini dış bağlantı noktalarından ayırmak için kullanılan görsel bir metafordur. Buradaki amaç belirli sayıda katman veya bağlantı zorunluluğu yaratmak değildir. Şeklin farklı kenarları HTTP, database, messaging, CLI veya harici API gibi farklı etkileşim yönlerini temsil edebilir. Önemli olan dış dünyanın çekirdekle doğrudan değil tanımlı portlar üzerinden iletişim kurmasıdır. Bu nedenle mimariyi geometrik şekilden çok bağımlılık sınırları üzerinden anlamak daha doğrudur.

Gerçekten Altı Port mu Olmalıdır?

Hayır, hexagonal architecture uygulamasında altı port bulunması gibi bir kural yoktur. Hexagon yalnızca mimari fikri anlatmak için kullanılan bir temsildir. Bir uygulamada üç port bulunabilirken başka bir uygulamada onlarca giriş ve çıkış port'u olabilir. Port sayısı business capability, use case ve dış dünya entegrasyonlarına göre ortaya çıkar. Port tasarımında sayı hedeflemek yerine anlamlı sınırlar oluşturmak ve gereksiz abstraction üretmemek gerekir.

Framework-Agnostic Business Logic Ne Demektir?

Framework-agnostic business logic, iş kurallarının çalışabilmek için belirli bir framework API'sine ihtiyaç duymaması anlamına gelir. Örneğin Order sınıfının Spring annotation'ı, ASP.NET attribute'u veya NestJS decorator'ı bilmemesi buna iyi bir örnektir. Domain nesnesi normal bir nesne olarak oluşturulabilir ve unit test içinde application context başlatılmadan kullanılabilir. Framework yalnızca uygulamanın dış sınırında request alma, dependency wiring veya persistence gibi görevleri üstlenir. Böylece business logic'in yaşam döngüsü framework'ün yaşam döngüsünden ayrılır.

Geleneksel Katmanlı Mimari Hangi Problemi Oluşturur?

Katmanlı mimari kendi başına yanlış değildir ve birçok projede başarıyla kullanılabilir. Problem, katmanların yalnızca klasör seviyesinde ayrılıp bağımlılık yönünün kontrol edilmemesiyle başlar. Controller service'i, service repository'yi, repository ORM'yi çağırırken zamanla business logic framework annotation'larına, ORM entity'lerine ve infrastructure exception'larına bağlanabilir. Böyle bir yapıda teknik değişiklikler iş mantığına kadar ilerleyen geniş refactoring ihtiyacı yaratır. Hexagonal yaklaşım katmanları tamamen ortadan kaldırmak yerine bu bağımlılık yönünü daha açık ve kontrollü hâle getirmeyi hedefler.

Controller → Service → Repository Zinciri

Controller, service ve repository zinciri başlangıçta anlaşılır ve pratik görünür. Ancak service katmanı zaman içinde ORM entity, HTTP exception ve framework transaction API'lerini birlikte kullanmaya başlayabilir. Böylece service aslında application core olmaktan çıkar ve teknik detayların birleştiği bir katmana dönüşür. Test etmek için database veya framework context başlatma ihtiyacı ortaya çıkabilir. Sorun zincirin varlığı değil, her katmanın kendisinden aşağıdaki teknolojiye doğrudan bağımlı hâle gelmesidir.

Business Logic'in Framework'e Bağımlı Hale Gelmesi

Business logic framework decorator, annotation veya base class kullanmaya başladığında framework kodun merkezine yerleşir. İlk aşamada bu kolaylık faydalı görünebilir çünkü dependency injection, validation veya transaction yönetimi hızlı biçimde uygulanır. Ancak framework değişikliği veya major upgrade sırasında domain kodu da etkilenmeye başlar. Business rule'ları framework'ten bağımsız tutmak bu değişikliklerin etkisini daraltır. Framework kodu adapter ve composition katmanında tutulduğunda core daha uzun ömürlü kalır.

ORM'nin Domain Modeline Sızması

ORM annotation'larının doğrudan domain entity üzerinde kullanılması küçük projelerde oldukça pratik olabilir. Fakat domain modeli persistence ihtiyaçlarına göre şekillenmeye başladığında iş kuralları database modelinin sınırlamalarını taşımaya başlar. Lazy loading, proxy davranışı veya parameterless constructor gereksinimi domain tasarımını etkileyebilir. Büyük ve uzun ömürlü sistemlerde persistence model ile domain modelini ayırmak bu bağımlılığı kontrol altına alır. Her projede ayrı model zorunlu değildir, fakat maliyet ve fayda bilinçli değerlendirilmelidir.

Database Değişikliğinin Business Logic'i Etkilemesi

Business logic doğrudan database SDK'sı veya ORM query API'si kullanıyorsa database değişikliği geniş kod alanını etkileyebilir. Port yaklaşımında application core kendi ihtiyacını business language ile ifade eden bir interface tanımlar. Persistence adapter bu interface'i gerçek database teknolojisiyle uygular. Böylece PostgreSQL'den başka bir veri sistemine geçildiğinde değişikliğin büyük bölümü adapter tarafında kalabilir. Elbette veri modelinin kökten değiştiği durumlarda core tamamen etkilenmez demek gerçekçi değildir, fakat bağımlılık önemli ölçüde sınırlandırılır.

Framework Upgrade'lerinin Tüm Sistemi Etkilemesi

Framework API'si domain ve application kodunun her yerine yayıldığında major upgrade geniş çaplı düzenleme gerektirir. Annotation değişiklikleri, lifecycle davranışları veya exception tipleri iş mantığı koduna kadar ulaşabilir. Framework adapter sınırında tutulduğunda migration kapsamı daha dar olur. Core'un büyük bölümü aynı testlerle çalışmaya devam eder. Bu özellik özellikle yıllarca yaşayan kurumsal backend sistemlerinde ciddi bakım avantajı sağlar.

Testlerin Database veya Application Context Gerektirmesi

Business logic test etmek için sürekli database container veya application context başlatılması feedback süresini uzatır. Bir unit test birkaç milisaniye yerine saniyeler sürmeye başladığında binlerce test büyük CI maliyeti yaratabilir. Hexagonal mimaride use case port üzerinden fake repository veya stub gateway alabilir. Böylece core testleri gerçek infrastructure olmadan çalışabilir. Database adapter'ın doğruluğu ise ayrı integration testlerle değerlendirilerek test seviyeleri birbirinden ayrılır.

Hexagonal Architecture'ın Temel Dependency Rule'u

Hexagonal architecture'ın merkezinde bağımlılıkların içeri doğru yönelmesi bulunur. Application core dış dünyadaki database, HTTP, framework veya vendor SDK detaylarını import etmemelidir. Bunun yerine core ihtiyacı olan sözleşmeyi kendisi tanımlar ve dış katman bu sözleşmeyi uygular. Bu yaklaşım Dependency Inversion Principle ile doğrudan ilişkilidir. Hexagonal architecture dependency inversion adapter port ve test edilebilirlik stratejileri birlikte düşünüldüğünde mimarinin neden teknoloji değişikliklerine daha dayanıklı olduğu daha açık görülür.

Bağımlılıklar Hangi Yöne Gitmelidir?

Kaynak kodu bağımlılıkları dış teknik bileşenlerden application core'a doğru yönelmelidir. Adapter core içindeki port interface'ini import edebilir fakat core adapter sınıfını import etmemelidir. Bu kural business logic'in teknik uygulama ayrıntılarından bağımsız kalmasını sağlar. Runtime sırasında kontrol akışı dışarı doğru gidebilir fakat source dependency yine içeri bakar. Dependency direction ile runtime call direction aynı şey değildir ve bu ayrımı anlamak mimarinin temelidir.

Application Core Dış Dünyayı Neden Bilmemelidir?

Application core'un görevi iş kurallarını ve use case davranışlarını uygulamaktır. HTTP status code, database connection veya vendor SDK gibi detaylar bu sorumluluğun parçası değildir. Core bu detayları bildiğinde değişiklik sebeplerinin sayısı artar. Aynı sınıf hem business rule hem teknik entegrasyon değişikliğinden etkilenebilir. Dış dünya portlar arkasına alındığında core yalnızca business gereksinimler değiştiğinde değişmeye daha yakın bir yapıya kavuşur.

Dependency Inversion Principle

Dependency Inversion Principle yüksek seviyeli modüllerin düşük seviyeli modüllere doğrudan bağımlı olmamasını önerir. Her iki taraf da abstraction üzerinden ilişki kurar ve abstraction iş ihtiyacını yansıtır. Hexagonal mimaride PaymentGateway gibi bir port application core tarafından tanımlanabilir. Stripe benzeri bir ödeme adapter'ı bu port'u uygular ve core provider detayını bilmez. Böylece bağımlılık teknik servisten business sözleşmesine doğru çevrilmiş olur.

Inversion of Control

Inversion of Control uygulama bileşenlerinin oluşturulması ve birbirine bağlanması sorumluluğunu business nesnelerinin dışına taşır. Dependency injection container bu işi gerçekleştirebilir fakat tek yöntem değildir. Manuel composition da aynı ilkeyi uygulayabilir. Önemli olan use case'in gidip kendi repository adapter'ını oluşturmamasıdır. Bağımlılıklar dışarıdan verildiğinde test sırasında farklı adapter bağlamak da kolaylaşır.

Abstraction'ın Sahibi Kim Olmalıdır?

Bir port'un sahibi genellikle o abstraction'a ihtiyaç duyan application core olmalıdır. Örneğin use case ödeme gerçekleştirmek istiyorsa PaymentGateway interface'ini vendor SDK paketi değil application katmanı tanımlamalıdır. Böylece interface vendor terminolojisi yerine business ihtiyacını ifade eder. Bu yaklaşım abstraction'ın yanlış katmana taşınmasını önler. Port'un dili mümkün olduğunca uygulamanın domain ve use case kavramlarından oluşmalıdır.

Hexagonun İçinde Ne Bulunur?

Hexagonun içi tek bir zorunlu klasör yapısıyla tanımlanmaz. Genellikle domain entity'leri, value object'ler, business rule'lar, use case'ler, application service'ler, domain service'ler ve port interface'leri burada bulunur. Önemli ölçüt, bu bileşenlerin dış teknik ayrıntılara bağımlı olmamasıdır. DDD kullanan bir ekip içeride daha zengin domain modeli oluşturabilirken daha basit uygulamalar yalnızca use case ve port yapısıyla ilerleyebilir. Hexagonal architecture dış sınırları netleştirir, iç tasarım konusunda ise ekibe önemli bir esneklik bırakır.

Domain Entities

Domain entity kimliği bulunan ve iş davranışını taşıyan domain nesnesidir. Order, Customer veya Subscription gibi kavramlar buna örnek olabilir. Entity yalnızca getter ve setter koleksiyonu olmak zorunda değildir. Geçerli durum değişikliklerini kendi metotları içinde koruyabilir. Framework bağımsız entity kullanmak business rule'ların daha kolay test edilmesini ve anlaşılmasını sağlar.

Value Objects

Value object kimliğinden çok taşıdığı değerlerle anlam kazanan domain kavramıdır. Money, EmailAddress veya DateRange buna örnek olabilir. Validasyon ve invariant değer nesnesinin oluşturulması sırasında korunabilir. Böylece geçersiz verinin domain içinde dolaşması azaltılır. Value object'ler framework veya database detaylarından bağımsız tutulduğunda farklı adapter'lardan gelen veriler aynı domain kurallarına tabi olur.

Business Rules

Business rule sistemin iş açısından doğru davranmasını belirleyen kurallardır. Siparişin yalnızca belirli durumda iptal edilebilmesi veya indirim oranının belirli sınırı aşmaması buna örnek olabilir. Bu kurallar controller veya repository içinde dağınık biçimde bulunmamalıdır. Domain entity, value object veya domain service içinde açık biçimde ifade edilmesi daha sürdürülebilir yapı oluşturur. Business rule dış teknolojiye bağımlı olmadığında unit test yazmak da oldukça kolaylaşır.

Use Cases

Use case uygulamanın dış dünyaya sunduğu anlamlı iş işlemini temsil eder. PlaceOrder, CancelOrder veya RegisterCustomer gibi isimler teknik action yerine business amacı anlatır. Use case domain nesnelerini ve gerekli driven port'ları kullanarak süreci koordine eder. HTTP request veya Kafka mesajı use case'in doğrudan sorumluluğu değildir. Aynı use case farklı driving adapter'lardan çağrılabilir.

Application Services

Application service bir use case'in akışını koordine eden bileşendir. Repository'den veri alabilir, domain davranışını çağırabilir ve başka bir port üzerinden çıktı üretebilir. Burada amaç ağır business rule yazmak değil uygulama işleminin sırasını yönetmektir. Transaction boundary de çoğu projede application service seviyesinde bulunabilir. Application service framework annotation'larından uzak tutulduğunda core daha bağımsız kalır.

Domain Services

Domain service tek bir entity veya value object içine doğal biçimde yerleşmeyen business davranışını ifade eder. Örneğin birden fazla aggregate arasında hesaplama yapan saf bir iş kuralı domain service olabilir. Domain service'in infrastructure bağımlılığı minimum olmalıdır. Eğer dış servis çağrısı gerekiyorsa bu iletişim bir port üzerinden yapılabilir. Domain service'i application orchestration ile karıştırmamak tasarımı daha anlaşılır hâle getirir.

Port Interfaces

Port interface'leri uygulama çekirdeğinin dış dünya ile kurduğu sözleşmeleri tanımlar. Driving port'lar dış dünyanın çağırabileceği use case'leri, driven port'lar ise core'un dış sistemlerden beklediği yetenekleri ifade eder. İyi port isimleri teknik ürün adlarından çok business anlam taşır. Örneğin SaveOrderDatabase yerine OrderRepository daha sağlıklı bir isim olabilir. Port tasarımı doğru yapıldığında adapter değişimleri core'da daha az etki yaratır.

Hexagonal Architecture İç Yapıyı Belirler mi?

Hexagonal architecture uygulamanın içini ayrıntılı olarak tarif eden katı bir şablon değildir. DDD, transaction script veya farklı domain modelleme yaklaşımları hexagonun içinde kullanılabilir. Mimari esas olarak dış dünya ile uygulama çekirdeği arasındaki sınır ve bağımlılık yönüyle ilgilenir. Bu yönüyle Clean Architecture'a göre daha az prescriptive kabul edilebilir. Ekibin domain yapısına uygun iç tasarım seçmesi mümkündür.

Domain Entity Nasıl Framework Bağımsız Tutulur?

Domain entity'nin framework bağımsız kalması için nesnenin business kavramları dışında teknik metadata taşımaması hedeflenir. Spring, Hibernate, Entity Framework veya validation framework attribute'ları doğrudan domain sınıfına eklendiğinde core teknik platformla ilişki kurmaya başlar. Bu durum küçük sistemlerde kabul edilebilir bir trade-off olabilir, fakat uzun ömürlü domain modellerinde bağımlılık maliyeti büyüyebilir. Plain domain object yaklaşımı entity'nin normal constructor ve metodlarla çalışmasını sağlar. Böylece unit testler application context veya ORM davranışı olmadan gerçek iş kurallarına odaklanabilir.

Plain Domain Object

Plain domain object herhangi bir framework base class veya annotation gerektirmeyen normal programlama dili nesnesidir. Constructor üzerinden geçerli state oluşturabilir ve business metotlarıyla kontrollü değişim sağlayabilir. Bu nesneyi unit test içinde doğrudan oluşturmak mümkündür. Persistence adapter gerektiğinde domain nesnesini kendi modeline map eder. Bu sadelik domain kodunun okunabilirliğini ve portability özelliğini güçlendirir.

Framework Annotation'larından Kaçınmak

Framework annotation'larını tamamen yasaklamak her proje için gerekli değildir. Ama kritik soru annotation'ın business modelin merkezine teknik bağımlılık ekleyip eklemediğidir. Controller veya adapter sınıfındaki annotation doğal olarak infrastructure concern olabilir. Domain entity üzerindeki framework metadata'sı ise core ile dış dünya sınırını zayıflatır. Uzun ömürlü kurumsal projelerde bu ayrımın bilinçli yapılması bakım maliyetini azaltır.

JPA / Hibernate Annotation'ları

JPA ve Hibernate annotation'ları persistence mapping için son derece pratiktir. Ancak doğrudan domain entity üzerinde kullanıldığında entity'nin lifecycle ve structure tasarımı ORM beklentilerinden etkilenebilir. Lazy collection veya proxy davranışı business kodunda beklenmeyen sonuçlar oluşturabilir. Ayrı persistence entity kullanmak bu teknik davranışı adapter sınırında tutar. Mapping maliyeti yüksekse ve domain basitse tek model bilinçli olarak tercih edilebilir.

Entity Framework Attributes

Entity Framework attribute'ları C# projelerinde mapping ve configuration süreçlerini kolaylaştırabilir. Fakat domain model üzerinde yoğun kullanıldığında application core persistence teknolojisini tanımaya başlar. Fluent configuration ile mapping bilgisini infrastructure tarafında tutmak alternatif bir yöntemdir. Daha güçlü izolasyon gereken projelerde ayrı persistence model kullanılabilir. Seçim domain karmaşıklığı, ekip boyutu ve uzun vadeli bakım hedeflerine göre yapılmalıdır.

Domain Invariant'larını Entity İçinde Tutmak

Domain invariant bir entity'nin her zaman geçerli kalması gereken iş kuralıdır. Örneğin negatif sipariş toplamına izin verilmemesi veya iptal edilmiş siparişin tekrar gönderilememesi invariant olabilir. Bu kurallar yalnızca controller validation içinde tutulursa farklı adapter'lardan gelen çağrılar kuralları atlayabilir. Entity davranışı kendi geçerliliğini koruduğunda giriş kanalından bağımsız güvence sağlanır. Bu yaklaşım domain modelinin gerçekten business logic taşımasını sağlar.

Anemic Domain Model Problemi

Anemic domain model entity'lerin yalnızca veri taşıdığı, tüm iş kurallarının service sınıflarında toplandığı yapıyı ifade eder. Bu model basit CRUD sistemlerinde yeterli olabilir. Ancak karmaşık business rule'lar büyüdükçe service katmanı çok sayıda koşul ve state değişikliğiyle şişebilir. Entity'nin kendi davranışını taşıması kuralları ilgili veriyle aynı yerde tutar. Hexagonal architecture anemic modeli otomatik olarak engellemez, fakat zengin domain modeli uygulamak için uygun bir sınır sağlar.

Port Nedir?

Port, uygulamanın belirli bir etkileşim ihtiyacını temsil eden boundary sözleşmesidir. Çoğu programlama dilinde interface ile ifade edilebilir, fakat kavramsal olarak yalnızca interface sözdiziminden ibaret değildir. Port'un temel görevi uygulamanın hangi yeteneği sunduğunu veya dış dünyadan hangi yeteneği beklediğini tanımlamaktır. Bu nedenle port isimlerinin vendor veya transport dili yerine business kavramlarını taşıması önemlidir. İyi tasarlanmış port, adapter değişse bile uygulamanın ihtiyacının aynı kalmasını sağlar.

Port Bir Interface midir?

Port çoğu statically typed dilde interface olarak uygulanır. Python veya JavaScript gibi dillerde protocol, abstract class veya duck typing yaklaşımıyla da ifade edilebilir. Önemli olan belirli implementation'a bağlı olmayan açık bir sözleşme bulunmasıdır. Interface kullanmak teknik bir araçtır, mimarinin kendisi değildir. Her interface'in port olduğunu varsaymak bu nedenle doğru olmaz.

Port Bir Teknik Abstraction mı Business Contract mı?

İyi port mümkün olduğunca business contract olmalıdır. Örneğin PaymentGateway.charge ifadesi uygulamanın ödeme ihtiyacını anlatırken StripeClient.executeRequest gibi bir port adı vendor ayrıntısını core'a taşır. Port'un method signature'ı da vendor DTO veya framework type içermemelidir. Business odaklı veri tipleri değişim etkisini sınırlar. Teknik abstraction gereken durumlar olabilir fakat varsayılan yön business dilini korumak olmalıdır.

Port İsimleri Nasıl Seçilmelidir?

Port isimleri uygulamanın niyetini okuyucuya doğrudan anlatmalıdır. OrderRepository, PaymentGateway, NotificationSender veya PlaceOrderUseCase gibi adlar bu amaca uygundur. DatabaseOrderManager veya StripePort gibi isimler implementation ayrıntısını abstraction'a taşır. İsimlendirme mimari sınırın ne kadar temiz olduğunu anlamak için güçlü bir sinyaldir. Domain ekibinin kullandığı dil port tasarımında temel referans olmalıdır.

Bir Use Case İçin Bir Port mu Kullanılmalı?

Her use case için ayrı primary port oluşturmak okunabilirliği artırabilir fakat kesin bir zorunluluk değildir. Birbirine çok yakın operasyonlar tek interface içinde gruplanabilir. Çok geniş interface ise istemcilerin ihtiyaç duymadığı metotlara bağımlı olmasına neden olabilir. Interface Segregation Principle burada yararlı bir rehber sunar. Port granularity ekip ve domain yapısına göre dengeli belirlenmelidir.

Çok Genel Port Tasarımından Kaçınmak

Save, update, delete ve findAll gibi her domain nesnesine aynı generic interface'i uygulamak business anlamını zayıflatabilir. Uygulamanın gerçekte ihtiyaç duyduğu sorgular ve davranışlar daha spesifik port'larda ifade edilebilir. Örneğin findPendingOrdersForCustomer gibi method business use case'i daha iyi anlatabilir. Bu tasarım persistence implementation'ını da gereksiz generic abstraction'dan kurtarır. Port'un genişliği mevcut business ihtiyaçlarıyla sınırlı tutulmalıdır.

Primary / Driving Port Nedir?

Primary veya driving port dış dünyanın uygulamaya hangi use case'ler üzerinden erişebileceğini tanımlar. Bir REST controller, CLI komutu veya message consumer bu port'u çağırabilir. Driving port transport detayını değil uygulama yeteneğini ifade etmelidir. PlaceOrderUseCase veya CancelOrderUseCase buna örnek verilebilir. Böylece aynı business operation farklı giriş adapter'larından çağrılsa bile core davranışı değişmez.

Uygulamanın Dış Dünyaya Sunduğu Use Case'ler

Application core dış dünyaya teknik endpoint değil business use case sunar. HTTP tarafında POST /orders görülebilir fakat core açısından işlem PlaceOrder olabilir. Aynı use case ileride bir CLI veya message consumer tarafından da çalıştırılabilir. Bu ayrım transport ve business kavramlarının birbirine karışmasını engeller. Primary port application API'sinin business seviyesindeki karşılığıdır.

Command-Based Primary Port

Command-based port sistem state'ini değiştiren bir use case'i temsil eder. PlaceOrderCommand gibi bir input modeli use case için gerekli business verilerini taşıyabilir. HTTP request DTO doğrudan command olmak zorunda değildir. Driving adapter request modelini application command'a map edebilir. Böylece application core transport validation ve serialization detaylarından uzak kalır.

Query-Based Primary Port

Query-based port uygulamadan veri okumayı amaçlayan use case'leri temsil eder. GetOrderDetails veya SearchOrders buna örnek olabilir. CQRS uygulanmasa bile command ve query niyetlerini ayırmak API tasarımını netleştirebilir. Query sonucu domain entity olmak zorunda değildir ve application read model döndürülebilir. Amaç use case ihtiyacına uygun, framework bağımsız bir sözleşme oluşturmaktır.

Örnek: PlaceOrderUseCase

PlaceOrderUseCase müşterinin yeni sipariş oluşturma iş akışını temsil eder. Port bir PlaceOrderCommand alıp OrderId veya benzeri application sonucu döndürebilir. İçeride inventory kontrolü, order aggregate oluşturma ve repository çağrısı gerçekleştirilebilir. HTTP status code veya JSON serialization bu port'un konusu değildir. REST controller yalnızca request'i command'a dönüştürür ve sonucu uygun HTTP response'a map eder.

Örnek: CancelOrderUseCase

CancelOrderUseCase mevcut siparişin iptal edilme iş kuralını çalıştırır. Use case repository port üzerinden siparişi yükler ve domain entity üzerindeki cancel davranışını çağırabilir. Geçersiz state durumunda domain veya application error üretilir. Controller bu hatayı uygun HTTP status code'a dönüştürür. Aynı use case message consumer tarafından çağrıldığında business davranışı değişmeden kalır.

Primary / Driving Adapter Nedir?

Driving adapter dış dünyadan gelen isteği uygulamanın primary port'una dönüştüren bileşendir. REST controller, GraphQL resolver, gRPC handler, CLI veya message consumer bu rolü üstlenebilir. Adapter'ın görevi transport protokolünü anlamak, veriyi application input modeline çevirmek ve sonucu dış formatta sunmaktır. Business rule mümkün olduğunca adapter içinde bulunmamalıdır. Bu tasarım sayesinde yeni giriş kanalı eklendiğinde mevcut application core yeniden yazılmaz.

REST Controller

REST controller HTTP request, route, header ve status code gibi web detaylarını yönetir. Request DTO'yu application command veya query'ye map eder. Use case sonucunu HTTP response formatına dönüştürür. Business invariant kontrolünü controller içine koymak farklı adapter'larda tekrar yaratabilir. Controller ince kaldığında web framework değişikliklerinin etkisi daha sınırlı olur.

GraphQL Resolver

GraphQL resolver query ve mutation çağrılarını application use case'lerine bağlayan driving adapter olabilir. GraphQL schema type'larının doğrudan domain entity olarak kullanılmaması boundary'nin korunmasına yardımcı olur. Resolver input'u application command'a map edebilir. Business logic yine core içinde çalışır. Böylece REST ve GraphQL aynı use case'leri paylaşabilir.

gRPC Handler

gRPC handler protobuf message ve RPC lifecycle ayrıntılarını yönetir. Protobuf generated class'larını domain'e taşımak yerine adapter sınırında map etmek daha güçlü izolasyon sağlar. Handler application port'u çağırır ve sonucu tekrar protobuf response'a dönüştürür. Error mapping de gRPC status kodlarına bu katmanda yapılabilir. Core böylece gRPC runtime bağımlılığı taşımaz.

CLI

CLI adapter terminalden alınan argümanları application use case'lerine dönüştürür. Aynı business logic'in web server olmadan çalıştırılabilmesi mimari sınırın güçlü testlerinden biridir. CLI özellikle bakım, migration veya yönetim operasyonlarında yararlı olabilir. Input parsing ve output formatting adapter'ın sorumluluğundadır. Core terminal library'sini veya process API'sini bilmek zorunda kalmaz.

Scheduled Job

Scheduled job belirli zamanda application use case'i tetikleyen driving adapter olarak görülebilir. Cron veya framework scheduler detayı bu adapter içinde kalır. Job doğrudan repository kullanıp business logic'i atlamamalıdır. Bunun yerine normal use case'i çağırarak diğer giriş kanallarıyla aynı kuralları kullanır. Böylece schedule mekanizması değişse bile business operation korunur.

Kafka Consumer

Kafka consumer dış topic'ten gelen mesajı alıp application command'a dönüştüren driving adapter rolünü üstlenebilir. Kafka record, offset ve acknowledgement detayları core'a taşınmamalıdır. Adapter mesajı parse eder, use case'i çağırır ve başarılı veya başarısız sonuca göre acknowledgement politikasını uygular. Business error ile technical processing error birbirinden ayrılmalıdır. Bu tasarım domain'i Kafka bağımlılığından korur.

RabbitMQ Consumer

RabbitMQ consumer queue mesajını application use case'e bağlayan başka bir driving adapter örneğidir. Delivery tag, exchange veya routing key gibi kavramlar infrastructure sınırında kalmalıdır. Mesaj DTO'su application command'a map edilir. Retry ve dead-letter davranışı çoğu durumda adapter veya messaging infrastructure katmanında ele alınır. Business use case RabbitMQ kullanıldığını bilmeden çalışabilir.

Test Harness

Test harness application port'larını doğrudan çağıran özel bir driving adapter olarak düşünülebilir. HTTP veya messaging katmanını devreye almadan use case davranışlarını test etmeye yarar. Bu yaklaşım acceptance test'lerde hızlı geri bildirim sağlayabilir. Aynı port production adapter ve test adapter tarafından kullanılabildiği için boundary'nin doğruluğu da görülür. Test harness business behavior'ı dış teknoloji gürültüsünden ayırır.

Controller Neden İnce Olmalıdır?

Controller'ın temel işi request almak, map etmek ve use case çağırmaktır. Business logic controller içinde büyüdükçe aynı davranış başka bir adapter için tekrar yazılmak zorunda kalır. Testler de HTTP framework'e gereksiz biçimde bağımlı hâle gelir. İnce controller application core'un gerçekten yeniden kullanılmasını sağlar. Validation'ın business kısmı core'da, transport formatına ilişkin kısmı ise controller sınırında tutulabilir.

Secondary / Driven Port Nedir?

Secondary veya driven port application core'un dış dünyadan ihtiyaç duyduğu yetenekleri tanımlar. Repository, payment gateway, notification sender veya external API client bu kategoriye girebilir. Port implementation'ın nasıl çalıştığını değil core'un hangi sonucu beklediğini ifade eder. Bu port'ların sahibi uygulama katmanı olduğu için vendor-specific type kullanmamak önemlidir. Driven adapter gerçek teknolojiyi bu sözleşmenin arkasında uygular.

Repository Port

Repository port domain aggregate veya application ihtiyacına göre veri erişim sözleşmesi sunar. findById, save veya domain'e özgü sorgular içerebilir. ORM repository interface'ini core'a taşımak yerine kendi port'unuzu tanımlamak daha kontrollü bir boundary oluşturur. Persistence adapter ORM detaylarını içeride tutar. Böylece test sırasında aynı port in-memory adapter ile gerçekleştirilebilir.

Payment Gateway Port

Payment Gateway port uygulamanın ödeme sağlayıcısından beklediği business yeteneği tanımlar. charge, refund veya authorize gibi operasyonlar kullanılabilir. Vendor request ve response tipleri port signature'ına taşınmamalıdır. Adapter provider SDK'sı ile application model arasında mapping yapar. Provider değişikliği bu sayede mümkün olduğunca adapter seviyesinde kalır.

Notification Port

Notification port email, SMS veya push gönderme ihtiyacını business seviyesinde tanımlayabilir. Core mesajın gönderilmesini ister fakat hangi servis veya SDK kullanıldığını bilmez. Adapter template, provider response ve retry gibi teknik detayları yönetebilir. Testlerde fake notification adapter çağrıları kaydedebilir. Böylece business testleri gerçekten dış mesaj göndermeden çalışır.

Message Publisher Port

Message publisher port uygulamanın dış sisteme event yayınlama ihtiyacını ifade eder. Kafka producer veya başka broker API'si bu interface'e sızmamalıdır. Application event modeli port üzerinden adapter'a iletilebilir. Adapter serialization, topic ve broker metadata'sını yönetir. Bu ayrım broker değişikliğinin domain koduna yayılmasını engeller.

File Storage Port

File storage port uygulamanın dosya saklama veya okuma ihtiyacını soyutlar. Local filesystem, object storage veya farklı cloud servisleri adapter olabilir. Port business seviyesinde file identifier ve content gibi kavramlar kullanabilir. Provider SDK object type'ları core'a taşınmamalıdır. Bu yaklaşım testlerde memory tabanlı storage adapter kullanmayı da kolaylaştırır.

External API Port

External API port uygulamanın başka sistemden beklediği capability'yi tanımlar. Örneğin ExchangeRateProvider veya CustomerRiskService gibi domain odaklı bir isim kullanılabilir. HTTP client, authentication header ve vendor DTO adapter'ın içinde kalır. Core yalnızca gerekli business sonucu görür. Harici API versiyonu değiştiğinde mapping ve request logic çoğunlukla adapter içinde güncellenir.

Secondary / Driven Adapter Nedir?

Driven adapter application core tarafından tanımlanan output port'u gerçek teknolojiyle uygular. PostgreSQL repository, Redis cache, payment provider client veya object storage client bu role örnek verilebilir. Adapter teknik SDK, connection, serialization ve provider error detaylarını üstlenir. Core adapter'ın varlığını doğrudan bilmez ve yalnızca port interface'ine bağımlıdır. Bu model infrastructure değişikliklerini daha lokal hâle getirir.

PostgreSQL Adapter

PostgreSQL adapter repository port'unu SQL veya ORM kullanarak gerçekleştirebilir. Query, connection ve transaction integration gibi detaylar burada bulunur. Persistence entity gerekiyorsa domain model ile mapping de adapter sınırında yapılabilir. Database exception'ları application seviyesinde anlamlı error tiplerine çevrilebilir. Core PostgreSQL driver paketini import etmeden veri erişimini kullanır.

MongoDB Adapter

MongoDB adapter document modelini application repository port'una uyarlar. Collection, document id ve query API'si adapter içinde kalır. Domain aggregate ile document schema arasında mapping yapılabilir. Böylece MongoDB'nin persistence özellikleri business modelin tasarımını zorunlu olarak belirlemez. Eğer database değişirse yeni adapter aynı port'u uygulayabilir.

Redis Adapter

Redis adapter cache veya key-value ihtiyaçları için kullanılan driven adapter olabilir. TTL, serialization ve connection pool gibi konular infrastructure concern'dür. Core CachePort gibi bir abstraction üzerinden gerekli davranışı kullanabilir. Cache unavailable olduğunda fallback politikasının business veya infrastructure niteliği ayrıca değerlendirilmelidir. Redis-specific API'lerin core'a yayılmaması ilerideki değişiklikleri kolaylaştırır.

Stripe Adapter

Stripe adapter PaymentGateway port'unu belirli provider SDK'sı üzerinden gerçekleştiren örnek bir driven adapter olarak düşünülebilir. Provider'a özgü request ve exception tipleri adapter sınırında kalmalıdır. Core ödeme başarılı mı, başarısız mı ve gerekli business reference nedir gibi sonuçlarla ilgilenir. Provider API değiştiğinde adapter güncellenir. Böylece ödeme iş kuralları provider SDK'sına doğrudan bağlı olmaz.

SendGrid Adapter

SendGrid adapter NotificationPort benzeri bir sözleşmeyi email provider üzerinden uygulayan örnek olabilir. Template id, provider authentication ve API response detayları infrastructure katmanında tutulur. Core yalnızca bildirim gönderme niyetini ifade eder. Test ortamında aynı port fake adapter ile değiştirilebilir. Bu tasarım email sağlayıcısının business logic üzerinde doğrudan etkisini azaltır.

Kafka Producer Adapter

Kafka producer adapter EventPublisher port'unu topic, serialization ve producer configuration kullanarak gerçekleştirir. Core event yayınlamak ister fakat partition, acknowledgement veya broker client API'sini bilmez. Adapter technical exception'ları uygun application error'a çevirebilir. Outbox kullanılıyorsa adapter doğrudan broker yerine outbox writer şeklinde de tasarlanabilir. Seçim consistency gereksinimine göre yapılmalıdır.

S3 Adapter

S3 adapter file storage port'unu object storage API'si üzerinden uygulayabilir. Bucket, object key ve presigned URL gibi teknik kavramlar mümkün olduğunca adapter sınırında tutulur. Core daha genel file reference veya storage result modeliyle çalışabilir. Testlerde local veya in-memory adapter kullanılabilir. Bu yaklaşım storage provider bağımlılığını daraltır.

In-Memory Adapter

In-memory adapter gerçek database veya dış servis yerine memory üzerinde çalışan basit implementation'dır. Unit ve use case testlerinde hızlı feedback sağlar. Aynı port contract'ını uyguladığı için production adapter ile davranış beklentisi paylaşabilir. Ancak gerçek database transaction veya query semantics birebir taklit edilmez. Bu nedenle in-memory testler integration testlerin yerini tamamen almamalıdır.

Aynı Port İçin Birden Fazla Adapter Kullanmak

Bir port'un birden fazla adapter'a sahip olması hexagonal architecture'ın doğal kullanım biçimlerinden biridir. Aynı repository port production ortamında PostgreSQL, test ortamında in-memory adapter ile uygulanabilir. PaymentGateway farklı ülke veya tenant için farklı provider implementation'larına bağlanabilir. Aynı application use case REST ve GraphQL gibi birden fazla driving adapter üzerinden çağrılabilir. Bu esneklik abstraction'ın gerçekten business ihtiyacını temsil ettiğini gösterir.

PostgreSQL → MongoDB Geçişi

PostgreSQL'den MongoDB'ye geçiş yalnızca adapter değiştirerek her zaman tamamlanabilecek kadar basit değildir. Veri modeli ve transaction davranışları önemli ölçüde farklı olabilir. Buna rağmen repository port core ile persistence arasındaki doğrudan bağımlılığı azaltır. Yeni MongoDB adapter aynı business contract'ı mümkün olduğu ölçüde gerçekleştirebilir. Böylece migration etkisinin büyük bölümü persistence sınırında tutulabilir.

Stripe → Farklı Payment Provider

Ödeme provider değişimi port ve adapter yapısının değerini gösteren güçlü bir örnektir. Application core PaymentGateway abstraction'ına bağlıysa yeni provider için yeni adapter geliştirilebilir. Provider-specific token ve error modelleri boundary içinde map edilir. Business rule aynı kalıyorsa core değişikliği oldukça sınırlı olabilir. Contract testleri yeni adapter'ın beklenen davranışı sağladığını doğrulayabilir.

REST → GraphQL

REST ve GraphQL farklı giriş teknolojileri olmasına rağmen aynı application use case'lerini çağırabilir. REST controller request DTO'yu command'a çevirirken GraphQL resolver kendi input type'ını aynı command'a dönüştürür. Business logic tekrar yazılmaz. Bu durum transport protokolünü application behavior'dan ayırmanın pratik avantajıdır. Aynı yaklaşım CLI veya gRPC için de uygulanabilir.

Production Adapter

Production adapter gerçek database, vendor veya message broker ile iletişim kurar. Connection management, security configuration ve observability gibi ek sorumluluklar taşır. Port contract'ını eksiksiz uygulaması gerekir. Integration testler gerçek veya production'a yakın servisle bu adapter'ı doğrulamalıdır. Business core production adapter'ın teknik ayrıntılarını bilmeden çalışmaya devam eder.

Test Adapter

Test adapter hızlı ve deterministik test davranışı sağlamak için tasarlanır. Fake repository belirli domain nesnelerini memory içinde tutabilir. Stub gateway belirli ödeme sonucunu döndürebilir. Test adapter'ın amacı production sistemi tamamen taklit etmek değildir. Core'un belirli business senaryolarını dış I/O olmadan test etmeyi kolaylaştırmaktır.

Runtime'da Adapter Seçmek

Bazı sistemlerde adapter runtime configuration'a göre seçilebilir. Tenant, ülke veya feature flag farklı payment veya storage adapter kullanılmasını gerektirebilir. Composition root uygun implementation'ı port'a bağlayabilir. Bu seçim business rule içeriyorsa application katmanında strateji belirlenmesi gerekebilir. Teknik environment seçimi ise bootstrap veya infrastructure configuration içinde tutulabilir.

İş Mantığını Framework'ten Nasıl İzole Ederiz?

Framework izolasyonunun ilk adımı core katmanında framework import'larını minimuma indirmektir. Use case ve domain nesneleri normal programlama dili sınıfları olarak oluşturulabilir. Spring Boot, ASP.NET Core veya NestJS dependency injection, web routing ve configuration görevlerini dış katmanda üstlenir. Framework annotation veya decorator'ları adapter ve composition bölgesinde tutulur. Böylece kurumsal Hexagonal Architecture ve yazılım mimarisi danışmanlığı çalışmalarında sık gördüğümüz framework değişikliği riski daha yönetilebilir hâle gelir.

Spring Boot Örneği

Spring Boot projesinde REST controller ve configuration sınıfları framework katmanında tutulabilir. Application use case normal Java veya Kotlin sınıfı olarak yazılabilir. Driven port interface application paketinde, JPA implementation ise adapter paketinde yer alabilir. Spring configuration bu sınıfları bean olarak bağlar. Core böylece Spring annotation'larını kullanmadan application behavior'ını koruyabilir.

ASP.NET Core Örneği

ASP.NET Core tarafında controller veya minimal API endpoint application port'unu çağırabilir. Use case normal C# sınıfı ve interface'lerle oluşturulabilir. Entity Framework DbContext infrastructure adapter içinde tutulur. Program.cs veya ayrı composition extension'ları interface implementation binding işlemini gerçekleştirir. Domain assembly'nin ASP.NET veya EF Core dependency'si taşımaması güçlü bir boundary sağlar.

NestJS Örneği

NestJS module ve decorator yapısı uygulama wiring işlemleri için oldukça pratiktir. Buna rağmen domain ve application sınıflarının NestJS decorator'ı kullanması zorunlu değildir. Adapter ve module katmanı provider binding yaparak normal TypeScript class'larını bağlayabilir. Repository implementation NestJS infrastructure paketlerini kullanabilir. Core ise TypeScript interface veya token abstraction üzerinden çalışabilir.

Framework Annotation'larını Adapter Katmanında Tutmak

Framework annotation'ları request routing, ORM mapping veya component discovery gibi teknik amaçlara hizmet eder. Bu nedenle doğal yerleri adapter ve bootstrap sınırıdır. Domain entity üzerinde annotation bulunmaması core'un framework bağımlılığını azaltır. Application service'in transaction annotation kullanması bazı ekiplerde bilinçli trade-off olabilir. Daha güçlü izolasyon gerekiyorsa transaction boundary decorator veya composition katmanında uygulanabilir.

Domain'i Framework DI Container'ından Bağımsız Tutmak

Domain nesnesinin dependency injection container tarafından oluşturulması çoğu durumda gerekli değildir. Entity ve value object normal constructor kullanabilir. Application service ihtiyaç duyduğu port'ları constructor üzerinden alabilir fakat container API'sini bilmez. Composition root bu bağımlılıkları dışarıdan bağlar. Böylece unit testte service doğrudan new ile oluşturulabilir.

Dependency Injection Hexagonal Architecture'da Nasıl Kullanılır?

Dependency injection port ile adapter arasındaki bağlantıyı runtime sırasında kurmak için kullanışlı bir tekniktir. Ancak hexagonal architecture'ın kendisi belirli bir DI framework gerektirmez. Constructor injection application dependency'lerini açık hâle getirir ve test sırasında fake adapter vermeyi kolaylaştırır. Composition root hangi adapter'ın hangi port'u gerçekleştireceğini belirler. Framework container kullanılabilir veya bağlantılar manuel biçimde yapılabilir.

Constructor Injection

Constructor injection bağımlılıkları nesnenin oluşturulması sırasında açıkça belirtir. Use case bir OrderRepository ve PaymentGateway gerektiriyorsa bunları constructor parametresi olarak alabilir. Bu yaklaşım gizli service locator kullanımını azaltır. Testlerde dependency'ler kolayca fake implementation ile değiştirilebilir. Aynı zamanda sınıfın çalışmak için neye ihtiyaç duyduğu doğrudan görülebilir.

Interface → Adapter Binding

Interface adapter binding runtime'da belirli port'un hangi implementation ile karşılanacağını tanımlar. Production ortamında OrderRepository PostgreSQL adapter'a bağlanabilir. Test ortamında aynı port InMemoryOrderRepository ile kullanılabilir. Binding bilgisi application core'un dışında tutulur. Böylece core deployment environment veya technology choice hakkında bilgi taşımaz.

Composition Root Nedir?

Composition root uygulamadaki dependency graph'ın oluşturulduğu merkezi noktadır. Adapter instance'ları burada oluşturulur veya DI container'a kaydedilir. Application use case'leri ihtiyaç duydukları port implementation'larıyla burada bağlanır. Configuration ve environment seçimi de çoğu zaman bu alanda yapılır. Composition root'un business logic taşımaması önemlidir.

Spring Configuration

Spring configuration class'ları port ve adapter bean'lerini bağlamak için composition root görevi görebilir. Application sınıflarının component scanning'e bağımlı olması yerine explicit bean tanımları kullanılabilir. Böylece core üzerinde Spring annotation ihtiyacı azalır. Configuration hangi repository veya gateway implementation'ının kullanılacağını belirler. Test configuration farklı adapter'ları aynı port'lara bağlayabilir.

ASP.NET Program.cs

ASP.NET Core uygulamasında Program.cs veya ilgili dependency registration extension'ları composition root görevi görebilir. IServiceCollection üzerinden application interface'leri infrastructure implementation'larına bağlanır. Domain ve application assembly container API'sini doğrudan kullanmak zorunda değildir. Environment'a göre farklı adapter registration yapılabilir. Bu yapı dependency direction kuralını açık tutar.

NestJS Module

NestJS Module provider registration ve dependency wiring için doğal composition noktalarından biridir. Port token'ı belirli adapter class ile eşleştirilebilir. Application use case framework decorator kullanmadan normal class olarak kalabilir. Factory provider daha gelişmiş runtime seçimleri için kullanılabilir. Module business behavior yerine wiring sorumluluğuna odaklanmalıdır.

Manuel Composition

DI framework olmadan da hexagonal architecture rahatlıkla uygulanabilir. Uygulamanın bootstrap kodu önce repository adapter'ı, ardından service ve controller instance'larını oluşturabilir. Bu yöntem dependency graph küçük olduğunda son derece anlaşılır olabilir. Framework magic azalır ve wiring açık biçimde görülür. Sistem büyüdüğünde container kullanmak tekrar eden composition kodunu azaltabilir.

Dependency Injection Framework Kullanmak Zorunlu mu?

Hayır, dependency injection framework hexagonal architecture için zorunlu değildir. Mimari bağımlılık yönüyle ilgilenir, dependency oluşturma aracıyla değil. Constructor injection manuel olarak da uygulanabilir. Küçük servislerde manuel composition daha sade olabilir. Büyük uygulamalarda DI container lifecycle ve configuration yönetimini kolaylaştırabilir.

Repository Pattern ve Hexagonal Architecture

Repository pattern persistence detaylarını domain veya application logic'ten ayırmak için hexagonal architecture ile doğal biçimde kullanılabilir. Repository interface driven port rolünü üstlenir ve adapter gerçek database işlemlerini gerçekleştirir. Burada kritik nokta repository API'sinin ORM API'sini kopyalayan generic bir abstraction'a dönüşmemesidir. Port business use case'lerin gerçekten ihtiyaç duyduğu operasyonları ifade etmelidir. Böylece persistence teknoloji değişiklikleri daha kontrollü tutulabilir.

Repository Bir Driven Port mudur?

Çoğu hexagonal tasarımda repository application core'un dış persistence sisteminden beklediği capability olduğu için driven port'tur. Core repository interface'ini bilir fakat implementation'ı bilmez. Database adapter bu port'u gerçekleştirir. Runtime call core'dan database'e doğru olsa bile kaynak kod bağımlılığı adapter'dan port'a doğrudur. Bu dependency inversion mimarinin temel mekanizmalarından biridir.

Repository Interface Nerede Bulunmalıdır?

Repository interface'i genellikle domain veya application tarafında, yani ihtiyacı tanımlayan core içinde bulunmalıdır. Infrastructure paketinde tanımlanan interface core tarafından import edilirse dependency direction tersine döner. Port business terminology kullanmalıdır. Persistence implementation bu interface'i dış katmanda uygular. Böylece abstraction teknik altyapının değil application ihtiyacının kontrolünde kalır.

ORM Repository'sini Doğrudan Kullanmanın Sakıncaları

ORM repository interface'ini application service içinde doğrudan kullanmak başlangıçta az kod gerektirir. Ancak query method, entity type ve transaction davranışları core'a sızabilir. Framework değişikliği daha geniş etki yaratabilir. Ayrıca test sırasında ORM altyapısı gereksiz hâle gelebilir. Basit CRUD uygulamalarında bu trade-off kabul edilebilir olsa da karmaşık domainlerde özel port daha güçlü ayrım sağlar.

Domain-Specific Repository API

Domain-specific repository API business use case'in gerçekten ihtiyaç duyduğu işlemleri açık biçimde ifade eder. findPendingOrders veya save aggregate gibi metotlar generic data access ifadelerinden daha anlamlı olabilir. Bu tasarım persistence query detayını adapter'a bırakır. Core query language veya ORM predicate type taşımak zorunda kalmaz. Repository böylece domain boundary'nin bir parçası hâline gelir.

Generic Repository Anti-Pattern'i

Generic repository her entity için aynı CRUD API'sini sunmayı amaçlar. Bu yaklaşım çoğu ORM'nin zaten sunduğu abstraction'ı tekrar edebilir. Ayrıca business use case'lerin farklı sorgu ihtiyaçlarını gizlemeye çalışırken interface hızla büyüyebilir. Domain-specific port daha açık niyet sunar. Generic repository ancak gerçekten ortak ve sınırlı bir ihtiyaç varsa tercih edilmelidir.

ORM Entity ile Domain Entity Neden Ayrılmalı?

ORM entity persistence mekanizmasının ihtiyaçlarını, domain entity ise business modelin kurallarını taşır. Bu iki amaç her zaman aynı class içinde rahat biçimde karşılanmaz. Persistence modelde annotation, foreign key veya lazy loading detayları bulunabilirken domain model invariant ve davranışlara odaklanabilir. Ayrı model mapping maliyeti getirir fakat güçlü izolasyon sağlar. Bu maliyet özellikle karmaşık domain ve uzun ömürlü sistemlerde anlamlı olabilir.

Persistence Model

Persistence model database schema ve ORM davranışına göre tasarlanır. Primary key, navigation property ve persistence annotation gibi teknik bilgiler içerebilir. Bu model adapter katmanına ait olduğu için database değişikliği burada karşılanır. Application core persistence modelini doğrudan kullanmaz. Mapper gerekli dönüşümü domain nesnesiyle gerçekleştirir.

Domain Model

Domain model iş kurallarını ve business kavramlarını temsil eder. Entity ve value object'ler geçerli state'i korumaya odaklanır. Database tablosunun birebir yansıması olmak zorunda değildir. Bu özgürlük business modelin daha doğal biçimde ifade edilmesini sağlar. Domain model persistence teknolojisi değiştiğinde mümkün olduğunca aynı kalmalıdır.

Mapper

Mapper persistence model ile domain model arasında veri dönüşümü yapar. Bu ek kod ilk bakışta tekrar gibi görünebilir. Ancak boundary'nin nerede olduğunu açık biçimde gösterir ve teknik type'ların core'a sızmasını engeller. Mapping testleri iki modelin doğru çevrildiğini doğrulayabilir. Otomatik mapping araçları kullanılabilir fakat business anlam taşıyan dönüşümlerde explicit kod daha okunabilir olabilir.

ORM Annotation Leakage

ORM annotation leakage persistence metadata'sının domain sınıflarına yayılmasıdır. Bu durum domain package'in database framework dependency'si taşımasına neden olabilir. Entity tasarımı lazy loading veya proxy requirement'larına göre şekillenmeye başlayabilir. Ayrı persistence model bu sızıntıyı tamamen dış katmanda tutar. Daha basit projelerde leakage bilinçli kabul edilebilir fakat karar mimari hedeflerle uyumlu olmalıdır.

Mapping Maliyetine Değer mi?

Mapping ek class ve test ihtiyacı oluşturduğu için gerçek bir maliyettir. Basit CRUD sisteminde bu maliyet elde edilen izolasyondan daha yüksek olabilir. Karmaşık domain, farklı data model veya uzun yaşam süresi bulunan projelerde ise ayrımın değeri artar. Framework ve database değişikliği ihtimali de değerlendirmeye dahil edilmelidir. Mimari prensibi otomatik kural yerine cost-benefit kararı olarak uygulamak daha sağlıklıdır.

Ne Zaman Tek Model Kullanmak Kabul Edilebilir?

Domain son derece basitse ve sistem büyük ölçüde CRUD davranışı taşıyorsa tek model yeterli olabilir. ORM seçiminin uzun süre değişmeyeceği kabul ediliyorsa mapping maliyeti gereksiz görülebilir. Küçük ekip ve kısa proje ömrü de bu kararı destekleyebilir. Ancak business rule büyümeye başladığında model ayrımı tekrar değerlendirilmelidir. Hexagonal architecture pragmatik uygulanmalı ve gereksiz abstraction oluşturmamalıdır.

DTO'lar Hexagonun Neresinde Olmalıdır?

DTO'lar hangi boundary'ye hizmet ettiklerine göre farklı katmanlarda bulunabilir. HTTP request DTO web adapter'a, application command application katmanına ve response DTO yine dış adapter'a ait olabilir. Tek DTO'yu bütün sistem boyunca taşımak boundary'leri birbirine bağlar. Özellikle framework generated veya serialization annotation taşıyan DTO'ların domain'e sokulmaması önemlidir. Mapping kodu ek maliyet getirir fakat farklı modellerin sorumluluklarını açık tutar.

HTTP Request DTO

HTTP request DTO web protokolünden gelen verinin şeklini temsil eder. JSON field, validation annotation veya serialization metadata içerebilir. Bu nedenle doğal yeri web adapter katmanıdır. Controller DTO'yu application command'a dönüştürür. Domain entity'nin doğrudan request body olarak kullanılmaması boundary'yi korur.

Application Command

Application command belirli use case'in çalışması için gerekli input'u taşır. HTTP, GraphQL veya Kafka message gibi transportlardan bağımsız olmalıdır. Birden fazla driving adapter aynı command tipini oluşturabilir. Command business operation'ın input contract'ını açık biçimde gösterir. Validation'ın use case'e ait kısmı application veya domain seviyesinde uygulanabilir.

Domain Entity

Domain entity application input veya transport DTO'su değildir. Kendi identity, invariant ve davranışını taşır. Dış request'ten gelen veri entity constructor veya factory aracılığıyla kontrollü şekilde domain'e alınabilir. Bu işlem geçersiz state oluşmasını engeller. Entity'nin dış serialization formatlarına göre şekillenmemesi uzun vadeli modeli korur.

Response DTO

Response DTO dış istemcinin görmesi gereken veriyi temsil eder. Domain entity'nin tamamını serialize etmek istemeden internal bilgi sızıntısına yol açabilir. Adapter veya application query layer gerekli alanları response modeline map edebilir. API versioning bu model üzerinden daha rahat yönetilir. Domain model böylece public API contract'ından bağımsız kalır.

Framework DTO'sunu Domain'e Sokmamak

Framework DTO'su validation decorator, serialization attribute veya generated code içerebilir. Domain bu type'ı kabul ederse transport technology application core'a sızar. Boundary mapping bu bağımlılığı keser. Controller veya resolver framework DTO'yu application modeline çevirir. Aynı business use case farklı protokollerden daha rahat çağrılabilir.

Boundary Mapping

Boundary mapping farklı katmanların veri modelleri arasında dönüşüm yapar. Bu işlem boilerplate gibi görünse de coupling'i görünür ve kontrollü hâle getirir. Mapping yeri boundary'ye yakın tutulmalıdır. Business rule mapper içine gizlenmemelidir. Net dönüşüm kodu framework veya vendor model değişikliklerinin etki alanını sınırlar.

Application Service ile Domain Service Arasındaki Fark

Application service use case orchestration yaparken domain service business rule uygular. Bu ayrım kodun neden değiştiğini daha net hâle getirir. Application service repository, gateway ve domain entity'leri belirli sırayla koordine edebilir. Domain service ise teknik workflow yerine domain hesaplama veya kararını taşır. Controller'ın bu iki sorumluluğu üstlenmesi boundary'yi zayıflatır.

Use Case Orchestration

Use case orchestration gerekli adımların hangi sırada gerçekleşeceğini yönetir. Önce order yüklenebilir, sonra domain davranışı çağrılabilir ve son olarak repository üzerinden kaydedilebilir. Bu akış application service içinde doğal olarak bulunabilir. Business invariant domain nesnelerinde kalmalıdır. Böylece orchestration ile business decision birbirinden ayrılır.

Business Rule

Business rule uygulamanın iş açısından doğru davranmasını tanımlar. Örneğin gönderilmiş siparişin iptal edilememesi domain rule'dur. Bu kontrol controller veya repository içinde değil domain model içinde bulunmalıdır. Aynı use case farklı adapter'dan çağrılsa bile kural korunur. Business rule framework ve transport değişikliklerinden bağımsız kalmalıdır.

Domain Service

Domain service bir entity içine doğal biçimde yerleşmeyen domain davranışını barındırır. Birden fazla value object veya aggregate arasında hesaplama yapabilir. Infrastructure çağrısı yapmak domain service'in temel görevi değildir. Gerekiyorsa port abstraction üzerinden sınırlı bağımlılık kullanılabilir. Domain service'in adı teknik işlem değil business kavramı ifade etmelidir.

Application Service

Application service application boundary içinde use case akışını uygular. Driven port'ları kullanabilir ve transaction boundary yönetebilir. Domain entity üzerinde business davranışını tetikler fakat mümkün olduğunca kuralları kendi içine yığmaz. Application service primary port implementation'ı olarak da çalışabilir. Framework dependency'si olmadan yazıldığında hızlı unit test edilebilir.

Controller'da Business Logic Olmamalı

Controller'da business logic bulunması aynı kuralların başka driving adapter'larda tekrar edilmesine neden olur. CLI veya Kafka consumer eklendiğinde aynı validation ve karar kodu yeniden yazılabilir. Controller yalnızca protocol mapping ve transport concern'lere odaklanmalıdır. Business decision application veya domain katmanında bulunmalıdır. Bu ayrım test stratejisini de daha sade hâle getirir.

Transaction Boundary Nerede Olmalıdır?

Transaction boundary çoğu application use case için iş işleminin atomik sınırıyla hizalanmalıdır. Bir use case iki repository kullanıyorsa tek database transaction ihtiyacı olabilir. Domain modelin transaction API'sini bilmesi gerekmez. Unit of Work veya infrastructure transaction decorator gibi yaklaşımlar teknik detayı core'dan gizleyebilir. Dağıtık sistemlerde ise database transaction mantığının dış servis ve broker işlemlerine doğrudan uygulanamayacağını kabul etmek gerekir.

Use Case Seviyesinde Transaction

Use case seviyesi çoğu transaction için doğal sınırdır. Kullanıcı tek business operation başlatır ve gerekli database değişikliklerinin birlikte başarılı olması beklenir. Transaction yönetimi application service'i framework annotation'a bağlamadan decorator veya composition üzerinden uygulanabilir. Bazı framework'lerde annotation kullanımı bilinçli trade-off olarak kabul edilebilir. Önemli olan transaction semantics'in business operation ile uyumlu olmasıdır.

Unit of Work

Unit of Work birden fazla repository değişikliğini tek transaction kapsamında koordine etmeye yardımcı olabilir. Port olarak tanımlanıp infrastructure adapter tarafından uygulanabilir. Application service işlemi başlatma ve tamamlama isteğini ifade edebilir. Ancak abstraction'ın database API'sini kopyalamaması gerekir. Gereksiz Unit of Work katmanı basit projelerde fazladan yük oluşturabilir.

Database Transaction Detaylarını Core'dan Gizlemek

Isolation level, connection veya ORM session gibi kavramlar infrastructure concern olarak tutulmalıdır. Core business operation'ın atomik olması gerektiğini bilir fakat teknik transaction API'sini bilmek zorunda değildir. Transaction decorator application port çağrısını çevreleyebilir. Böylece framework değiştiğinde core class'ları aynı kalabilir. Transaction davranışı integration testlerle doğrulanmalıdır.

Birden Fazla Repository Kullanan Use Case

Bir use case aynı database üzerindeki birden fazla repository'yi kullanabilir. Transaction boundary bu değişikliklerin birlikte commit edilmesini sağlayabilir. Repository implementation'ların aynı Unit of Work veya context'i paylaşması gerekebilir. Bu wiring infrastructure ve composition katmanında çözülmelidir. Core yalnızca business operation'ın bütünlüğünü ifade etmelidir.

Distributed Transaction Problemi

Database ve message broker gibi farklı sistemleri tek ACID transaction içine almak çoğu modern mimaride zor ve pahalıdır. İki phase commit her ortamda uygun olmayabilir. Outbox, saga veya idempotent processing gibi teknikler eventual consistency yaklaşımını destekleyebilir. Business requirement hangi consistency seviyesinin gerekli olduğunu belirlemelidir. Hexagonal architecture bu problemi ortadan kaldırmaz, yalnızca entegrasyon noktalarını daha açık hâle getirir.

I/O Dışında Neleri Port Arkasına Almalıyız?

Port yalnızca database veya HTTP client için kullanılmaz. Testlerde deterministik davranışı bozan clock, UUID generator, random number generator ve feature flag gibi kaynaklar da gerektiğinde port arkasına alınabilir. Bu yaklaşım business logic'in environment ve runtime durumuna doğrudan bağlı kalmasını azaltır. Ancak her standart library çağrısı için interface oluşturmak gereksiz abstraction üretebilir. Ölçüt, dependency'nin test edilebilirlik ve değişim açısından anlamlı bir boundary oluşturup oluşturmadığıdır.

System Clock

Business rule mevcut zamana bağlıysa doğrudan system clock kullanmak testleri kırılgan hâle getirebilir. Clock port sabit test zamanı vermeyi kolaylaştırır. Production adapter gerçek sistem saatini döndürür. Subscription expiry veya deadline hesaplamaları deterministik test edilebilir. Bu küçük abstraction zaman bağımlı domainlerde yüksek değer sağlayabilir.

UUID Generator

Yeni entity kimliği oluşturmak için doğrudan random UUID çağırmak birçok projede yeterlidir. Ancak belirli testlerde üretilen identifier'ın kontrol edilmesi gerekiyorsa generator port yararlı olabilir. Test adapter sabit veya sıralı id üretir. Production implementation normal UUID library kullanır. Gereksinim yoksa sırf mimari görünüm için ayrıca abstraction oluşturmak şart değildir.

Random Number Generator

Random davranış business outcome'u etkiliyorsa testlerin tekrar üretilebilir olması zorlaşabilir. Random generator abstraction kontrollü test değerleri vermeyi sağlar. Örneğin oyun, dağıtım veya sampling logic'i deterministik olarak doğrulanabilir. Production adapter gerçek random source kullanır. Security-sensitive randomness söz konusuysa uygun güvenli generator seçimi infrastructure sorumluluğudur.

File System

File system dış I/O olduğu için doğal bir driven port adayıdır. Core belirli dosyayı saklama veya okuma capability'sine ihtiyaç duyabilir. Local disk, network share veya object storage farklı adapter'lar olabilir. Test adapter memory üzerinde çalışabilir. Bu ayrım file path ve OS API detaylarını business logic'ten uzak tutar.

Feature Flags

Feature flag bazı behavior'ların runtime'da açılıp kapanmasını sağlar. Flag provider SDK'sının application core'a doğrudan girmesi gerekmeyebilir. FeatureFlagPort business anlamlı flag durumunu döndürebilir. Ancak kritik business decision'ı tamamen infrastructure flag sistemine bırakmak domain görünürlüğünü azaltabilir. Flag kullanımının geçici ve yönetilebilir olması önemlidir.

Environment-Based Configuration

Environment variable ve configuration dosyaları application wiring için kullanışlıdır. Domain nesnesinin doğrudan environment variable okuması ise test ve portability açısından zayıf tasarım oluşturur. Bootstrap katmanı config değerini okuyup gerekli application option'a dönüştürebilir. Core yalnızca business için anlamlı configuration nesnesini görür. Secret değerler ayrıca güvenli secret management sistemiyle yönetilmelidir.

Non-Determinism'i İzole Etmenin Test Avantajı

Zaman, randomness ve dış servisler test sonuçlarını değişken hâle getirebilir. Bu kaynaklar port arkasına alındığında test belirli input için her çalışmada aynı sonucu üretir. Hızlı ve güvenilir unit test suite oluşturmak kolaylaşır. Aynı zamanda failure scenario'ları bilinçli biçimde simüle edilebilir. Test double kullanımının en güçlü faydalarından biri budur.

Error Handling Hexagonal Architecture'da Nasıl Yapılır?

Error handling mimari sınırları korumak için önemli bir alandır. Domain error, application error ve infrastructure error aynı anlama gelmez. HTTP status code veya vendor SDK exception'ının domain'e girmesi boundary'yi teknik detaylarla kirletir. Adapter dış hataları core'un anlayacağı abstraction'a çevirir, driving adapter ise core error'larını kendi protokol formatına map eder. Böylece her katman kendi hata dilini kullanır.

Domain Error

Domain error business kuralının ihlal edildiğini ifade eder. OrderAlreadyCancelled veya InsufficientBalance buna örnek olabilir. Bu hata HTTP 400 veya 409 olduğunu bilmek zorunda değildir. Aynı domain operation CLI üzerinden çalıştırıldığında farklı sunum gerekebilir. Transport mapping driving adapter'ın sorumluluğudur.

Application Error

Application error use case seviyesinde işlemin tamamlanamadığını ifade edebilir. Requested entity bulunamaması veya dependency'nin geçici olarak kullanılamaması bu seviyede modellenebilir. Domain error doğrudan propagate edilebilir veya application error'a çevrilebilir. Hata modelinin aşırı katmanlı olmaması önemlidir. Amaç framework exception'larını core API'sinden uzak tutmaktır.

Infrastructure Error

Infrastructure error database driver, HTTP client veya broker kaynaklı teknik hatadır. Timeout, connection refused veya vendor-specific exception buna örnek olabilir. Adapter bu detayları loglayabilir ve uygulama için anlamlı error'a çevirebilir. Core doğrudan SQLException veya provider SDK exception'ı kullanmamalıdır. Böylece adapter değişikliği error handling contract'ını bozmaz.

HTTP Status Code Domain'e Ait midir?

HTTP status code transport protokolüne ait bir kavramdır. Domain entity'nin 404 veya 409 döndürmesi business model ile web protocol'ü birbirine bağlar. Domain business error üretir. REST controller veya error mapper bu sonucu HTTP status code'a dönüştürür. Aynı use case gRPC veya message consumer tarafından kullanıldığında farklı mapping uygulanabilir.

Domain Error → HTTP Response Mapping

Driving adapter domain veya application error türünü HTTP response'a çevirir. NotFound türü bir application sonucu 404 olarak map edilebilir. Business conflict 409 gibi bir response olabilir. Mapping policy web katmanında merkezi biçimde uygulanabilir. Core bu mapping bilgisine bağımlı olmaz.

Vendor Exception'larını Core'a Sızdırmamak

Vendor exception core'a ulaştığında application API provider'a bağımlı hâle gelir. Adapter exception'ı yakalayıp business veya application açısından anlamlı bir hata üretmelidir. Örneğin PaymentProviderTimeout daha genel PaymentTemporarilyUnavailable sonucuna dönüştürülebilir. Loglarda vendor detayları yine tutulabilir. Bu translation provider migration sürecini önemli ölçüde kolaylaştırır.

External API Entegrasyonlarını Nasıl İzole Ederiz?

Harici API entegrasyonları değişim ve hata olasılığı yüksek sınırlar olduğu için hexagonal architecture açısından güçlü adapter adaylarıdır. Application core Gateway Port aracılığıyla business ihtiyacını ifade eder. HTTP client adapter vendor endpoint, authentication, serialization ve timeout işlemlerini yönetir. Vendor DTO application veya domain modeline boundary üzerinde map edilir. Anti-Corruption Layer yaklaşımı dış sistemin modelinin iç modele yayılmasını engelleyebilir.

Gateway Port

Gateway Port harici servisten ihtiyaç duyulan capability'yi tanımlar. İsim provider ürününden değil business işlevinden türetilmelidir. CurrencyRateProvider veya FraudChecker buna örnek olabilir. Core bu port üzerinden gerekli bilgiyi ister. Adapter gerçek HTTP veya SDK çağrısını gerçekleştirir.

HTTP Client Adapter

HTTP client adapter endpoint, header, authentication ve serialization detaylarını kapsar. Retry ve timeout gibi teknik resilience politikaları da burada uygulanabilir. HTTP response vendor DTO'ya parse edilir ve application modeline çevrilir. Network exception application core'a olduğu gibi aktarılmamalıdır. Adapter observability için latency ve status metric'leri üretebilir.

Vendor DTO → Domain Model Mapping

Vendor DTO'nun doğrudan domain içinde kullanılması dış sistem contract'ını internal modele bağlar. Mapping katmanı vendor field'larını application veya domain kavramlarına dönüştürür. API field adı değiştiğinde adapter güncellenir. Core aynı business modeli kullanmaya devam eder. Bu mapping özellikle uzun ömürlü entegrasyonlarda güçlü koruma sağlar.

Anti-Corruption Layer

Anti-Corruption Layer dış sistemin modelini kendi domain modelinize dönüştüren koruma sınırıdır. Özellikle legacy veya farklı business terminology kullanan sistemlerde değerlidir. Adapter yalnızca teknik çağrı değil semantic translation da yapabilir. Böylece dış sistemin kavramları domain dilini bozmaz. DDD ile hexagonal architecture birlikte kullanıldığında bu model oldukça doğal çalışır.

API Versiyonu Değiştiğinde Core'u Korumak

Harici API v1'den v2'ye geçtiğinde mümkün olan değişiklik adapter içinde kalmalıdır. Yeni request ve response DTO'lar adapter'da uygulanır. Port contract business ihtiyacı değişmediği sürece aynı kalabilir. Contract ve integration testler migration sırasında güvence sağlar. Bu yaklaşım vendor değişikliklerinin application core'u zincirleme etkilemesini azaltır.

Retry, Timeout ve Circuit Breaker Nerede Olmalıdır?

Retry, timeout ve circuit breaker çoğunlukla infrastructure resilience concern olarak ele alınır. Business core'un HTTP library veya circuit breaker framework'ü bilmesi gerekmez. Adapter harici sistem çağrısının teknik davranışını yönetebilir. Bununla birlikte business retry ile technical retry aynı şey değildir. Ödeme işlemini tekrar denemek business sonuçları doğurabileceği için idempotency ve domain kuralları ayrıca değerlendirilmelidir.

Infrastructure Concern Olarak Resilience

Network çağrıları doğal olarak timeout ve geçici hata riski taşır. Adapter bu durumları teknik seviyede yönetmek için uygun yerdir. Resilience library'nin configuration'ı infrastructure katmanında kalır. Core yalnızca dependency'nin kullanılamadığını ifade eden anlamlı sonucu görür. Böylece library değişikliği business koduna yayılmaz.

Retry Adapter İçinde mi Olmalı?

Teknik retry çoğu durumda adapter veya adapter'ı çevreleyen infrastructure decorator içinde uygulanabilir. Idempotent GET çağrıları buna iyi örnektir. Side effect oluşturan işlemlerde otomatik retry ciddi risk taşıyabilir. Payment veya order creation gibi işlemler için idempotency semantics bilinmelidir. Retry policy operation type'a göre dikkatle tasarlanmalıdır.

Timeout

Her dış network çağrısının sınırsız beklemesi sistem kaynaklarını tüketebilir. Adapter explicit timeout tanımlamalıdır. Timeout süresi use case latency bütçesiyle uyumlu olmalıdır. Çok uzun timeout downstream problemi yukarı servise taşır. Çok kısa timeout ise normal request'leri gereksiz başarısız kılabilir.

Circuit Breaker

Circuit breaker başarısız dependency'ye sürekli çağrı yapmayı geçici olarak durdurabilir. Böylece kaynak tüketimi ve zincirleme failure riski azaltılır. State ve threshold infrastructure configuration içinde yönetilir. Core circuit breaker'ın açık veya kapalı olduğunu bilmek zorunda değildir. Uygulama yalnızca dependency unavailable sonucuna göre business fallback uygulayabilir.

Business Retry ile Technical Retry Farkı

Technical retry aynı operation'ı geçici network hatası nedeniyle tekrar dener. Business retry ise iş sürecinin yeni bir adımı veya kullanıcı kararı olabilir. Örneğin başarısız ödeme için ertesi gün yeniden tahsilat denemesi business workflow'dur. Bunu HTTP client retry policy ile karıştırmak risklidir. İki kavram farklı katmanlarda açık biçimde modellenmelidir.

Mesajlaşma Sistemlerinde Hexagonal Architecture

Kafka veya RabbitMQ gibi mesajlaşma sistemleri de adapter olarak ele alınabilir. Consumer uygulamayı tetikleyen driving adapter, producer ise application core'un kullandığı driven adapter olabilir. Message DTO doğrudan domain'e taşınmamalı ve application command'a map edilmelidir. Acknowledgement, retry ve dead-letter davranışı broker sınırında yönetilebilir. Böylece domain belirli messaging ürününe bağımlı hâle gelmez.

Kafka Consumer Driving Adapter

Kafka consumer topic'ten gelen record'u alır ve application port'a iletir. Record key, offset ve partition core'un business modeli değildir. Adapter payload'ı deserialize eder ve application command oluşturur. Use case sonucu acknowledgement kararını etkileyebilir. Broker configuration ve consumer lifecycle adapter içinde tutulur.

RabbitMQ Consumer Driving Adapter

RabbitMQ consumer queue mesajlarını application behavior'a bağlar. Exchange ve routing key gibi broker kavramları core'a taşınmamalıdır. Adapter mesaj schema'sını application input modeline dönüştürür. Retry veya dead-letter routing teknik failure durumlarında uygulanabilir. Business rejection ise farklı bir sonuç olarak ele alınabilir.

Event Publisher Driven Port

Event Publisher port application'ın dış dünyaya event yayınlama ihtiyacını tanımlar. publishOrderPlaced gibi business odaklı bir API kullanılabilir. Topic adı ve serializer port contract'ının parçası olmak zorunda değildir. Broker adapter bu detayları belirler. Testlerde fake publisher yayınlanan event'leri memory içinde kaydedebilir.

Kafka Producer Driven Adapter

Kafka producer EventPublisher port'unun infrastructure implementation'ıdır. Topic selection, partition key ve serialization işlemleri adapter'da gerçekleşir. Producer error application seviyesinde anlamlı sonuca çevrilebilir. Outbox pattern kullanıldığında broker publish doğrudan transaction içinde yapılmak yerine ayrı süreçle yürütülebilir. Bu karar consistency gereksinimine göre verilmelidir.

Message DTO → Command Mapping

Message DTO broker contract'ını temsil eder ve application command ile aynı olmak zorunda değildir. Schema version veya transport metadata DTO içinde bulunabilir. Adapter bu modeli use case'in ihtiyaç duyduğu command'a dönüştürür. Mapping boundary sayesinde mesaj schema değişiklikleri core'u daha az etkiler. Invalid message handling de adapter'ın sorumluluklarından biridir.

Acknowledgement ve Retry

Acknowledgement broker'a mesajın başarıyla işlendiğini bildirir. Use case tamamlanmadan acknowledgement vermek mesaj kaybı riskini artırabilir. Buna karşılık her business validation error için sonsuz retry yapmak da doğru değildir. Technical transient error ile permanent business rejection ayrılmalıdır. Dead-letter politikası bu sınıflandırmaya göre tasarlanabilir.

Domain Events ve Integration Events

Domain event domain içinde gerçekleşen anlamlı olayı, integration event ise başka sistemlerle paylaşılacak mesaj contract'ını temsil eder. İki kavram aynı event class'ı olmak zorunda değildir. Domain'i Kafka schema veya external consumer beklentisine bağlamamak için mapping uygulanabilir. Event Publisher port integration boundary sağlar. Outbox Pattern ise database değişikliği ile event publication arasında daha güvenilir coordination sunabilir.

Domain Event Nedir?

Domain event domain içinde gerçekleşmiş business olayını ifade eder. OrderPlaced veya SubscriptionExpired buna örnek olabilir. Event domain terminology kullanır ve broker type'larını bilmez. Domain handler aynı process içinde başka business behavior tetikleyebilir. Dış dünyaya gönderilecek event için ayrıca integration mapping yapılabilir.

Integration Event Nedir?

Integration event başka sistemler tarafından tüketilecek public mesaj sözleşmesidir. Schema versioning ve backward compatibility bu seviyede önemlidir. Domain event'ten türetilebilir fakat birebir aynı model olmak zorunda değildir. External consumer ihtiyaçları domain modelin iç yapısını belirlememelidir. Adapter veya application event mapping bu ayrımı korur.

Event Publisher Port

Event Publisher port application'ın event yayınlama niyetini teknik broker'dan ayırır. Port integration event veya business event abstraction'ı kabul edebilir. Kafka, RabbitMQ veya managed messaging service ayrı adapter olarak uygulanabilir. Core topic ve producer configuration detaylarını bilmez. Contract test yayın davranışının beklendiği gibi olduğunu doğrulayabilir.

Domain'i Kafka'dan Bağımsız Tutmak

Domain entity'nin Kafka message type üretmesi güçlü coupling oluşturur. Bunun yerine domain event normal domain nesnesi olarak tasarlanmalıdır. Adapter event'i Kafka payload formatına dönüştürür. Kafka kullanımı sonlandırılırsa domain event modelini değiştirmek gerekmez. Bu ayrım messaging technology lifecycle ile business lifecycle'ı birbirinden uzaklaştırır.

Outbox Pattern

Outbox Pattern database değişikliği ile integration event kaydını aynı transaction içinde güvenilir biçimde saklamayı amaçlar. Ayrı publisher process outbox kayıtlarını broker'a gönderir. Böylece database commit olup event publish başarısız olduğunda ortaya çıkan tutarsızlık riski azaltılır. Pattern ek storage ve processing bileşeni getirir. Kullanım kararı event delivery gereksiniminin önemine göre verilmelidir.

Atomicity Problemi

Database kaydı başarılı olup broker publish başarısız olabilir. Tersi durumda mesaj yayınlanıp database transaction rollback olabilir. Bu iki bağımsız sistem arasında doğrudan atomicity sağlamak zordur. Outbox aynı database transaction içinde event niyetini kaydederek problemi daraltır. Publisher daha sonra mesajı güvenilir biçimde gönderebilir.

Outbox Table

Outbox table application transaction sırasında integration event verisini saklar. Event id, type, payload ve timestamp gibi alanlar bulunabilir. Business record ile outbox record aynı transaction içinde commit edilir. Publisher işlenmemiş kayıtları okuyup broker'a gönderir. Retention ve cleanup süreçleri operational planın parçasıdır.

Event Publisher

Outbox publisher tablo veya stream üzerindeki bekleyen event'leri broker'a gönderir. Publish başarılı olduğunda kayıt işlenmiş olarak işaretlenebilir veya silinebilir. Duplicate publish mümkün olduğu için consumer idempotency önemlidir. Monitoring publisher lag ve failure durumlarını görünür kılmalıdır. Bu bileşen infrastructure concern olarak core business modelden ayrı tutulabilir.

Idempotency Nasıl Tasarlanır?

Idempotency aynı işlemin birden fazla kez alınması durumunda business sonucunun kontrolsüz biçimde tekrar oluşmamasını sağlar. HTTP retry, message redelivery veya network timeout duplicate operation yaratabilir. Idempotency key veya business identifier bu tekrarları tanımaya yardımcı olur. Infrastructure duplicate tespiti yapabilir fakat hangi işlemin gerçekten aynı business operation olduğunu core belirleyebilir. Bu nedenle sorumluluk teknik ve business katmanları arasında bilinçli biçimde ayrılmalıdır.

Duplicate HTTP Request

Kullanıcı veya client timeout nedeniyle aynı POST request'i tekrar gönderebilir. Eğer operation ödeme veya sipariş oluşturma ise duplicate side effect ciddi problem oluşturur. Idempotency key request adapter tarafından alınabilir ve application use case'e aktarılabilir. Persistence layer daha önce işlenmiş key'i kontrol edebilir. Business response önceki sonuçla tutarlı biçimde döndürülebilir.

Duplicate Message

At-least-once messaging sistemlerinde aynı mesajın birden fazla kez teslim edilmesi normal kabul edilmelidir. Consumer yalnızca broker'ın tek teslim yapacağını varsaymamalıdır. Message id veya business key üzerinden işlem kaydı tutulabilir. Domain operation zaten doğal olarak idempotent tasarlanabilir. Duplicate handling strategy message semantics'e göre belirlenmelidir.

Idempotency Key

Idempotency key istemcinin veya producer'ın aynı operation'ı tanımlamasına yardımcı olur. Key storage ve expiration policy infrastructure tarafında yönetilebilir. Ancak key'in hangi business scope içinde benzersiz olduğu application tarafından bilinmelidir. Aynı key'e farklı payload gelmesi hata olarak ele alınabilir. Bu mekanizma özellikle ödeme ve order creation API'lerinde değerlidir.

Business Core ile Infrastructure Sorumluluğunu Ayırmak

Infrastructure duplicate request'i tespit edebilir fakat tekrar işlemenin business anlamını her zaman bilemez. Core hangi state değişikliğinin tekrar uygulanmasının güvenli olduğunu belirler. İyi tasarım technical deduplication ile domain invariant'larını birlikte kullanır. Her şeyi adapter'a bırakmak business riskini gizleyebilir. Her şeyi domain'e taşımak ise broker ve storage detaylarını core'a sokabilir.

Hexagonal Architecture ile Test Stratejisi

Hexagonal architecture'ın en güçlü sonuçlarından biri test seviyelerinin daha net ayrılabilmesidir. Domain unit testleri framework veya database olmadan çalışabilir. Use case testleri driven port'ları fake implementation ile kullanabilir. Adapter integration testleri gerçek database veya API davranışını doğrular. E2E test sayısı ise kritik akışlarla sınırlı tutulabilir ve bu yapı hızlı feedback ile gerçek entegrasyon güvenini dengeleyebilir.

Domain Unit Tests

Domain unit test entity, value object ve business rule davranışlarını doğrular. Dış I/O bulunmadığı için çok hızlı çalışmalıdır. Framework context veya database başlatılması gerekmez. Edge case ve invariant senaryoları ayrıntılı biçimde test edilebilir. Domain modelin gerçekten bağımsız olup olmadığını anlamak için güçlü bir göstergedir.

Use Case Tests

Use case test application service'i fake repository ve stub gateway gibi test double'larla çalıştırır. Orchestration, error handling ve business flow kontrol edilir. HTTP veya messaging adapter devreye girmez. Bu nedenle testler E2E testlere göre daha hızlı ve stabil olabilir. Port tasarımının test edilebilirliği bu seviyede doğrudan görünür.

Driving Adapter Tests

Driving adapter testleri HTTP routing, serialization ve mapping gibi boundary davranışlarını doğrular. Controller testinde core use case mock veya fake olabilir. Amaç business rule'ları tekrar test etmek değildir. Status code, validation ve request mapping gibi web sorumlulukları kontrol edilir. Böylece adapter'ın protocol contract'ı güvence altına alınır.

Driven Adapter Integration Tests

Driven adapter integration test gerçek database veya external service sandbox ile implementation'ın port contract'ına uyduğunu doğrular. SQL query, ORM mapping ve transaction behavior burada test edilir. Test container kullanımı environment tutarlılığını artırabilir. Bu testler unit testlerden daha yavaş olsa da kritik infrastructure risklerini yakalar. Core test suite'i bu nedenle gerçek database'e bağımlı olmak zorunda kalmaz.

End-to-End Tests

E2E test uygulamanın tüm önemli parçalarını birlikte doğrular. HTTP request'ten database veya messaging sonucuna kadar gerçek akış çalıştırılabilir. Yavaş ve kırılgan olabileceği için sayısını kontrollü tutmak faydalıdır. Kritik business flow ve deployment smoke test için yüksek değer sağlar. Hexagonal architecture E2E test ihtiyacını kaldırmaz, sadece her davranışı E2E ile doğrulama zorunluluğunu azaltır.

Test Pyramid

Test pyramid çok sayıda hızlı unit test, daha az integration test ve sınırlı E2E test yaklaşımını anlatır. Hexagonal boundaries bu dağılımı uygulamayı kolaylaştırır. Core davranışlarının büyük bölümü hızlı test edilebilir. Adapter ve infrastructure için daha ağır testler ayrı tutulur. CI süresi ve hata teşhisi açısından dengeli bir test stratejisi oluşur.

Test Double Adapter'lar

Test double'lar gerçek dependency yerine test sırasında kontrollü davranış sağlayan implementation'lardır. Fake, stub, mock ve spy farklı test amaçlarına hizmet eder. Hexagonal architecture driven port'lar sayesinde bu implementation'ları doğal biçimde kullanmayı kolaylaştırır. Ancak test double'ın gerçek adapter behavior'ını eksiksiz temsil ettiği varsayılmamalıdır. Contract ve integration testler production implementation'ı ayrıca doğrulamalıdır.

Fake

Fake gerçek sistemden daha basit çalışan fakat işlevsel implementation'dır. In-memory repository buna iyi örnektir. Veri listede tutulabilir ve normal save veya find işlemleri gerçekleştirilebilir. Use case testlerinde hızlı ve anlaşılır behavior sağlar. Gerçek database semantics farklı olabileceği için integration test yine gereklidir.

Stub

Stub belirli input için önceden tanımlanmış output döndürür. PaymentGateway stub her çağrıda başarılı ödeme sonucu verebilir. Failure scenario için farklı stub kullanılabilir. Testin yalnızca dependency sonucuna ihtiyacı olduğunda sade çözüm sunar. Interaction count gibi ayrıntıları doğrulamak zorunda değildir.

Mock

Mock belirli method çağrılarının gerçekleşip gerçekleşmediğini doğrulamak için kullanılabilir. Fazla mock kullanımı testleri implementation detail'e bağlayabilir. Business sonucu doğrulamak mümkünse state-based test daha dayanıklı olabilir. Yine de notification gönderildi mi gibi belirli interaction senaryolarında mock faydalıdır. Mock kullanımının test amacını açık tutması önemlidir.

Spy

Spy çağrıları kaydeden ve daha sonra test tarafından incelenebilen implementation'dır. Fake event publisher yayınlanan event listesini saklayabilir. Test use case sonrasında hangi event'lerin üretildiğini kontrol eder. Mock framework kullanmadan basit interaction verification sağlar. Özellikle port API'si küçük olduğunda okunabilir testler üretir.

In-Memory Repository

In-memory repository repository port'unu memory data structure ile gerçekleştirir. Use case testlerinde database kurulum ihtiyacını ortadan kaldırır. Transaction ve query semantics gerçek database ile birebir aynı değildir. Bu nedenle production repository integration testleri ayrıca çalıştırılmalıdır. In-memory adapter test suite'i hızlandıran bir araç olarak görülmelidir.

Test Double Ne Zaman Kullanılmalı?

Test double dış I/O testin asıl konusu olmadığında kullanışlıdır. Domain veya application behavior'ı hızlı ve deterministik test etmek için özellikle değerlidir. Adapter implementation'ın kendisini test ederken gerçek dependency veya uygun test environment tercih edilmelidir. Her collaborator'ı mock yapmak test tasarımını kırılgan hâle getirebilir. Test seviyesi ve hedeflenen risk doğru seçilmelidir.

Contract Testing

Contract testing port ile adapter arasındaki davranış beklentisinin korunmasına yardımcı olur. Aynı port'un farklı adapter'ları varsa ortak contract test suite çalıştırılabilir. External API entegrasyonlarında provider veya consumer contract ayrıca doğrulanabilir. Bu yaklaşım fake adapter ile production adapter arasında behavior farklarını daha erken yakalamaya yardımcı olur. Contract test özellikle teknoloji değişikliklerinde güvenli migration için güçlü bir araçtır.

Port Contract'ını Test Etmek

Port contract test interface'in beklenen davranışını örnek senaryolarla tanımlar. Repository save ve find operations için ortak test suite oluşturulabilir. Farklı implementation'lar aynı suite üzerinden test edilir. Böylece in-memory ve PostgreSQL adapter arasında temel semantics korunur. Contract implementation detayını değil observable behavior'ı doğrulamalıdır.

Adapter'ın Contract'a Uyduğunu Doğrulamak

Yeni adapter eklendiğinde mevcut port contract testleri tekrar kullanılabilir. Bu yöntem implementation'ın application beklentilerini karşıladığını doğrular. Database veya vendor-specific behavior ayrıca özel testlerle kontrol edilebilir. Ortak contract migration riskini azaltır. Port tasarımında belirsiz davranışlar test yazarken daha görünür hâle gelir.

External API Contract Tests

External API contract test vendor endpoint veya sandbox ile entegrasyon beklentilerini doğrular. Request field, response mapping ve error behavior test edilebilir. API değişikliği olduğunda adapter testleri erken sinyal verir. Testlerin vendor availability'ye aşırı bağımlı olmaması için sınırlı ve planlı çalıştırılması gerekebilir. Mock server ile schema doğrulama da destekleyici yöntem olabilir.

Consumer-Driven Contracts

Consumer-driven contract downstream consumer'ın gerçekten ihtiyaç duyduğu API behavior'ını sözleşme olarak ifade eder. Microservice ortamında provider değişikliklerinin consumer'ları bozmasını erken tespit edebilir. Contract version control içinde tutulabilir. CI pipeline provider ve consumer tarafında doğrulama yapabilir. Bu yöntem özellikle bağımsız deployment yapan ekiplerde değerlidir.

Pact Kullanımı

Pact consumer-driven contract testing için kullanılabilen araçlardan biridir. Consumer beklentileri contract artifact olarak üretilebilir ve provider doğrulaması yapılabilir. Araç architecture'ın kendisi değildir ve yalnızca ihtiyaç varsa kullanılmalıdır. Çok küçük sistemlerde ek süreç maliyeti faydasını aşabilir. Bağımsız microservice ekiplerinde ise API compatibility riskini önemli ölçüde azaltabilir.

Mimari Sınırları Otomatik Olarak Test Etmek

Mimari kurallar yalnızca dokümantasyonda kaldığında zaman içinde ihlal edilme ihtimali yüksektir. Domain'in infrastructure import etmemesi veya adapter paketlerinin belirli yönlerde bağımlılık kurması otomatik testlerle doğrulanabilir. Java, .NET ve TypeScript ekosistemlerinde farklı architecture testing araçları bulunur. CI pipeline bu testleri her değişiklikte çalıştırabilir. Böylece dependency rule iyi niyete değil otomatik kontrole dayanır.

Domain → Infrastructure Import'unu Engellemek

En temel architecture test domain package'in infrastructure package'e bağımlı olmadığını doğrulayabilir. Bu kural yanlış import eklendiği anda build'i durdurur. Büyük ekiplerde code review sırasında gözden kaçabilecek boundary ihlallerini erken yakalar. Kural çok katıysa gerekli istisnalar açık biçimde belgelenebilir. Otomatik kontrol mimari kararın günlük geliştirme akışında korunmasını sağlar.

Java'da ArchUnit

ArchUnit Java class dependency'lerini test koduyla doğrulamaya yardımcı olabilir. Domain package'in Spring veya persistence package'lerine bağımlı olmaması gibi kurallar yazılabilir. Test normal CI suite içinde çalışır. Mimari kural version control'de görünür hâle gelir. Araç kullanmak zorunlu değildir fakat büyük Java projelerinde güçlü otomasyon sağlayabilir.

.NET Architecture Tests

.NET projelerinde assembly dependency veya namespace kurallarını test eden architecture test yaklaşımı kullanılabilir. Domain assembly'nin Infrastructure assembly reference'ı taşımadığı doğrulanabilir. Reflection veya özel testing library'leri bu kontrolleri kolaylaştırabilir. CI üzerinde her pull request'te çalıştırılması boundary drift'i azaltır. Kurallar proje architecture dokümanıyla uyumlu tutulmalıdır.

TypeScript Dependency Rules

TypeScript projelerinde ESLint rule, dependency analysis aracı veya custom script ile import sınırları kontrol edilebilir. Domain klasörünün adapter klasörünü import etmesi engellenebilir. Monorepo build araçları module boundary kurallarını destekleyebilir. Compile-time kontrol yanlış dependency'yi erken yakalar. Böylece decorator ve framework import'larının core'a sızması daha zor hâle gelir.

CI'da Architecture Test Çalıştırmak

Architecture test yalnızca geliştirici bilgisayarında çalışıyorsa kolayca atlanabilir. CI pipeline'a eklemek tüm değişikliklerde ortak güvence sağlar. Test hızlı olmalı ve ihlal mesajı geliştiriciye neyin yanlış olduğunu açıkça göstermelidir. Gereksiz yüzlerce kural developer experience'ı bozabilir. En kritik dependency ve boundary kurallarıyla başlamak daha iyi sonuç verir.

Hexagonal Architecture ve Domain-Driven Design

Hexagonal Architecture ile Domain-Driven Design aynı şey değildir. Hexagonal yaklaşım application core ile dış dünya arasındaki dependency sınırına odaklanır. DDD ise domain bilgisini modelleme, bounded context ve ubiquitous language gibi daha geniş kavramlar sunar. İki yaklaşım birlikte kullanıldığında hexagon dış sınırı korurken DDD içeride zengin business model oluşturabilir. Ancak DDD kullanmadan da hexagonal architecture uygulanabilir.

Aynı Şeyler mi?

Hayır, iki yaklaşım farklı problemlere odaklanır. Hexagonal architecture dependency direction ve adapter boundary üzerinden teknik izolasyon sağlar. DDD business domain'in modellenmesine yönelik yöntem ve pattern'ler sunar. Bir sistem DDD kullanmadan port ve adapter yapısına sahip olabilir. Aynı şekilde DDD uygulanıp farklı architecture style tercih edilebilir.

Entity

DDD entity kimliği olan ve zaman içinde state değiştirebilen domain nesnesidir. Hexagonun core bölümünde doğal olarak bulunabilir. Persistence entity ile aynı olması zorunlu değildir. Entity business invariant'larını koruyabilir. Framework bağımsız tutulması DDD modelinin teknik detaylardan korunmasına yardımcı olur.

Value Object

Value object kimlik yerine değerleriyle tanımlanır. Currency, Money veya Address gibi kavramlar buna örnek olabilir. Immutable tasarım sık kullanılan bir yaklaşımdır. Validation constructor veya factory içinde uygulanabilir. Hexagonal core içinde framework bağımsız value object kullanmak business dilini güçlendirir.

Aggregate

Aggregate birlikte tutarlı kalması gereken entity ve value object grubudur. Aggregate root dış erişimin kontrol edildiği ana entity'dir. Repository genellikle aggregate root seviyesinde tasarlanır. Transaction boundary aggregate consistency ile uyumlu olabilir. Hexagonal port yapısı persistence implementation'ını aggregate modelinden ayırmaya yardımcı olur.

Domain Service

Domain service tek entity'ye ait olmayan iş davranışını taşır. DDD içinde domain dilinin parçasıdır. Hexagonal core içinde framework bağımsız kalabilir. Dış dependency gerekiyorsa uygun port üzerinden erişim sağlanabilir. Service'in application orchestration yerine gerçek domain rule taşıması önemlidir.

Repository

DDD repository aggregate collection benzeri bir erişim abstraction'ı sunar. Hexagonal architecture açısından repository doğal driven port örneğidir. Interface core içinde, implementation infrastructure adapter'da bulunabilir. Query API business ihtiyacına göre tasarlanır. ORM-specific repository doğrudan core'a taşınmadığında iki yaklaşım iyi biçimde birleşir.

Bounded Context

Bounded Context belirli domain modelinin geçerli olduğu anlam sınırını tanımlar. Aynı kelime farklı context'lerde farklı model veya kurala sahip olabilir. Hexagonal architecture her context'in dış entegrasyon sınırlarını belirgin hâle getirebilir. Context'ler arası iletişim port ve adapter üzerinden tasarlanabilir. Bu yapı modular monolith ve microservice modellerinde faydalıdır.

DDD Hexagonun İçini Nasıl Zenginleştirir?

Hexagonal architecture core'un neye bağımlı olmaması gerektiğini güçlü biçimde anlatır. DDD ise bu core'un business açısından nasıl modellenebileceğine daha fazla araç sunar. Aggregate, value object, domain service ve domain event bu araçlardan bazılarıdır. Böylece core yalnızca procedural use case kodundan oluşmak yerine zengin business model taşıyabilir. Karmaşık domainlerde iki yaklaşım birlikte güçlü sonuç verebilir.

Her Bounded Context Ayrı Bir Hexagon Olabilir mi?

Evet, özellikle modular monolith veya microservice tasarımlarında her bounded context kendi application core ve adapter sınırına sahip olabilir. Bu yaklaşım context'ler arasında doğrudan database veya internal class bağımlılığını azaltır. Communication internal port, message veya explicit API üzerinden gerçekleştirilebilir. Her modülü ayrı hexagon yapmak otomatik kural değildir fakat ownership ve domain sınırları güçlü olduğunda yararlı olabilir. Böylece gelecekte belirli context'i ayrı servise taşımak da daha kontrollü hâle gelir.

Modular Monolith

Modular monolith tek deployment içinde birden fazla güçlü module boundary barındırır. Her modül kendi domain ve application katmanına sahip olabilir. Adapter'lar module dışı integration noktalarını yönetir. Database fiziksel olarak ortak olsa bile ownership mantıksal olarak ayrılabilir. Architecture test modüller arası yasak import'ları engelleyebilir.

Context Boundary

Context boundary hangi model ve business language'in hangi modülde geçerli olduğunu belirler. Başka context'in entity'sini doğrudan kullanmak coupling yaratabilir. Bunun yerine public application port veya integration model kullanılabilir. Böylece internal model bağımsız gelişebilir. Boundary ownership ve ekip sorumluluğunu da daha net hâle getirir.

Context'ler Arası Port

Bir bounded context başka context'ten capability bekliyorsa port tanımlayabilir. Adapter aynı process içinde diğer module API'sini çağırabilir veya messaging kullanabilir. Core iletişimin fiziksel biçimini bilmez. Bu yaklaşım gelecekte deployment topology değiştiğinde etkileri azaltabilir. Port contract context'lerin ortak business dilini dikkatle yansıtmalıdır.

Internal Adapter

Internal adapter aynı uygulama process'i içindeki başka modülle iletişim kuran adapter olabilir. Network çağrısı yapmak zorunlu değildir. Adapter karşı context'in public interface'ini çağırıp kendi port contract'ına mapping yapar. Böylece modüller birbirinin internal class'larını import etmez. Microservice'e geçiş gerekirse internal adapter remote adapter ile değiştirilebilir.

Anti-Corruption Layer

Bounded context'lerin modelleri farklı anlamlar taşıyabilir. Anti-Corruption Layer bir context'in modelini diğerine doğrudan yaymak yerine translation yapar. Bu translation internal adapter içinde uygulanabilir. Böylece her context kendi ubiquitous language'ini korur. Özellikle legacy module entegrasyonlarında ciddi değer sağlar.

Hexagonal Architecture ve Clean Architecture Arasındaki Fark

Ports and Adapters ile Clean Architecture arasındaki farklar sorulduğunda önce ortak noktayı görmek gerekir. Her iki yaklaşım business logic'i framework ve infrastructure detaylarından korumayı hedefler. Clean Architecture daha belirgin iç halkalar ve role separation önerirken Hexagonal Architecture esas olarak port ve adapter boundary'sine odaklanır. Pratik uygulamalar birbirine oldukça benzeyebilir. Seçim çoğu zaman ekip terminolojisi ve ihtiyaç duyulan yönlendirme seviyesine bağlıdır.

Ortak Dependency Rule

Her iki yaklaşımda source dependency business core'a doğru yönelmelidir. Framework, database ve UI daha dışta kabul edilir. Core dış implementation'ları import etmez. Dependency inversion abstraction üzerinden sağlanır. Bu ortak kural iki mimarinin pratik kod yapılarının benzer görünmesinin temel nedenidir.

Hexagonal Architecture Ne Belirler?

Hexagonal architecture application ile dış dünya arasındaki port ve adapter ilişkisine odaklanır. Driving ve driven adapter ayrımı iletişim yönünü açık hâle getirir. İç modelin tam olarak kaç katmandan oluşacağını zorunlu kılmaz. DDD veya daha basit application model kullanılabilir. Bu nedenle yaklaşım daha az prescriptive kabul edilir.

Clean Architecture Ne Ekler?

Clean Architecture entities, use cases, interface adapters ve frameworks gibi daha belirgin halkalar tarif eder. Her iç katmanın dış katmanlardan bağımsız olması hedeflenir. Use case ve entity ayrımı daha görünür biçimde vurgulanır. Bu yapı bazı ekipler için daha açıklayıcı rehber sağlar. Fazla mekanik uygulanırsa gereksiz class sayısı oluşturma riski de vardır.

Entities ve Use Cases

Clean Architecture'da entities enterprise business rules, use cases ise application-specific behavior olarak ayrıştırılır. Hexagonal design aynı ayrımı yapabilir fakat bunu zorunlu terminoloji olarak tanımlamaz. DDD kullanan ekip entity ve domain service kavramlarını tercih edebilir. Önemli olan iç business modelin dış teknolojiye bağımlı olmamasıdır. Terminoloji amaçtan daha önemli hâle getirilmemelidir.

Interface Adapters

Clean Architecture interface adapters katmanı dış formatlarla use case ve entity modelleri arasında dönüşüm yapar. Hexagonal architecture'daki adapter kavramıyla büyük ölçüde benzer görev üstlenir. Controller, presenter ve repository implementation bu alanda bulunabilir. Mapping burada gerçekleşir. Bu ortaklık nedeniyle iki yaklaşım pratik projelerde kolayca birlikte yorumlanabilir.

Frameworks & Drivers

Clean Architecture'ın en dış halkasında framework ve driver detayları yer alır. Database, web framework ve cihaz gibi bileşenler burada düşünülür. Hexagonal architecture da bunları adapter olarak core'un dışında tutar. İki yaklaşım değişebilir teknolojileri sistem merkezinden uzaklaştırır. Fark çoğunlukla görsel model ve terminoloji seviyesindedir.

Hangisi Daha Az Prescriptive?

Hexagonal Architecture genellikle daha az prescriptive kabul edilir. İçeride kaç layer veya role bulunması gerektiğini ayrıntılı biçimde dikte etmez. Clean Architecture daha tanımlı halkalar sunar. Bazı ekipler bu yapıdan rehberlik kazanırken bazıları daha sade port ve adapter modelini tercih eder. İki yaklaşımın amacı yarışmak değil bağımlılıkları daha sürdürülebilir hâle getirmektir.

Hexagonal Architecture ve Onion Architecture Arasındaki Fark

Onion Architecture da business domain'i merkeze alır ve dependency'lerin iç katmanlara doğru yönelmesini ister. Hexagonal Architecture ise aynı fikri port ve adapter etkileşimi üzerinden anlatır. Onion görsel olarak concentric layer yapısını vurgularken hexagonal model dış dünya bağlantı noktalarını daha görünür kılar. Pratik projede iki yaklaşımın sonucu oldukça benzer olabilir. Ekip için hangi mental model dependency sınırlarını daha iyi anlatıyorsa o terminoloji kullanılabilir.

Concentric Layers

Onion Architecture iç içe halkalar üzerinden dependency direction anlatır. Domain model en merkezde bulunur. Application service gibi katmanlar domain çevresinde yer alabilir. Infrastructure en dış halkadadır. Bu görsel model domain merkezli tasarım için güçlü bir rehberdir.

Ports & Adapters

Hexagonal Architecture dış etkileşimleri port ve adapter olarak modeller. Web ve database aynı çekirdeğin farklı dış bağlantıları olarak görülür. Driving ve driven ayrımı call direction hakkında ek kavramsal netlik sağlar. İç katman sayısı daha serbesttir. Bu yaklaşım özellikle integration boundary'lerini konuşurken anlaşılır olabilir.

Dependency Direction

Her iki yaklaşımda bağımlılık yönü merkeze doğru olmalıdır. Infrastructure domain'e bağlı olabilir fakat domain infrastructure'a bağlı olmaz. Runtime control flow bunun tersine gidebilir. Interface ve dependency inversion bu ayrımı sağlar. Bu ortak prensip iki mimarinin teknik olarak yakın sonuç vermesini sağlar.

Pratikte Nerede Ayrışırlar?

Pratik fark çoğu zaman klasör isimlerinden ve takımın modeli nasıl anlattığından ibarettir. Onion ekipleri domain, application ve infrastructure halkalarını daha belirgin ayırabilir. Hexagonal ekipler inbound ve outbound adapter terimlerini daha fazla kullanabilir. Aynı proje iki modelin fikirlerinden birlikte yararlanabilir. Önemli olan terminolojik saflık değil dependency sınırlarının korunmasıdır.

Hexagonal Architecture ile CQRS Kullanmak

CQRS command ve query davranışlarının ayrılmasını öneren bağımsız bir pattern'dir. Hexagonal Architecture ile birlikte kullanılabilir fakat zorunlu değildir. Command ve query için ayrı primary port'lar tanımlanabilir. Read adapter ve write adapter farklı persistence stratejileri kullanabilir. Sistem basitse tek model kullanmak daha düşük maliyetli olabilir.

Command Port

Command port state değiştiren use case'leri ifade eder. PlaceOrder veya CancelOrder gibi business action'lar buna örnektir. Command input modeli application katmanına ait olabilir. HTTP request DTO'dan bağımsız tutulur. Command handler domain aggregate ve driven port'ları kullanabilir.

Query Port

Query port veri okuma use case'lerini temsil eder. Domain aggregate yüklemek yerine doğrudan read model döndürmek bazı sistemlerde daha verimli olabilir. Query behavior state değişikliği yapmamalıdır. Read requirement'ları write modelden bağımsız optimize edilebilir. CQRS kullanılıyorsa bu separation daha belirgin hâle gelir.

Read Adapter

Read adapter query port için database view, search engine veya read replica kullanabilir. Mapping sonucu application response modeline dönüştürür. Domain entity oluşturmak gerekmeyebilir. Bu yaklaşım karmaşık read query'lerinde performans avantajı sağlayabilir. Ancak ayrı read infrastructure yeni operasyon maliyeti getirir.

Write Adapter

Write adapter aggregate persistence ve transaction gereksinimlerine odaklanır. Relational database güçlü consistency için kullanılabilir. Read model farklı teknolojiyle çalışsa bile application command behavior aynı kalabilir. Event veya CDC ile read model güncellenebilir. Consistency beklentisi business gereksinimine göre tasarlanmalıdır.

CQRS Kullanmak Zorunlu mu?

Hayır, hexagonal architecture CQRS kullanmayı gerektirmez. Çoğu sistem tek application model ile gayet iyi çalışabilir. CQRS read ve write ihtiyaçları önemli ölçüde ayrıştığında değer üretir. Gereksiz kullanıldığında daha fazla model, mapping ve eventual consistency yükü oluşturur. Pattern gerçek problem olduğunda eklenmelidir.

Hexagonal Architecture ve Event Sourcing

Event Sourcing aggregate state'ini mevcut kayıt yerine geçmiş domain event'lerinden üretmeyi amaçlayan ayrı bir pattern'dir. Hexagonal Architecture ile birlikte kullanılabilir fakat iki kavram birbirine bağlı değildir. Event Store driven port olarak tanımlanabilir ve farklı storage adapter'ları uygulanabilir. Projection adapter read model oluşturabilir. Bu yapı güçlü audit ve temporal model gereksinimlerinde anlamlı olabilir.

Event Store Driven Port

Event Store port aggregate event stream'ini okuma ve yeni event ekleme ihtiyacını tanımlar. Core belirli database ürününü bilmez. Expected version veya concurrency semantics business model için gerekli seviyede ifade edilebilir. Adapter gerçek event store teknolojisini uygular. Test implementation memory üzerinde çalışabilir.

Event Store Adapter

Event Store adapter gerçek persistence teknolojisiyle event stream saklar. Serialization, stream naming ve connection detayları burada bulunur. Vendor SDK core'a taşınmamalıdır. Concurrency exception application seviyesinde anlamlı error'a çevrilebilir. Integration test event ordering ve persistence behavior'ını doğrulamalıdır.

Domain Model

Event-sourced domain model mevcut state'i geçmiş event'leri replay ederek oluşturabilir. Aggregate command aldığında yeni domain event üretir. Framework ve database detayları bu davranışın parçası değildir. Hexagonal core bu modeli doğal biçimde barındırabilir. Event sourcing'in ek learning ve operation cost'u ayrıca değerlendirilmelidir.

Projection Adapter

Projection adapter domain veya integration event'leri okuyup query için optimize edilmiş read model oluşturabilir. Relational table, search index veya farklı storage kullanılabilir. Projection failure ve replay mekanizması infrastructure concern'dür. Query port bu read modelden veri okuyabilir. CQRS ve event sourcing birlikte kullanıldığında bu yapı sık görülür.

İki Pattern'in Bağımsız Olması

Hexagonal architecture kullanmak event sourcing gerektirmez. Aynı şekilde event sourcing uygulayan sistem mutlaka hexagonal boundary kullanmak zorunda değildir. İki pattern farklı problem çözer. Biri dependency separation, diğeri state persistence modeline odaklanır. Gereksinim yoksa event sourcing eklemek mimari maliyeti gereksiz artırabilir.

Modular Monolith'te Hexagonal Architecture

Modular monolith, hexagonal architecture için oldukça uygun bir uygulama alanıdır. Her modül kendi application core, input port ve output port yapılarına sahip olabilir. Modüller birbirinin database tablosuna veya internal class'ına doğrudan erişmek yerine public contract üzerinden iletişim kurabilir. Bu yapı deployment basitliğini korurken domain sınırlarını güçlendirir. İleride belirli modülün microservice'e taşınması gerekirse mevcut port ve adapter sınırları migration riskini azaltabilir.

Module Boundary

Module boundary belirli business capability'nin kod ve data ownership sınırını tanımlar. Başka modül internal entity veya repository'yi doğrudan import etmemelidir. Public application port modülün dış API'sini oluşturabilir. Architecture test bu sınırı otomatik koruyabilir. Bu yaklaşım monolith içinde microservice benzeri ownership sağlar.

Her Modülde Application Core

Her modül kendi use case, domain model ve driven port'larına sahip olabilir. Ortak framework runtime kullanılmasına rağmen business core'lar birbirinden bağımsız tutulur. Shared utility minimum seviyede olmalıdır. Modüller business language bakımından kendi bounded context'lerini koruyabilir. Bu yapı büyük monolith kod tabanında cognitive load'u azaltır.

Module-to-Module Communication

Modüller arası iletişim doğrudan method call, internal event veya application port üzerinden yapılabilir. Önemli olan internal implementation detail'e bağımlı olmamaktır. Bir modül başka modülün database tablosunu doğrudan güncellememelidir. Public contract değişiklikleri versioning veya compatibility yaklaşımıyla yönetilebilir. Communication modeli deployment topology'den bağımsız düşünülmelidir.

Shared Database Problemi

Modular monolith tek database kullanabilir fakat tablo ownership açık olmalıdır. Bir modül başka modülün tablolarına doğrudan query yazarsa code boundary veri seviyesinde kırılır. Repository ve module API üzerinden erişim sağlamak daha sürdürülebilir olur. Database schema fiziksel olarak ortak olsa bile logical ownership korunabilir. Microservice extraction ihtiyacı olduğunda bu disiplin büyük avantaj sağlar.

Microservice'e Geçişi Kolaylaştırmak

Modül zaten clear port ve adapter boundary'lerine sahipse ayrı deployable service'e taşımak daha kontrollü olabilir. Internal adapter network adapter'a dönüştürülebilir. Data ownership önceden ayrılmışsa migration daha basit olur. Yine de microservice'e geçiş messaging, observability ve distributed consistency gibi yeni problemler getirir. Hexagonal architecture bu problemleri yok etmez, sadece core'u daha iyi izole eder.

Microservice'lerde Hexagonal Architecture

Microservice'lerde her servis kendi bounded context veya business capability'sini temsil ediyorsa hexagonal architecture güçlü bir iç yapı sunabilir. REST, messaging, database ve external service bağlantıları farklı adapter'lar olarak ele alınır. Core domain ve use case davranışını taşır. Bununla birlikte distributed system problemlerini adapter arkasında tamamen görünmez saymak doğru değildir. Latency, consistency ve failure semantics application design üzerinde gerçek etkiye sahiptir.

Her Microservice Bir Hexagon mu?

Bir microservice ayrı bir hexagon olarak tasarlanabilir fakat zorunlu değildir. Servis çok basitse port ve adapter katmanlarının maliyeti gereksiz olabilir. Karmaşık business logic ve çok sayıda integration varsa yapı daha fazla değer sağlar. Service boundary ile hexagonal boundary farklı kavramlardır. Mimari servis içindeki dependency modelini tanımlar.

REST Adapter

REST adapter servis API'sini dış dünyaya sunar. Endpoint, request validation ve response mapping burada bulunur. Core use case HTTP hakkında bilgi taşımaz. Error mapping de adapter seviyesinde yapılabilir. API versioning domain modelin iç yapısından bağımsız tutulmalıdır.

Messaging Adapter

Messaging adapter event veya command mesajlarını application core'a bağlar. Consumer driving adapter, producer driven adapter olabilir. Broker metadata ve serializer dış katmanda kalır. Message schema application modelden ayrı tutulabilir. Retry ve acknowledgement distributed failure semantics'e göre tasarlanmalıdır.

Database Adapter

Database adapter repository port'larını gerçekleştirir. ORM veya native driver yalnızca infrastructure katmanında bulunur. Domain entity persistence modelden ayrılabilir. Transaction behavior integration testlerle doğrulanır. Core database technology choice hakkında doğrudan bilgi taşımaz.

External Service Adapter

External service adapter başka microservice veya third-party API ile iletişim kurar. HTTP client, service discovery ve authentication gibi detayları kapsar. Gateway port application ihtiyacını business dilinde tanımlar. Timeout ve resilience politikaları adapter çevresinde uygulanabilir. Vendor veya service response modelleri core'a map edilmeden sokulmamalıdır.

Distributed System Karmaşıklığını Gizlememek

Adapter sınırı distributed system davranışını tamamen yok edemez. Remote call local function call'dan farklı latency ve failure özelliklerine sahiptir. Business operation eventual consistency veya partial failure ile karşılaşabilir. Application design bu gerçekleri açık biçimde ele almalıdır. Hexagonal architecture teknik detayları izole ederken sistem davranışını inkâr etmemelidir.

Authentication ve Authorization Hexagonun Neresinde?

Authentication çoğunlukla infrastructure concern olarak değerlendirilirken authorization bazı durumlarda business rule olabilir. JWT parsing, session veya identity provider entegrasyonu driving adapter sınırında tutulabilir. Adapter doğrulanmış principal bilgisini application context veya CurrentUser port'u üzerinden core'a aktarabilir. Domain'in JWT claim isimlerini bilmesi gerekmez. Yetkilendirme kararı iş kuralına bağlıysa application veya domain seviyesinde uygulanabilir.

Authentication Infrastructure Concern

Authentication kullanıcının kim olduğunu doğrulama sürecidir. Token doğrulama, certificate veya session handling framework ve infrastructure araçlarıyla gerçekleştirilir. Bu detayların domain'e taşınması gereksiz coupling oluşturur. Adapter doğrulanmış kullanıcı kimliğini daha sade application modeline dönüştürür. Böylece identity provider değişikliği core'u mümkün olduğunca az etkiler.

Authorization Business Rule Olabilir mi?

Evet, bazı authorization kararları doğrudan business rule niteliğindedir. Örneğin yalnızca sipariş sahibi belirli state'teki siparişi iptal edebilir. Bu kural yalnızca HTTP middleware seviyesinde bırakılmamalıdır. Core current actor bilgisi üzerinden business policy uygulayabilir. Framework role veya claim type'ları domain'e girmeden bu kontrol yapılabilir.

Current User Port

Current User port application'ın mevcut actor hakkında ihtiyaç duyduğu bilgiyi sunabilir. userId, tenantId veya business role gibi sade veriler döndürülebilir. Adapter framework security context'ten bu bilgileri çıkarır. Testlerde fake current user kolayca verilebilir. Bu yöntem application service'in HTTP veya framework authentication API'sine bağımlılığını azaltır.

Principal → Application Context Mapping

Framework principal nesnesi teknik claim ve security metadata taşır. Driving adapter gerekli bilgileri application context modeline dönüştürebilir. Core yalnızca business açısından anlamlı identity bilgilerini görür. Claim isimleri veya token provider değiştiğinde mapping adapter'da güncellenir. Authorization rule aynı kalabilir.

JWT Detaylarını Domain'e Sokmamak

JWT bir transport ve identity implementation detayıdır. Domain entity'nin token parse etmesi veya claim adı kontrol etmesi güçlü coupling yaratır. Adapter token'ı doğrular ve business kimliğini çıkarır. Application veya domain policy bu kimlik üzerinden karar verir. Böylece ileride session veya farklı identity protocol kullanılabilir.

Observability Hexagonal Architecture'da Nasıl Ele Alınır?

Observability gerekli bir production concern'dür fakat domain'i belirli logging framework'üne bağlamak zorunlu değildir. Logging, metrics ve distributed tracing çoğunlukla adapter veya decorator yaklaşımıyla eklenebilir. Correlation ID driving adapter'dan application execution context'e aktarılabilir. OpenTelemetry gibi standartlar infrastructure seviyesinde telemetry üretimini kolaylaştırır. Business metric gerektiğinde application event veya explicit port üzerinden ifade edilebilir.

Logging

Logging request, integration ve error behavior'ını anlamaya yardımcı olur. Controller ve adapter'lar teknik context'i loglayabilir. Application service için decorator yaklaşımı use case başlangıç ve sonucunu merkezi biçimde kaydedebilir. Domain entity içinde logger dependency'si taşımak çoğu durumda gerekli değildir. Business açısından önemli olaylar domain event olarak daha anlamlı ifade edilebilir.

Metrics

Metrics latency, success rate ve dependency behavior'ını ölçmek için kullanılır. Adapter dış servis çağrı sürelerini ve failure oranlarını kaydedebilir. Use case decorator business operation metric'leri üretebilir. Metric library core class'larına yayılmak zorunda değildir. Business metric gerekiyorsa anlamlı application event üzerinden telemetry üretilebilir.

Distributed Tracing

Distributed tracing request'in birden fazla servis ve adapter boyunca izlenmesini sağlar. HTTP veya messaging adapter trace context'i alıp propagation yapabilir. Outbound adapter aynı context'i harici çağrıya aktarabilir. Core trace SDK'sını doğrudan kullanmadan instrumentation decorator'larıyla gözlemlenebilir olabilir. Bu yaklaşım vendor bağımlılığını azaltır.

Correlation ID

Correlation ID aynı business request'e ait log ve event'leri ilişkilendirmeye yardımcı olur. Driving adapter gelen header veya message metadata'dan id alabilir. Yoksa yeni id oluşturabilir. Application execution context üzerinden driven adapter'lara taşınabilir. Domain modelin correlation header formatını bilmesine gerek yoktur.

OpenTelemetry Adapter Yaklaşımı

OpenTelemetry instrumentation web, database ve messaging adapter'larına uygulanabilir. Framework automatic instrumentation birçok teknik span'i hazır üretebilir. Application use case için decorator custom span ekleyebilir. Core OpenTelemetry API'sine doğrudan bağlanmak zorunda değildir. Böylece observability vendor değişikliği daha kolay yönetilebilir.

Domain'i Logging Framework'üne Bağımlı Yapmamak

Domain entity içine logger inject etmek business modelin teknik concern taşımasına neden olabilir. Çoğu business davranışı explicit return, domain error ve domain event ile yeterince gözlemlenebilir. Logging application boundary veya decorator seviyesinde yapılabilir. Çok özel audit ihtiyacı varsa business event daha doğru model olabilir. Bu ayrım domain kodunu daha sade tutar.

Legacy Bir Uygulama Hexagonal Architecture'a Nasıl Taşınır?

Legacy uygulamayı hexagonal architecture'a taşırken tüm sistemi bir anda yeniden yazmak genellikle yüksek risk taşır. Daha güvenli yaklaşım en değerli veya en çok değişen business use case'lerden birini seçip sınır oluşturmaktır. Mevcut repository adapter ile sarılabilir ve controller yeni use case'e yönlendirilebilir. Domain içindeki framework import'ları zaman içinde temizlenebilir. Architecture test eklendiğinde yeni kodun eski coupling modeline geri dönmesi de engellenebilir.

Big-Bang Rewrite Yapmamak

Tüm legacy sistemi tek seferde yeniden yazmak yıllar içinde oluşmuş iş kurallarının kaybolması riskini taşır. Proje uzun sürebilir ve bu sırada mevcut ürün geliştirilmeye devam eder. Kademeli migration daha küçük risk alanları oluşturur. Her use case production'da doğrulanabilir. Hexagonal boundary adım adım genişletilebilir.

En Değerli Business Use Case'i Seçmek

Migration için sık değişen veya test edilmesi zor kritik use case iyi başlangıç noktası olabilir. Bu alanın mevcut behavior'ı testlerle önce güvence altına alınmalıdır. Ardından framework ve persistence dependency'leri belirlenir. Yeni application port ve domain behavior oluşturulur. Elde edilen fayda ekip için somut örnek hâline gelir.

Port Tanımlamak

Legacy service'in gerçekten ihtiyaç duyduğu dış dependency'ler belirlenir. Database, external API veya clock için anlamlı port'lar tanımlanabilir. Interface'i mevcut implementation detayına göre değil business ihtiyacına göre tasarlamak önemlidir. İlk adımda tüm sistemi abstract etmeye çalışmak gereksizdir. Seçilen use case'in ihtiyacı kadar port oluşturmak yeterlidir.

Mevcut Repository'yi Adapter ile Sarmak

Mevcut ORM repository veya data access service tamamen yeniden yazılmak zorunda değildir. Yeni port'u uygulayan adapter mevcut repository'yi içeride çağırabilir. Bu wrapper legacy teknolojiyi yeni core'dan ayırır. Zamanla persistence implementation bağımsız biçimde iyileştirilebilir. Böylece migration sırasında business behavior daha az riskle korunur.

Controller'ı Use Case'e Yönlendirmek

Legacy controller doğrudan database veya büyük service class çağırıyor olabilir. Yeni application use case oluşturulduktan sonra belirli endpoint bu port'a yönlendirilir. Request mapping controller'da kalır. Business logic use case ve domain tarafına taşınır. Böylece aynı endpoint davranışı architecture sınırına geçirilmiş olur.

Domain'den Framework Import'larını Temizlemek

Migration sırasında domain class'larında bulunan framework annotation ve helper kullanımları tespit edilir. Bunların hangilerinin gerçekten gerekli olduğu değerlendirilir. Persistence annotation ayrı model veya configuration tarafına taşınabilir. Framework validation yerine domain invariant oluşturulabilir. Bu işlem adım adım yapılmalı ve her değişiklik testlerle korunmalıdır.

Architecture Test Eklemek

Yeni boundary oluşturulduktan sonra domain'in eski infrastructure package'lerini import etmesini engelleyen test eklemek faydalıdır. Bu kontrol refactoring sonrası geri kaymayı önler. CI üzerinde otomatik çalışmalıdır. Kural ihlali açık mesajla geliştiriciye gösterilmelidir. Böylece architecture migration yalnızca doküman olarak kalmaz.

Hexagonal Architecture Anti-Pattern'leri

Hexagonal architecture yanlış uygulandığında gereksiz interface, mapping ve dosya sayısıyla projeyi zorlaştırabilir. Her class için interface yazmak veya her küçük utility için port oluşturmak mimarinin amacı değildir. Generic repository, framework exception veya adapter DTO'sunu core'a taşımak ise gerçek boundary faydasını zayıflatır. Port'lar business need üzerinden tasarlanmalı ve abstraction cost sürekli sorgulanmalıdır. Mimariyi tören hâline getirmek yerine değişim ve test riskini azaltan yerlerde kullanmak gerekir.

Her Class İçin Interface Oluşturmak

Her concrete class'ın karşısına otomatik interface koymak hexagonal architecture gereği değildir. Interface boundary veya değişebilir dependency için anlamlıdır. Bir domain service yalnızca internal kullanılıyorsa interface'e ihtiyaç duymayabilir. Fazla interface navigation ve maintenance maliyetini artırır. Abstraction gerçek bağımlılığı tersine çevirdiğinde kullanılmalıdır.

Generic Repository Kullanmak

Generic repository business language yerine CRUD odaklı API üretir. ORM zaten benzer abstraction sağlıyor olabilir. Application use case'ler ihtiyacından fazla method'a erişebilir. Domain-specific repository daha açık sözleşme oluşturur. Generic yaklaşım yalnızca gerçekten ortak davranış varsa tercih edilmelidir.

ORM Entity'sini Domain Entity Yapmak

ORM entity'yi domain entity olarak kullanmak her zaman hata değildir. Ancak business model karmaşıklaştığında persistence gereksinimleri domain tasarımını zorlayabilir. Lazy loading veya annotation detayları core'a yayılabilir. Ayrı model gerektiğinde mapping sınırı oluşturulmalıdır. Karar proje büyüklüğü ve domain davranışına göre verilmelidir.

Controller'a Business Logic Yazmak

Controller'daki business rule yalnızca HTTP girişinden çağrıldığında çalışır. Başka adapter aynı use case'i kullandığında kural tekrarlanmak zorunda kalır. Testing de framework'e bağlanır. Controller mapping ve protocol concern'lerle sınırlı tutulmalıdır. Business behavior application veya domain katmanında bulunmalıdır.

Framework Exception'ını Core'da Kullanmak

HTTP exception veya ORM exception core'a taşındığında framework dependency boundary'yi geçer. Application kendi error modelini kullanmalıdır. Adapter technical error'ı translate eder. Driving adapter application error'ı kendi protocol response'una dönüştürür. Böylece error handling teknoloji değişikliğine daha dayanıklı olur.

Port'ları Teknik Detaylara Göre İsimlendirmek

KafkaPublisherPort veya PostgreSQLRepositoryPort gibi isimler abstraction'ın implementation detail taşıdığını gösterebilir. Port business capability'yi ifade etmelidir. EventPublisher veya OrderRepository daha uzun ömürlü isimlerdir. Adapter ise KafkaEventPublisher veya PostgresOrderRepository olarak teknik adı taşıyabilir. Bu naming separation mimari rolü daha anlaşılır kılar.

Adapter DTO'sunu Domain'e Sokmak

HTTP veya vendor DTO'nun domain method'larına verilmesi dış contract'ı business modelle bağlar. API field değişikliği domain signature'ını etkileyebilir. Boundary mapping bu bağımlılığı keser. Domain kendi value object veya application command modellerini kullanmalıdır. Mapping maliyeti değişim etkisini daraltan bilinçli bir yatırımdır.

Gereksiz Fazla Katman Oluşturmak

Her küçük operation için controller, mapper, command, handler, service, domain service ve birkaç interface oluşturmak basit projelerde aşırı yük yaratabilir. Mimari pattern gerçek problemden daha ağır hâle gelebilir. Katman sayısı değil dependency direction önemlidir. Basit business logic daha sade yapıyla korunabilir. Ekip abstraction tax'i sürekli değerlendirmelidir.

Hexagonal Architecture Ne Zaman Kullanılmamalı?

Hexagonal architecture her uygulamaya otomatik uygulanması gereken bir şablon değildir. Basit CRUD uygulamaları, kısa ömürlü script'ler ve birkaç gün içinde doğrulanacak PoC'ler için abstraction maliyeti faydadan daha yüksek olabilir. Framework'ün kendisinin ürünün temel domain'i olduğu library projelerinde de farklı trade-off'lar bulunur. Mimari yatırım uygulamanın beklenen yaşam süresi, business rule yoğunluğu ve entegrasyon değişkenliğiyle ilişkilendirilmelidir. Amaç mümkün olan en fazla katman değil gerekli bağımsızlığı en düşük maliyetle sağlamaktır.

Basit CRUD Uygulamaları

Basit CRUD sisteminde business behavior çok sınırlı olabilir. Controller, service ve ORM kullanımı yeterince anlaşılır ve düşük riskli olabilir. Her entity için port ve ayrı domain model oluşturmak geliştirme süresini artırabilir. Sistem büyürse boundary daha sonra güçlendirilebilir. Architecture complexity business complexity ile dengeli olmalıdır.

Throwaway Script'ler

Tek seferlik data migration veya bakım script'inin yıllarca geliştirilmesi beklenmeyebilir. Bu durumda full hexagonal structure gereksiz dosya ve abstraction üretir. Basit fonksiyonlar daha okunabilir olabilir. Yine de güvenlik ve veri doğruluğu gibi kritik gereksinimler korunmalıdır. Mimari yaşam süresine uygun seçilmelidir.

Kısa Ömürlü Proof of Concept

PoC'nin amacı bir hipotezi hızlı test etmekse production architecture maliyetini baştan taşımak gereksiz olabilir. Kodun çöpe atılacağı gerçekten kabul ediliyorsa hızlı framework usage makuldür. Sorun PoC'nin fark edilmeden production ürününe dönüşmesidir. Böyle bir olasılık varsa minimum boundary prensipleri yine değerlendirilebilir. PoC ve production hedefleri başlangıçta açıkça ayrılmalıdır.

Framework'ün Gerçekten Domain Olduğu Library'ler

Bazı library veya plugin projelerinin amacı doğrudan belirli framework için extension üretmektir. Bu durumda framework bağımlılığı ürünün doğal domain'inin parçasıdır. Framework'ü tamamen adapter arkasına saklamaya çalışmak gereksiz abstraction oluşturabilir. Yine de internal logic ayrı test edilebilir. Architecture ürünün gerçek değişim eksenine göre tasarlanmalıdır.

Abstraction Maliyetinin İş Değerinden Büyük Olduğu Durumlar

Her abstraction class, mapping ve maintenance maliyeti üretir. Eğer dependency'nin değişme ihtimali son derece düşük ve business logic çok basitse bu yatırım geri dönmeyebilir. Ekibin teslim süresi veya öğrenme maliyeti de dikkate alınmalıdır. Pragmatik architecture yalnızca teoriye göre değil business value üzerinden karar verir. Gereksiz abstraction da teknik borç kadar gerçek bir maliyettir.

Hexagonal Architecture'ın Maliyeti

Hexagonal architecture daha iyi isolation sağlarken bedelsiz değildir. Daha fazla interface, mapping ve dosya sayısı özellikle yeni ekiplerde geliştirme hızını başlangıçta düşürebilir. Debugging sırasında bir request'in birkaç boundary'den geçmesi takip yükünü artırabilir. Abstraction tax ancak framework, database veya integration değişkenliği yeterince yüksekse karşılığını verir. Bu nedenle mimariyi dogmatik biçimde değil sistemin risk profiline göre uygulamak gerekir.

Daha Fazla Interface

Port yaklaşımı doğal olarak belirli boundary'lerde interface veya benzeri abstraction üretir. Bu durum code navigation sayısını artırabilir. Küçük class'lar için gereksiz interface oluşturmamak maliyeti azaltır. Sadece dış dependency veya application boundary için abstraction kullanmak çoğu projede yeterlidir. Interface sayısı kalite metriği değildir.

Daha Fazla Mapping

Request DTO, application command, domain entity ve persistence model ayrıldığında mapping kodu artar. Bu kod bazen tekrarlı görünebilir. Buna karşılık her model kendi değişim nedenine sahip olur. API veya database değişikliği daha sınırlı etki yaratır. Mapping maliyeti domain ve proje ömrüyle birlikte değerlendirilmelidir.

Daha Fazla Dosya

Port, adapter ve model ayrımı dosya sayısını artırabilir. İyi klasör yapısı olmazsa geliştirici basit use case'i bulmakta zorlanabilir. Feature-based organization bu sorunu azaltabilir. Dosya sayısı yerine navigation ve cognitive load ölçülmelidir. Gereksiz abstraction kaldırılmalıdır.

Öğrenme Eğrisi

Driving port, driven port ve dependency inversion kavramları yeni ekip üyeleri için başlangıçta yabancı olabilir. Örnek module ve project template öğrenme süresini azaltır. Architecture guide ve code review standardı ortak anlayış oluşturur. Ekibin pattern'i neden kullandığını bilmesi isimleri ezberlemesinden daha önemlidir. Basit bir referans use case en iyi eğitim araçlarından biridir.

Debugging Karmaşıklığı

Bir request adapter, mapper, use case ve repository adapter üzerinden ilerlediğinde call path uzayabilir. IDE navigation ve distributed tracing bu akışı takip etmeyi kolaylaştırır. Gereksiz wrapper katmanları debugging maliyetini yükseltir. Her abstraction'ın gerçek sınır temsil etmesi gerekir. Bu nedenle architecture sade tutulmalıdır.

Abstraction Tax

Abstraction tax değişimi kolaylaştırmak için bugün ödediğiniz ek tasarım ve kod maliyetidir. Gelecekte dependency değişmezse yatırımın bir bölümü kullanılmayabilir. Buna rağmen test edilebilirlik ve separation gibi faydalar hemen değer üretebilir. Hangi boundary'lerin riskli olduğu analiz edilmelidir. Her technical dependency için aynı seviyede abstraction kurulması gerekli değildir.

Hangi Projelerde Bu Maliyet Karşılığını Verir?

Uzun ömürlü, business rule yoğun ve çok sayıda external integration içeren sistemler güçlü adaylardır. Büyük ekipler ve sık framework veya provider değişikliği beklenen projeler de fayda görebilir. Regülasyon veya testability ihtiyacı yüksek uygulamalarda core isolation değer kazanır. Basit CRUD ürünlerinde ise daha hafif yapı yeterli olabilir. Mimari karar proje bağlamına göre verilmelidir.

Hangi Programlama Dili Hexagonal Architecture İçin En İyi?

Hexagonal architecture belirli bir programlama diline bağlı değildir. Java, C#, TypeScript, Kotlin, Go ve Python ile uygulanabilir. Dilin interface, module ve dependency management özellikleri implementation şeklini değiştirebilir fakat temel prensip aynı kalır. Application core dış dünya sözleşmelerini kendi tarafında tanımlar ve adapter'lar bu sözleşmelere uyar. Bu nedenle en iyi dil sorusu mimariden çok ekip, domain ve workload ihtiyacıyla cevaplanmalıdır.

Java

Java interface ve package sistemi sayesinde port ve adapter sınırlarını açık biçimde ifade etmeye uygundur. Spring Boot composition ve infrastructure wiring için kullanılabilir. ArchUnit gibi araçlar dependency rule'ları otomatik test etmeye yardımcı olur. Domain plain Java object olarak kalabilir. Dilin olgun testing ekosistemi uzun ömürlü kurumsal projelerde avantaj sağlar.

C#

C# interface ve project reference yapısı hexagonal boundary oluşturmayı kolaylaştırır. Domain ve Application ayrı project olarak tutulabilir. ASP.NET Core DI composition root görevi görebilir. Entity Framework adapter katmanında sınırlandırılabilir. Architecture testleri yanlış project dependency'lerini kontrol edebilir.

TypeScript

TypeScript NestJS veya farklı Node.js framework'leriyle hexagonal model uygulamak için yeterli araçlara sahiptir. Interface'lerin runtime'da bulunmaması DI token tasarımını etkileyebilir. Domain ve application class'ları framework decorator'larından bağımsız yazılabilir. ESLint veya monorepo boundary rule'ları import yönünü koruyabilir. Type safety mapping ve port contract'larında güçlü fayda sağlar.

Kotlin

Kotlin JVM ekosisteminin araçlarından yararlanırken sade domain modelleme imkânı sunar. Data class ve sealed type gibi özellikler value object ve error model için faydalıdır. Spring veya başka framework yalnızca adapter katmanında kullanılabilir. Java interoperability enterprise entegrasyonlarda avantaj sağlar. Temel dependency rule Java projeleriyle aynıdır.

Go

Go'da interface'lerin implementation tarafından açık declaration gerektirmemesi port tasarımını esnek hâle getirir. Küçük interface yaklaşımı business contract'lar için uygundur. Manual dependency composition oldukça yaygın ve anlaşılırdır. Package dependency rule architecture sınırı oluşturabilir. Framework kullanımının sınırlı olması core isolation açısından doğal avantaj sağlayabilir.

Python

Python abstract base class, Protocol veya duck typing ile port abstraction'ları oluşturabilir. Framework bağımsız domain object yazmak oldukça doğaldır. Dependency wiring manuel veya container library ile yapılabilir. Dynamic typing nedeniyle contract test ve type checking desteği önem kazanabilir. FastAPI veya Django gibi framework'ler adapter rolünde sınırlandırılabilir.

Mimari Programlama Dilinden Bağımsızdır

Hexagonal architecture'ın temel amacı dependency direction ve boundary management'tır. Interface syntax veya framework seçimi bu prensibin implementation ayrıntısıdır. Her dilde core dış teknolojiye bağımlı olmadan modellenebilir. Dil seçimi domain, ekip yetkinliği ve production ihtiyaçlarına göre yapılmalıdır. Mimariyi yalnızca belirli Java veya .NET klasör şablonuyla özdeşleştirmek doğru değildir.

Open Source ve İşbirliği ile Hexagonal Mimari Geliştirmek

Büyük ekiplerde mimari sınırların korunması yalnızca birkaç senior geliştiricinin bilgisinde kalmamalıdır. Architecture Decision Records, contribution guidelines ve module boundary dokümantasyonu ortak anlayış oluşturur. Pull request sırasında port ve adapter değişiklikleri kısa architecture review ile değerlendirilebilir. Open source adapter veya library kullanırken core'un vendor modeline bağlanmaması önemlidir. Yeni adapter eklenirken business core'un değişmemesi güçlü bir tasarım sinyalidir.

Architecture Decision Records

ADR belirli mimari kararın neden verildiğini kısa biçimde belgeler. Neden hexagonal boundary kullanıldığı veya neden ayrı persistence model seçildiği kaydedilebilir. Alternatifler ve kabul edilen maliyetler de yazılmalıdır. Yeni ekip üyeleri geçmiş kararı daha hızlı anlayabilir. ADR dokümanı version control altında tutulabilir.

Contribution Guidelines

Contribution guideline yeni kodun hangi dependency kurallarına uyması gerektiğini açıklar. Domain'de framework import edilmemesi veya vendor type'ın port API'sine girmemesi gibi kurallar yazılabilir. Örnek klasör yapısı onboarding süresini azaltır. Review checklist kod kalitesini daha tutarlı hâle getirir. Kural listesi kısa ve uygulanabilir tutulmalıdır.

Module Boundary Dokümantasyonu

Module boundary dokümanı her modülün hangi business capability'den sorumlu olduğunu açıklar. Public port'lar ve yasak internal dependency'ler belirtilebilir. Architecture diagram yeni geliştiricinin sistemi anlamasını kolaylaştırır. Dokümantasyon code structure ile uyumlu tutulmalıdır. Eski ve güncel olmayan diagram güvenilirliği azaltır.

Port Contract'larını Dokümante Etmek

Port interface type signature yanında davranış beklentisine de sahiptir. Error semantics, idempotency veya transaction expectation dokümante edilebilir. Adapter geliştiren ekip bu bilgilerle daha güvenli implementation üretir. Contract test dokümantasyonun çalıştırılabilir tamamlayıcısı olabilir. Çok uzun açıklamalar yerine kritik davranışlar net tutulmalıdır.

Pull Request'te Architecture Review

Her pull request için ağır architecture committee süreci gerekli değildir. Ancak yeni port, adapter veya module dependency ekleniyorsa kısa architecture checklist faydalıdır. Reviewer dependency direction ve vendor leakage gibi konuları kontrol edebilir. Architecture testlerin otomatik çalışması manuel yükü azaltır. Amaç geliştiriciyi yavaşlatmak değil hatalı coupling'i erken yakalamaktır.

Open Source Adapter Ekosistemi

Hazır adapter library'leri infrastructure geliştirme süresini azaltabilir. Ancak library'nin port modelinizi belirlemesine izin vermemek gerekir. Vendor veya library adapter sizin application contract'ınıza uyarlanmalıdır. Dependency maintenance ve security durumu ayrıca değerlendirilmelidir. Kritik adapter gerekirse ince wrapper ile izole edilebilir.

Yeni Adapter Eklerken Core'u Değiştirmemek

Aynı business capability için yeni adapter eklerken core'da büyük değişiklik gerekiyorsa port abstraction yeterince doğru olmayabilir. Elbette yeni teknoloji farklı business capability gerektiriyorsa core değişebilir. Ancak yalnızca provider veya transport değişiyorsa değişiklik çoğunlukla dış sınırda kalmalıdır. Bu özellik hexagonal architecture'ın practical fitness testlerinden biridir. Architecture test ve contract test bu davranışı korumaya yardımcı olur.

Production İçin Önerilen Hexagonal Proje Yapısı

Production proje yapısında amaç klasör sayısını artırmak değil sorumluluk ve dependency yönünü görünür hâle getirmektir. Domain, Application, Adapters ve Bootstrap bölümleri birçok ekip için anlaşılır başlangıç sağlar. Domain business model, Application use case ve port'lar, Adapters teknik entegrasyonlar, Bootstrap ise composition işlemlerini barındırır. Feature veya bounded context bazlı üst seviye organizasyonla bu yapı birleştirilebilir. Proje büyüdükçe sınırların otomatik testlerle korunması klasör isimlerinden daha önemli hâle gelir.

Domain

Domain bölümü business kavramlarını ve kurallarını taşır. Framework ve infrastructure dependency'si bulunmaması hedeflenir. Entity, value object, domain service ve domain event burada yer alabilir. Kod normal programlama dili nesneleriyle ifade edilir. Domain testleri dış I/O olmadan çalışabilmelidir.

Entities

Entities business identity ve davranışı temsil eder. State değişiklikleri kontrollü metotlarla yapılabilir. Invariant'lar entity içinde korunabilir. Persistence annotation zorunlu tutulmamalıdır. Testlerde entity doğrudan oluşturulabilmelidir.

Value Objects

Value Objects business değerlerini geçerli ve anlamlı type'lar olarak ifade eder. Money, Email veya Quantity gibi kavramlar primitive obsession riskini azaltabilir. Genellikle immutable tasarlanır. Validation nesne oluşturulurken uygulanabilir. Framework serialization concern'leri domain type'ın merkezine yerleşmemelidir.

Domain Services

Domain Services tek bir entity'ye doğal olarak ait olmayan business logic'i taşır. Adları business language kullanmalıdır. Infrastructure integration yerine domain decision'a odaklanmalıdır. Gereksiz service class oluşturulmamalıdır. Entity behavior mümkün olduğunda entity üzerinde kalabilir.

Domain Events

Domain Events domain içinde gerçekleşmiş anlamlı değişiklikleri ifade eder. Broker veya transport formatı içermez. Application bu event'i integration event'e dönüştürebilir. Event handler domain içi yan davranışları tetikleyebilir. Naming geçmiş zaman business ifadesi kullanabilir.

Application

Application bölümü sistemin use case'lerini ve dış boundary contract'larını taşır. Input port'lar driving adapter'ların çağırdığı API'yi oluşturur. Output port'lar repository, gateway ve publisher gibi dış dependency ihtiyaçlarını ifade eder. Command ve query modelleri transport framework'ten bağımsız tutulur. Application service bu parçaları belirli business workflow içinde koordine eder.

Use Cases

Use Cases uygulamanın business operasyonlarını gerçekleştirir. Birden fazla port ve domain nesnesini koordine edebilir. HTTP veya database detaylarını bilmez. Transaction boundary çoğu zaman bu seviyeye yakın bulunur. Use case testleri fake adapter'larla hızlı çalışabilir.

Input Ports

Input Ports application'ın dışarı sunduğu business API'dir. PlaceOrderUseCase gibi spesifik isimler kullanılabilir. Driving adapter bu interface'i çağırır. Transport DTO signature'a girmemelidir. Contract uygulama davranışını net biçimde anlatmalıdır.

Output Ports

Output Ports application'ın dış dünyadan beklediği capabilities'i tanımlar. Repository, gateway veya event publisher bu gruptadır. Vendor SDK type'ları signature içinde bulunmamalıdır. Adapter implementation infrastructure katmanında yer alır. Test double'lar aynı port'u uygulayabilir.

Commands ve Queries

Commands state değiştiren, Queries veri okuyan application request modelleri olarak kullanılabilir. CQRS uygulanmasa bile niyet ayrımı okunabilirlik sağlayabilir. Bu modeller HTTP DTO'dan bağımsızdır. Application validation burada bulunabilir. Domain behavior daha sonra entity ve service üzerinde çalıştırılır.

Adapters

Adapters dış dünyanın application core ile teknik iletişimini gerçekleştirir. Web, persistence, messaging ve external API alt grupları oluşturulabilir. Her adapter belirli port veya protocol sorumluluğuna sahip olmalıdır. Framework ve vendor dependency'lerinin büyük bölümü bu alanda bulunur. Mapping ve technical error translation da boundary üzerinde gerçekleştirilir.

Web

Web adapter controller, resolver veya RPC handler içerebilir. Routing, authentication integration ve serialization burada yapılır. Request model application command'a çevrilir. Use case sonucu response DTO'ya map edilir. Business logic web package içinde büyümemelidir.

Persistence

Persistence adapter database erişimini ve ORM davranışını taşır. Repository port implementation'ları burada bulunur. Domain ve persistence model mapping'i gerekiyorsa aynı boundary içinde yapılabilir. Transaction infrastructure bu katmanda desteklenir. Database-specific error application error'a çevrilebilir.

Messaging

Messaging adapter consumer ve producer integration'larını kapsar. Broker client, serializer ve topic configuration burada bulunur. Consumer application command'a mapping yapar. Producer event publisher port'unu uygular. Retry ve acknowledgement policy infrastructure seviyesinde yönetilebilir.

External APIs

External API adapter HTTP veya SDK üzerinden dış servislerle konuşur. Authentication token, endpoint ve vendor DTO bu katmanda tutulur. Gateway port'a uygun business result üretilir. Timeout ve circuit breaker uygulanabilir. Vendor API version değişikliği core'a yayılmadan burada karşılanabilir.

Bootstrap / Composition

Bootstrap veya composition bölümü uygulamanın dependency graph'ını oluşturur. Framework startup, environment configuration ve adapter binding burada bulunabilir. Core class'ları kendi implementation'larını oluşturmaz. Composition root environment'a göre uygun adapter seçebilir. Bu katmanın business logic taşımaması önemlidir.

Dependency Wiring

Dependency wiring input ve output port'ların concrete implementation'larla eşleştirilmesini sağlar. Spring bean, ASP.NET service registration veya NestJS provider binding kullanılabilir. Manuel composition da mümkündür. Test ortamı farklı binding kullanabilir. Wiring bilgisinin tek yerde görünür olması debugging sürecini kolaylaştırır.

Configuration

Configuration environment variable, connection string ve feature option gibi runtime değerlerini yönetir. Bu değerler doğrudan domain tarafından okunmamalıdır. Bootstrap gerekli configuration'ı adapter veya application option nesnesine dönüştürür. Secret bilgiler güvenli secret store üzerinden alınmalıdır. Configuration validation startup sırasında yapılabilir.

Hexagonal Architecture Development Workflow

Hexagonal Architecture geliştirirken teknoloji dosyalarından başlamak yerine use case'ten başlamak daha sağlıklı sonuç verir. Önce business operation ve domain modeli tanımlanır. Ardından driving port ve gerekli driven port'lar oluşturulur. Core framework olmadan test edilir ve sonrasında web, database veya messaging adapter'ları eklenir. Composition root bağlantıları yapar ve architecture tests dependency sınırlarının zaman içinde bozulmasını engeller.

1. Use Case'i Tanımla

İlk adım kullanıcının veya dış sistemin gerçekleştirmek istediği business sonucu tanımlamaktır. PlaceOrder gibi açık bir isim kullanılabilir. Input, output ve önemli failure scenario'lar yazılmalıdır. Framework endpoint detaylarına bu aşamada ihtiyaç yoktur. Use case architecture tasarımının business başlangıç noktasıdır.

2. Domain Modelini Oluştur

Use case'in ihtiyaç duyduğu entity, value object ve business rule'lar modellenir. Domain object'ler framework bağımsız yazılır. Invariant'lar mümkün olduğunca model içinde korunur. Gereksiz entity ve service üretmekten kaçınılır. Domain testleri davranışı doğrular.

3. Driving Port'u Tanımla

Application'ın dış dünyaya sunduğu use case contract'ı oluşturulur. Method signature transport-specific type kullanmamalıdır. Command veya query application modeli kullanılabilir. Port business intent'i açıkça ifade etmelidir. Daha sonra REST veya başka adapter bu interface'i çağırır.

4. Gerekli Driven Port'ları Tanımla

Use case'in database, payment, notification veya dış API ihtiyacı belirlenir. Her gerçek dış dependency için business odaklı output port oluşturulur. Interface vendor API'sini kopyalamamalıdır. Yalnızca use case'in ihtiyaç duyduğu operasyonlar tanımlanır. Bu yaklaşım port'ların gereksiz genişlemesini engeller.

5. Business Logic'i Framework'süz Yaz

Domain ve application kodu normal language construct'larıyla oluşturulur. Framework annotation, HTTP type veya ORM API kullanılmaz. Driven port'lar constructor üzerinden alınabilir. Business behavior hızlı biçimde çalıştırılabilir. Bu aşama architecture isolation'ın gerçek testidir.

6. Unit Testlerini Yaz

Domain ve use case testleri fake veya stub adapter'larla yazılır. Database veya application server çalıştırılmaz. Success ve failure scenario'ları ayrıntılı biçimde doğrulanır. Test suite hızlı feedback vermelidir. Core'un external technology olmadan test edilebilmesi önemli kalite göstergesidir.

7. Driving Adapter'ı Ekle

REST controller, GraphQL resolver veya message consumer gibi giriş adapter'ı eklenir. Adapter input'u application command'a map eder. Use case'i çağırır ve sonucu protocol response'a çevirir. Business logic bu katmana taşınmaz. Adapter için protocol-specific testler yazılır.

8. Driven Adapter'ları Ekle

Repository, payment gateway veya message publisher implementation'ları oluşturulur. Gerçek SDK ve framework dependency'leri bu aşamada kullanılır. Adapter port contract'ını gerçekleştirir. Integration test gerçek dependency davranışını doğrular. Error translation boundary üzerinde uygulanır.

9. Composition Root'ta Bağla

Input ve output port'lar concrete adapter'larla composition root içinde bağlanır. Environment configuration burada uygulanabilir. Production ve test adapter seçimleri ayrılabilir. Core container API'sini bilmez. Runtime dependency graph tek yerde görünür hâle gelir.

10. Architecture Tests ile Sınırları Koru

Domain'in adapter veya infrastructure package import etmemesi otomatik testle korunabilir. Framework dependency'lerinin yalnızca izin verilen alanlarda bulunması doğrulanabilir. CI pipeline bu kuralları her pull request'te çalıştırır. Yeni geliştiriciler yanlış dependency eklediğinde hızlı feedback alır. Böylece architecture zaman içinde erimez.

Hexagonal Architecture Checklist

Hexagonal Architecture uygulamasını değerlendirirken yalnızca klasör isimlerine bakmak yeterli değildir. Domain'in framework bağımsızlığı, port'ların business dili kullanması, adapter sınırında mapping yapılması ve core'un gerçek database olmadan test edilebilmesi daha güçlü göstergelerdir. Ayrıca database veya transport adapter değiştiğinde business logic'in ne kadar değiştiği incelenebilir. Architecture test kullanımı boundary'nin zaman içinde korunmasına yardımcı olur. Aşağıdaki checklist ekiplerin tasarımı code review ve architecture review sırasında daha sistematik değerlendirmesini sağlar.

Domain

Domain bölümünde ilk kontrol technical framework dependency'lerinin gerçekten gerekli olup olmadığıdır. Entity ve value object'ler mümkün olduğunca normal language object'leri olmalıdır. Business rule'ların controller veya repository yerine doğru domain alanında bulunması önemlidir. ORM requirement'ları domain modelin şeklini belirlememelidir. Basit projelerde yapılan bilinçli trade-off'lar ise açık biçimde belgelenmelidir.

Framework import'u var mı?

Domain package içinde web, DI veya persistence framework import'u bulunması boundary ihlali için güçlü sinyal olabilir. Her import otomatik hata değildir fakat gerekçesi sorgulanmalıdır. Technical type yalnızca convenience için kullanılıyorsa sade alternatif tercih edilebilir. Architecture test bu kontrolü otomatikleştirebilir. Amaç domain'in bağımsız compile edilebilir kalmasını sağlamaktır.

ORM annotation'ı var mı?

ORM annotation domain entity üzerinde bulunuyorsa persistence coupling seviyesi değerlendirilmelidir. Basit CRUD sisteminde bu tercih kabul edilebilir. Karmaşık domain ve uzun ömürlü projede ayrı persistence model daha sağlıklı olabilir. Annotation nedeniyle business model şekilleniyorsa ayrım ihtiyacı güçlenir. Karar otomatik kural değil risk değerlendirmesi olmalıdır.

Business rules domain'de mi?

Business rule'lar controller veya ORM lifecycle hook'larına dağılmışsa aynı behavior farklı giriş kanallarında tutarsızlaşabilir. Entity, value object veya domain service uygun yer olabilir. Application service orchestration yaparken domain decision'ı modele bırakmalıdır. Unit test business behavior'ı doğrudan doğrulayabilmelidir. Bu kontrol domain modelin gerçek değerini gösterir.

Ports

Port değerlendirmesinde isimlendirme ve contract kalitesi temel kriterdir. Port business language kullanmalı ve vendor-specific type taşımamalıdır. Gereğinden geniş interface'ler adapter implementation'ını zorlaştırabilir. Her port gerçek application ihtiyacına dayanmalıdır. Sırf interface sayısını artırmak için port oluşturulmamalıdır.

Business language kullanıyor mu?

Port adı application veya domain niyetini anlatmalıdır. OrderRepository veya PaymentGateway bu açıdan anlaşılır örneklerdir. DatabaseClientPort gibi teknik adlar business meaning'i zayıflatır. Method isimleri de CRUD veya vendor jargonundan mümkün olduğunca uzak tutulmalıdır. Ubiquitous language port design için güçlü rehberdir.

Vendor-specific type içeriyor mu?

Port signature içinde vendor SDK request veya response type bulunuyorsa abstraction teknik implementation'a bağlı hâle gelir. Adapter bu type'ları internal application modeline dönüştürmelidir. Core primitive veya business value object kullanabilir. Provider değişikliğinde port contract mümkün olduğunca sabit kalmalıdır. Bu kontrol özellikle payment ve external API entegrasyonlarında önemlidir.

Gereğinden genel mi?

Çok genel port gelecekteki tüm ihtiyaçları önceden tahmin etmeye çalışabilir. Bu durum büyük interface ve belirsiz semantics üretir. Yalnızca mevcut use case'lerin ihtiyaç duyduğu operasyonlar eklenmelidir. Yeni requirement geldiğinde port kontrollü biçimde genişletilebilir. YAGNI yaklaşımı abstraction tasarımında da geçerlidir.

Adapters

Adapter'ların görevi dış teknoloji ile core contract arasında translation yapmaktır. Mapping, vendor SDK ve technical error handling doğal olarak burada bulunur. Adapter business rule taşımaya başladığında core sınırı zayıflar. Her adapter belirli protocol veya technology concern'e odaklanmalıdır. Integration test gerçek implementation'ın contract'a uyduğunu doğrulamalıdır.

Mapping adapter sınırında mı?

Transport veya persistence model domain'e girmeden boundary üzerinde map edilmelidir. Controller HTTP DTO'yu command'a, repository adapter persistence entity'yi domain entity'ye çevirebilir. Mapping business rule içermemelidir. Tekrarlanan basit field mapping acceptable cost olabilir. Bu ayrım değişiklik etkisini daraltır.

Vendor SDK adapter içinde mi?

Vendor SDK dependency'si application veya domain package'e sızmamalıdır. Adapter SDK request, authentication ve error type'larını yönetir. Port daha genel business contract kullanır. Provider upgrade veya replacement bu sayede daha lokal kalır. Build dependency graph bu kuralı otomatik doğrulayabilir.

Error translation yapılıyor mu?

Database veya vendor exception'ı core'a olduğu gibi aktarılmamalıdır. Adapter technical error'ı application için anlamlı bir hata modeline dönüştürebilir. Log içinde original exception korunabilir. Retry policy technical error class'ına göre çalışabilir. Core provider exception hierarchy'sini bilmeden karar verir.

Testing

Testing checklist core isolation'ın pratik olarak çalışıp çalışmadığını gösterir. Domain ve use case testleri database veya web server olmadan çalışabilmelidir. Fake adapter kullanımı kolay olmalıdır. Production adapter için ayrı integration test bulunmalıdır. Architecture boundaries de mümkün olduğunca CI üzerinde otomatik doğrulanmalıdır.

Core database olmadan test edilebilir mi?

Use case testi çalıştırmak için her seferinde database başlatmak zorunluysa persistence dependency core'a fazla yakın olabilir. Repository port fake implementation kullanmayı mümkün kılmalıdır. Domain testleri tamamen I/O bağımsız olmalıdır. Database adapter'ın doğruluğu ayrı integration test suite'inde test edilir. Bu separation CI süresini önemli ölçüde iyileştirebilir.

Fake adapter kullanılabiliyor mu?

Port contract sade ve business odaklıysa fake adapter yazmak genellikle kolaydır. Fake repository memory data structure kullanabilir. Fake gateway önceden belirlenmiş response verebilir. Çok karmaşık fake gerekiyorsa port abstraction yanlış seviyede olabilir. Test double tasarımı port kalitesi hakkında önemli feedback sağlar.

Architecture boundary otomatik test ediliyor mu?

Boundary yalnızca code review hafızasına bırakılırsa zamanla ihlal edilebilir. Architecture test domain'in infrastructure import etmesini engelleyebilir. CI her pull request'te bu testi çalıştırmalıdır. Hata mesajı geliştiriciye çözüm yolunu göstermelidir. Böylece architecture standardı ölçeklenebilir hâle gelir.

Maintainability

Maintainability kontrolünde gerçek teknoloji değişikliklerinin core üzerinde ne kadar etkisi olduğu incelenir. Database adapter değişikliğinin business logic'i yeniden yazmayı gerektirip gerektirmediği güçlü bir göstergedir. REST'e ek olarak CLI giriş kanalı eklemek de boundary kalitesini test edebilir. Framework upgrade sırasında domain dosyalarının değişmesi beklenenden yüksek coupling'e işaret edebilir. Bu sorular mimarinin teorik değil pratik değerini ölçer.

Database adapter değiştirilince core değişiyor mu?

Yalnızca database vendor değişiyorsa core'da geniş değişiklik beklenmemelidir. Veri modelinin semantics'i değişiyorsa belirli business etkiler doğal olabilir. Repository port mümkün olan ortak business contract'ı korur. Yeni adapter integration testlerle doğrulanır. Değişiklik alanının büyük bölümü infrastructure tarafında kalmalıdır.

REST yerine CLI eklenince business logic değişiyor mu?

Aynı business operation CLI üzerinden tetiklendiğinde yeni business rule yazılması gerekmemelidir. CLI adapter input'u application command'a map eder. Existing use case tekrar kullanılır. Eğer controller içinde business logic varsa bu değişiklik sırasında duplication ortaya çıkar. Bu senaryo driving boundary kalitesini değerlendirmek için iyi bir testtir.

Framework upgrade'i domain'i etkiliyor mu?

Framework major upgrade sırasında domain class'larının büyük bölümü değişiyorsa framework leakage yüksek olabilir. Adapter ve bootstrap sınıflarının değişmesi daha doğal sonuçtur. Annotation veya framework base type domain'e yayılmışsa migration kapsamı büyür. Architecture test framework import'larını sınırlandırabilir. Böylece domain daha uzun ömürlü kalır.

Sık Sorulan Sorular

Hexagonal Architecture hakkında en sık sorulan konular port ve adapter ayrımı, Clean Architecture ilişkisi, DDD gereksinimi ve test avantajları etrafında toplanır. Bu soruların ortak noktası mimarinin klasör şemasından çok dependency direction üzerinden anlaşılması gerektiğidir. Her class için interface oluşturmak veya her projede ayrı persistence model kullanmak zorunlu değildir. Mimari business logic'i değişken teknik detaylardan koruduğu ölçüde değer üretir. Aşağıdaki yanıtlar uygulama sırasında karşılaşılan pratik karar noktalarını daha net hâle getirir.

Hexagonal Architecture Nedir?

Hexagonal Architecture application core ile dış teknik dünya arasında port ve adapter sınırı oluşturan mimari yaklaşımdır. Business logic framework, database ve vendor SDK'larından mümkün olduğunca bağımsız tutulur. Driving adapter application use case'i çağırırken driven adapter core'un dış dependency ihtiyacını gerçekleştirir. Dependency source code seviyesinde core'a doğru yönelir. Bu yapı test edilebilirlik ve teknoloji değişikliklerine dayanıklılık sağlar.

Ports and Adapters Nedir?

Ports application'ın sunduğu veya ihtiyaç duyduğu sözleşmeleri ifade eder. Adapters bu sözleşmeleri belirli teknolojiyle bağlar. REST controller driving adapter, PostgreSQL repository ise driven adapter olabilir. Core adapter implementation'ını doğrudan bilmez. Bu model teknik detayların application boundary'sini daha görünür hâle getirir.

Hexagonal Architecture ile Clean Architecture Aynı mı?

Aynı değildir fakat temel dependency prensipleri oldukça benzerdir. İki yaklaşım da business logic'i framework ve infrastructure'dan korumayı hedefler. Clean Architecture iç halkaları daha ayrıntılı tarif eder. Hexagonal Architecture port ve adapter interaction'ına daha fazla odaklanır. Pratik projede iki yaklaşımın fikirleri birlikte kullanılabilir.

DDD Olmadan Hexagonal Architecture Kullanılabilir mi?

Evet, DDD zorunlu değildir. Hexagonal Architecture dependency boundary ile ilgilenir. İçeride basit application model veya transaction script kullanılabilir. DDD karmaşık business domain'in modellemesi gerektiğinde ek araçlar sunar. Projenin domain complexity seviyesine göre birlikte veya ayrı kullanılabilirler.

Repository Bir Port mudur?

Çoğu uygulamada repository driven port olarak modellenebilir. Application core persistence capability'sine ihtiyaç duyar ve interface'i kendi tarafında tanımlar. Database adapter bu interface'i uygular. ORM veya driver detail adapter içinde kalır. Repository API'sinin business ihtiyacına göre tasarlanması önemlidir.

Her Class İçin Interface Yazmak Gerekir mi?

Hayır, her class için interface oluşturmak gerekli değildir. Interface dış boundary, değişebilir dependency veya meaningful abstraction olduğunda değer üretir. Domain içindeki basit service için interface gerekmeyebilir. Gereksiz interface sayısı navigation ve maintenance maliyetini artırır. Hexagonal architecture interface sayısıyla ölçülmez.

Domain Entity ile Database Entity Ayrılmalı mı?

Her projede zorunlu değildir. Karmaşık domain ve güçlü persistence isolation gerekiyorsa ayırmak faydalıdır. Basit CRUD sisteminde tek model daha ekonomik olabilir. ORM constraint'leri domain design'ı etkilemeye başladığında ayrı model ihtiyacı güçlenir. Mapping maliyeti proje yaşam süresiyle birlikte değerlendirilmelidir.

Spring Boot Hexagonal Architecture İçin Uygun mu?

Evet, Spring Boot hexagonal architecture ile rahatlıkla kullanılabilir. Framework controller, configuration ve infrastructure adapter katmanlarında tutulabilir. Application ve domain normal Java veya Kotlin class'ları olarak yazılabilir. Spring configuration port ve adapter binding işlemini gerçekleştirir. Core üzerinde framework annotation kullanmak zorunlu değildir.

NestJS ile Hexagonal Architecture Kullanılabilir mi?

Evet, NestJS port ve adapter yaklaşımıyla kullanılabilir. Module ve provider yapısı composition root görevini destekler. Controller ve infrastructure service'ler framework decorator kullanabilir. Domain ve application sınıfları normal TypeScript code olarak kalabilir. DI token tasarımı port binding için kullanılabilir.

Microservice'lerde Hexagonal Architecture Kullanılmalı mı?

Karmaşık business logic ve çok sayıda integration bulunan microservice'lerde güçlü fayda sağlayabilir. Çok basit servislerde ise abstraction maliyeti gereksiz olabilir. Her microservice'in otomatik olarak büyük hexagonal structure taşıması gerekmez. Domain, testability ve değişim riski değerlendirilmelidir. Distributed system failure semantics ayrıca açık biçimde tasarlanmalıdır.

Basit CRUD Projede Hexagonal Architecture Gerekli mi?

Genellikle tam kapsamlı uygulama gerekli olmayabilir. Basit controller, service ve repository yapısı daha hızlı ve okunabilir olabilir. Yine de business logic büyüme ihtimali yüksekse bazı boundary prensipleri erken uygulanabilir. Architecture maliyeti business complexity'den büyük olmamalıdır. Proje geliştikçe separation seviyesi artırılabilir.

Hexagonal Architecture Testleri Nasıl Kolaylaştırır?

Core dış dependency'lere interface üzerinden bağlandığı için testlerde fake veya stub adapter kullanılabilir. Database, web server veya message broker başlatmak gerekmez. Domain ve use case testleri daha hızlı çalışır. Adapter'lar ayrı integration testlerle doğrulanır. Bu separation hata teşhisini ve CI feedback süresini iyileştirir.

Dependency Injection Zorunlu mu?

Hayır, belirli bir DI framework zorunlu değildir. Dependency injection constructor üzerinden manuel olarak da yapılabilir. Önemli olan core class'ın concrete adapter oluşturup kendini infrastructure'a bağlamamasıdır. Composition root implementation seçimini yapar. Framework container büyük projelerde wiring işlemini kolaylaştırabilir.

Bir Port'un Birden Fazla Adapter'ı Olabilir mi?

Evet, aynı port birden fazla adapter tarafından uygulanabilir. Production ve test adapter'ları buna en basit örnektir. Farklı payment provider veya storage teknolojileri de aynı business contract'ı gerçekleştirebilir. Runtime configuration hangi adapter'ın kullanılacağını seçebilir. Contract test bütün implementation'ların beklenen davranışı korumasına yardımcı olur.

Hexagonal Architecture Performansı Düşürür mü?

Interface çağrısı ve mapping overhead'i çoğu business application'da database veya network latency yanında çok küçük kalır. Gerçek performans problemi ölçümle doğrulanmalıdır. Gereksiz mapping büyük veri processing senaryolarında etkili olabilir ve optimize edilebilir. Architecture seçimini mikro seviyedeki function call maliyetine göre yapmak genellikle doğru değildir. Low latency sistemlerde ise allocation ve mapping behavior benchmark üzerinden özel olarak değerlendirilmelidir.

Hexagonal Mimari nedir ve iş mantığını framework bağımlılıklarından nasıl izole eder?

Hexagonal Mimari application core'u port ve adapter sınırlarıyla dış dünyadan ayırır. Business logic HTTP framework, ORM veya vendor SDK'sını doğrudan kullanmaz. Core ihtiyacı olan interface'leri kendi business diliyle tanımlar ve teknik adapter'lar bu sözleşmeleri gerçekleştirir. Dependency direction bu nedenle dışarıdan core'a doğru olur. Framework değiştiğinde business rule'ların büyük bölümünün aynı kalabilmesi yaklaşımın temel hedefidir.

Hexagonal Mimari’de port ve adapter yapısı nasıl çalışır?

Port application'ın sunduğu veya dışarıdan beklediği capability sözleşmesidir. Driving adapter dış request'i primary port'a bağlar. Driven adapter repository veya external API gibi output port'ları gerçek teknolojiyle uygular. REST, CLI ve messaging farklı driving adapter örnekleridir. PostgreSQL repository veya payment provider client ise driven adapter olarak kullanılabilir.

Hexagonal Mimari ile Clean Architecture ve Onion Architecture arasındaki farklar nelerdir?

Üç yaklaşım da dependency'lerin business core'a doğru yönelmesini önemser. Hexagonal Architecture port ve adapter boundary'sini öne çıkarır. Clean Architecture entity, use case ve interface adapter gibi daha tanımlı halkalar sunar. Onion Architecture concentric layer ve domain merkezli görsel modeli vurgular. Pratik uygulamada aralarındaki ortak prensipler farklılıklardan daha önemli olabilir.

Hexagonal Mimari test edilebilirliği, sürdürülebilirliği ve teknoloji değişikliklerine uyumu nasıl artırır?

Core dış dependency'lere concrete implementation yerine port üzerinden bağlanır. Bu nedenle unit testlerde fake repository veya stub gateway kullanılabilir. Framework ve database değişiklikleri çoğunlukla adapter sınırında kalır. Business rule'lar daha az değişim nedenine sahip olur. Architecture testler de bu boundary'lerin zaman içinde korunmasına yardımcı olur.

Yakınımda Hexagonal Mimari ve kurumsal yazılım mimarisi konusunda danışmanlık veya eğitim veren firma nasıl bulabilirim?

Hexagonal mimari ve backend yazılım danışmanlığı yakınımda şeklinde araştırma yaparken yalnızca kullanılan framework isimlerine bakmak yeterli değildir. Danışmanlık yaklaşımının domain boundaries, testability, dependency inversion, legacy migration ve production operability konularını birlikte ele alması önemlidir. Kurumsal Hexagonal Architecture ve yazılım mimarisi danışmanlığı kapsamında gerçek proje örnekleri, migration yaklaşımı ve ekip eğitim modeli incelenebilir. Diyarbakır Yazılım Topluluğu'nun proje çalışmalarına https://www.diyarbakiryazilim.com.tr/projects adresinden ulaşabilirsiniz. Topluluk ve çalışma yaklaşımı hakkında daha fazla bilgi için https://www.diyarbakiryazilim.com.tr/about sayfasını inceleyebilirsiniz.

Sonuç: Framework'ü Mimari Değil, Değiştirilebilir Bir Adapter Olarak Görmek

Hexagonal Mimari: İş Mantığını Çerçevelerden İzole Etmek yaklaşımının en önemli fikri oldukça basittir: framework uygulamanın merkezi değil, dış dünyayla iletişim kuran araçlardan biridir. Database, web framework, message broker ve üçüncü taraf API'ler zaman içinde değişebilirken business rule'ların mümkün olduğunca istikrarlı kalması gerekir. Port ve adapter sınırları bu değişim hızlarını birbirinden ayırır, dependency inversion core'un teknolojiye doğrudan bağlanmasını azaltır ve test stratejisini daha hızlı hâle getirir. Özellikle düşük gecikmeli kurumsal uygulamalarda architecture ile performance gereksiniminin birlikte nasıl ele alınabileceğine ilişkin içerik için https://www.diyarbakiryazilim.com.tr/posts/dusuk-gecikmeli-low-latency-kurumsal-uygulama-gelistirme adresini inceleyebilirsiniz. Hexagonal Architecture, backend mimarisi, legacy modernization veya kurumsal uygulama tasarımı konusunda çalışma yaklaşımımızı görmek için https://www.diyarbakiryazilim.com.tr/about ve proje örnekleri için https://www.diyarbakiryazilim.com.tr/projects adreslerini ziyaret edebilirsiniz.

share
share:

İletişim

Birlikte inşa edelim

İşbirliklerine, ilginç sorunlara ve kod, tasarım ile diğer konular hakkında sohbetlere açığız.

bize ulaş→

Bizi başka yerlerde bulun

GitHub
@diyarbakir-yazilim
Twitter
@diyaryazilim
LinkedIn
diyarbakir-yazilim-toplulugu
Instagram
@diyarbakiryazilim
YouTube
@diyarbakiryazilim
Slack
diyarbakiryazilim
WhatsApp
Topluluğa Katıl
Email
info@diyarbakiryazilim.org
Sevgiyle ve kodla inşa ediliyor

© 2026 Diyarbakır Yazılım Topluluğu — Tüm hakları saklıdır.