Exploring Infinite Possibilities on FrontEnd 🚀
Burak Sağlık
Search...

Sat Sep 26 2026

Strangler Fig Deseni: COBOL'den Mikroservise Kademeli Geçiş Rehberi

Strangler Fig Deseni: COBOL'den Mikroservise Kademeli Geçiş Rehberi

🎯 Giriş: Neden Strangler Fig?

Merhaba! 👋 Otur, kahveni al, hadi sohbet edelim.

Bazen bir projeye baktığında "Nasıl buraya geldik?" diye soruyorsun kendine. Geçen yıl tam da böyle bir durumla karşılaştım: 30 yıldır ayakta duran, COBOL ile yazılmış bir faturalandırma sistemi. Sistem hala çalışıyordu — evet, çalışıyordu — ama her yeni özellik isteği bir kabus gibiydi.

COBOL Sistemi Neden Artık Zor? ❗️

  • Bilgi kaybı: Sistemi yazanlar emekli oldu, dokümantasyon yok (veya 1995'ten kalma kağıtlar üzerinde) 📄
  • Değişim maliyeti: Küçük bir kural değişikliği için haftalarca test, onay süreci... 🐢
  • Entegrasyon ağrıları: Modern API'lerle konuşması için "adapter" katmanları üzerine adapter katmanları... 🍝
  • İş bulmak zor: COBOL bilen geliştirici bulmak, iğneyi samanlıkta aramak kadar zor 🎯

Kısa özetle: Sistem çalışıyor ama geliştirilebilir değil. Ve iş dünyası beklemiyor.


Strangler Fig (Yırtıcı İncir) Deseni Nedir? 🌱

İsmini doğadaki bir ağaçtan alan bu desen, eski sistemi kademeli olarak yeni sistemle değiştirme stratejisidir.

Fikir basit: Eski sistemi bir kerede yıkıp yenisini yazmak yerine (big bang 💥), yeni işlevleri yanına yazarsın, trafiği yavaş yavaş kaydırırsın, eski kodu parçalar halinde "yutarsın".

Eski Sistem ──▶ [Proxy / Router] ──▶ Yeni Servis (özellik bazlı)
                    │
                    └──▶ Kalan Eski Kod (henüz geçirilmemiş kısımlar)

Neden bu proje için uygun? ✅ Risk düşük — aniden her şey çökmez
✅ Değer erken gelir — en acil özellikler önce taşınır
✅ Geri dönüş var — bir servis bozulursa trafik eskiye yönlendirilir
✅ Ekip öğrenirken yapar — COBOL'dan kurtulurken domain bilgisini de kazanırsın


Kişisel Bir Not 📝

Ben de geçen yıl benzer bir geçişteydim. Müşteri "6 ayda bitirebilir misiniz?" dedi, ben "18 ay buluruz, ama her 2 ayda bir canlıya değerli bir parça çıkarırız" dedim. Sonuç? 14 ayda bitecek olmuştu, hiçbir hafta sonu çalışmadık, ve eski sistem son gününde kapatıldı — kimse fark etmedi bile. 😎

Strangler Fig sabır ister, ama getirdiği güven o kadar değerli ki...

Hazırsan bir sonraki bölümde nasıl başladığımızı, hangi parçayı ilk kestiğimizi ve proxy katmanını nasıl kurduğumu anlatayım. Hadi devam! 🚀


❗️ Mevcut COBOL Faturalandırma Sistemi: Sorunlar ve Riskler

Hazırsan bu eski dostumuzun iç yapısını ve bize günlük baş ağrısı veren riskleri tek tek inceleyelim 🕵️‍♂️

🏗️ Mimarinin Özeti (Kısaca)

  • Ana akış: Mainframe (z/OS) üzerinde çalışan batch job'lar → CICS transaction'lar → DB2/VSAM dosyaları
  • Veri modeli: Sabit uzunluklu record'lar, COPYBOOK tanımlarıyla paylaşılan layout'lar, çok az normalizasyon
  • İş akışı:
    1. Günlük fatura dosyası (EBCDIC, packed decimal) gelir
    2. JCL ile sırayla: VALIDATE → ENRICH → CALCULATE → PRINT/ARCHIVE
    3. Hata olursa SYSOUT'a dumplanır, manuel müdahale beklenir

Ne oluyor burada?
Her şey sıralı, stateful ve tek noktadan yönetiliyor. Paralelizm? Yok. Rollback? Manuel. Güncelleme? Derleme + test + deployment haftası.


🚨 Başımıza Gelen Riskler (Madde Madde)

  • 💸 Bakım Maliyeti Patlıyor

    • Yılın %70'i yalnızca "keep the lights on" harcıyoruz
    • Her küçük değişiklik için derleyici lisansı, test ortamı, change control board… masraflar artıyor 📈
  • 👴 Yetkin Personel Tükeniyor

    • COBOL/JCL/DB2 bilen seniorlar emekli oluyor
    • Yeni mezunlar "mainframe" duymuyor bile — knowledge gap her yıl büyüye 📉
  • 📦 Ölçeklenemezlik (Vertical Only)

    • Yük arttığında daha büyük mainframe almak dışında çare yok
    • Cloud burst? Container? Kubernetes? Hayal bile edemezsin ☁️❌
  • 🔍 Hata İzolasyonu Neredeyse İmkansız

    • Batch job orta noktada çökerse → baştan başlatırsın (idempotent değil)
    • Loglar SYSOUT'ta kaybolur, correlation ID yok → root cause bulmak günler sürer 🕵️‍♀️🕵️‍♂️
  • 🔒 Compliance & Audit Gece Kabusu

    • Veri maskeleme, şifreleme, GDPR/KVKK uyumu → her seferinde custom exit routine yazıyorsun
    • Audit trail parçalı, merkezi değil → ceza riski her zaman üzerinde 🎯
  • 🧪 Test Otomasyonu Yok denecek Kadar

    • Unit test kavramı yok, integration test = production'a atıp dua etmek
    • Regression? Manuel → her release riskli bir kumar 🎲

🤝 Siz de Tanıdıq Gibi Geldi mi?

  • "Bu ay yine end-of-month batch'i 14 saat sürdü, sabah 6'ya kadar bekledik" 😩
  • "Yeni bir vergi oranı eklenecek, 3 hafta change request süresi verildi" 📅
  • "Son çökmede şarjlı kart verisi kayboldu, müşteriye manuel fatura kesmek zorunda kaldık" 💳

Evet, heimiz aynı teknolojik borçlanmanın kurbanıyız.
Sonraki bölümde bu borcu nasıl ödeyeceğimizi (modernize edeceğimizi) planlayacağız — strateji, araçlar ve adım adım roadmap ile 🚀


🔁 Strangler Fig Deseni Nedir ve Nasıl Çalışır?

Hadi bu deseni günlük hayattan bir benzetme ile koyalım zihninize 🌱


Düşün ki... Bir Eski Ağaç Ve Yabani Bir İhlamur 🌳

Strangler Fig (Örümcek İhlamuru), doğada şöyle bir şey yapar:

  • Bir ağacın gövdesine sarılır
  • Köklerini yere doğru indirir
  • Zamanla ana ağacı tamamen sarar ve yutar
  • Sonunda eski ağaç çürür, yerini ihlamur alır

Bizim yazılım dünyamızda da birebir aynısı geçer: Eski (legacy) sistem "ana ağaç", yeni sistem "ihlamur". Kademeli olarak eski sistemi sarar, onun işlevlerini üstlenir ve bir gün tamamen yerini alırsınız.


Temel Prensipler — 3 Adımda Özetle 🎯

Adım Ne Oluyor? Benzetme
1. İnceleyici Katman (Facade/Proxy) Eski sistemin önüne bir arabulucu koyarsınız. Tüm istekler önce buraya gelir. Kapıda bir güvenlik görevlisi — kim nereye gidecek entsche eder.
2. Kademeli Yönlendirme (Incremental Cutover) Yeni fonksiyonları parça parça yazarsınız, proxy'yi güncellersiniz. Eski sistemden trafiği yavaş yavaş yeni sisteme çekersiniz. Her ay bir oda yeniliyor, evde yaşamaya devam ediyorsunuz 🏠🔨
3. Eski Sistemin Yavaşça "Yutulması" Tüm işlevler taşındığında, eski sistemi kapatırsınız. Artık sadece yeni sistem ayakta. Eski ağaç çürüdü, ihlamur artık tek başına ayakta 🌿

Adım Adım Süreç — Metinle Akış Şeması 📋

1️⃣ Başlangıç: Her Şey Eski Sistemde

İstek → [Eski Monolit] → Yanıt

2️⃣ Proxy / Strangler Façade Devreye Girer

İstek → [Strangler Facade] → [Eski Monolit] → Yanıt

Ne değişti? İstemci artık doğrudan eski sistemle konuşmuyor. Arada bir "akıllı yönlendirici" var.

3️⃣ İlk Yeni Servis Yazılır, Yönlendirme Güncellenir

İstek → [Strangler Facade] 
    ├─ /api/users/* → [Yeni User Servisi] ✅
    └─ Diğer her şey → [Eski Monolit] 🔄

Kural: Yeni servis production'da canlıya alınır, ama sadece ilgili trafik yönlendirilir. Geri kalanı eski sistem hallediyor.

4️⃣ Döngü Devam Edir (Her Sprint Bir Parça Daha)

  • Sipariş servisi → Yeni servise taşındı ✅
  • Ödeme servisi → Yeni servise taşındı ✅
  • Raporlama → Hala eski sistemde 🔄

5️⃣ Son Durum: Eski Sistem Tamamen Boşaldı

İstek → [Strangler Facade] → [Yeni Mikroservisler Topluluğu] → Yanıt

Ve bir gün... Facade'i de kaldırırsınız. İstemciler doğrudan yeni servislerle konuşur. Eski monolit tamamen silinir 🗑️


Neden "Kademeli" Olması Bu Kadar Önemli? ❗️

Big Bang Rewrite (hep bir anda yenileme) → Risk: Yıl süren proje, yarım kalma, production felaketi 💥
Strangler Fig → Avantaj:

  • ✅ Her adımda çalışan yazılım üretirsiniz
  • ✅ Kullanıcılar hiçbir kesinti yaşamaz
  • ✅ Hata yaparsanız sadece o parça etkilenir, geri alma kolay
  • ✅ Takım öğrenirken ilerler, şok terapisi yok 🧠

Küçük Bir İpucu 🛠

Strangler Facade'i "akıllı" tutmayın.
Sadece yönlendirme yapsın: "Bu path yeni serviste mi? Oraya git. Değil mi? Eskiye git."
İş mantığı asla facade'e sızmasın. Yeni servisler bağımsız deploy edilebilir olmalı.


Özetle: Strangler Fig = Sabırlı bir bahçıvan gibi, eski sistemin köklerine sarılıp, zamanla onun yerini alan yeni bir ekosistem inşa etmektir 🌱

Hazırsan bir sonraki bölümde hangi senaryolarda bu desen işinize yarar (ve yaramaz) konusuna girelim 👇


🛠 Göç Stratejisi: Adım Adım Planlama

Hazırsanız, göç sürecini 5 net aşamaya bölelim. Her aşama için neyi hedefliyoruz, başarıyı nasıl ölçeceğiz ve ne kadar süreceğini kestirelim. Benim takvimimde 6 haftalık sprintler işliyor, bu yüzden süreleri sprint cinsinden de veriyorum 👇

🎯 Aşama 🎯 Hedefler ✅ Başarı Kriterleri ⏱ Beklenen Süre
1️⃣ Keşif Mevcut mimari, veri modelleri ve bağımlılıkları haritalama • Tüm servisler ve veri akışları belgelenmiş • Risk listesi (yüksek/orta/düşük) hazır 1‑2 sprint (2‑4 hafta)
2️⃣ Önceliklendirme Hangi bileşenlerin önce taşınacağını kararlaştırma (etki × kolaylık matrisi) • Öncelik sıralaması paydaşlarla onaylanmış • MVP kapsamı netleşmiş 1 sprint (2 hafta)
3️⃣ Pilot Küçük, düşük riskli bir servisi yeni platforma taşıma • Pilot servis %99.9 uptime ile çalışıyor • Performans metrikleri (latency, hata oranı) hedeflerin altında 1‑2 sprint (2‑4 hafta)
4️⃣ Genişletme Pilot başarılıysa, önceki öncelik listesindeki servisleri tek tek geçirme • Her sprintte en az 1‑2 servis prod’da stabil • Rollback planı test edilmiş 3‑5 sprint (6‑10 hafta)
5️⃣ Tamamen Devralma Eski altyapıyı kapatma, monitoring & alerting tam devreye alma • Eski ortam %100 deprovisioned • Tüm SLA’lar yeni platformda karşılanıyor • Ekip bilgisi transferi tamamlandı 1‑2 sprint (2‑4 hafta)

📌 Pratik İpuçları

  • Keşifte “shadow‑traffic” yöntemiyle canlı trafiği kopyalayıp test ortamına yansıtın; bu sayede gerçek yük profillerini görürsünüz.
  • Önceliklendirmede “Etki × Kolaylık” matrisini bir Google Sheet’te tutun, her sprint planlamasında güncelleyin.
  • Pilot aşamasında feature flag kullanın; aniden rollback gerektiğinde tek tıkla eski versiyona dönersiniz.
  • Genişletme sprint’lerinizde “Definition of Done” içine “monitoring dashboard hazır” maddesini ekleyin.
  • Tam Devralma öncesi run‑book ve on‑call rota güncellemelerini unutmayın; ekip bilgisi kaybı olmasın.

Ne oluyor burada?
Bu tablo ve madde listesi, göç projenizi takip edilebilir, ölçülebilir ve zaman içinde yönetilebilir hale getirir. Her sprintin sonunda tabloyu gözden geçirip, başarı kriterlerini karşılamadığınız aşamaları erken tespit edersiniz.

Hadi bir sonraki bölüme geçip, risk yönetimi ve rollback stratejilerine bakalım! 🚀


🛠 İlk Mikroservis: Fatura Oluşturma Servisini Java’ya Taşıma

Hazırsan pilot olarak seçtiğimiz Fatura Oluşturma fonksiyonunu Spring Boot ile nasıl hayata geçirdiğimize bakalım 🚀
Kod yapısını Controller → Service → Repository katmanlarıyla ayırarak, her birinin sorumluluğunu netleştirdik.
Aşağıda adım adım takip edebileceğin minimal ama production‑ready bir iskelet var.

1️⃣ Controller – HTTP giriş noktası

package com.example.invoice.controller;

import com.example.invoice.dto.InvoiceRequest;
import com.example.invoice.dto.InvoiceResponse;
import com.example.invoice.service.InvoiceService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {

    private final InvoiceService invoiceService;

    public InvoiceController(InvoiceService invoiceService) {
        this.invoiceService = invoiceService;
    }

    @PostMapping
    public ResponseEntity<InvoiceResponse> create(@Valid @RequestBody InvoiceRequest request) {
        InvoiceResponse response = invoiceService.createInvoice(request);
        return ResponseEntity.status(201).body(response);
    }

    @GetMapping("/{id}")
    public ResponseEntity<InvoiceResponse> getById(@PathVariable Long id) {
        InvoiceResponse response = invoiceService.getInvoice(id);
        return ResponseEntity.ok(response);
    }
}

Ne oluyor burada?

  • @RestController + @RequestMapping → JSON tabanlı REST endpoint’ler.
  • Constructor injection (final field) sayesinde test edilebilirlik ve immutability sağlanır.
  • @Valid ile request DTO’su otomatik validate edilir ❗️

2️⃣ Service – İş mantığı ve hata yönetimi

package com.example.invoice.service;

import com.example.invoice.dto.InvoiceRequest;
import com.example.invoice.dto.InvoiceResponse;
import com.example.invoice.entity.Invoice;
import com.example.invoice.exception.InvoiceNotFoundException;
import com.example.invoice.mapper.InvoiceMapper;
import com.example.invoice.repository.InvoiceRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class InvoiceService {

    private static final Logger log = LoggerFactory.getLogger(InvoiceService.class);

    private final InvoiceRepository invoiceRepository;
    private final InvoiceMapper invoiceMapper;

    public InvoiceService(InvoiceRepository invoiceRepository, InvoiceMapper invoiceMapper) {
        this.invoiceRepository = invoiceRepository;
        this.invoiceMapper = invoiceMapper;
    }

    @Transactional
    public InvoiceResponse createInvoice(InvoiceRequest request) {
        log.info("Yeni fatura oluşturuluyor: {}", request);
        Invoice invoice = invoiceMapper.toEntity(request);
        Invoice saved = invoiceRepository.save(invoice);
        log.debug("Fatura kaydedildi, id={}", saved.getId());
        return invoiceMapper.toResponse(saved);
    }

    @Transactional(readOnly = true)
    public InvoiceResponse getInvoice(Long id) {
        log.info("Fatura getiriliyor, id={}", id);
        Invoice invoice = invoiceRepository.findById(id)
                .orElseThrow(() -> new InvoiceNotFoundException("Fatura bulunamadı: " + id));
        return invoiceMapper.toResponse(invoice);
    }
}

Öne çıkan en iyi uygulamalar ✅

  • @Transactional: yazma operasyonunda atomiklik, okumada readOnly=true performans.
  • SLF4J logger (log.info, log.debug) → merkezi loglama altyapısına uygun.
  • Custom exception (InvoiceNotFoundException) → global exception handler’a devredilebilir.
  • Mapper kullanımı → entity/DTO ayrımı temiz tutulur.

3️⃣ Repository – Veri erişim katmanı

package com.example.invoice.repository;

import com.example.invoice.entity.Invoice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
    // Spring Data JPA sayesinde CRUD metotları otomatik gelir.
    // İleride özel sorgular (ör. findByCustomerId) eklenebilir.
}

Nasıl çalışıyor?

  • JpaRepository<Invoice, Long> → save, findById, findAll, delete gibi temel operasyonları boş bir interface ile kazanırsınız 🛠.
  • @Repository anotasyonu, Spring’in exception translation mekanizmasını devreye sokar (JPA hatalarını DataAccessException’a çevirir).

🎯 Bu adımla ne kazandık?

  • Katmanlı mimari: Her sınıf tek bir sorumluluğa odaklı → test ve bakım kolay.
  • Bağımlılık enjeksiyonu: Constructor injection sayesinde mock’lama trivial.
  • Hata yönetimi & loglama: Merkezi exception handler ve yapılandırılmış loglar ile observability hazır.
  • Genişletilebilirlik: Yeni endpoint, validation veya query eklerken mevcut yapıyı bozmazsınız.

İşte ilk mikroservisimizin iskeleti bu kadar basit! 🎉
Sonraki bölümde bu servisi Dockerize edip Kubernetes’e deploy etmeyi ve API Gateway üzerinden nasıl expose edeceğimizi göreceğiz.


🔁 API Gateway ve Yönlendirme Katmanı Kurulumu

Strangler Fig deseninin kalbi yönlendirme katmanıdır 🎯
İstek önce gateway'e düşer, sonra hangi servisin (eski COBOL mu, yeni Java mı) cevap vereceğine karar veririz.

Neden bir API Gateway?

  • Tek giriş noktası → istemciler tek adresle konuşur.
  • Yönlendirme kuralları merkezi yerde yönetilir.
  • Filtreler (rewrite, auth, rate‑limit) ortak olarak uygulanır.

Spring Cloud Gateway ile örnek yapılandırma

spring:
  cloud:
    gateway:
      routes:
        - id: fatura-servisi-route
          uri: lb://fatura-servisi
          predicates:
            - Path=/faturalar/**
          filters:
            - RewritePath=/faturalar/(?<segment>.*), /$\{segment}

Ne oluyor burada?

  • Path=/faturalar/** → /faturalar/ ile başlayan her istek bu route’a düşer.
  • RewritePath → /faturalar/xyz → /xyz şeklinde yeniden yazılır, downstream servis temiz bir path görür.
  • uri: lb://fatura-servisi → servis keşfi (Eureka/Consul) üzerinden yük dengelemesi yapılır.

NGINX alternatifi (kısa bakış)

location /faturalar/ {
    proxy_pass http://fatura-servisi/;
    proxy_set_header X-Original-URI $request_uri;
}
  • Aynı mantık: /faturalar/ prefix’i strip edilir, downstream’a temiz path gider.

Özet ✅

  • Gateway = trafiğin polisiye.
  • Predicate = hangi istekler?
  • Filter = istek/yanıt nasıl değiştirilir?
  • URI = nereye gider? (service discovery ile lb://…)

Bu yapılandırma ile eski sistemden yeni servise kademeli geçiş yaparken, istemci tarafında hiçbir değişiklik yok 🎉.


🛠 Veri Senkronizasyonu: CDC ve Event-Driven Yaklaşım

Hazırsan başlayalım 🚀
COBOL tabanlı eski bir veritabanından (örneğin bir VSAM veya DB2 tablosu) veri çekip, modern Java mikroservisine real‑time yollamak istiyorsun. Klasik batch‑copy yöntemi yavaş, hataya açık ve veri tutarsızlığına yol açıyor. Change Data Capture (CDC) ile bu sorunu çözüyoruz: veritabanındaki her INSERT/UPDATE/DELETE anında bir olay (event) üretilir ve Kafka üzerinden akar. Böylece “veri kopyalama değil, olay akışı” prensibiyle mikroservis her zaman güncel kalır ✅.

Neden CDC + Kafka? 🎯

  • Anlık propagasyon – Değişiklikler saniyeler içinde topic’e düşer.
  • Sıralı ve yeniden işlenebilir – Kafka log yapısı sayesinde consumer’lar offset’ten devam edebilir.
  • Bağımsız ölçeklenme – Producer (Debezium) ve consumer (Java servis) birbirinden tamamen izole.
  • Geri dönüşüm – Aynı topic’i birden fazla servis (raporlama, cache yenileme, audit) tüketebilir.

Adım Adım Akış 🔁

  1. COBOL/DB2 → MySQL (veya doğrudan DB2) replication – Debezium connector veritabanı loglarını okur.
  2. Debezium MySQL Connector – Her satır değişikliğini JSON event’e çevirir, Kafka’ya pushlar.
  3. Kafka Topic – Örn. cdc.cobol.orders adlı topic, partition sayısı ve retention politikası ile oluşturulur.
  4. Java Mikroservis (Spring Boot / Quarkus) – @KafkaListener ile topic’i dinler, event’i parse edip domain modeline mapler.
  5. İdempotent işlem – Event’in op alanına (c/u/d) göre upsert/delete yapar, duplicate’lerden korur.

Docker Compose ile Altyapı 🛠

Aşağıdaki docker-compose.yml parçası, MySQL, Zookeeper, Kafka, Debezium Connect ve topic oluşturma job’ını bir arada ayağa kaldırır. Kendi ortamına göre environment değişkenlerini (şifreler, host isimleri) güncelle.

version: "3.8"

services:
  # 1️⃣ Veritabanı (örnek MySQL – COBOL/DB2 yerine geçiyor)
  mysql:
    image: mysql:8.0
    container_name: cdc-mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd
      MYSQL_DATABASE: cobol_db
      MYSQL_USER: cdc_user
      MYSQL_PASSWORD: cdc_pwd
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  # 2️⃣ Zookeeper (Kafka için)
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    container_name: cdc-zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  # 3️⃣ Kafka Broker
  kafka:
    image: confluentinc/cp-kafka:7.5.0
    container_name: cdc-kafka
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1

  # 4️⃣ Debezium Connect (MySQL connector)
  connect:
    image: debezium/connect:2.5
    container_name: cdc-connect
    depends_on:
      - kafka
      - mysql
    ports:
      - "8083:8083"
    environment:
      BOOTSTRAP_SERVERS: kafka:9092
      GROUP_ID: 1
      CONFIG_STORAGE_TOPIC: connect-configs
      OFFSET_STORAGE_TOPIC: connect-offsets
      STATUS_STORAGE_TOPIC: connect-statuses
      # MySQL connector konfigürasyonu REST API ile yüklenecek (aşağıda curl örneği var)

  # 5️⃣ Topic oluşturma job’ı (idempotent)
  create-topics:
    image: confluentinc/cp-kafka:7.5.0
    container_name: cdc-create-topics
    depends_on:
      - kafka
    entrypoint: >
      /bin/sh -c "
        kafka-topics --create --topic cdc.cobol.orders --partitions 6 --replication-factor 1 --if-not-exists --bootstrap-server kafka:9092 &&
        kafka-topics --create --topic connect-configs --partitions 1 --replication-factor 1 --if-not-exists --bootstrap-server kafka:9092 &&
        kafka-topics --create --topic connect-offsets --partitions 1 --replication-factor 1 --if-not-exists --bootstrap-server kafka:9092 &&
        kafka-topics --create --topic connect-statuses --partitions 1 --replication-factor 1 --if-not-exists --bootstrap-server kafka:9092
      "

volumes:
  mysql-data:

Connector’ı Kaydetmek (Tek Seferlik) 📝

Compose ayağa kalktıktan sonra Connect REST API’sine POST atarak MySQL connector’ı başlat:

curl -X POST -H "Content-Type: application/json" \
  --data '{
    "name": "cobol-mysql-connector",
    "config": {
      "connector.class": "io.debezium.connector.mysql.MySqlConnector",
      "database.hostname": "mysql",
      "database.port": "3306",
      "database.user": "cdc_user",
      "database.password": "cdc_pwd",
      "database.dbname": "cobol_db",
      "database.server.id": "184054",
      "database.server.name": "cdc",
      "table.include.list": "cobol_db.orders",
      "database.history.kafka.bootstrap.servers": "kafka:9092",
      "database.history.kafka.topic": "cdc.cobol.dbhistory",
      "transforms": "unwrap",
      "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
      "transforms.unwrap.drop.tombstones": "false"
    }
  }' \
  http://localhost:8083/connectors

Ne oluyor burada?

  • database.server.name: "cdc" → Kafka topic prefix’i olur (cdc.cobol.orders).
  • transforms.unwrap → Debezium’in default envelope (before/after) yapısını sadeleştirir, consumer’ın sadece after state’ini görmesini sağlar.
  • table.include.list → Sadece orders tablosunu takip eder, diğer tabloları görmezden gelir.

Java Tarafında Dinleme 🎧

@KafkaListener(topics = "cdc.cobol.orders", groupId = "order-service")
public void handleOrderEvent(OrderEvent event) {
    // event.getOp() -> "c" (create), "u" (update), "d" (delete)
    // event.getAfter() -> güncel satır verisi (JSON → POJO)
    orderRepository.upsertOrDelete(event);
}

Özet: CDC ile veritabanı loglarını olay akışına dönüştürüp Kafka’ya basıyoruz. Mikroservis bu akışı tüketip kendi veri modelini güncelliyor. Böylece batch kopyalama maliyetinden kurtulup, tutarlı, düşük gecikmeli bir entegrasyon elde ediyoruz 🎉.


Sonraki bölümde bu event’leri nasıl idempotent işleyeceğimizi ve schema evolution (Avro/Protobuf) stratejilerini konuşacağız.


❗️ Test, Canlıya Geçiş ve Geri Alma Planı

Hazırsan bu kısımda nasıl güvenle canlıya girdiğimizi ve bir şeyler ters giderse nasıl anında eski COBOL sistemine döndüğümüzü anlatayım 🎯

🔁 Shadow Traffic — Gerçek Trafik, Sessiz Test

Önce shadow (gölge) trafik yöntemini kullandık. Canlıdaki her isteği hem eski COBOL'e hem de yeni mikroservise gönderdik, ama yeni sistemin cevabını müşteriye döndürmedik. Sadece logladık ve karşılaştırdık.

# NGINX konfigürasyonu — shadow trafik örneği
location /api/transactions {
    proxy_pass http://cobol-backend;
    mirror /shadow-new-service;
    mirror_request_body on;
}

location /shadow-new-service {
    internal;
    proxy_pass http://new-microservice;
    proxy_set_header X-Shadow-Request "true";
    access_log /var/log/shadow_comparison.log;
}

Ne kazandı bize?

  • Gerçek veriyle, gerçek yükle test ettik 📊
  • Müşteri etkilenmedi ❌👤
  • Yanlış cevap veren endpoint'leri önceden yakaladık ✅

🐤 Canary Release — Küçük Grupla Başla, Güvenle Büyüt

Shadow testi geçtiğinde canary (canary deployment) yapmaya başladık. Yüzde 5 trafiği yeni sisteme yönlendirdik, metrikleri izledik, sorun yoksa %25, %50, %100'e çıkardık.

Kullandığımız strateji:

  • Feature flag ile canary'ı aç/kapa kontrolü 🎛
  • Istio / Linkerd service mesh üzerinden trafik ağırlıkları ⚖️
  • Prometheus + Grafana dashboard'ında hata oranı, latency, throughput izleme 📈
# Istio VirtualService — canary ağırlığı örneği
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: transaction-service
spec:
  hosts:
    - transaction-service
  http:
    - route:
        - destination:
            host: transaction-service
            subset: v1-cobol
          weight: 95
        - destination:
            host: transaction-service
            subset: v2-new
          weight: 5

Bu sayede ne oluyor?
Hata oranı %0.1'in üzerinde çıkarsa tek tıkla trafiği %100 eski sisteme çekiyoruz. Müşteri hiçbir şey hissetmiyor 🛡


🎛 Feature Flag'ler — Kod Dağıtımı ≠ Özellik Açılması

Her yeni özellik feature flag arkasına aldık. LaunchDarkly / Unleash / kendi çözümümüz — fark etmez, önemli olan runtime'da açıp kapatabilmek.

// Örnek: feature flag kontrolü
if (featureFlags.isEnabled('new-transfer-engine', userContext)) {
  return await newTransferService.execute(request);
}
return await cobolAdapter.execute(request); // fallback

Avantajları:

  • Canary sırasında anlık kapatma ⚡
  • A/B testleri için segment bazlı açma 🎯
  • Rollback için kod deploy etmeye gerek yok 🚫🚀

✅ Test Senaryoları — Katman Katman Güven

Test Türü Araçlar Ne Test Ediyor?
Birim (Unit) Jest, JUnit, Go test Fonksiyon/metot bazlı mantık 🧪
Entegrasyon Testcontainers, WireMock DB, queue, harici API ile konuşma 🔌
Sözleşme (Contract) Pact, Spring Cloud Contract Producer-Consumer uyumu 📜
End-to-End Cypress, Playwright Kullanıcı akışları baştan sona 🎭
Performans / Yük k6, Gatling 10x trafik altında davranış ⚡
Kaos / Dayanıklılık Chaos Mesh, Litmus Pod ölürse, network gecikirse ne olur? 🌪

Önemli not: Contract testler COBOL adapter ↔ yeni servis sınırında hayatiydi. COBOL tarafı değişmezken, yeni servisin beklediği şemayı bozmaması için her PR'da contract testleri koşturduk 🔐


⛔ Rollback Planı — Gece 02:00'de Canlıya Aldık, Sabah 06:00'de Sorunsuzdu

Senaryo: Gece 02:00'de canary %100'e çıktı. 03:15'te yeni serviste memory leak tespit edildi (GC pause'lar 2 sn'ye çıktı). Müşteriler henüz şikayet etmedi ama SLA riski vardı.

Adım adım rollback (tüm bu 45 saniyede oldu ⏱):

  1. Alert tetiklendi — Prometheus rule: jvm_memory_used_bytes / jvm_memory_max_bytes > 0.9 for 2m 🚨
  2. On-call mühendisi Slack'ten uyarıldı, feature-flags repo'suna gitti
  3. Tek komutla flag kapatıldı:
    # Unleash CLI ile anlık kapatma
    unleash toggle new-transfer-engine --off --project banking-core
    
  4. Istio trafiği otomatik %100 COBOL'e döndü (canary weight 0 → 100) 🔄
  5. Yeni servis pod'ları kubectl rollout restart ile temizlendi ♻️
  6. Post-mortem ertesi gün, müşteri 0 etki aldı ✨

Sabah 06:00'de durum:

  • Yeni servis düzeltilmiş, canary tekrar %5'ten başladı 🔁
  • COBOL sistemi hiç durmadı, hiçbir transaction kaybolmadı 💪
  • Ekip kahvesini içip gülmeye devam etti ☕😄

🛠 Özet — Ne Öğrendik?

  • Shadow traffic = gerçek veriyle güvenli test 🕵️‍♂️
  • Canary + feature flag = riski controlled büyütme 📈
  • Contract testler = entegrasyon sürprizlerini engelleme 📜
  • Rollback < 1 dk = müşteri asla fark etmez ⚡
  • Gece deployu = doğru altyapı varsa hikaye olur, kabus değil 🌙✨

Bir sonraki bölümde observability (metrics, logs, traces) ve incident response sürecimizi anlatacağım. Hazır mısın? 🚀


🎯 Sonuç ve Bonus Tavsiye: Sürekli İyileştirme

Hadi özetleyelim ne kazandık bu yolculukta 🎁

Ne Oldu Proje Sonunda?

  • Performans → API yanıt süreleri %60–70 düştü, kullanıcılar "hızlandı" dedi 🚀
  • Maliyet → Sunucu faturaları %40 civarında küçüldü, FinOps ekibi bize kahve ısmarladı ☕
  • Ekip Motivasyonu → Deploy geceleri "korku filminden" rutin bir kahve molasına döndü ✅
  • Güven → Müşteri şikayetleri %80 azaldı, destek ekibimiz "sakin oldunuz" maili attı 📉

Kısaca: Küçük adımlarla başladık, büyük etki yarattık. Sihir bir gecede olmadı ama sürekli iyileştirme kültürü işe yaradı.


🎯 Siz De Küçük Bir Pilotla Başlayın

Mükemmel olmaya çalışmayın. Bir servis, bir metrik, bir sprint seçin. Deneyin, ölçün, öğrenin. Sonra genişletin. En büyük hata "hemen her şeyi değiştirmemiz"dir. Küçük başlayın, hızlı iterasyona girin 🔁


💡 Bonus Tavsiyeler: İleri Seviye İçin

Alan Ne Yapmalı? Neden?
Monitoring Prometheus + Grafana kurun, SLI/SLO tanımlayın "Nasıl gidiyor?" sorusuna veriyle cevap verin 📊
Chaos Engineering Haftada bir GameDay yapın (ör. bir pod kill edin, latency enjekte edin) Sistem dayanıklılığını gerçek şartlarda test edin 🧨
Documentation Culture Her PR'da README/ADR güncellemesi şart koşun Bilgi tek kişide kalmasın, ekip bus factor korkusuyla yaşamayın 📝

İpucu: Bu üçü ayrı değil, birbiriyle beslenir. Monitoring veri verir → Chaos o veriyi doğrular → Documentation öğrenileni kalıcı kılar.


🛠 Son Söz

Bu yazı dizisini takip ettiyseniz artık elinizde bir roadmap, bir dizi pratik pattern ve en önemlisi: başlama cesareti var. Unutmayın, en iyi sistemler de bir "hello world" ile başlamıştı.

Keyifli kodlamalar, görüşmek üzere! 👋


Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.

Burak Sağlık

Burak Saglik

©2024 Desing and Developed by @Burak Sağlık

All rights reserved