Skip to content
Yusuf Özdemir
ALL ARTICLES

Web Uygulaması Mimarisi: Nginx, Hono, Fastify, Nitro ve Runtime Seçimi

22 MIN READ 4,282 WORDS
ALSO IN English

Yeni bir servis kurarken genellikle teknoloji seçimiyle başlıyoruz. Node mu, Bun mı? Hono mu, Fastify mı? Nginx mi, Caddy mi? Arayüz ayrı mı olsun, aynı uygulamadan mı çıksın?

Bu soruların hepsi geçerli ama yanlış sırayla sorulduğunda gereğinden fazla büyüyor.

Çünkü bu araçların çoğu birbirinin alternatifi değil. Bir web uygulamasının farklı katmanlarında farklı işler yapıyorlar. Nginx uygulamanın önünde reverse proxy olarak durabilir, arkasında Node çalışabilir, Node üzerinde Fastify veya Hono bulunabilir. Aynı uygulamada Vite ve Nitro da server tarafında kullanılabilir.

Dolayısıyla "hangisi daha iyi?" sorusundan önce "hangi probleme çözüm arıyorum?" diye bakmak gerekiyor.

Ben bir servis kurarken önce deployment ortamını düşünüyorum. Sonra runtime'ı, onun ardından uygulama katmanını ve en son giriş katmanını seçiyorum. Arayüz ayrı bir uygulama olacak mı, servisleri bölmek için gerçekten bir neden var mı gibi kararlar da bunun etrafında şekilleniyor.

Bu yazıda bu katmanları tek tek ele alacağım. Hono, Fastify, Elysia, Express ve Nitro'yu uygulama tarafında; Nginx, Caddy ve Traefik'i giriş katmanında; Node, Bun, Deno ve edge runtime'ları da çalışma ortamı olarak değerlendireceğim.

Ama amaç bir "en iyiler" listesi çıkarmak değil.

Küçük bir VPS'te birkaç servis çalıştıran ekiple, onlarca servisi farklı ekiplerin deploy ettiği bir sistemin aynı araçları seçmesi gerekmiyor. Hatta çoğu zaman seçmemesi daha iyi.

Bizim çalışma düzenimiz de bu yazının temelini oluşturuyor. İş mantığı ağırlıklı servisler yazıyoruz ve bunları mümkün olduğunca ince bir arayüzle dışarı veriyoruz. Ağır bir SPA yok; kullanıcıya dönen şey çoğunlukla API ve onun üstüne oturan az sayıda ekran.

Bu yüzden aşağıdaki tercihler belirli bir çalışma biçimine göre şekilleniyor. Başka bir ürünün ihtiyaçları farklıysa sonuç da farklı çıkabilir.

Bir isteğin geçtiği katmanlar

İstek kabaca şöyle ilerliyor:

istemci
  → [ giriş katmanı ]     Nginx / Caddy / Traefik / gateway
      → [ runtime ]       Node / Bun / Deno / Workers
          → [ uygulama ]  Hono / Fastify / Elysia / Nitro / Express

Bu katmanları birbirinden ayırınca teknoloji tartışması da biraz daha anlaşılır hale geliyor.

Örneğin Nginx ile Hono arasında doğrudan bir seçim yapmak gerekmiyor. Biri reverse proxy olarak dışarıdan gelen isteği karşılayabilir, diğeri uygulamanın kendisini çalıştırabilir.

Aynı şekilde Nitro ile Fastify'ı da sadece "hangisi daha hızlı?" diye karşılaştırmak çok anlamlı değil. Fastify doğrudan Node üzerinde çalışan bir web framework'ü olarak öne çıkarken Nitro daha çok server uygulamasını farklı ortamlara build edip taşıma tarafında değer kazanıyor.

Kararların sırası da bu yüzden önemli.

Önce nereye deploy edeceğini bilmen gerekiyor. Deployment hedefi runtime seçeneklerini daraltıyor. Runtime seçimi de hangi framework'lerin doğal seçenek olduğunu belirliyor.

Tek VPS'te birkaç servis çalıştırıyorsan edge runtime tartışmasının büyük kısmı senin için gereksiz olabilir. Vite tabanlı bir uygulamada SSR istiyorsan Nitro anlamlı hale gelebilir. Docker üzerinde servisler sık sık gelip gidiyorsa Traefik'in servis keşfi önemli olabilir.

Yani araçtan önce sistemi tarif etmek gerekiyor.

Uygulama katmanı

Hono

Hono'nun temel fikri basit: Web standartlarına mümkün olduğunca yakın durmak.

Request, Response ve benzeri Web API'leri üzerinden çalışıyor. Node.js, Bun, Deno, Cloudflare Workers, AWS Lambda ve başka ortamlarda çalışabiliyor. Hono'nun kendi dokümantasyonunda da aynı uygulama kodunun farklı runtime'larda kullanılabildiği özellikle vurgulanıyor.

Hono'nun hono/tiny preset'i minify edilmiş halde oldukça küçük. Bu, framework'ün küçük olduğunu göstermek açısından güzel bir özellik ama bunu doğrudan "soğuk başlangıcı şu kadar hızlandırır" diye yorumlamamak lazım. Gerçek uygulamanın bağımlılıkları ve çalıştığı platform çok daha fazla şeyi belirliyor.

Basit bir servis mesela şöyle olabilir:

import { Hono } from 'hono'

const app = new Hono()

app.get('/health', (c) => c.json({ ok: true }))

app.post('/invoices', async (c) => {
  const body = await c.req.json()
  const invoice = await createInvoice(body)

  return c.json(invoice, 201)
})

export default app

Bu kodun farklı runtime'larda kullanılabilmesi güzel bir özellik. Fakat burada önemli bir ayrım var: Hono'nun taşınabilir olması, senin uygulamanın otomatik olarak taşınabilir olduğu anlamına gelmiyor.

Bir noktada fs, Node'a özel bir API, native bir modül veya uzun süren bir işlem kullanmaya başladığında runtime sınırları ortaya çıkıyor. Framework Web standartlarına bağlı kalabilir ama uygulamanın geri kalanı kalmayabilir.

Hono'nun başka bir dezavantajı da Node ekosisteminin yıllar içinde oluşturduğu middleware ve plugin dünyasının tamamını arkasında bulundurmaması. Bu çoğu yeni proje için problem değil ama mevcut bir Express uygulamasını taşırken önemli olabilir.

Küçük ve orta boy API'ler, edge'e gidecek servisler ve deployment hedefini değiştirme ihtimali olan uygulamalar için iyi bir başlangıç noktası.

Fastify

Node üzerinde kalacaksan benim varsayılan tercihim Fastify.

Buradaki avantajı yalnızca performans değil. Fastify'ın asıl farkı şema yaklaşımında ortaya çıkıyor.

JSON Schema ile request doğrulama ve response serialization yapabiliyorsun. Response şeması verdiğinde Fastify bunu çıktıların serialize edilmesinde de kullanıyor. Şemada olmayan alanların response'a yanlışlıkla sızmasını önlemeye yardımcı olması özellikle servis tarafında değerli.

Örneğin:

import Fastify from 'fastify'

const app = Fastify({ logger: true })

app.post('/invoices', {
  schema: {
    body: {
      type: 'object',
      required: ['customerId', 'amount'],
      properties: {
        customerId: { type: 'string' },
        amount: { type: 'number', minimum: 0 },
      },
    },
    response: {
      201: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          amount: { type: 'number' },
        },
      },
    },
  },
}, async (req, reply) => {
  const invoice = await createInvoice(req.body)

  return reply.code(201).send(invoice)
})

Burada response şeması yalnızca dokümantasyon için durmuyor. Fastify bunu serialization sırasında da kullanıyor. Örneğin response nesnesinde yanlışlıkla passwordHash gibi bir alan olsa bile şemada olmadığı için serialize edilen çıktıya girmiyor.

Bu, performans kazancından daha önemli bir özellik olabilir.

Eklenti sistemi de olgun. Node ekosistemindeki kütüphanelere erişimin devam ediyor ve uygulamayı plugin sınırlarıyla bölmek mümkün.

Bunun bedeli daha fazla disiplin. Şema yazmak küçük bir projede gereksiz iş gibi gelebilir. Fakat servis sayısı arttığında request ve response sözleşmelerinin açık olması ciddi rahatlık sağlıyor.

Node'da çalışan API'ler için benim tercihim bu yüzden genellikle Fastify.

Elysia

Elysia, Bun için tasarlanmış bir framework. Tip sistemi ve şema tanımları üzerinden oldukça iyi bir geliştirici deneyimi sunuyor.

Bunun karşılığında framework seçimin runtime seçimine daha fazla bağlanıyor. Elysia seçtiğinde aslında Bun'ı da seçmiş oluyorsun.

Bu kötü bir şey değil. Zaten Bun kullanıyorsan gayet mantıklı. Fakat henüz runtime konusunda karar vermediysen yalnızca Elysia'nın tip deneyimi için Bun'a geçmek bana güçlü bir gerekçe gibi gelmiyor.

Özellikle birden fazla runtime üzerinde çalışma ihtimali olan bir servis için Hono daha doğal bir seçim olabilir. Node'da kalacaksan da Fastify daha geniş bir Node ekosistemi sunuyor.

Express

Express hâlâ son derece yaygın ve olgun bir framework.

Yeni bir proje için ilk tercihim olmazdı ama "Express artık kullanılmaz" demek de doğru değil. Mevcut bir Express servis çalışıyorsa, sırf daha modern bir framework var diye onu taşımak için teknik bir gerekçe olmayabilir.

Express'in en büyük avantajlarından biri hâlâ ekosistemi. Aradığın middleware, örnek veya entegrasyonun bulunma ihtimali yüksek.

Yeni bir servis yazarken ise Fastify'ın şema ve plugin modeli veya Hono'nun Web API tabanlı yaklaşımı bana daha iyi başlangıç noktaları veriyor.

Yani Express'i bırakmak başka, yeni projede Express seçmemek başka şeyler.

Nitro

Nitro'yu diğerlerinin yanına koyarken biraz dikkat etmek gerekiyor.

Nitro yalnızca bir HTTP router veya klasik bir web framework'ü değil. Sunucu tarafını build edip farklı deployment ortamlarına hazırlayan bir katman.

Nuxt'ın server tarafında kullandığı teknoloji Nitro. Ama Nitro yalnızca Nuxt'a bağlı değil. Vite ile de doğrudan kullanılabiliyor. Nitro'nun Vite entegrasyonu sayesinde bir Vite uygulamasına server tarafı ekleyebilir, server route'ları tanımlayabilir ve uygulamayı farklı deployment hedeflerine uygun şekilde build edebilirsin.

Basit bir Vite + Nitro kurulumu kabaca şöyle görünüyor:

import { defineConfig } from 'vite'
import { nitro } from 'nitro/vite'

export default defineConfig({
  plugins: [nitro()],
  nitro: {
    serverDir: './server',
  },
})

Bu ayrım önemli. Nitro'yu "Nuxt'ın içinde çalışan backend" diye anlatmak eksik. Nuxt bunun en bilinen kullanım alanlarından biri ama Nitro kendi başına da kullanılabiliyor.

Nitro'nun asıl avantajı deployment tarafında ortaya çıkıyor. Uygulama kodunu belirli bir hosting sağlayıcısının çalışma biçimine mümkün olduğunca az bağlamak istiyorsan farklı preset'lerle farklı ortamlara çıkabilmek işine yarıyor.

Bunun bir bedeli var: araç zinciri büyüyor.

Fastify ile yazılmış düz bir Node servisinde problem çıktığında genellikle uygulama koduna ve Node'a bakıyorsun. Nitro kullandığında araya build ve deployment katmanı da giriyor. Bu çoğu zaman yönetilebilir ama sistemin nasıl paketlendiğini bilmek gerekiyor.

Bu yüzden Nitro'yu "Fastify'dan daha iyi HTTP framework" gibi değerlendirmek yanlış. İkisi farklı problemlere daha fazla ağırlık veriyor.

Eğer Vite tabanlı bir uygulamada SSR istiyorsan veya aynı server kodunu farklı deployment hedeflerine taşıma ihtimalin varsa Nitro daha anlamlı hale geliyor. Sadece tek bir Node sunucusunda birkaç API endpoint'i çalıştıracaksan Fastify kadar basit bir çözüm olmayabilir.

İnce arayüzü nasıl servis etmeli?

Bizim durumda arayüz ürünün kendisi değil, servisin kullanıcıya açılan tarafı.

Bu ayrımı baştan yapmak birçok kararı kolaylaştırıyor.

Üç yaklaşım var.

Statik dosya + ayrı API

Arayüzü build edip CDN veya web sunucusu üzerinden servis ediyorsun. API ise başka bir yerde duruyor.

Teknik olarak basit. Fakat iki farklı origin olduğunda bazı işler zorlaşıyor.

Cookie ayarları, CORS, preflight istekleri, authentication ve deployment koordinasyonu devreye giriyor.

Bunların hiçbiri çözülemeyecek problemler değil. Ama beş ekranlık bir iç yönetim paneli için sırf frontend ve backend birbirinden bağımsız olsun diye bu karmaşıklığı eklemek bana gereksiz geliyor.

Aynı uygulamadan SSR

Arayüz ve API aynı uygulamanın içinde duruyor. Nitro, Nuxt, Next veya başka bir SSR çözümü burada kullanılabilir.

Tek origin'in önemli bir avantajı var: tarayıcı tarafındaki authentication ve cookie yönetimi daha basit hale geliyor.

Ayrıca ilk HTML'i sunucuda üretebildiğin için bazı uygulamalarda istemci tarafında büyük bir JavaScript uygulaması çalıştırmaya gerek kalmıyor.

Bunun karşılığında build sistemi ve uygulamanın kendisi büyüyor.

Sunucudan doğrudan HTML

Arayüz gerçekten küçükse bunu daha da basit tutmak mümkün.

PHP tarafında Blade gibi template sistemleri veya benzeri server-rendered HTML yaklaşımı kullanılabilir. Üzerine gereken yerlerde biraz JavaScript eklenir.

Bu yaklaşımın avantajı sıkıcı olması.

Ayrı frontend deployment'ı yok. Ayrı bağımlılık ağacı yok. API ile frontend arasında versiyonlama problemi yok.

Ekran sayısı bir elin parmaklarını geçmiyorsa ve uygulamanın büyük kısmı form, tablo ve birkaç etkileşimden oluşuyorsa ben önce bunu düşünürüm.

Arayüz büyürse daha sonra ayrı bir frontend'e geçmek mümkün. API zaten ayrı bir sözleşme olarak duruyorsa bu geçiş çok zor değil.

Tersi yönde, baştan frontend ve backend'i iki ayrı deployment olarak kurup sonra "aslında buna gerek yokmuş" demek daha zahmetli.

Giriş katmanı

Nginx

Nginx'i sadece "eski bir web sunucusu" olarak görmek hata olur. Bugün de reverse proxy olarak çok güçlü ve üretimde son derece yaygın.

Biz de Nginx'i çoğunlukla uygulamanın önünde reverse proxy olarak kullanıyoruz. İstek önce Nginx'e geliyor, TLS burada sonlanıyor ve ardından ilgili uygulamaya aktarılıyor.

Aynı katmanda birden fazla servisi farklı domain veya path'lere yönlendirmek, yük dengelemek, statik dosya servis etmek, caching yapmak ve bağlantı yönetimini kontrol etmek de mümkün.

Örneğin birkaç servisin olduğu basit bir kurulum şöyle olabilir:

server {
    listen 443 ssl;
    server_name api.ornek.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 443 ssl;
    server_name admin.ornek.com;

    location / {
        proxy_pass http://127.0.0.1:4000;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Burada uygulamalar kendi portlarında çalışmaya devam ediyor. Dışarıya ise yalnızca Nginx açılıyor.

Nginx'in bir diğer güçlü tarafı kontrol seviyesi. Proxy timeout'ları, request boyutları, header'lar, buffering, caching, compression, connection yönetimi ve load balancing gibi konularda ayrıntılı kontrol sağlayabiliyorsun.

Bu yüzden "Caddy daha kolay, o halde Nginx gereksiz" sonucu çıkarmamak gerekiyor.

Caddy'nin avantajı daha az yapılandırmayla özellikle küçük kurulumları ayağa kaldırabilmesi. Nginx'in avantajı ise çok uzun süredir kullanılan, davranışı iyi bilinen ve ayrıntılı biçimde kontrol edilebilen bir reverse proxy olması.

Topolojisi sabit bir sistemde veya proxy katmanını ayrıntılı biçimde kontrol etmek istediğin bir ortamda Nginx hâlâ çok iyi bir seçenek.

Bizim kullanımımızda da Nginx'i uygulama framework'ünün alternatifi olarak değil, onun önündeki katman olarak düşünmek daha doğru:

İnternet
   ↓
Nginx
   ↓
Fastify / Hono / Nitro / başka bir uygulama
   ↓
veritabanı / diğer servisler

Yani "Nginx mi Hono mu?" diye bir seçim yapmıyoruz. İhtiyaca göre ikisini aynı sistemde kullanıyoruz.

Caddy

Caddy'nin en büyük farkı yapılandırmanın ve HTTPS yönetiminin daha basit olması.

Alan adını verdiğinde otomatik HTTPS ve sertifika yönetimi yapabiliyor:

api.ornek.com {
    reverse_proxy localhost:3000
}

ornek.com {
    root * /var/www/ui
    try_files {path} /index.html
    file_server
}

Tek sunucuda birkaç servis çalıştırıyorsan bunun değeri büyük.

Nginx ile de aynı işi yapabilirsin. Buradaki mesele "Caddy daha hızlı" değil. Mesele aynı işi daha az yapılandırmayla yapabilmesi.

Çok özel bir proxy davranışına ihtiyacın yoksa veya mevcut sistemin Nginx'e göre kurulmamışsa yeni bir VPS kurarken Caddy'yi önce denemek mantıklı.

Traefik

Traefik'in farkı proxy olmaktan çok servisleri nasıl bulduğu konusunda ortaya çıkıyor.

Docker kullanıyorsan container etiketlerinden routing bilgisi okuyabiliyor. Kubernetes'te de kaynaklardan yapılandırma üretebiliyor.

Örneğin Docker Compose içinde:

services:
  invoices:
    image: ghcr.io/ornek/invoices:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.invoices.rule=Host(`api.ornek.com`) && PathPrefix(`/invoices`)"
      - "traefik.http.services.invoices.loadbalancer.server.port=3000"

Yeni bir container deploy edildiğinde ayrıca proxy config dosyası düzenlemek zorunda kalmamak büyük kolaylık.

Middleware sistemi de rate limit, authentication, header değiştirme ve benzeri işler için kullanılabiliyor.

Bedeli ise yapılandırmanın dağılması. Sistem büyüdüğünde hangi servisin hangi route'u, middleware'i veya hostname'i kullandığını takip etmek daha zor hale gelebiliyor.

Docker Compose veya Kubernetes üzerinde sık deploy edilen servislerin varsa Traefik anlamlı. Tek VPS'te iki servis çalıştırıyorsan Caddy'nin basitliği daha değerli olabilir.

Gateway'ler: Kong, Envoy, APISIX

Kong, Envoy ve APISIX'i Nginx veya Caddy ile aynı kategoriye koymak mümkün ama kullanım amaçları biraz farklı.

Bunlar özellikle API gateway, merkezi trafik politikaları ve daha büyük dağıtık sistemlerde anlam kazanıyor.

API key yönetimi, rate limit politikaları, authentication, kota, servisler arası trafik kontrolü veya birden fazla ekibin ortak giriş politikası gibi ihtiyaçların varsa gateway katmanı anlamlı hale geliyor.

Envoy ayrıca servis mesh dünyasında önemli bir yapı taşı.

Ama on servisi olan tek ekip için sırf "ileride lazım olur" diye gateway eklemek çoğu zaman iyi fikir değil.

Gateway'in getirdiği her özellik aynı zamanda başka bir konfigürasyon, gözlemleme ve bakım yüzeyi demek.

İhtiyaç ortaya çıktığında eklemek genellikle daha mantıklı.

Runtime tarafı

Node

Node.js burada en güvenli varsayılan.

Ekosistem çok geniş. Veritabanı sürücülerinden monitoring araçlarına kadar neredeyse her şey için seçenek var. Üretimde ne bekleyeceğin de oldukça iyi biliniyor.

Özellikle uygulamanın işi HTTP request'ini alıp birkaç veritabanı sorgusu yapmak, başka servislere çağrı göndermek ve sonucu döndürmekse Node'un özel bir dezavantajı yok.

Bun

Bun ilginç çünkü runtime'ın kendisi daha fazla şeyi tek pakette çözmeye çalışıyor.

JavaScript runtime, package manager, bundler ve çeşitli Web API'leri aynı ekosistemin parçası.

Performans tarafında da güçlü sonuçlar verebiliyor. Fakat burada yine benchmark ile gerçek uygulamayı ayırmak gerekiyor.

Bir router'ın benchmark'ta hızlı olması, veritabanına beş sorgu atan ve üçüncü parti bir ödeme API'sini bekleyen gerçek bir servisin otomatik olarak daha hızlı olacağı anlamına gelmiyor.

Bun kullanacaksan kullandığın kütüphanelerin ve native bağımlılıkların Bun ile gerçekten uyumlu olduğunu test etmek daha önemli.

Node'dan Bun'a geçişi yalnızca benchmark sonucu gördüğün için yapmak yerine kendi workload'unu ölçmek daha doğru.

Deno

Deno'nun izin modeli, Web API'lerine yakın yaklaşımı ve TypeScript deneyimi onu farklılaştırıyor.

Özellikle runtime'ın dosya sistemine, network'e veya environment'a erişimini daha açık biçimde kontrol etmek istediğin projelerde ilginç olabilir.

Ama burada da aynı kural geçerli: runtime'ın bir özelliği tek başına framework seçimini belirlememeli.

Cloudflare Workers ve benzeri edge runtime'lar

Edge runtime'ların avantajı uygulamayı kullanıcıya yakın noktalarda çalıştırabilmek.

Bu özellikle küçük request'lerde ve latency'nin önemli olduğu API'lerde işe yarayabilir.

Ama klasik sunucu ortamından farklı kısıtları var. CPU, bellek, runtime API'leri, dosya sistemi erişimi ve uzun süren işlemler konusunda kullandığın platformun modeline uyman gerekiyor.

Bu yüzden Workers'a uygun bir servis ile geleneksel bir Node servisinin tasarımı aynı olmak zorunda değil.

İstek başına yapılan iş küçükse ve kullanıcı ile sunucu arasındaki mesafe önemliyse edge mantıklı.

Büyük rapor üretmek, uzun süren hesap yapmak veya ağır dosya işlemleri yapmak gerekiyorsa bu işi ayrı bir worker veya klasik servis olarak çalıştırmak daha doğal.

Runtime'ı framework'ten önce seçmek bu yüzden mantıklı. Runtime seçince bazı framework'ler zaten kendiliğinden eleniyor.

Mikroservis mi, tek servis mi?

Çoğu ekip mikroservise düşündüğünden daha geç ihtiyaç duyuyor.

Bir uygulamayı bölmenin gerçek nedenleri var.

  • Parçaların ölçek ihtiyacı farklı olabilir. Bir servis çok trafik alırken diğeri neredeyse hiç trafik almıyorsa ayrı ölçeklemek mantıklı olabilir.
  • Farklı runtime veya dil gerekebilir. Görüntü işleme Go'da, geri kalan uygulama TypeScript'te çalışabilir.
  • Deploy ritimleri farklı olabilir. Bir parçayı günde birkaç kez deploy etmek gerekirken diğerinin ayda bir değişmesi mümkün.
  • Ekip sınırları farklı olabilir. İki ekip bağımsız deploy etmek ve kendi servisinin sorumluluğunu taşımak istiyorsa ayrı servis mantıklı hale gelebilir.

Bunların hiçbiri yoksa bölmenin maliyeti daha fazla hissedilir.

Özellikle tek ekip, tek veritabanı ve sıkı bağlı iş mantığı varsa servisleri ayırıp hepsinin aynı veritabanına yazması çoğu zaman dağıtık bir monolit ortaya çıkarıyor.

Aynı süreç içinde modül sınırı çizmek daha ucuz olabilir.

Mikroservise geçtiğinde en baştan üç konuya karar vermek gerekiyor.

İlki, servislerin nasıl konuşacağı.

Her şeyi senkron HTTP yaparsan bir isteğin zincir halinde beş servise gitmesi mümkün. Zincirdeki tek bir yavaşlama bütün isteği etkiliyor.

Her şeyi kuyruk üzerinden yapmak da çözüm değil. Bu sefer eventual consistency ve operasyonel karmaşıklık geliyor.

İkincisi, gözlemleme.

Bir request'in A servisinden B'ye, B'den C'ye gittiğini görebilmek için tracing ve merkezi loglama gibi araçlara ihtiyaç duyacaksın.

Üçüncüsü, sözleşme.

Servisler arasında hangi JSON'un gidip geldiği bir yerde açıkça tanımlı olmalı. OpenAPI, JSON Schema veya kullandığın başka bir sözleşme sistemi burada işe yarıyor.

JavaScript dışına çıkarsan

Bu yazının çoğu JavaScript ekosisteminden ilerliyor ama giriş katmanı ve arayüz stratejisi dilden bağımsız.

PHP tarafında FrankenPHP ilginç bir seçenek.

Caddy üzerine kurulu olduğu için web sunucusu, reverse proxy ve PHP uygulama sunucusunu tek binary etrafında topluyor. Laravel kullanıyorsan klasik PHP-FPM + Nginx kurulumuna göre bazı hareketli parçaları azaltabiliyor.

Laravel Octane de uygulamayı uzun ömürlü worker süreçleri üzerinden çalıştırarak her request'te uygulamanın baştan yüklenmesiyle ilgili maliyetleri azaltabiliyor.

Go tarafında ise durum daha da basit.

net/http ile doğrudan üretim servisi yazabilirsin. Çoğu backend için ayrıca büyük bir framework seçmek zorunda değilsin.

Go'nun tek binary olarak dağıtılabilmesi de deployment'ı basitleştiriyor.

Dil zaten belliyse bu yazının runtime bölümünün büyük kısmı senin için kapanmış oluyor. Geriye uygulama katmanı, giriş katmanı ve arayüzün nasıl servis edileceği kalıyor.

Senaryolara göre tercihler

Tek VPS, birkaç servis, ince arayüz

Nginx veya Caddy + Node + Fastify.

Arayüz küçükse aynı uygulamadan servis et. Ayrı frontend deployment'ı kurmak için gerçek bir neden yoksa kurma.

Nginx kullanıyorsan da değiştirmek için bir neden yok. Özellikle reverse proxy, TLS termination, caching veya daha ayrıntılı proxy kontrolü gerekiyorsa Nginx gayet yerinde.

Docker Compose, çok sayıda servis, sık deploy

Traefik + Node.

Servislerin ihtiyaçlarına göre Fastify veya Hono seçilebilir. Docker zaten merkezdeyse Traefik'in servis keşfi işini kolaylaştırıyor.

Küçük API, edge deployment

Hono + Workers.

İstek başına yapılan iş küçükse ve kullanıcıların farklı coğrafyalarda olması önemliyse mantıklı.

Ağır rapor, dosya işleme veya uzun süren hesaplamaları ise ayrı bir servise koy.

Vite tabanlı uygulama, SSR ve farklı deployment seçenekleri

Nitro burada daha anlamlı.

Vite uygulamasına server tarafı ekleyebilir, SSR ve server route'ları aynı proje içinde tutabilir ve deployment hedefini Nitro'nun preset'leri üzerinden değiştirebilirsin.

Nuxt kullanıyorsan zaten Nitro'nun bu modelini doğrudan kullanıyorsun.

Node üzerinde sözleşmesi önemli API

Fastify.

JSON Schema, response serialization ve plugin modeli burada daha önemli hale geliyor.

Bun kullanmaya zaten karar verdiğin proje

Elysia.

Runtime seçimin zaten Bun olduğu için Elysia'nın tip sistemi ve API'si anlamlı bir avantaj haline geliyor.

Çok ekipli, dışarıya API satan sistem

Gateway katmanı burada gerçekten karşılığını verebilir.

Kong, APISIX veya Envoy gibi araçlar merkezi authentication, rate limit, API policy ve trafik yönetimi gibi konularda anlamlı hale geliyor.

Ama bu ihtiyaçlar ortaya çıkmadan gateway eklemek gereksiz bir katman.

Sonuç

Hono, Nitro ve Nginx arasında "hangisi daha iyi?" diye bir sıralama yapmak zaten yanlış başlangıç.

Nginx veya Caddy giriş katmanında olabilir.

Onun arkasında Node, Bun, Deno veya bir edge runtime çalışabilir.

Runtime'ın içinde de Hono, Fastify, Elysia veya Nitro bulunabilir.

Hatta Nitro kullanan bir Vite uygulamasının önünde Nginx, onun arkasında da başka bir servis bulunabilir.

Dolayısıyla önce aracı seçmek yerine problemi tarif etmek daha doğru.

Nereye deploy edeceksin?

İstek başına yaptığın iş ne kadar ağır?

Node ekosistemine ne kadar bağımlısın?

Arayüz gerçekten ayrı bir uygulama mı?

Kaç servis olacak?

Servisleri neden ayırıyorsun?

Bu soruların cevapları belli olduktan sonra teknoloji seçimi çok daha kolaylaşıyor.

Benim için varsayılan hâlâ basit kurulum.

Tek VPS'te birkaç servis varsa Nginx veya Caddy, Node ve Fastify yeterli.

Nginx özellikle reverse proxy olarak hâlâ çok güçlü. Ayrıntılı proxy kontrolü, load balancing, caching veya mevcut altyapıyla uyum önemliyse sırf daha yeni bir araç çıktı diye değiştirmek için bir neden yok.

Edge gerekiyorsa Hono'ya bakarım.

Vite tabanlı bir uygulamada SSR ve farklı deployment hedefleri önemliyse Nitro'yu düşünürüm.

Bun zaten seçilmişse Elysia mantıklı.

Servis sayısı ve deployment sıklığı arttığında Traefik devreye girebilir.

Gateway, mikroservis ve Kubernetes ise ancak gerçekten çözdükleri bir problem olduğunda masaya gelmeli.

Çünkü basit bir sistemin sonradan büyütülmesi genellikle mümkün. Gereksiz yere karmaşık kurulmuş bir sistemi sonradan basitleştirmek ise çok daha zor.

More to read