Microservices mimarisi, büyük uygulamaları bağımsız deploy edilebilen küçük servislere bölme yaklaşımıdır. Doğru kullanıldığında ölçeklenebilirlik ve geliştirme hızı kazandırır; yanlış uygulandığında ise "dağıtık monolit" yaratır.
Monolith vs Microservices
Özellik Monolith Microservices Başlangıç karmaşıklığı Düşük Yüksek Deploy bağımsızlığı Hayır Evet Teknoloji çeşitliliği Kısıtlı Tam özgürlük Debug kolaylığı Basit Dağıtık tracing gerekli Ölçeklenebilirlik Tüm uygulama Servis bazlı Takım bağımsızlığı Zor İdeal Veri yönetimi Tek DB Her servis kendi DB
Servis Tasarımı
blog-platformu/
├── api-gateway/ → Tek giriş noktası
├── auth-service/ → JWT, OAuth, kullanıcı yönetimi
├── makale-service/ → Makaleler, taslaklar, slug
├── yorum-service/ → Yorumlar, moderasyon
├── bildirim-service/ → E-posta, push, in-app
├── arama-service/ → Elasticsearch entegrasyonu
└── medya-service/ → Dosya yükleme, CDN, resim işleme KOPYALA
Her servis:
▸ Kendi veritabanına sahip (database per service)
▸ Bağımsız deploy edilebilir
▸ Tek bir business domain'i kapsar
API Gateway
// api-gateway/src/index.ts
// http-proxy-middleware ile basit gateway
import express from 'express' ;
import { createProxyMiddleware } from 'http-proxy-middleware' ;
import rateLimit from 'express-rate-limit' ;
import helmet from 'helmet' ;
const app = express ();
app . use ( helmet ());
// Global rate limit
app . use ( rateLimit ({ windowMs : 60_000 , max : 100 }));
// Servis yönlendirmesi
const SERVISLER = {
AUTH : process . env . AUTH_SERVICE_URL || 'http://auth-service:3001' ,
MAKALE : process . env . MAKALE_SERVICE_URL || 'http://makale-service:3002' ,
YORUM : process . env . YORUM_SERVICE_URL || 'http://yorum-service:3003' ,
ARAMA : process . env . ARAMA_SERVICE_URL || 'http://arama-service:3004' ,
MEDYA : process . env . MEDYA_SERVICE_URL || 'http://medya-service:3005' ,
};
// Token doğrulama middleware
async function tokenDogrula ( req : express . Request , res : express . Response , next : express . NextFunction ) {
const token = req . headers . authorization ?. replace ( 'Bearer ' , '' );
if ( ! token ) {
if ( req . path . startsWith ( '/api/auth' )) return next (); // Auth rotaları hariç
return res . status ( 401 ). json ({ hata : 'Token gerekli.' });
}
try {
// Auth servisten token doğrula
const cevap = await fetch ( ` ${ SERVISLER . AUTH } /internal/verify` , {
headers : { Authorization : `Bearer ${ token } ` },
});
if ( ! cevap . ok ) return res . status ( 401 ). json ({ hata : 'Geçersiz token.' });
const payload = await cevap . json ();
req . headers [ 'x-user-id' ] = payload . userId ;
req . headers [ 'x-user-rol' ] = payload . rol ;
next ();
} catch {
res . status ( 503 ). json ({ hata : 'Auth servisi erişilemiyor.' });
}
}
app . use ( '/api/auth' , createProxyMiddleware ({ target : SERVISLER . AUTH , changeOrigin : true }));
app . use ( '/api/makaleler' , tokenDogrula , createProxyMiddleware ({ target : SERVISLER . MAKALE , changeOrigin : true }));
app . use ( '/api/yorumlar' , tokenDogrula , createProxyMiddleware ({ target : SERVISLER . YORUM , changeOrigin : true }));
app . use ( '/api/arama' , createProxyMiddleware ({ target : SERVISLER . ARAMA , changeOrigin : true }));
app . use ( '/api/medya' , tokenDogrula , createProxyMiddleware ({ target : SERVISLER . MEDYA , changeOrigin : true }));
app . listen ( 3000 , () => console . log ( 'API Gateway :3000' )); KOPYALA
Servisler Arası İletişim
// Senkron — HTTP (basit ama bağımlılık yaratır)
async function yorumSayisiniGetir ( makaleId : string ): Promise < number > {
const res = await fetch ( ` ${ YORUM_SERVICE } /internal/makaleler/ ${ makaleId } /sayi` , {
headers : { 'X-Internal-Secret' : process . env . INTERNAL_SECRET ! },
});
if ( ! res . ok ) return 0 ; // Circuit breaker pattern
const { sayi } = await res . json ();
return sayi ;
}
// Asenkron — Message Queue (gevşek bağlantı)
// makale-service — yayınla
import { Channel } from 'amqplib' ;
async function makaleYayinlandi ( kanallar : Channel , makale : Makale ) {
const olay = {
tur : 'MAKALE_YAYINLANDI' ,
zaman : new Date (). toISOString (),
veri : { makaleId : makale . id , baslik : makale . baslik , yazarId : makale . yazarId },
};
kanallar . publish ( 'makale-olaylari' , 'makale.yayinlandi' , Buffer . from ( JSON . stringify ( olay )));
}
// bildirim-service — dinle
kanallar . consume ( 'makale-yayinlandi-kuyrugu' , async ( msg ) => {
if ( ! msg ) return ;
const olay = JSON . parse ( msg . content . toString ());
await yazaraTakipcilereBildirimGonder ( olay . veri );
kanallar . ack ( msg );
}); KOPYALA
Circuit Breaker Pattern
// Arıza olduğunda sürekli istek atmayı engelle
class CircuitBreaker {
private hataSayisi = 0 ;
private sonHata : Date | null = null ;
private readonly esik = 5 ;
private readonly zaman_asimi = 60_000 ;
get durum (): 'kapali' | 'acik' | 'yari-acik' {
if ( this . hataSayisi < this . esik ) return 'kapali' ;
if ( Date . now () - this . sonHata ! . getTime () > this . zaman_asimi ) return 'yari-acik' ;
return 'acik' ;
}
async calistir < T >( fn : () => Promise < T >): Promise < T > {
if ( this . durum === 'acik' ) {
throw new Error ( 'Servis geçici olarak kullanılamıyor (circuit open).' );
}
try {
const sonuc = await fn ();
if ( this . durum === 'yari-acik' ) {
this . hataSayisi = 0 ; // Başarılıysa sıfırla
}
return sonuc ;
} catch ( hata ) {
this . hataSayisi ++ ;
this . sonHata = new Date ();
throw hata ;
}
}
}
const yorumServisBreaker = new CircuitBreaker ();
async function yorumlariGetir ( makaleId : string ) {
return yorumServisBreaker . calistir (() =>
fetch ( ` ${ YORUM_SERVICE } /makaleler/ ${ makaleId } /yorumlar` ). then ( r => r . json ())
);
} KOPYALA
Docker Compose ile Yerel Ortam
# docker-compose.yml
services :
api-gateway :
build : ./api-gateway
ports : [ "3000:3000" ]
environment :
AUTH_SERVICE_URL : http://auth-service:3001
MAKALE_SERVICE_URL : http://makale-service:3002
depends_on : [ auth-service , makale-service ]
auth-service :
build : ./auth-service
environment :
DATABASE_URL : postgresql://dev:dev@auth-db:5432/authdb
JWT_SECRET : ${JWT_SECRET}
depends_on : [ auth-db ]
makale-service :
build : ./makale-service
environment :
DATABASE_URL : postgresql://dev:dev@makale-db:5432/makaledb
RABBITMQ_URL : amqp://rabbit:5672
depends_on : [ makale-db , rabbit ]
auth-db :
image : postgres:16-alpine
environment : { POSTGRES_DB : authdb , POSTGRES_USER : dev , POSTGRES_PASSWORD : dev }
volumes : [ auth-data:/var/lib/postgresql/data ]
makale-db :
image : postgres:16-alpine
environment : { POSTGRES_DB : makaledb , POSTGRES_USER : dev , POSTGRES_PASSWORD : dev }
volumes : [ makale-data:/var/lib/postgresql/data ]
rabbit :
image : rabbitmq:3-management-alpine
ports : [ "15672:15672" ]
volumes :
auth-data :
makale-data : KOPYALA
Distributed Tracing
// OpenTelemetry ile her servise trace
import { NodeSDK } from '@opentelemetry/sdk-node' ;
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' ;
const sdk = new NodeSDK ({
traceExporter : new OTLPTraceExporter ({
url : process . env . OTEL_EXPORTER_OTLP_ENDPOINT ,
}),
serviceName : 'makale-service' ,
});
sdk . start ();
// Her request otomatik trace'lenir
// Jaeger/Zipkin UI'da tüm servis çağrıları görünür:
// [api-gateway] → [makale-service] → [yorum-service]
// ↘ [auth-service] KOPYALA
Sonuç
Microservices, takım bağımsızlığı ve servis bazlı ölçeklendirme sunar; ancak distributed systems karmaşıklığını beraberinde getirir (network hataları, eventual consistency, distributed tracing). API Gateway tek giriş noktası sağlar; Circuit Breaker arıza yönetimini kolaylaştırır; Message Queue servisleri gevşek bağlı tutar. Her büyük özellik doğru servisin olması kadar, yanlış servisi oluşturmamak da önemlidir.