Servis Sınırları — Bounded Context ve Database-per-Service
Önce şunu oku: Monolit mi, mikroservis mi?
30 saniyede özet
Servisleri tablolara göre bölersen (müşteri, ürün, sipariş) her iş bütün servisleri dolaşır. Yeteneğe göre bölüp her servise kendi verisini verirsen, bir iş çoğunlukla tek serviste başlar ve biter.
Mikroservise geçerken ilk refleks, veritabanı tablolarına bakıp her birine bir servis vermektir: CustomerService, ProductService, OrderService. Düzenli görünür. İlk “sipariş ver” isteği gelene kadar.
-
Bayt: Her tabloya bir servis verdim: müşteri, ürün, sipariş. Ne kadar düzenli!
-
Sen: Peki biri sipariş verince ne oluyor?
-
Bayt: Sipariş bütün servisleri tek tek dolaşıyor. Biri yavaşlasa hepsi bekliyor!
-
Bayt: Muhasebe ile lojistik aynı müşteri dosyasını mı paylaşmalı, yoksa ikisinin de kendi dosyası mı olmalı?
Tablolara göre bölmek: her şeyi herkese sormak
Varlıklara göre bölünmüş bir sistemde sipariş vermek şöyle görünür:
public OrderId place(PlaceOrderCommand cmd) { Address address = customerClient.addressOf(cmd.customerId()); // ağ Money price = productClient.priceOf(cmd.sku()); // ağ productClient.decreaseStock(cmd.sku(), cmd.quantity()); // ağ paymentClient.charge(cmd.customerId(), price); // ağ return orders.save(new Order(…)).id();}Tek bir kullanıcı isteği dört senkron çağrı oldu. Gecikmeler toplanır ve dört servisten biri bile düşse sipariş verilemez.
Sorun servis sayısı değil, sınırın yeri. İş akışları isimleri keser; isimlere göre çizilen sınırlar da iş akışlarını keser.
Kafam karıştı, daha basit anlat
Tablolara göre bölersen, tek bir sipariş için dört servise sormak zorunda kalırsın. Biri bile cevap vermezse sipariş verilemez.
Sistem CustomerService, ProductService, OrderService ve PaymentService olarak bölünmüş. "Sipariş ver" isteğinde ne olur?
Aynı kelime, üç anlam
“Ürün” her yerde aynı şey değil:
// Catalog: müşteriye ne gösterilirrecord Product(Sku sku, String title, List<Image> images, Money listPrice) {}
// Inventory: depoda ne varrecord StockItem(Sku sku, WarehouseId warehouse, int onHand, int reserved) {}
// Billing: nasıl faturalanırrecord BillableItem(Sku sku, TaxRate taxRate, Money unitPrice) {}Bir modelin ve kelimelerinin tutarlı olduğu bu sınıra bounded contextBir modelin ve kelimelerinin tek, tutarlı bir anlam taşıdığı sınır. "Ürün" katalog context'inde başka, stok context'inde başka bir modeldir.Sözlükte gör → denir. İyi bir servis sınırı genellikle bir bounded context’tir: katalog, sipariş, stok, faturalama.
Her context kendi verisinin sahibidir. Buna database-per-serviceHer servisin kendi verisine sahip olması ve bu veriye diğer servislerin yalnızca onun API'si ya da olayları üzerinden ulaşması. Tablolar paylaşılmaz.Sözlükte gör → denir: başka bir servisin verisine yalnızca onun API’si ya da yayınladığı olaylar üzerinden ulaşılır, tablosuna asla doğrudan.
Kafam karıştı, daha basit anlat
“Ürün” kelimesi vitrinde bir şey, depoda başka, kargoda bambaşka bir şey anlatır. Her bölümün kendi “ürün”ü olması doğaldır.
"Ürün" katalogda başlık, görsel ve fiyat; stokta depo ve adet; faturada vergi oranı demek. DDD bunu nasıl ele alır?
Ordering servisi fiyatı öğrenmek için neden Catalog'un product tablosunu doğrudan okumamalı?
Satır satır: sipariş vermek bir sınırın içinde
Yeteneğe göre bölünmüş bir sistemde Ordering servisi fiyatın bir kopyasını kendi tutar. Catalog fiyatı değiştirdiğinde bir olay yayınlar, Ordering kopyasını günceller.
Catalog servisi tamamen çökmüşken bu sistemde sipariş verilebilir mi? Cevabı göster
Evet. Ordering fiyatı kendi kopyasından okur ve yalnızca stok için Inventory’ye senkron gider. Bedeli şu: Catalog’daki son fiyat değişikliği henüz gelmediyse sipariş birkaç saniye eski fiyatla verilir.
Adım adım oku
- Servisler tablolara göre bölününce tek bir sipariş Customer, Product ve Inventory'yi sırayla çağırır.
- Zincirdeki bir servis yavaşlarsa sipariş vermek de yavaşlar; biri çökerse sipariş verilemez.
- Yeteneğe göre bölünen Ordering, işi için gereken fiyat ve adres bilgisinin kendi kopyasını tutar.
- Catalog fiyatı değiştirince bir olay yayınlar ve Ordering kendi kopyasını günceller; sipariş kendi sınırı içinde verilir.
İş akışı tek bir sınırın içinde
@Serviceclass PlaceOrder { private final PriceBook prices; // Catalog fiyatlarının yerel kopyası private final InventoryClient inventory; private final OrderRepository orders; private final Outbox outbox; @Transactional public OrderId place(PlaceOrderCommand cmd) { Money price = prices.priceOf(cmd.sku()); inventory.reserve(cmd.sku(), cmd.quantity()); Order order = orders.save(Order.place(cmd, price)); outbox.add(new OrderPlaced(order.id(), order.total())); return order.id(); }} @KafkaListener(topics = "catalog.price-changed")void on(PriceChanged event) { prices.update(event.sku(), event.newPrice()); }Debug
Ordering İstek Ordering'e geldi. Adres, sepet ve müşteri bilgisi komutun içinde.
Sol/sağ ok tuşlarıyla da gezebilirsin.
Veriyi kopyalamak bağımsızlık kazandırır, anlık tutarlılığı satar. Kopyanın bir süre eski kalabilmesine eventual consistencySistemin bir süre tutarsız kalıp sonunda tutarlı hâle gelmesi. Saga'nın verdiği garanti budur; arada geçilen ara durumu tasarımın kabul etmesi gerekir.Sözlükte gör → denir. Bunun kabul edilebilir olup olmadığı teknik değil, bir iş kararıdır.
Ordering, Catalog'un yayınladığı PriceChanged olaylarıyla fiyatların yerel bir kopyasını tutuyor. Bunun bedeli nedir?
Yeteneğe göre bölünmüş sistemde Catalog servisi tamamen çökmüş. Sipariş verilebilir mi?
Kendin gör
Aynı dükkân üç farklı biçimde bölünmüş. Dört senaryo sırayla çalışıyor: sipariş ver, fiyat değiştir, iade et, bir kolonu değiştir.
Servis sınırları — her senaryo ne ödüyor?
Tohum 859596- Customer
- Product
- Order
- Payment
Her servisin kendi veritabanı var
| Senaryo | Senkron | Olay | Yabancı tablo | Birlikte deploy |
|---|---|---|---|---|
| Sipariş ver | – | – | – | – |
| Fiyat değiştir | – | – | – | – |
| İade et | – | – | – | – |
| product.price kolonunu price_amount + currency yap | – | – | – | – |
Şu an ne oldu?
Varlığa göre (her isim bir servis)
Aynı dükkân, dört senaryo. Adımla ve her senaryonun servisler arasında ne kadar konuşma gerektirdiğini gör.
Aklında kalsın: Sınır doğru çizilmişse, bir senaryonun işi büyük ölçüde tek bir servisin içinde biter.
Görevler0/3
"Sipariş ver" için 4 senkron çağrı üretaçık
İpucu
Varsayılan stratejiyle ilk senaryoyu çalıştır. Her isim bir servisse, sipariş hepsine sormak zorunda.
Siparişi en fazla bir senkron çağrıyla ve başka servisin tablosuna dokunmadan veraçık
İpucu
Ordering fiyatın bir kopyasını kendi tutabilir. Hangi strateji "ürün"ün her context'te farklı bir şey olmasına izin veriyor?
Tek bir kolon değişikliğinin üç servisi birden kırdığını göraçık
İpucu
Paylaşılan veritabanı stratejisini seç ve dört senaryoyu da çalıştır.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varlığa göre bölünmüş hâliyle çalıştır. “Sipariş ver” dört senkron çağrı tutuyor, iade üç.
- Yeteneğe göre bölünmüş hâle geç. Sipariş bir senkron çağrı ve bir olayla bitiyor. Fiyat değişikliği yalnızca bir olay.
- Paylaşılan veritabanını seç. Senkron çağrı sıfır — ilk bakışta en iyisi gibi. Ama servisler birbirinin tablosuna yazıyor ve son senaryoda tek bir kolon değişikliği üç servisi birden kırıyor.
- Üç görevi de tamamla.
Kafam karıştı, daha basit anlat
Simülatörde her senaryonun kaç servise dokunduğuna bak. İyi çizilmiş sınırda bir değişiklik çoğu zaman tek bir servisin içinde kalır.
Simülatörde paylaşılan veritabanı stratejisinde hiç senkron çağrı yok. Neden yine de en kötü seçenek?
Tuzaklar
Paylaşılan veritabanı ağ çağrısı yok diye cazip görünür. Ama tablo şeması birden çok servisin ortak sözleşmesi olur ve bir değişiklik hepsini birlikte deploy etmeyi gerektirir. Servisler ayrı süreçlerde çalışır ama ayrı değişemez: bu bir 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 →.
Kopyanın sahibini karıştırmak: Ordering’deki fiyat bir kopyadır, doğrusu Catalog’dadır. Kopyayı Ordering içinde değiştirmek iki doğru kaynak yaratır. Kopyalar yalnızca olaylarla güncellenir.
Erken bölmek. Domain henüz iyi bilinmiyorsa sınırlar yanlış çizilir, ve yanlış bir servis sınırını taşımak pahalıdır. Modüler bir monolitle başla, sınırları önce kod içinde doğrula.
Takım yapısını yok saymak. Bir servisi iki takım değiştiriyorsa, ya da bir takım her özellik için beş servise dokunuyorsa, sınır takım yapısıyla çelişiyor demektir. İyi sınırlar genellikle takım sınırlarıyla örtüşür.
Aşağıdaki örnek bir bankadan: “müşteri” kimlik doğrulamada, kredide ve kartta başka şeyler demek. Kredi servisi kararını kendi sınırı içinde veriyor.
Derinleş · Bankada bounded context: kredi kendi sınırında 5 dosya · ~102 satır · ilk okumada atlayabilirsin
Kendini sına
Servisleri veritabanı tablolarına göre bölmek, tek bir işi birçok servise dağıtabilir.
Aşağıdakilerden hangisi dağıtık monolitin en güçlü belirtisidir?
Aklında kalacak üç şey
- 1 Tablolara göre bölünmüş servisler çok konuşur: tek bir sipariş, adres, fiyat, stok ve ödeme için dört ayrı çağrıya dönüşür. İyi bir sınırda iş çoğunlukla tek serviste biter.
- 2 Aynı kelime farklı yerlerde farklı anlama gelir. Katalog, stok ve fatura kendi 'ürün'ünü tutar; başkasının verisi gerekirse olaylarla bir kopyası alınır.
- 3 Ortak veritabanı ağ çağrısını kaldırır ama bağımlılığı tabloya taşır. Birlikte yayına çıkmak zorunda olan servisler bağımsız değildir.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- Mikroservisler & Design PatternsDağıtık Transaction — 2PC Neden Nadiren, Saga Neden Sıkİki servisin veritabanını tek bir büyük transaction'a bağlasan olmaz mı? Neden çoğu ekip sagayı seçiyor?Derse git
- Mikroservisler & Design PatternsStrangler Fig ve API Sürümleme — Monoliti Kimseyi Kırmadan TaşımakMonoliti bir gecede yeniden yazmadan, müşteriler hiçbir şey fark etmeden parça parça taşıyabilir misin?Derse git