İçeriğe geç

Monolit mi, mikroservis mi?

Başlangıç 9 dk Çok sık karşılaşılır

30 saniyede özet

Uygulamayı küçük servislere bölmek bir yükseltme değil, bir takas. Ekipler birbirini beklemeden yayına çıkabilir; karşılığında ağ hataları ve servisler arasında veri tutarlılığı derdi gelir.

Bir uygulamayı tek parça mı tutmalı, küçük servislere mi bölmeli? Bunun doğru bir cevabı yok, bir takası var. “Mikroservis daha modern” demek, dağıtık bir hatayı hiç kovalamamış olmaktan gelir.

Aynı ev ya da aynı sokakta ayrı evler: ikisinin de bir bedeli var.
Adım adım oku
  1. Monolitte her şey aynı evdedir: tuzu yan odaya seslenerek istersin, cevap hemen gelir.
  2. Mikroservislerde herkesin kendi evi var: her ekip kendi mutfağını istediği gibi düzenler ve ayrı yayın yapar.
  3. Ama tuz istemek artık kapıya çıkmak, yani ağ üzerinden çağrı yapmaktır; bazen kapıyı açan olmaz.
  4. Doğru cevap yok, bir takas var: bağımsızlık kazanılır, her çağrı pahalanır ve bozulabilir hâle gelir.
  1. Bayt: Herkes mikroservise geçiyor. Biz de uygulamamızı on parçaya bölelim mi?

  2. Sen: Daha modern gibi duruyor.

  3. Bayt: Modern mi, yoksa sadece başka türlü zor mu? Aynı ev ile ayrı evler arasındaki farka bakalım.

  4. Bayt: Doğru cevap yok, bir takas var. Önce neyi satın aldığını bil!

Asıl soru ne?

Siparişi ve ödemeyi tek bir COMMIT ile kaydeden bir monolit, ikisini ayrı mikroservislere böldü. O tek COMMIT'e ne olur? Cevabı göster

Artık yok. İki servis iki ayrı veritabanına yazar; birlikte tutarlılık saga ya da eventual consistency ile kurulur.

Üç mimari de aynı işi yapabilir. Fark, işin nerede zorlaştığında.

MonolitModüler monolitMikroservis
Deploy birimi11N
Modüller arası çağrıMetot çağrısıMetot çağrısı (sınırlı arayüz)Ağ çağrısı
TransactionTek COMMITTek COMMITSaga / eventual consistency
Bir modülü ölçekleHepsini ölçekleHepsini ölçekleSadece onu ölçekle
Hata yarıçapıTüm uygulamaTüm uygulamaTek servis (teoride)
Bir hatayı izlemekStack traceStack traceDağıtık trace
Takım bağımsızlığıDüşükOrtaYüksek

Dikkat et: mikroservis sütununda kazanç da var, kayıp da. Bu tablo bir kazanan ilan etmiyor.

Kafam karıştı, daha basit anlat

Monolit tek bir büyük ev, mikroservis ise aynı mahallede ayrı evlerdir. Ayrı evde herkes kendi odasını boyar, ama birbirine ulaşmak için dışarı çıkmak gerekir.

Hızlı kontrolBaşlangıç

Monolitte bir metot çağrısı, mikroserviste ağ çağrısına dönüştüğünde ne değişir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Yan odaya seslenmek, başka şehri aramak

Bu, tüm konunun özeti. Monolitte şu satır ya çalışır ya exception fırlatır:

OrderService.java
var stock = inventoryService.reserve(orderId, items);

Mikroservise böldüğünde aynı satır artık bir HTTP çağrısı:

OrderService.java
var stock = inventoryClient.reserve(orderId, items);
// Artık bu satır şu şekillerde de "başarısız" olabilir:
// - istek gitti, cevap dönmedi (timeout) -> rezervasyon yapıldı mı, bilmiyorsun
// - 503 döndü -> tekrar dene, ama kaç kez?
// - iki kez gitti -> idempotent değilse çift rezervasyon
// - cevap 4 saniye sürdü -> çağıran thread'i tuttu
// - servis ayakta ama yavaş -> en tehlikelisi, timeout'a takılmaz

Kod bir satır. Düşünmen gereken hata senaryosu beş tane. Mikroservisin gerçek maliyeti burada.

Kafam karıştı, daha basit anlat

Aynı evde yan odaya seslenmek her zaman duyulur. Başka şehri telefonla aradığında ise hat meşgul olabilir, çekmeyebilir ya da cevap geç gelebilir.

Hızlı kontrolBaşlangıç

Her biri %99.9 kullanılabilirliğe sahip dört servis senkron zincir hâlinde çağrılıyor. Zincirin kullanılabilirliği ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Senkron zincir gecikmeyi toplar

Dört servisi arka arkaya çağırırsan gecikmeler toplanır, ortalanmaz:

Gateway ──50ms──> Order ──40ms──> Payment ──60ms──> Stock ──30ms──> Shipping
toplam: 180ms (en yavaş halkaya değil, hepsinin toplamına bakılır)

Ve kullanılabilirlik çarpılır. Her servis %99.9 ise zincirin tamamı %99.9⁴ ≈ %99.6 olur: ayda yaklaşık üç saat kesinti. Dört tane “çok sağlam” servis, birlikte daha kırılgandır.

Aynı sipariş, üç mimari

Tohum 56252
  1. Gateway
  2. → →Sipariş
  3. → →Ödeme
  4. → →Stok
  5. → →Kargo
  • Gecikme

    —

  • Erişilebilirlik

    —

  • Transaction

    —

  • Deploy beklemesi

    —

Hız
Adım 0

Şu an ne oldu?

Mikroservis · 2 takım · 4 servislik zincir

Bir sipariş isteğinin yolunu ve ekiplerin yayın hızını birlikte izle.

Görevler0/3

  • Erişilebilirliği %99,3’ün altına düşüraçık

    İpucu

    Zinciri uzat.

  • Hazır bir değişikliği 3 gün bekletaçık

    İpucu

    Çok takım, tek deploy.

  • Çok takımı beklemeden yayınlat, erişilebilirliği %99,8’in üstünde tutaçık

    İpucu

    Bölmeyi gerektiren sebep var, zinciri kısa tut.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Dört servislik zincir: 180 ms ve %99,6.
  2. Zinciri 8 servise çıkar. Gecikme 340 ms’ye çıktı, erişilebilirlik %99,2’ye düştü.
  3. “Monolit” ve “6 takım” seç. Çağrılar ucuz ve tek COMMIT var, ama hazır bir değişiklik ortak sırada 3 gün bekliyor.
  4. “Mikroservis”, “6 takım” ve “2 servis” seç. Takımlar beklemiyor ve erişilebilirlik %99,8’de kaldı.

modüler monolitTek deploy birimi, ama modüller birbirinin iç yapısına dokunamaz. Sınırlar yanlışsa `git mv` ile düzeltilir, servis silip yeniden yazarak değil.Sözlükte gör → — genelde doğru başlangıç. Bölmeden önce sınırları kod içinde çiz. Tek deploy, tek veritabanı, ama modüller birbirinin iç yapısına dokunamıyor:

Paket yapısı
com.shop
├── order // sadece order.api paketi dışarı açık
│ ├── api // OrderFacade — diğer modüller yalnızca bunu görür
│ └── internal // entity'ler, repository'ler, servisler
├── payment
│ ├── api
│ └── internal
└── inventory
├── api
└── internal

Kazanç: sınırlar yanlışsa bunu bir git mv ile düzeltirsin, servis silip yeniden yazarak değil. Sınırlar doğruysa ayırmak zaten kolaydır.

Hızlı kontrolOrta

Her gerekçeyi, servisleri bölmek için geçerli bir sebep olup olmadığına göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Bölmek için geçerli

Bölmeden çözülemeyen gerçek bir kısıt

    Geçerli değil

    Monolitte de çözülebilir

      Ne zaman gerçekten bölünür?

      Bölmek için bir sebep gerekir, ve bu sebeplerin çoğu teknik değildir:

      • Takım sayısı. Altı takım aynı deploy kuyruğunda sıra bekliyorsa, sorun mimaridir.
      • Farklı ölçek profili. Görüntü işleme 32 GB RAM isterken ödeme servisi 512 MB istiyorsa, aynı kutuda taşımak pahalıdır.
      • Farklı değişim hızı. Günde on kez değişen katalog ile yılda iki kez değişen muhasebe aynı deploy’a bağlı olmamalı.
      • Farklı uyumluluk sınırı. Kart verisi PCI kapsamındaysa onu ayırmak kapsamı küçültür.

      Bu dördünden hiçbiri yoksa, mikroservis büyük ihtimalle sana sadece operasyon yükü getirir.

      Kafam karıştı, daha basit anlat

      Bölmenin iyi sebepleri çoğu zaman insanlarla ilgilidir: çok sayıda takımın birbirini beklemesi gibi. “Moda olduğu için” bölmek, mahalleye taşınıp her gün yan eve telefon etmektir.

      Hızlı kontrolİleri

      İki mikroservisin aynı veritabanı tablosuna yazması neden sorundur?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar

      En kötü sonuç ikisinin ortası — adı dağıtık monolitServisleri ayırıp bağımsızlığı kazanamamış sistem: ortak veritabanı, koordineli deploy, senkron zincirler.En kötü bileşim — monolitin kolaylığını kaybeder, mikroservisin faydasını almazsın. Teşhis sorusu tek: bir servisi tek başına deploy edebiliyor musun?Sözlükte gör →. Servisleri ayırdın ama:

      • hepsi aynı veritabanını paylaşıyor,
      • biri değişince diğerlerini de deploy etmen gerekiyor,
      • çağrılar senkron zincir halinde.

      Bu durumda monolitin kolaylığını kaybettin, mikroservisin faydasını kazanmadın. Bu tabloyu tanımak, aynı yola tekrar girmemenin en pratik yolu.

      Aşağıdaki örnek bir dijital bankanın modüler monolitinden: hesap, transfer ve kart modülleri tek deploy’da, ama sınırları bir testle korunuyor.

      Derinleş · Dijital bankanın modüler monoliti 5 dosya · ~96 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/java/com/bank/account/api/ AccountFacade.java Hesap modülünün dışarıya açık tek yüzü: bir arayüz ve değiştirilemez bir görünüm. Entity dışarı çıkmaz.

      src/main/java/com/bank/account/api/AccountFacade.java
      // The only door into the account module. Other modules see this and nothing else.
      public interface AccountFacade {
      Optional<AccountView> find(String iban);
      // Debit and credit in one local transaction: easy here, a saga once split.
      void move(String fromIban, String toIban, BigDecimal amount, String reference);
      }
      public record AccountView(String iban, String ownerName, BigDecimal available, boolean frozen) {
      }

      src/main/java/com/bank/account/internal/ AccountFacadeImpl.java Uygulama internal paketinde ve package-private: başka modül ona yazamaz, yalnızca arayüzü görür.

      src/main/java/com/bank/account/internal/AccountFacadeImpl.java
      @Service
      class AccountFacadeImpl implements AccountFacade { // package-private: invisible outside
      private final AccountRepository accounts; // internal too
      private final ApplicationEventPublisher events;
      AccountFacadeImpl(AccountRepository accounts, ApplicationEventPublisher events) {
      this.accounts = accounts;
      this.events = events;
      }
      @Override
      @Transactional(readOnly = true)
      public Optional<AccountView> find(String iban) {
      return accounts.findByIban(iban).map(Account::view);
      }
      @Override
      @Transactional
      public void move(String fromIban, String toIban, BigDecimal amount, String reference) {
      accounts.lockByIban(fromIban).debit(amount, reference);
      accounts.lockByIban(toIban).credit(amount, reference);
      }
      @Transactional
      public void freeze(String iban, String reason) {
      accounts.lockByIban(iban).freeze(reason);
      events.publishEvent(new AccountFrozen(iban, reason)); // who cares is not our business
      }
      }

      src/main/java/com/bank/transfer/internal/ TransferService.java Transfer modülü hesabı yalnızca facade üzerinden kullanır. Yarın hesap ayrı servis olursa değişen tek şey bu arayüzün arkası olur.

      src/main/java/com/bank/transfer/internal/TransferService.java
      @Service
      class TransferService {
      private final AccountFacade accounts; // the api, never AccountRepository
      TransferService(AccountFacade accounts) {
      this.accounts = accounts;
      }
      @Transactional
      public TransferResult send(TransferCommand cmd) {
      AccountView from = accounts.find(cmd.fromIban()).orElseThrow(UnknownAccountException::new);
      if (from.frozen()) return TransferResult.rejected("ACCOUNT_FROZEN");
      if (from.available().compareTo(cmd.amount()) < 0) return TransferResult.rejected("INSUFFICIENT_FUNDS");
      accounts.move(cmd.fromIban(), cmd.toIban(), cmd.amount(), cmd.reference());
      return TransferResult.sent(cmd.reference());
      }
      }

      src/main/java/com/bank/transfer/internal/ StandingOrderSuspender.java Modüller arası bildirim olayla: hesap dondurulunca transfer modülü bekleyen talimatları durdurur, hesap modülü transferi tanımaz.

      src/main/java/com/bank/transfer/internal/StandingOrderSuspender.java
      @Component
      class StandingOrderSuspender {
      private final StandingOrderRepository standingOrders;
      StandingOrderSuspender(StandingOrderRepository standingOrders) {
      this.standingOrders = standingOrders;
      }
      // After the freeze commits, stop this account's standing orders.
      // Today an in-process event; after a split, the same event on Kafka.
      @TransactionalEventListener
      void on(AccountFrozen event) {
      standingOrders.suspendAllFrom(event.iban(), event.reason());
      }
      }

      src/test/java/com/bank/ ModuleBoundariesTest.java ArchUnit testi sınırları derleme gibi korur: biri başka modülün internal paketine dokunursa ya da modüller döngü kurarsa build kırılır.

      src/test/java/com/bank/ModuleBoundariesTest.java
      @AnalyzeClasses(packages = "com.bank")
      class ModuleBoundariesTest {
      // Nothing outside the account module may touch its internals.
      @ArchTest
      static final ArchRule accountInternalsStayInside = noClasses()
      .that().resideOutsideOfPackage("com.bank.account..")
      .should().dependOnClassesThat().resideInAPackage("com.bank.account.internal..");
      // Modules may use each other's api, but never in a circle: a cycle is two modules
      // that can only be split together, i.e. one module.
      @ArchTest
      static final ArchRule noCycles = slices().matching("com.bank.(*)..").should().beFreeOfCycles();
      // The card module holds card data (PCI scope): nothing else may depend on its internals,
      // so splitting it out later keeps the audit scope small.
      @ArchTest
      static final ArchRule cardIsIsolated = noClasses()
      .that().resideOutsideOfPackage("com.bank.card..")
      .should().dependOnClassesThat().resideInAPackage("com.bank.card.internal..");
      }

      Kendini sına

      Şimşek turu1/5

      Mikroservisler her zaman monolitten daha iyidir.

      Soru 1/4Başlangıç

      Dört servis senkron zincir halinde çağrılıyor ve her birinin kullanılabilirliği %99.9. Zincirin tamamının kullanılabilirliği yaklaşık kaçtır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Mikroservis teknik değil, organizasyonel bir tercihtir: ekipler birbirini beklemeden yayına çıkabilsin diye bölersin.
      2. 2 Bedeli somut: ağ çağrısı başarısız olabilir, transaction artık tek bir COMMIT değildir, hata ayıklamak birden çok servisin izini sürmek demektir.
      3. 3 Arada üçüncü bir yol var: modüler monolit. Sınırları önce kodun içinde çiz, gerçekten gerekince ayrı servislere ayır.
      Sonraki kapı Servisleri tablolara göre böldün ve her iş bütün servisleri dolaşır oldu. Nerede yanlış kestin? Servis Sınırları — Bounded Context ve Database-per-Service · 10 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.