İçeriğe atla
Ayhan Sipahi Ayhan Sipahi

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.

HTTP Denetleyici (yonlendiren adaptor)

Gelen Port

Domain Cekirdegi (sunum kurallari)

Gateway Portu

Para Portu

i18n Portu

Alt Servis Gateway

Para Bicimleyici

Ceviri Adaptoru

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.

Sunum Servisi (yapi secimi)

UI Parcasi

HTTP Kenari

Domain Cekirdegi

Tek Domain Servisi

BFF (topoloji secimi)

Mobil UI

BFF

Servis A

Servis B

Servis C

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.

Hayir

Evet

Hayir

Evet

Hayir

Evet

Tek domain bagimliligi uzerinde sunum mantigi?

Duz proxy (hexagonu atla)

Uzun omurlu mu?

Gercek degistirme, sahteleme veya izolasyon ihtiyaci?

Hafif yapi (portlari ertele)

Port-ve-adaptor (onerilen varsayilan)

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

İlgili yazılar