Sunum Servisi için Hexagonal Mimari: BFF Karşılaştırması
Bir UI parçasının arkasındaki ince sunum servisi yapışkan koda dönüşür. Port-ve-adaptör, çekirdeği somut hiçbir şeye bağımlı bırakmayarak bunu sürdürülebilir tutar.
Sunum servisi, tek bir UI parçasının arkasında duran ince arka uçtur: parayı ve tarihleri biçimler, metni ve i18n’i çözer, oturum durumunu şekillendirir ve UI adına tek bir alt domain servisini proxy’ler. Ekipler bunu yapışkan kod için bir çöplük olarak görür ve bu kod zamanla çamur topuna dönüşür. Para biçimleme HTTP işleyicilerinin içine karışır, alt servis istemcisinin tuhaflıkları iş kurallarına sızar. Bu servisi sürdürülebilir tutan disiplin port-ve-adaptör (hexagonal mimari) yaklaşımıdır ve tek bir kurala iner: domain çekirdeğini somut hiçbir şeye bağımlı olmamaya zorla.
Yapışkan Koddan Çamur Topuna#
“İnce proxy” olarak başlayan bir servis, hiçbir sınır ayakta kalmayana kadar sessizce sorumluluk biriktirir:
- Biçimleme, i18n, oturum şekillendirme, kimlik doğrulama token işleme ve alt istemci çözümleri aralarında hiçbir ayrım olmadan yığılır.
- Para ve tarih biçimleme çağrıları HTTP denetleyicilerine gömülür, dolayısıyla biçimleme mantığını test etmenin tek yolu bir HTTP sunucusu ayağa kaldırmaktır.
- Alt servisin aktarım formatı (alan adları, hata şekilleri, sayfalama tuhaflıkları) iş kararlarına sızar. O istemciyi değiştirmek veya sürümlemek her şeye dokunmak demektir.
- Bir para veya i18n kütüphanesi onlarca dosyadan çağrılır, dolayısıyla onu değiştirmek başlı başına bir projeye dönüşür.
- Gözlemlenebilirlik kenarlarda bir kez bağlanmak yerine her işleyiciye tutarsızca eklenir.
Bunların her biri yanlış yöne işaret eden bir bağımlılıktır: çekirdek mantık somut bir kütüphaneye, somut bir protokole veya somut bir aktarım formatına bağımlıdır.
Bağımlılık Kuralı#
Tüm desen, bağımlılık yönü üzerindeki tek bir kısıttır. Cockburn’ün hexagonal mimari için 2005’teki özgün amacı, “bir uygulamanın kullanıcılar, programlar, otomatik testler veya toplu betikler tarafından eşit şekilde yönetilebilmesini ve nihai çalışma zamanı cihazlarından ve veritabanlarından izole olarak geliştirilip test edilebilmesini” sağlamaktı. Martin aynı kısıtı bağımlılık kuralı olarak ifade eder: “kaynak kodu bağımlılıkları yalnızca içeri işaret edebilir. İç bir halkadaki hiçbir şey, dış bir halkadaki herhangi bir şey hakkında en ufak bilgiye sahip olamaz” ve özellikle “dış bir halkada kullanılan veri formatları iç bir halkada kullanılmamalıdır.”
Pratikte bu, domain çekirdeğinin hiçbir HTTP kütüphanesi, para kütüphanesi veya i18n kataloğu içe aktarmaması anlamına gelir. Çekirdek ihtiyaç duyduğu arayüzleri (portları) tanımlar; somut uygulamalar (adaptörler) kenarlarda yaşar ve çekirdeğe bağımlıdır, asla tersi değil. Domain çekirdeği böylece ağ, saat veya yerel ayar olmadan çalıştırılabilir.
Hexagonal, onion ve clean mimari aynı ailenin üyeleridir. Onion (Palermo, 2008) ve clean (Martin, 2012), aynı bağımlılık tersine çevirme fikrini farklı çizimlerle inşa eder; böylece üç terim de tek bir ilkenin farklı adı olur.
Dosya Düzeni#
Bir sunum servisi hexagona sorunsuz oturur. Genelleştirilmiş haliyle düzen şöyle görünür:
domain/, sunum kurallarını tutan izole çekirdektir. HTTP, para kütüphaneleri veya i18n hakkında hiçbir bilgisi yoktur ve ihtiyaç duyduğu port arayüzlerinin sahibidir.gateways/, alt domain servisine giden dışa dönük istemcileri tutar. Bu, yönlendirilen (driven) taraftır.adapters/, değiştirilebilir kenarları tutar: HTTP, para biçimleme, tarih, çeviri ve kimlik doğrulama token kimlik bilgileri, her biri bir portu uygular.endpoints/(denetleyiciler), gelen HTTP kenarıdır. Bu, yönlendiren (driving) taraftır.- Küçük bir gözlemlenebilirlik katmanı (metrikler, izleme, yapılandırılmış günlükleme) kenarlarda bağlanır.
AWS Prescriptive Guidance aynı yapıyı tarafsız biçimde çerçeveler: “uygulama, dış bileşenlerle port adı verilen arayüzler üzerinden iletişim kurar ve teknik alışverişleri çevirmek için adaptörler kullanır” ve domain-driven design uyumunu, “her uygulama bileşeni DDD’de bir alt domaini temsil eder” diyerek belirtir.
Gözlemlenebilirlik konusunda OpenTelemetry tarafsız çerçeveyi sağlar. Üç sinyali vardır: izler (traces), metrikler ve günlükler. Enstrümantasyon ise bunları yayma eylemidir. Bir sunum servisi için adaptörleri enstrümante edin: HTTP kenarı ve alt servis gateway’i, isteklerin bir sınırı geçtiği ve gecikmeyle hataların gözlemlenebildiği yerlerdir; domain çekirdeği ise sinyalsiz kalır, böylece testleri saf, mantığı taşınabilir kalır.
Adaptörler: Değiştirilebilir Kenarlar#
Çekirdek, hiçbir şeye bağımlı olmayan bir port tanımlar:
// domain/ports.ts: çekirdeğe ait, somut hiçbir şeye bağımlı değil
export interface MoneyFormatter {
format(amountMinor: number, currency: string, locale: string): string;
}
Bir adaptör onu kenarda uygular:
// adapters/money/intl-money-formatter.ts: değiştirilebilir bir uygulama
import type { MoneyFormatter } from "../../domain/ports";
export class IntlMoneyFormatter implements MoneyFormatter {
format(amountMinor: number, currency: string, locale: string): string {
return new Intl.NumberFormat(locale, { style: "currency", currency })
.format(amountMinor / 100);
}
}
Para kütüphanesini, alt istemciyi veya i18n arka ucunu değiştirmek artık tek bir adaptör dosyasına dokunur. Domain çekirdeğinin testleri hiç değişmez, çünkü çekirdek baştan beri yalnızca MoneyFormatter arayüzünü görür. Bağımlılık enjeksiyonu ile bağımlılık kuralı arasındaki ayrıma dikkat edin. Enjeksiyon, başlangıçta çekirdeğe bir adaptör veren bağlama mekanizmasıdır; bağımlılık kuralı ise çekirdeğin adaptörü hiç içe aktarmadan derlenmesini sağlayan yönsel kısıttır ve bu kural olmadan enjeksiyon sizi yine de bağımlı bırakır.
İzole Çekirdeği Test Etmek#
Bu, Cockburn’ün özgün yazısında en çok anılan faydadır: izolasyon içinde geliştir ve test et. Çekirdek yalnızca portlarına bağımlı olduğu için, onu bellek içi sahtelerle test edersiniz; HTTP sunucusu yok, ağ yok, sistem saati yok ve gerçek çeviri kataloğu yok.
// domain/__tests__/present-checkout.test.ts
import { presentCheckout } from "../present-checkout";
import type { MoneyFormatter, CheckoutGateway } from "../ports";
const fakeFormatter: MoneyFormatter = {
format: (m, c, l) => `${c} ${(m / 100).toFixed(2)} [${l}]`,
};
const fakeGateway: CheckoutGateway = {
async getCart() {
return { totalMinor: 12999, currency: "USD" };
},
};
test("sepet toplamını yerel ayara göre şekillendirir", async () => {
const view = await presentCheckout(
{ locale: "en-US" },
{ formatter: fakeFormatter, gateway: fakeGateway },
);
expect(view.total).toBe("USD 129.99 [en-US]");
});
Test milisaniyeler içinde çalışır ve şekillendirilmiş sunum çıktısını sıfır I/O ile doğrular. Alt servis aktarım formatını değiştirdiğinde, bu değişiklik gateway adaptöründe soğurulur. Bir tasarımcı, bir yerel ayarda paranın nasıl okunacağını değiştirdiğinde, çekirdeğin mantığına dokunmadan yeni dizgeyi sahte bir biçimleyiciye karşı doğrularsınız.
Sunum Servisi ile BFF Farkı#
En yaygın hata, bir sunum servisini Backend-for-Frontend ile karıştırmaktır. İkisi farklı soruları yanıtlar ve bu ayrım önemlidir.
Bir BFF, onu kimin tükettiğiyle tanımlanır. Desen SoundCloud’da geliştirildi; terimi web teknoloji lideri Nick Fisher ortaya attı. Sam Newman kanonik tanımı verir: “genel amaçlı bir API arka ucu yerine, her kullanıcı deneyimi için bir arka ucunuz olur” ve BFF “belirli bir kullanıcı deneyimine sıkı sıkıya bağlıdır” ve “kullanıcı arayüzüyle aynı ekip tarafından bakımı yapılır.” Bir BFF tipik olarak birçok alt servise yayılır ve UI başına toplama yapar. Bu yayılmanın sipariş, envanter ve ödeme servisleri üzerinden işlenmiş bir örneği için AppSync Subscription’larını AppSync Dışından Tetiklemek yazısına bakın.
Bir sunum servisi ise ne yaptığıyla tanımlanır: esas olarak tek bir domain bağımlılığı üzerinde sunum mantığı, içeride hexagonal katmanlamayla düzenlenir. Bir BFF olabilir, çünkü topoloji ile iç yapı birbirinden bağımsız (orthogonal) eksenlerdir. BFF bir topoloji ve sahiplik sorusunu yanıtlar: her ön uç için bir arka uç, UI ekibinin sahip olduğu, birçok servise yayılan. Hexagonal ise servis içindeki bağımlılıkların hangi yöne işaret ettiğini, yani iç yapıyı belirler.
Calçado, bu karışıklığı kolaylaştıran tarihsel kaymayı belgeler: SoundCloud’un ilk BFF’leri, sunum kavramları (örneğin profil sayfasının sunucu tarafı bir kavram olarak ele alınması) içine göç etmeden önce “hâlâ büyük ölçüde genel API’ye benziyordu.” Yani bir BFF sunum mantığı biriktirebilir; iç yapı sorusunun topoloji sorusundan ayrı kalmasının nedeni tam da budur. Port-ve-adaptör düzeni bir şeyi BFF yapmaz ve bir BFF’in içeride hexagonal olması gerekmez. Servisin tek bir domain bağımlılığı varsa ona sunum servisi deyin ki geçerli olmayan yayılma dayanıklılığı varsayımlarını miras almayın.
Simetri Üzerine Duruş#
Adı konmaya değer gerçek bir gerilim var. Fowler, hexagonu “bir çekirdeği saran arayüzlerden oluşan simetrik bileşenler yaratmak için sunum katmanı ile veri kaynağı katmanı arasındaki benzerlikleri kullandığı” için över, ancak aynı özelliği bir dezavantaj olarak işaretler: “katmanlar olarak daha iyi temsil edilecek olan bir servis sağlayıcı ile servis tüketicisi arasındaki doğal asimetriyi” gizler.
Bir sunum servisi için asimetri gerçektir. Bir gelen HTTP kenarı ve birkaç yönlendirilen adaptör vardır. Pratik duruş şu: port disiplinini koruyun (çekirdek arayüzlere sahiptir, bağımlılıklar içeri işaret eder), yönlendirilen tarafın ise sıradan katmanlar gibi okunmasına izin verin; böylece gelen ve giden tarafları ayna görüntüsü saymadan portlardan test edilebilirlik kazanırsınız.
Hexagonun Maliyeti#
Yapının gerçek bir başlangıç maliyeti var. Şu durumlarda atlayın:
- Biçimleme, i18n veya oturum şekillendirme içermeyen, gerçekten önemsiz bir geçirgenin (pass-through) hexagona ihtiyacı yoktur. Kurulan yapı, yerine geçtiği yapışkan koddan daha pahalıya mal olur.
- Her şeye erkenden port açmak. Tam olarak tek uygulaması olan ve test dikişi bulunmayan şeyler için port tanımlamak, karşılığı olmayan dolaylama ekler. Uygulamayı gerçekten değiştirecek, testte yerine bir sahte koyacak ya da çekirdekten ayıracak somut bir nedeniniz olduğunda port tanıtın.
- Kısa ömürlü veya tek kişilik işler. Tek seferlik bir deney veya bir prototip bu yapıyı nadiren geri öder.
AWS Prescriptive Guidance pragmatist çekinceyi doğrudan belirtir: adaptör kodu “yalnızca uygulama bileşeni birkaç girdi kaynağı ve çıktı hedefi gerektiriyorsa… veya girdiler ve çıktı veri deposu zaman içinde değişmek zorundaysa gerekçelendirilir. Aksi takdirde adaptör, bakım yükü getiren, bakımı yapılacak başka bir katman hâline gelir.” Ayrıca “port ve adaptör kullanmak başka bir katman ekler ve bu gecikmeye yol açabilir” der.
Bu, maksimalist ile pragmatist arasındaki ayrımdır. Node ve TypeScript eğitimleri port-ve-adaptörü varsayılan iyi yapı olarak sunma eğilimindedir; pragmatist okuma ise şudur: bu yatırım, ancak sınırların maliyetini karşılayacak kadar uzun yaşayan ve yeterince yapışkan kod biriktiren servislerde kazanca dönüşür.
Lint Kuralının Yakaladığı Sızıntılar#
Önce iki sızıntı görünür: denetleyicilerde yaşayan biçimleme ve i18n çağrıları, bir de çekirdeğe ulaşan alt servis aktarım şekilleri. İlkini çekirdeğin sahip olduğu bir portun arkasına taşıyın; ikincisinin yeri, gelen şekilleri sınırda domain tiplerine eşleyen gateway adaptörüdür. domain/ içinde herhangi bir HTTP, para veya i18n içe aktarmasını yasaklayan bir lint kuralı ikisini de yakalar. Gözden geçirmeye değer iki sessiz sorun daha var: tek uygulamayla ve testsiz tanımlanmış portlar, bir de her işleyiciye serpiştirilen gözlemlenebilirlik.
Varsayılan Nerede Geçerli#
Varsayılan, gerçekten biçimleme, i18n ve oturum işi yapan uzun ömürlü bir sunum servisi için geçerlidir: protokolü, formatı, sağlayıcıyı ve gözlemlenebilirliği kenarlarda tutun. Önemsiz bir geçirgen veya tek seferlik bir prototip söz konusuysa varsayılanı uygulamayın.
Kaynaklar#
- Hexagonal Architecture - Alistair Cockburn (yansı) (yeni sekmede açılır) - Amacın kanonik ifadesi: cihazlardan ve veritabanlarından izole olarak geliştir ve test et.
- The Clean Architecture - Robert C. Martin (yeni sekmede açılır) - Bağımlılık kuralı birebir: kaynak kodu bağımlılıkları yalnızca içeri işaret eder.
- Hexagonal architecture pattern - AWS Prescriptive Guidance (yeni sekmede açılır) - Tarafsız port ve adaptör tanımı, DDD uyumu ve pragmatist bakım-yükü çekincesi.
- Hexagonal architecture (software) - Wikipedia (yeni sekmede açılır) - Onion ve clean mimariyle soy bağı, ayrıca Fowler’ın simetri-asimetri eleştirisi.
- Pattern: Backends For Frontends - Sam Newman (yeni sekmede açılır) - Kanonik BFF tanımı: her kullanıcı deneyimi için bir arka uç.
- The Back-end for Front-end Pattern (BFF) - Phil Calçado (yeni sekmede açılır) - BFF’in SoundCloud’daki kökeni ve sunum mantığının zamanla BFF’lere nasıl göç ettiği.
- OpenTelemetry - Concepts (yeni sekmede açılır) - Bir servisin yaydığı üç sinyal: izler, metrikler ve günlükler.
- OpenTelemetry - Instrumentation (yeni sekmede açılır) - Enstrümantasyonun ne anlama geldiği ve telemetrinin bir servisten nasıl yayıldığı.
- aws-lambda-domain-model-sample (GitHub) (yeni sekmede açılır) - Somut bir dosya yapısı için desenin işlenmiş referans düzeni.
- Hexagonal Architecture and Clean Architecture (with examples) - DEV (yeni sekmede açılır) - Portları arayüz, adaptörleri sınıf olarak ele alan topluluk kaynaklı Node ve TypeScript anlatımı.
- Future-Proof Your Code: Ports & Adapters - Alex Rusin (yeni sekmede açılır) - Desenin erişilebilir, TypeScript ağırlıklı bir anlatımı.
İlgili yazılar
Tek bir PII branded type'ı observability API imzalarınıza yerleştirin; TypeScript hassas alanları runtime redactor görmeden, çağrı yerinde reddetsin.
typescript · zod · observability +3
API, ödeme ve mesaj tüketicisi geliştirenler için idempotency'ye pratik giriş: HTTP metot semantiği, idempotency key'leri, upsert ve yaygın tuzaklar.
idempotency · api-design · distributed-systems +4
Model Context Protocol için kurumsal kalıplar: araç bileşimi, çoklu ajan orkestrasyonu, rol tabanlı erişim kontrolü ve production gözlemlenebilirlik.
mcp · ai-adoption-strategy · authorization +4
Agent geliştirmek için TypeScript SDK karşılaştırması: Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu, kod örnekleri ve karar frameworkleri ile.
typescript · ai-tools · serverless +4
AWS CDK projelerinde service-based, domain-based, feature-based ve layer-based organizasyon patternlerini karar çerçeveleri ve örneklerle ne zaman seçeceğini öğren.
aws-cdk · typescript · infrastructure-as-code +3