Decomposition və boundary-lər
Decomposition və boundary-lər
Hər architecture, mahiyyət etibarilə, xətlər toplusudur. Xəttin bir tərəfində şeylər sərbəst dəyişə bilər; onu keçmək razılaşma tələb edir. Xətləri yaxşı çəkin — komandalar paralel işləyər və dəyişikliklər lokal qalar. Pis çəkin — hər feature danışıqlara çevrilər.
Bu fəsil xətləri harada çəkməkdən bəhs edir.
Məqsəd: high cohesion, low coupling
Bu iki söz o qədər təkrarlanıb ki, kəskinliyini itirib. Yenidən ifadə edək:
- Cohesion boundary daxilindəki şeylərin birlikdə dəyişmə dərəcəsidir. Cohesive module elə module-dur ki, tək bir biznes səbəbi adətən yalnız ona toxunur.
- Coupling boundary-nin müxtəlif tərəflərindəki şeylərin birlikdə dəyişməyə məcbur olma dərəcəsidir. Coupled module cütü elə cütdür ki, birindəki dəyişiklik digərində dəyişikliyə məcbur edir.
Praktik test diaqram deyil. Change history-dir. Son əlli pull request-ə baxın. Hansı fayllar birlikdə dəyişdirilir? Bu fayllar eyni module-da olmaq istəyir. Hansı module-lar birlikdə dəyişdirilir? Bu module-ların ya coupling problemi var, ya da onlar bir module olmalıdır.
Technical layer-ə görə yox, domain-ə görə decompose edin
Ən geniş yayılmış decomposition səhvi texniki xətlər üzrə dilimləməkdir: "services" package-i, "repositories" package-i, "controllers" package-i. Bu düzülüş hər biznes dəyişikliyinin hər package-ə toxunmasına zəmanət verir.
Domain anlayışına görə decomposition isə, əksinə, ordering, inventory, billing kimi module-lar verir. Endirimlərin işləmə qaydasına dəyişiklik bir yerdə yaşayır. Technical layer-lər hələ də mövcuddur, amma hər domain module-unun içində.
Domain-driven design bu ideyaya ad verir: bounded context — model-in və onun lüğətinin ardıcıl olduğu ərazi. Billing context-ində "Customer"in credit limit-i var; marketing context-ində "Customer"in segment-i və preference set-i var. Onları bir obyektə sığışdırmağa məcbur etmək heç kimi qane etməyən bir əcaib yaradır.
Boundary-nin yanlış yerdə olmasının əlamətləri
- Chatty interface-lər. Bir operation ərzində bir-birini onlarla dəfə çağıran iki module iki papaq geyinmiş bir module-dur.
- Shared mutable data. Hər iki tərəf eyni cədvələ yazırsa, boundary yoxdur, sadəcə slayddakı xətt var.
- Anemic yarılar. Bir tərəf bütün data-nı, digəri bütün logic-i saxlayır. Boundary anlayışın ortasından kəsir.
- Sızan lüğət. Interface məqsədli qurulmuş contract əvəzinə daxili data model-i açır, ona görə daxili refactoring-lər breaking change-lərə çevrilir.
- Eynivaxtlı release-lər. İki deployable unit həmişə birlikdə ship olunursa, onların müstəqilliyi uydurmadır.
Interface-lər vədlərdir
Boundary yalnız onun üzərindəki contract qədər yaxşıdır. Üç xüsusiyyət contract-ı davamlı edir:
- Dar. Implementation-da olanı deyil, consumer-lərin ehtiyac duyduğunu açın. Nə qədər az field və operation varsa, bir o qədər az şey sına bilər.
- Mənada sabit.
statusadlı field gələn il də eyni mənanı daşımalıdır. Adı dəyişmədən semantics-i dəyişmək pozmanın ən amansız növüdür. - Niyyətlə versioned. Additive dəyişikliklər pulsuz olmalıdır. Breaking change-lər nadir, explicit və migration dövrü ilə müşayiət olunan olmalıdır.
Consumer tərəfdə tolerant reader-lər kömək edir: başa düşmədiyiniz field-ləri nəzərə almayın, çatışmayan optional field-lərə dözün və ordering-dən asılı olmayın.
Data ownership
Ən nəticəli boundary qərarı data-ya kimin sahib olduğudur. Distributed sistemlərdə qayda ifadə etmək üçün sadə, əməl etmək üçün çətindir: hər data parçasının tam olaraq bir writer-i var. Qalan hər kəs copy, view və ya API alır.
Independent deployability-ni real edən məhz bu qaydadır. İki service bir database cədvəlini share edirsə, birindəki schema dəyişikliyi hər ikisində schema dəyişikliyidir və əlinizdə distributed monolith var: bütün network xərci, heç bir autonomy.
Copy-lərə ehtiyac olan yerdə freshness barədə explicit olun. "Search-də göstərilən inventory sayları otuz saniyəyə qədər stale ola bilər" — bu, product adamlarının üzərində düşünə biləcəyi design qərarıdır. Səssiz staleness baş verməyi gözləyən bug report-dur.
Boundary-lər və komandalar
Conway's law deyir ki, sistemlər onları quran təşkilatların kommunikasiya strukturunu əks etdirir. Əksi də doğrudur və daha faydalıdır: müəyyən sistem strukturu istəyirsinizsə, komandaları ona uyğun düzün.
Hər tərəfində bir komanda olan boundary-yə hörmət ediləcək, çünki onu keçmək söhbət tələb edir. Bir komandanın ortasına düşən boundary aşınacaq, çünki contract-a əməl etməkdənsə o tərəfə uzanmaq daha sürətlidir. Heç biri yanlış deyil, amma hansını aldığınızı bilməlisiniz.
Mövcud sistemin decomposition-ı
Monolith-dən başlayanda işləyən ardıcıllıq budur:
- Seam-ləri müəyyən edin — change history-dən və domain lüğətindən istifadə edərək.
- Seam-ləri monolith-in içində explicit edin: published interface-lərə malik module-lar, aralarında shared cədvəllər yoxdur. Bu, Fəsil 3-dəki modular monolith-dir.
- Bununla yaşayın. Bir çox sistem elə burada dayanmalıdır.
- Yalnız səbəb olduqda extract edin və səbəb demək olar ki, həmişə quality attribute-dür: bir module fərqli scale olunmalı, independent release olunmalı və ya başqa dildə yenidən yazılmalıdır.
Strangler pattern extraction-ı incremental edir: traffic-in bir dilimini yeni component-ə route edin, verify edin, genişləndirin, köhnə path-i təqaüdə göndərin. Big-bang rewrite-lar iki il itirməyin ən etibarlı yoludur.
Hazırkı sisteminiz haqqında suallar
- Son əlli dəyişiklikdə hansı fayllar həmişə birlikdə dəyişir, amma fərqli module-larda yaşayır?
- Hansı cədvəlin birdən çox writer-i var?
- İki komandanın hörmət etdiyi bir boundary-nin və tək komandanın müntəzəm olaraq saymadığı bir boundary-nin adını çəkin.