← Back to list

Dönüşüm Günlüğü: Monolitten Microservice’e #2

Canlı Sistemi Öldürmeden Değiştirmek: Strangler Fig Pattern

oğuz musapaşaoğlu · 2026-05-26 14:38 · 0 claps · 4.8 min read
#microservices #design-patterns #software-architecture
Open on Medium ↗
Wiki topics: 🏛️ · Architecture

Dönüşüm Günlüğü: Monolitten Microservice’e #2

Canlı Sistemi Öldürmeden Değiştirmek: Strangler Fig Pattern

Bir önceki makalede şunu söylemiştim: eski projeyi bir kenara atıp sıfırdan yazma şansınız, gökkuşağının altında hazine sandığı bulmaktan daha az. Peki yaşayan, üretimde olan, şirketin bel kemiği olan bir sistemi nasıl değiştiriyorsunuz?

İşte tam burada Strangler Fig Pattern devreye giriyor.

Önce Metafor

Strangler Fig, tropikal bölgelerde yetişen bir sarmaşık türü. Hayatına bir ağacın üzerinde tohum olarak başlıyor. Zamanla o ağacın dallarına, gövdesine sarılıyor. Işığı, suyu, besinleri yavaş yavaş kendi bünyesine çekiyor. Ev sahibi ağaç ne olduğunu anlayana kadar iş işten geçmiş oluyor — sarmaşık artık kendi başına ayakta durabiliyor. Ev sahibi ağaç zamanla çürüyüp yok oluyor, sarmaşık onun yerini almış oluyor.

Martin Fowler bu metaforu yazılım dünyasına taşıdı. Fikir şu: monoliti bir anda öldürmeye çalışmak yerine, yeni sistemi onun etrafında büyütürsünüz. Parça parça. Trafik yavaş yavaş yeni tarafa kayar. Monolit zamanla boşalır ve günü gelir, içinde artık çalışan hiçbir şey kalmaz.

Neden Böyle Bir Şeye İhtiyaç Var?

Çünkü gerçek dünyada “sistemi durdurup yeniden yazalım” diyemezsiniz.

Sistem durduğunda fatura kesilemiyor, gemi limana giriş yapamıyor, ödeme geçmiyor. Yönetim “ne zaman hazır olur?” diye soruyor, siz “6 ay” diyorsunuz, 6 ay sonra “hâlâ hazır değil” diyorsunuz ve iş bitiyor.

Big Bang Rewrite denen bu yaklaşım, yazılım tarihinin en meşhur başarısızlıklarından biriyle özdeşleşmiştir: Netscape’in Navigator’ı sıfırdan yeniden yazmaya karar vermesi. 3 yıl sürdü. Rakipler bu sürede piyasayı ele geçirdi. Netscape bir daha toparlanamadı.

Strangler Fig tam bu riski ortadan kaldırıyor. Sistem çalışmaya devam eder. Siz arka planda inşa edersiniz. Hazır olduğunda anahtarı çevirirsiniz.

Nasıl Çalışır?

Adımlar net:

1. Monolite dokunmayın. Önce stabilize edin. Neyin nerede olduğunu belgeleyin. Bir önceki makalede T0 dökümantasyonundan bahsetmiştim — işte o belge burada işe yarıyor.

2. En kıyıdaki, en bağımsız parçayı seçin. Yüksek bağımlılığı olan bir yerden başlamak felakete davetiye çıkarmaktır. En yalnız, en sade, en az şeye dokunan parçayı seçin.

3. O parçayı yeni bir servis olarak yazın. Sıfırdan, temiz, test edilebilir, bağımsız deploy edilebilir.

4. Trafiği API Gateway üzerinden yönlendirin. İlgili endpoint’lere gelen istekler artık yeni servise gider. Monolitteki eski kod hâlâ duruyor ama artık o isteği görmüyor.

5. Eski kodu monolitten silin. Bu adım çoğu zaman atlanır. Atlamayın. Monolit küçülmeli.

6. Tekrarlayın.

Liman Projesinde Nasıl Görünür?

Bir önceki makaledeki liman faturalandırma sistemimize dönelim. Monolitin içinde şu an her şey bir arada:

public class FaturaService
{
    private readonly IHariciApiClient _hariciApiClient;
    private readonly ILimanKurallariService _limanKurallariService;
    private readonly ISapClient _sapClient;
    public FaturaService(
        IHariciApiClient hariciApiClient,
        ILimanKurallariService limanKurallariService,
        ISapClient sapClient)
    {
        _hariciApiClient = hariciApiClient;
        _limanKurallariService = limanKurallariService;
        _sapClient = sapClient;
    }
    public async Task FaturaOlusturAsync(Gemi gemi)
    {
        // 1. API'dan hizmetleri çek
        var hizmetler = await _hariciApiClient.GetHizmetlerAsync(gemi.Id);
        // 2. Faturalanabilir olanları filtrele
        var faturalanabilir = hizmetler
            .Where(h => h.IsFaturalanabilir)
            .ToList();
        // 3. Liman kurallarına göre hesapla
        var tutar = _limanKurallariService.Hesapla(faturalanabilir, gemi.Liman);
        // 4. SAP'a aktar
        await _sapClient.FaturaGonderAsync(gemi, tutar);
        // 5. Mail gönder — bu ne işi yapıyor burada?
        var smtpClient = new SmtpClient("smtp.example.com");
        var mail = new MailMessage
        {
            To = { gemi.IletisimEmail },
            Subject = "Fatura Bilgisi",
            Body = $"Fatura oluşturuldu. Tutar: {tutar}"
        };
        await smtpClient.SendMailAsync(mail);
    }
}

Görüyorsunuz — mail gönderme işi fatura servisinin içine sıkışmış. Bu hem Cohesion sorunudur hem de Coupling sorunudur. (Bu kavramlara bir sonraki makalede detaylı gireceğiz.)

Şimdi bu mail gönderme işini dışarı çıkaralım.

Adım 1: Yeni Communication Servisini Yazın

Yeni bir .NET Web API projesi açıyoruz. Tek sorumluluğu var: mesaj göndermek.

// CommunicationService/Controllers/EmailController.cs
[ApiController]
[Route("api/email")]
public class EmailController : ControllerBase
{
    private readonly IEmailService _emailService;
    public EmailController(IEmailService emailService)
    {
        _emailService = emailService;
    }
    [HttpPost("gonder")]
    public async Task<IActionResult> EmailGonder([FromBody] EmailIstegi istek)
    {
        await _emailService.GonderAsync(istek.Alici, istek.Konu, istek.Mesaj);
        return Ok();
    }
}
// EmailService.cs
public class EmailService : IEmailService
{
    private readonly SmtpClient _smtpClient;
    private readonly string _fromAddress;
    public EmailService(SmtpClient smtpClient, IConfiguration config)
    {
        _smtpClient = smtpClient;
        _fromAddress = config["Email:FromAddress"];
    }
    public async Task GonderAsync(string alici, string konu, string mesaj)
    {
        var mail = new MailMessage
        {
            From = new MailAddress(_fromAddress),
            Subject = konu,
            Body = mesaj
        };
        mail.To.Add(alici);
        await _smtpClient.SendMailAsync(mail);
    }
}

Temiz. Bağımsız. Deploy edilebilir. Bu servis hiçbir şeyden habersiz — sadece kendisine gelen isteği yerine getiriyor.

Adım 2: API Gateway Yönlendirmesini Kurun

YARP (Yet Another Reverse Proxy) kullanıyoruz — .NET ekosisteminin API Gateway çözümü. Konfigürasyon şöyle:

// api-gateway/appsettings.json
{
  "ReverseProxy": {
    "Routes": {
      "communication-route": {
        "ClusterId": "communication-cluster",
        "Match": {
          "Path": "/api/email/{**catch-all}"
        }
      },
      "monolith-route": {
        "ClusterId": "monolith-cluster",
        "Match": {
          "Path": "{**catch-all}"
        }
      }
    },
    "Clusters": {
      "communication-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "http://communication-service:8081/"
          }
        }
      },
      "monolith-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "http://monolith:8080/"
          }
        }
      }
    }
  }
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();

Bu kadar. Gateway artık email isteklerini yeni servise, diğer her şeyi eski monolite yönlendiriyor. Kullanıcı tarafında hiçbir şey değişmedi.

Adım 3: Monolitteki Mail Kodunu Güncelleyin

Monolit artık mail göndermek için kendi içindeki kodu değil, yeni servisi çağırıyor:

public class FaturaService
{
    private readonly IHariciApiClient _hariciApiClient;
    private readonly ILimanKurallariService _limanKurallariService;
    private readonly ISapClient _sapClient;
    private readonly ICommunicationClient _communicationClient; // yeni
    public FaturaService(
        IHariciApiClient hariciApiClient,
        ILimanKurallariService limanKurallariService,
        ISapClient sapClient,
        ICommunicationClient communicationClient)
    {
        _hariciApiClient = hariciApiClient;
        _limanKurallariService = limanKurallariService;
        _sapClient = sapClient;
        _communicationClient = communicationClient;
    }
    public async Task FaturaOlusturAsync(Gemi gemi)
    {
        var hizmetler = await _hariciApiClient.GetHizmetlerAsync(gemi.Id);
        var faturalanabilir = hizmetler
            .Where(h => h.IsFaturalanabilir)
            .ToList();
        var tutar = _limanKurallariService.Hesapla(faturalanabilir, gemi.Liman);
        await _sapClient.FaturaGonderAsync(gemi, tutar);
        // Artık kendi yazmıyor, yeni servisi çağırıyor
        await _communicationClient.EmailGonderAsync(
            gemi.IletisimEmail,
            "Fatura Bilgisi",
            $"Fatura oluşturuldu. Tutar: {tutar}"
        );
    }
}
// Monolitteki CommunicationClient — yeni servisi çağıran basit bir HTTP client
public class CommunicationClient : ICommunicationClient
{
    private readonly HttpClient _httpClient;
    public CommunicationClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    public async Task EmailGonderAsync(string alici, string konu, string mesaj)
    {
        var istek = new EmailIstegi(alici, konu, mesaj);
        await _httpClient.PostAsJsonAsync("/api/email/gonder", istek);
    }
}
// Program.cs — DI kaydı
builder.Services.AddHttpClient<ICommunicationClient, CommunicationClient>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["CommunicationService:Url"]);
});

Adım 4: Eski Mail Kodunu Monolitten Silin

Bu adımı atlamayın. Monolit küçülmeli. Çıkardığınız her parça, monolitin biraz daha boşalması demek.

// Silinen satırlar:
// var smtpClient = new SmtpClient("smtp.example.com");
// await smtpClient.SendMailAsync(mail);

Süreç Böyle İlerler

Başlangıç:
[Client] ──────────────────────────► [Monolit]
                                      (email + fatura + SAP + raporlar)
1. Communication Service çıkarıldı:
[Client] ──► [API Gateway] ─── /api/email/** ──► [Communication Service]
                           └── /**            ──► [Monolit]
                                                  (fatura + SAP + raporlar)
2. Rapor Servisi çıkarıldı:
[Client] ──► [API Gateway] ─── /api/email/**   ──► [Communication Service]
                           ─── /api/rapor/**   ──► [Rapor Service]
                           └── /**             ──► [Monolit]
                                                   (fatura + SAP)
... ve böyle devam eder.
Son:
[Client] ──► [API Gateway] ─── /api/email/**   ──► [Communication Service]
                           ─── /api/rapor/**   ──► [Rapor Service]
                           ─── /api/fatura/**  ──► [Fatura Service]
                           └── /api/sap/**     ──► [SAP Entegrasyon Service]
                                                   [Monolit artık yok]

Dikkat Edilmesi Gerekenler

Yanlış yerden başlamayın. Strangler Fig’in işe yaraması için başlangıç noktasının gerçekten izole olması lazım. “Burası zaten bağımlı ama halledeceğiz” diye başlarsanız, yarı yolda çıkmazda kalırsınız.

API Gateway tek nokta zaafiyeti olabilir. Gateway düşerse her şey düşer. Yüksek erişilebilirlik için gateway’i de ölçeklendirmek, izlemek gerekiyor. Bunu baştan düşünmezseniz yeni bir baş ağrısı edinmiş olursunuz.

Monoliti küçültmeyi unutmayın. En sık yapılan hata şu: yeni servisi yazdılar, trafiği yönlendirdiler, eski kodu monolitte bıraktılar. Artık iki yerde iki ayrı kod var, ikisi de bakım istiyor. Bu distributed monolith’e giden yolun ta kendisi.

Veri henüz ayrılmadı. Bu örnekte Communication Service’in kendi veritabanı yok çünkü o kadar basit bir servis. Ama daha karmaşık servisleri çıkarırken veritabanını da ayırmak gerekecek — o konu başlı başına bir makale: veri kaybı riski, dual write stratejisi, CDC ile migration. Sıra gelecek.

Strangler Fig’in özü şu: cesaret isteyen ama kontrollü bir dönüşüm. Monoliti bir gecede yıkmıyorsunuz. Onu yavaşça boşaltıyorsunuz.

Sırada Cohesion ve Coupling var. Aslında bu makalede kodun içinde bu iki kavrama zaten değindik — fatura servisinin içinde mail kodu ne işi yapıyordu? Neden yanlıştı? Bir sonraki makalede bu soruları derinlemesine cevaplayacağız.

microservices #stranglerFigPattern #designpatterns #softwarearchitecture #backend #dotnet #aspnetcore #yazılımmimarisi #apigateway #legacymigration


메타데이터
post_id
ed233dcea36a
slug
dönüşüm-günlüğü-monolitten-microservicee-2-ed233dcea36a
url
https://medium.com/@oguzmusa/d%C3%B6n%C3%BC%C5%9F%C3%BCm-g%C3%BCnl%C3%BC%C4%9F%C3%BC-monolitten-microservicee-2-ed233dcea36a
canonical_url
https://medium.com/@oguzmusa/d%C3%B6n%C3%BC%C5%9F%C3%BCm-g%C3%BCnl%C3%BC%C4%9F%C3%BC-monolitten-microservicee-2-ed233dcea36a
author_url
https://medium.com/@oguzmusa
status
ok
fetched_at
2026-06-24 11:06:28