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 yapışkan kod bir çamur topuna dönüşür; para biçimlemesi HTTP işleyicilerine karışır ve 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 Kod Çamur Topu
Başarısızlık biçimi tahmin edilebilir. “İ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 tel 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 tel formatına bağımlıdır. Bu okları tersine çevirin, çamur topu oluşmayı bırakı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. Katmanlama bürokrasi değildir. Domain çekirdeğinin bir ağ, bir saat veya bir yerel ayar olmadan çalıştırılabilmesi için vardır.
Hexagonal, onion ve clean mimari bir aile, rakip değil. Onion (Palermo, 2008) ve clean (Martin, 2012), aynı bağımlılık tersine çevirme fikrini farklı çizimlerle inşa eder. Terimleri benimsenme yarışındaki rakipler olarak değil, tek bir ilkeyi tanımlayan adlar olarak ele alın.
Katmanlar
Bir sunum servisi hexagona temiz biçimde eşlenir. 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) çekirdekte değil, 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 izler (traces), metrikler ve günlüklerdir; 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ğini sinyalsiz tutun ki testleri saf, mantığı taşınabilir kalsın.
Değiştirilebilir Kenarlar Olarak Adaptörler
Karşılığı somuttur. Çekirdek, hiçbir şeye bağımlı olmayan bir port tanımlar:
// domain/ports.ts — cekirdege ait, somut hicbir seye bagimli degil
export interface MoneyFormatter {
format(amountMinor: number, currency: string, locale: string): string;
}
Bir adaptör onu kenarda uygular:
// adapters/money/intl-money-formatter.ts — degistirilebilir 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 yalnızca MoneyFormatter arayüzünü gördü. 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. 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 toplamini yerel ayara gore sekillendirir", 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 tel formatını değiştirdiğinde, bu değişiklik burada değil, 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 ve BFF
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 ve terim, web teknoloji lideri Nick Fisher tarafından bulundu. 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 dayanıklılık etkileri için GraphQL BFF 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, ancak topoloji ve iç yapı dik (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 bir iç yapı sorusunu yanıtlar: servis içindeki bağımlılıklar hangi yöne işaret eder.
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 Bir 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ş, port disiplinini korumaktır (çekirdek arayüzlere sahiptir, bağımlılıklar içeri işaret eder), bu arada yönlendirilen tarafın sıradan katmanlar gibi okunmasına izin verir. Portlardan test edilebilirlik kazanırsınız ama gelen ve giden tarafların ayna görüntüsü olduğunu varsaymazsınız.
Bunu Ne Zaman YAPMAMALI
Yapının gerçek bir başlangıç maliyeti var ve ona karşı dürüst sav aynı yetkin kaynaklardan gelir. Şu durumlarda atlayın:
- Gerçekten önemsiz bir geçirgen (pass-through), biçimleme, i18n veya oturum şekillendirme olmadan, bir hexagona ihtiyaç duymaz. Tören, yerine geçtiği yapışkan koddan daha pahalıya mal olur.
- Erken her-şeye-port. Tam olarak tek uygulaması olan ve test dikişi bulunmayan şeyler için port tanımlamak, karşılığı olmayan dolaylama ekler. Değiştirmek, sahtelemek veya izole etmek için gerçek 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, bahsin yalnızca servis sınırları geri ödeyecek kadar uzun yaşadığında ve yeterince yapışkan kod biriktirdiğinde işe yaradığıdır.
Sık Yapılan Hatalar
- Biçimleme ve i18n çağrıları denetleyicilerde yaşar. Onları çekirdeğin sahip olduğu bir portun arkasına taşıyın.
- Alt servis tel formatı çekirdeğe sızar. Gateway adaptörü tel şekillerini sınırda domain tiplerine eşlemelidir.
- Portlar tek uygulamayla ve testsiz tanımlanır. Portları spekülatif olarak değil, gerçek bir değiştirme, sahteleme veya izolasyon ihtiyacında tanıtın.
- Servise BFF denir ve sahip olmadığı yayılma dayanıklılığı varsayımlarını miras alır. Ona tek domain bağımlılığı olan bir sunum servisi deyin.
- Gözlemlenebilirlik her işleyiciye serpiştirilir. Adaptörleri enstrümante edin ve çekirdeği sinyalsiz tutun.
Burada hafif bir koruma yardımcı olur: domain/ içinde herhangi bir HTTP, para veya i18n içe aktarmasını yasaklayan bir lint kuralı. O kural geçerse bağımlılık okları doğru yöne işaret eder ve çekirdek hâlâ sıfır I/O ile test edilebilir.
Kapanış
Uzun ömürlü bir sunum servisi için onu port-ve-adaptör olarak düzenleyin ve domain çekirdeğini somut hiçbir şeye bağımlı bırakmayın. Protokolü, formatı, sağlayıcıyı ve gözlemlenebilirliği kenarlarda bağlayın; orada iş mantığına dokunmadan değiştirilebilir, sahtelenebilir ve enstrümante edilebilirler. Sınır dürüsttür: önemsiz geçirgenler ve kısa ömürlü prototipler için yapıyı atlayın; orada dolaylama getirdiğinden fazlasına mal olur. Bir desen kataloğuyla değil, içeri işaret eden bağımlılıkların disipliniyle başlayın ve BFF sorusunu ayrı tutun, çünkü o, iç yapıyla değil topolojiyle ilgilidir.
Kaynaklar
- Hexagonal Architecture — Alistair Cockburn (yansı) - Amacın kanonik ifadesi: cihazlardan ve veritabanlarından izole olarak geliştir ve test et.
- The Clean Architecture — Robert C. Martin - 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 - Tarafsız port ve adaptör tanımı, DDD uyumu ve pragmatist bakım-yükü çekincesi.
- Hexagonal architecture (software) — Wikipedia - Onion ve clean mimariyle soy bağı, ayrıca Fowler’ın simetri-asimetri eleştirisi.
- Pattern: Backends For Frontends — Sam Newman - Kanonik BFF tanımı: her kullanıcı deneyimi için bir arka uç.
- The Back-end for Front-end Pattern (BFF) — Phil Calçado - BFF’in SoundCloud’daki kökeni ve sunum mantığının zamanla BFF’lere nasıl göç ettiği.
- OpenTelemetry — Concepts - Telemetriyi kenarlarda bağlamanın temelindeki üç sinyal (izleme, metrikler, günlükler).
- OpenTelemetry — Instrumentation - Enstrümantasyonun ne anlama geldiği, kenar-enstrümantasyon çerçevesini destekler.
- aws-lambda-domain-model-sample (GitHub) - Somut bir dosya yapısı için desenin işlenmiş referans düzeni.
- Hexagonal Architecture and Clean Architecture (with examples) — DEV - 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 - 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.
Model Context Protocol için kurumsal kalıplar: araç bileşimi, çoklu ajan orkestrasyonu, rol tabanlı erişim kontrolü ve production gözlemlenebilirlik.
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.
Domain-Driven Design'a kapsamlı giriş: temel kavramlar, yapı taşları, stratejik desenler ve DDD'yi ne zaman ve nasıl uygulayacağına dair rehber.
CDK TypeScript Lambda için 9 bundler ve 3 cdk synth runner'ının ölçümlü karşılaştırması; her katman için varsayılan ve onu seçtiren kural.