Problem: çox servis, tək release qatarı

Komanda Spring Boot tətbiqini texniki laylara görə on iki servisə bölür: user-api, order-api, database-service, validation-service. Checkout-a bir sahə əlavə etmək üçün beş servisi dəyişir, onları sabit sıra ilə deploy edir və validation-service-dəki xəta hər şeyi dayandırır. Paylanmanın bütün xərcləri var, müstəqillik isə yoxdur. Bu, distributed monolith-dir.

Mikroservislər komandalara sistemin hissələrini müstəqil dəyişmək və deploy etmək imkanı verir. Bu vədin tutub-tutmayacağını sərhədlər həll edir. Yanlış sərhəd yanlış sinif adından qat-qat bahalıdır, çünki o, şəbəkə müqavilələrinə, verilənlər bazalarına və komanda vərdişlərinə hopur.

Sərhədlər biznes imkanlarından gəlir

Biznes imkanı (business capability) təşkilatın etdiyi işdir: qiymətləndirmə, sifariş, ödəniş, yerinə yetirmə, müştəri dəstəyi. İmkanlar ekranlardan və ya texniki laylardan daha sabitdir. Yaxşı servis bir imkana sahibdir: onun qaydaları, datası və həmin imkan üçün istifadəçiyə baxan davranışı.

Domain-driven design daha kəskin alət verir: bounded context, bir modelin və bir lüğətin ardıcıl olduğu sahə. "Müştəri" faktura kontekstində billing hesabıdır, çatdırılmada ünvandır, marketinqdə üstünlüklər toplusudur. Hər üçü üçün bir Customer sinfini bölüşmək onları bir-birinə bağlayır. Hər kontekst anlayışın ona lazım olan variantını saxlayır. Kontekst daxilində modelləşdirmə üçün aggregate məqaləsinə baxın.

Sərhədin düzgün yerdə olduğunu göstərən əlamətlər:

  • bir feature-ın dəyişikliklərinin çoxu bir servis daxilində qalır;
  • servisi "və" demədən bir cümlə ilə izah etmək olur;
  • daxildəki lüğət ardıcıldır;
  • servisin dəyişmək üçün bir əsas səbəbi var.

Data sahibliyi əsl sərhəddir

Ən sərt test verilənlər bazasıdır. Hər servis öz datasına sahibdir və yeganə yazandır. Başqaları ona servisin API-si və ya yayımladığı event-lər vasitəsilə çatır.

 Yaxşı                             Pis
 +--------+     API/event         +--------+   +--------+
 | Orders |----------------->     | Orders |   | Billing|
 |  (DB)  |   +---------+         +---+----+   +----+---+
 +--------+   | Billing |             \            /
              |  (DB)   |              +-- ortaq DB --+
              +---------+

Ortaq verilənlər bazası versiyalaşdırılmayan gizli API-dir: istənilən sxem dəyişikliyi başqa komandanın servisini sındıra bilər və asılılıq kodda görünmür. Servislər arası foreign key-lər, servis cədvəlləri arası join-lər və "sadəcə onların cədvəlini oxuyaq" qısa yolları sərhədi silir.

Datanı sahiblənmək nəticələri qəbul etmək deməkdir. Sifarişləri və müştəriləri bir SQL sorğusunda birləşdirən hesabat indi ya API kompozisiyası, ya event-lərlə qidalanan replikasiya olunmuş read model, ya da ayrıca analitika saxlancı tələb edir. Servislər arası biznes qaydaları artıq tək ACID transaction-a söykənə bilmir; kompensasiyalı saga-ya çevrilir.

Distributed monolith əlamətləri

  • Servislər birlikdə və ya sabit sıra ilə deploy olunmalıdır.
  • Çoxlu sinxron çağırış zənciri; bir yavaş servis hamısını yavaşladır.
  • İki servis verilənlər bazasını və ya domain siniflərilə dolu "common" kitabxananı bölüşür.
  • Bir dəyişiklik müntəzəm olaraq bir neçə repozitoriyada Pull Request tələb edir.
  • Chatty çağırışlar: servis bir ekranı qurmaq üçün digərinə onlarla sorğu göndərir.
  • Servislər imkan yox, layer və ya isim adı daşıyır (order-database).
  • Güvən verən yeganə testlər bütün sistemin integrasiya testləridir.

Modular monolith-dən başlayın

Əksər komandalar üçün praktik yol əvvəldən mikroservis deyil. Modular monolith qurun: bir deployable, amma daxildə açıq public interfeysi, gizli daxili hissəsi və modul üzrə ayrı sxem və ya cədvəlləri olan modullar. Modullar bir-birini yalnız həmin interfeyslər və ya daxili event-lər vasitəsilə çağırır.

 app (bir deployable)
 ├── ordering/   public: OrderFacade    private: domain, persistence
 ├── payment/    public: PaymentFacade  private: ...
 └── shipping/   public: ShippingFacade private: ...
 qayda: ordering heç vaxt payment-in daxilini import etmir

Sürətli refaktorinq, həqiqətən lazım olan yerdə tək transaction, sadə debugging və tək deployment əldə edirsiniz, eyni zamanda əsl bölgü xətlərinin harada olduğunu öyrənirsiniz. Bir modul fərqli miqyaslanma profili, release ritmi və ya sahiblik tələb edəndə onu çıxarırsınız; sərhədlər əvvəlcədən tətbiq edildiyi üçün (arxitektura testi import-ları yoxlaya bilər) çıxarma arxeologiya yox, mexaniki iş olur. Domen başa düşülməmiş erkən bölmə adətən yanlış sərhədlər yaradır və onları köçürmək bahalıdır.

Komanda strukturu arxitekturanı formalaşdırır

Təşkilatın forması sistem dizaynında özünü göstərməyə meyllidir. Üç komanda bir feature-ı çıxarmaq üçün əlaqələndirilməlidirsə, ya arxitekturada, ya da komanda sərhədlərində bağlılıq problemi var. Elə servislər hədəfləyin ki, bir komanda onlara başdan-sona, əməliyyat da daxil (kod, data, növbətçilik) sahib ola bilsin. Servisləri komanda məsuliyyəti ilə uyğunlaşdırmaq təhvil-təslimləri azaldır; çoxlu komandanın paylaşdığı servis isə darboğaza çevrilməyə meyllidir. Komanda sərhədlərini və servis sərhədlərini vahid dizayn məsələsi kimi görün və biri ağrıyanda ikisini də nəzərdən keçirin.

Qranulyarlıq: çox xırda olmasın

Hər funksiya və ya entity üçün bir nanoservis şəbəkə çağırışlarını, deployment-ləri, pipeline-ları və xəta rejimlərini çoxaldır. Hər servis əməliyyat xərci əlavə edir: monitorinq, versiyalama, təhlükəsizlik, növbətçilik, kontrakt testləri. Servis bu xərci əsaslandıracaq qədər böyük olmalıdır, adətən bounded context və ya onun mühüm hissəsi. İki servis həmişə birlikdə dəyişirsə və deploy olunursa, birləşdirin.

Tez-tez edilən səhvlər

  • İmkan əvəzinə texniki layer və ya entity cədvəli üzrə bölmək.
  • Verilənlər bazasını "müvəqqəti" bölüşmək.
  • Eyni vaxtda yenilənməyə məcbur edən domain kitabxanasını bölüşmək.
  • Çoxlu servis arasında sinxron HTTP-ni pulsuz hesab etmək.
  • Deployment və ya miqyas səbəbi olmadan servis çıxarmaq.
  • Data consistency-ni production üzə çıxarana qədər nəzərə almamaq.
  • Servis üzrə sahibin olmaması.

Kompromislər

Mikroservislər müstəqil deployment, hədəfli miqyaslanma, servis üzrə texnologiya seçimi və xəta izolyasiyası verə bilər. Qiyməti əməliyyat mürəkkəbliyi, şəbəkə xəta rejimləri, paylanmış data, daha çətin başdan-sona test və platforma bacarıqlarıdır. Modular monolith strukturun böyük hissəsini daha az xərclə verir. Mikroservisləri təşkilat miqyası və ya fərqli tələblər qiyməti əsaslandıranda seçin, default olaraq yox.

Servisi addım-addım çıxarmaq

Modul həqiqətən monolit-dən çıxmalıdırsa, onu kiçik və geri qaytarıla bilən addımlarla köçürün. Əvvəlcə modulun interfeysini yeganə giriş edin və cədvəllərini başqalarına foreign key olmadan ayrıca sxemə köçürün. Sonra proses daxili çağırışları eyni interfeysin arxasında şəbəkə API-si ilə əvəz edin, ortaq oxunuşları event və ya API çağırışları ilə dəyişin. Yalnız bundan sonra kodu və datanı ayrıca deployable-a köçürün. Bir müddət hər iki yolu yanaşı işlədin və geri qayıtmaq imkanını saxlayın. Bu, əvvəl kodu bölüb sonra ortaq datanı kəşf etmək kimi tipik uğursuzluğun qarşısını alır.

Yoxlama siyahısı

  1. Hər servis bir imkana və ya bounded context-ə uyğun gəlir.
  2. Hər servis öz datasına sahibdir; ortaq cədvəl yoxdur.
  3. Servislər arası axınların aydın xəta və consistency hekayəsi var.
  4. Feature adətən bir komanda tərəfindən bir servisdə çıxarıla bilir.
  5. Servislər müstəqil deploy oluna bilir və bu test olunur.
  6. Modullar arası asılılıqlar xoş niyyətlə yox, testlərlə tətbiq edilir.
  7. Əvvəlcə modular monolith nəzərdən keçirilib; rədd edilibsə, səbəbi var.

Növbəti addım