Xizmat chegaralarini saqlagan holda backendni integratsiyalashgan holda loyihalash va umumiy avtomatik konfiguratsiyani ilgari surish jarayoni
1. Kirish
Mikroxizmatlar soni oshgani sari nafaqat biznes funksionalligi, balki infratuzilma yuklamasi ham ortadi. Har bir xizmat uchun Pod va JVM talab qilinadi, shuningdek, har bir JVM ichida Spring Context, oqimlar havzasi va ulanishlar havzasi yaratiladi. Joylashtirish va monitoring obyektlari ham xizmatlar soniga mutanosib ravishda ko‘payadi. Hatto trafigi past bo‘lgan xizmatlar ham mustaqil ijro birliklari sifatida saqlansa, xizmat chegaralari aniq bo‘lib qoladi, biroq buning evaziga ish vaqtidagi resurslar takroran sarflanadi va operatsion yuklama ortadi.
Integratsiya yondashuvini qayta ko‘rib chiqishga bevosita turtki sog‘liqni saqlash loyihasi bo‘ldi. O‘sha vaqtda taxminan 90 ta Bounded Context aniqlangan edi va har bir BC tarkibida 3–5 ta xizmat bor deb hisoblasak, 250–300 ta ijro instansiyasi talab qilinishi mumkin edi. Agar har bir xizmat mustaqil masshtablanishi kerak bo‘lsa, bu tabiiy konfiguratsiya hisoblanadi. Biroq real yuklamasi past bo‘lgan xizmatlar uchun ham bir xil ijro xarajati yuzaga kelishi masalani yanada chuqurroq ko‘rib chiqishni talab qildi.
Biz MultiDatasource ni boshidanoq joriy etishni maqsad qilmagandik. Boshlang‘ich savol quyidagicha edi.
Xizmatlarning mantiqiy chegaralarini saqlagan holda, Pod va JVM larning takroriy xarajatlarini kamaytira olamizmi?
Ushbu savolga javob berish uchun maqolada biz ko‘rib chiqqan integratsiya yondashuvlari, manba darajasidagi integratsiya PoC i, MultiDatasource ni qo‘llash jarayoni va Spring Context ziddiyatlarini hal qilish tajribamiz umumlashtiriladi. Shuningdek, bir nechta xizmatlarda takrorlangan konfiguratsiyalar nima sababdan Prologue ning umumiy avtomatik konfiguratsiyasiga aylantirilgani va uni qo‘llash natijalari tushuntiriladi.
2. Podlar soni va JVM lar soni — turli masalalar
Birinchi xayolga keladigan yondashuv — bir nechta konteynerni bitta Pod ichiga joylashtirish. Kubernetes nuqtayi nazaridan bu Podlar sonini kamaytirishi mumkin, bitta Pod ichidagi konteynerlar esa tarmoq nomlar maydonini bo‘lishishi mumkin. Biroq har bir konteynerda mustaqil Spring Boot ilovasi ishga tushirilsa, JVM lar soni o‘zgarmay qoladi.
Har bir JVM mustaqil ravishda o‘z heap xotirasi, GC si, oqimlar havzasi, Spring Context i va ulanishlar havzasiga ega bo‘ladi. Shuning uchun faqat Podlarni birlashtirish Kubernetes obyektlari sonini kamaytirishi mumkin, ammo ushbu ishda kamaytirish maqsad qilingan takroriy ish vaqti xarajatlarini yetarli darajada qisqartirmaydi.
Operatsion resurslarni sezilarli darajada kamaytirish uchun biz bir nechta xizmat modullarini bitta Boot ilovasiga yig‘ib, ularni bitta JVM va Spring Context ichida ishga tushirishimiz kerak edi.
1-rasm. Mustaqil Pod va JVM konfiguratsiyalaridan integratsiyalashgan Boot konfiguratsiyasiga o‘tish
Bu bir nechta xizmatni bitta xizmat sifatida qayta loyihalashni maqsad qilganimizni anglatmaydi. Asosiy nuqta — har bir xizmatning kodi va ma’lumotlarga egalik qilish chegaralarini saqlagan holda, joylashtirish va ijro birliklarini birlashtirishdir.
3. Integratsiyaning maqsadi — qaytariluvchanlik edi
Hozirda ushbu yondashuvdan foydalanayotgan asosiy ilovalar Gate va Guard, Metro va Inflo, shuningdek, Bro va Porto ni bitta Boot ilovasiga yig‘adigan konfiguratsiyalardir. Har bir holatda ikkita xizmat yig‘iladi, biroq tuzilma faqat ikkita xizmat bilan cheklanmaydi. Topologiyaga xizmatlar qo‘shish orqali sayt talablariga muvofiq beshta yoki undan ortiq xizmatni ham bitta Boot va JVM ichida yig‘ish mumkin.
Integratsiyadan so‘ng muayyan xizmat trafigi oshishi yoki nosozliklarni kuchliroq izolyatsiya qilish zarurati paydo bo‘lishi mumkin. Bunday holatlarda ushbu xizmatni yana mustaqil ijro birligiga ajratish imkoniyati bo‘lishi kerak. Agar integratsiya bir tomonlama o‘zgarishga aylansa, qisqa muddatli resurs tejalishi evaziga uzoq muddatli operatsion moslashuvchanlikni yo‘qotishimiz mumkin.
Shu sababli quyidagi tamoyillarni belgiladik.
• Imkon qadar ijro resurslarini bo‘lishish.
• Har bir xizmat uchun paket va Gradle moduli chegaralarini saqlash.
• Har bir xizmat uchun ma’lumotlarga kirish va tranzaksiya chegaralarini saqlash.
• Har bir sayt uchun zarur bo‘lgan xizmatlar tarkibini o‘zgartirish imkonini berish.
• Zarurat tug‘ilganda integratsiyalashgan xizmatlarni yana mustaqil Boot ilovalariga ajratish mumkinligini ta’minlash.
Xarajat muammosining asosiy manbai DB emas, Pod va JVM larning takrorlanishi edi. O‘sha vaqtda DB resurslari bilan bog‘liq jiddiy muammolar yo‘q edi. Agar DB konfiguratsiyasi ham bir vaqtning o‘zida o‘zgartirilsa, ma’lumotlarga egalik qilish, migratsiya, nosozliklar ko‘lami va orqaga qaytarish strategiyasi bir paytning o‘zida o‘zgarib ketardi. Shu sababli biz ish vaqti integratsiyasi va DB integratsiyasini alohida qarorlar sifatida ko‘rib chiqdik.
Har bir xizmat uchun mavjud persistence chegaralarini saqlagan holda ijro birliklarini integratsiyalashdik. Bir nechta DataSource dan bitta Boot ilovasida foydalanishimizga aynan shu yondashuv asos bo‘ldi.
4. Biz bir nechta integratsiya yondashuvlarini ko‘rib chiqdik
Ish vaqti muhitini integratsiyalashning bir nechta usuli mavjud edi. Biz bir nechta konteynerni bitta Pod ga joylashtirish, mavjud JAR larni classpath da birlashtirish, Parent va Child ApplicationContext lardan foydalanish hamda xizmatlarni manba darajasida bitta build grafigiga yig‘ish kabi yondashuvlarni ko‘rib chiqdik. Shuningdek, DB ulanishlarining o‘zini kamaytirish uchun proksi yoki umumiy ulanishlar havzalarini, ish vaqti xotirasini kamaytirish uchun esa Native Image dan foydalanish imkoniyatlarini ham tekshirdik.
|
Yondashuv |
JVM ni kamaytirish |
Mavjud kodga ta’siri |
Xizmatlar tarkibini shakllantirish moslashuvchanligi |
Ko‘rib chiqish natijasi |
|---|---|---|---|---|
|
Bitta Pod ichidagi bir nechta konteyner |
Past |
Past |
Yuqori |
Podlar soni kamayadi, biroq JVM xarajatlari saqlanib qoladi. |
|
Mavjud JAR larni classpath da birlashtirish |
Yuqori |
Past |
Yuqori |
Config va artefakt versiyalari o‘rtasidagi izchillikni boshqarish murakkab. |
|
Parent va Child ApplicationContext lar |
Yuqori |
O‘rtacha |
Yuqori |
Context izolyatsiyasiga erishish mumkin, biroq amalga oshirish va operatsion murakkablik yuqori. |
|
Manba darajasidagi integratsiyalashgan build |
Yuqori |
O‘rtacha |
Yuqori |
Ziddiyatlarni yagona build grafigida erta aniqlash mumkin. |
|
DB proksisi yoki ulanishlar havzasini ulashish |
Yo‘q |
Past |
Aloqasiz |
U faqat DB ulanishlarini boshqaradi va JVM'lar sonini kamaytira olmaydi. |
|
Native Image |
Qisman mumkin |
Yuqori |
Aloqasiz |
U servis kompozitsiyasi va Spring Context o‘rtasidagi ziddiyatlarni bevosita hal qilmaydi. |
JAR kompozitsiyasi yondashuvi ham texnik jihatdan mumkin. Har bir repository bajariladigan Boot JAR o‘rniga kutubxona artefaktini nashr qilishi, alohida assembly loyihasi esa kerakli versiyalarni tanlab, ularni yagona Boot JAR tarkibiga joylashi mumkin. Biroq mustaqil bajarilish uchun ishlab chiqilgan ilovalarni deploymentdan oldin shunchaki birlashtirish qiyin ekanligi ma’lum bo‘ldi.
DataSource, JPA, Flyway, SecurityFilterChain, Component Scan va Bean nomlarining barchasi mustaqil bajarilish taxminiga asoslanib sozlangan edi. Buning ustiga har bir servis uchun artefakt versiyalari matritsasini boshqarish deployment kombinatsiyalarining murakkabligini oshiradi.
Oxir-oqibat, har bir servisning Boot modulini chiqarib tashlagan holda source modullar yagona Gradle workspace ichida yig‘iladigan va integratsiyaga bag‘ishlangan Boot moduli yagona bajarilish kirish nuqtasi bo‘lib xizmat qiladigan yondashuvni avval tekshirdik. Bu yondashuv dastlabki integratsiya xarajatlarini talab qilsa-da, compile va startup bosqichlarida ziddiyatlarni tezda aniqlash imkonini berdi.
5. Biz beshta servis bilan integratsiya qilish imkoniyatini tekshirdik
PoC doirasida Checkpoint, Metro, Gallery, Pigeon va Vault servislarini yagona repository va yagona Boot ilovasiga birlashtirdik. Ushbu konfiguratsiya shunchaki ikkita servisni qo‘llab-quvvatlashni emas, balki beshta yoki undan ortiq servis kombinatsiyalari mumkinligini tekshirishga qaratilgan edi.
integrated
├─ checkpoint-* modules
├─ metro-* modules
├─ gallery-* modules
├─ pigeon-* modules
├─ vault-* modules
└─ integrated-boot
Integratsiya quyidagi tartibda amalga oshirildi.
1. Mustaqil bajarilish uchun mas’ul Boot modullarini chiqarib tashlagan holda, har bir loyihadan servis modullarini qo‘shdik.
2. Butun ilova uchun kirish nuqtasi sifatida integrated-boot ni yaratdik.
3. Mavjud Boot modullaridagi Config obyektlarini ularning haqiqiy mas’uliyatiga ko‘ra servis modullariga yoki integratsiya Boot moduliga ko‘chirdik.
4. Tashqi artefakt bog‘liqliklarini ayni build ichidagi Gradle loyiha bog‘liqliklariga o‘zgartirdik.
5. Client auto-configurationni aniq sozladik va takroriy Component ro‘yxatdan o‘tkazishlarini olib tashladik.
6. Yagona ilovani ishga tushirdik va initialization API'ni chaqirdik.
7. Har bir servis ma’lumotlari uning servisga xos DataSource'ida to‘g‘ri saqlanganini tekshirdik.
Funksional tekshiruv beshta servis yagona Pod va JVM ichida ishlaganini, shuningdek ma’lumotlar har bir servisning DataSource'iga to‘g‘ri yuklanganini ko‘rsatdi. Bu integratsiya qilinishi mumkin bo‘lgan servislar soni ikkita bilan cheklanmasligini, balki topologiya va resurs cheklovlariga muvofiq kengayishi mumkinligini tasdiqladi.
Biroq ushbu PoC amaldagi production trafigi sharoitida CPU ishlatilishi, heap ishlatilishi yoki xarajatlarni kamaytirish darajasini o‘lchamadi. Biz Podlar soni va takroriy runtime resurslarini kamaytirishning strukturaviy ta’sirini tasdiqladik, ammo buni bevosita xarajatlarni kamaytirish ko‘rsatkichi sifatida ifodalamadik. Haqiqiy tejamkorlik production muhitida CPU, heap, GC, thread soni va DB ulanishlarini birgalikda hisobga olgan holda o‘lchanishi kerak.
6. Qiyin qism packaging emas, Spring Context edi
PoC davomida source fayllarni yagona repositoryga joylashtirishdan ko‘ra ko‘proq vaqt talab qilgan qism Bean va Config ziddiyatlarini hal qilish bo‘ldi.
Mustaqil ilovalarda har bir Bean faqat bir marta mavjud bo‘lgan, biroq integratsiyalangan Context ichida ular bir vaqtning o‘zida aniqlangan. Xususan, RestClient @Component sifatida ro‘yxatdan o‘tkazilgan va ayni paytda AutoConfiguration orqali ham yaratilgan, natijada Bean nomlari yoki turlari bilan bog‘liq ziddiyatlar yuzaga kelgan. Muayyan client package'si Component Scan'dan chiqarib tashlanganda, ayni nomga ega tashqi integratsiya clientlari ham chiqarib tashlangan va bu yana bir muammoni keltirib chiqargan.
Faqat muvaffaqiyatli compilation integratsiya yakunlanganini anglatmasdi. Yagona Spring Context barcha servislarni birgalikda scan qilgani sababli, mustaqil bajarilish muhitlarida ko‘rinmagan takroriy ro‘yxatdan o‘tkazishlar va conditional configuration muammolari yuzaga chiqdi.
Ushbu tajriba integratsiya qilinishi mumkin bo‘lgan servis modullari uchun quyidagi qoidalar zarurligini tasdiqladi.
• Integratsiya Boot'i bajarilish kirish nuqtasiga egalik qiladi.
• Qayta foydalaniladigan client faqat Component Scan yoki AutoConfiguration'dan bittasi yordamida ro‘yxatdan o‘tkazilishi kerak.
• Package nomlari nafaqat routingda, balki scan va exclude qoidalarida ham servis chegaralarini ifodalashi kerak.
• Servis modullari mustaqil va integratsiyalangan bajarilish rejimlarida takroriy Bean'larni yaratmasligi kerak.
• Umumiy infratuzilma konfiguratsiyasi servisga xos moslashtirishlardan ajratib ko‘rsatilishi kerak.
Oxir-oqibat, source integratsiyasi va JAR kompozitsiyasi o‘rtasidagi farqdan ko‘ra muhimroq masala bir nechta servisning yagona Spring Context ichida ziddiyatsiz birga ishlashini ta’minlash edi. Ushbu tajriba keyingi Prologue standartlashtirish doirasini belgilash mezoniga aylandi.
7. Biz DataSource va DB chegaralarini saqlab qoldik
Yagona JVM'dan foydalanish albatta yagona DataSource bo‘lishi kerak degani emas. Runtime chegaralari va ma’lumotlar chegaralari alohida qarorlardir.
Integratsiyadan keyin ham har bir servis quyidagi persistence komponentlarini mustaqil ravishda saqlab qoldi.
• Har bir servis o‘zining DataSource'ini saqlab qoladi.
• Har bir servis o‘zining EntityManagerFactory'sini saqlab qoladi.
• Har bir servis o‘zining JpaTransactionManager'ini saqlab qoladi.
• Har bir servis o‘zining EntityManager'ini saqlab qoladi.
• Har bir servis o‘zining Spring Data JPA Repository scan doirasini saqlab qoladi.
• Har bir servis o‘zining Flyway migration'ini saqlab qoladi.
Gate va Guard yagona Boot ilovasiga birlashtirilganda ham, ularning persistence unit'lari quyidagicha ajratilgan holda qoladi.
2-rasm. Yagona Boot ichida saqlangan Gate va Guard persistence chegaralari
Ushbu tuzilma bilan bir servisning Entity va Repository'si boshqa servisning persistence unit'iga aralashib ketmaydi. Har bir servisning migration tarixi va bajarilish tartibi mustaqil boshqarilishi mumkin, shuningdek ma’lum bir servisni yana mustaqil Boot'ga ajratishda data layer'ga kiritiladigan o‘zgarishlarni minimallashtirish mumkin. Yana bir muhim afzallik shundaki, bajarilish birliklarini integratsiya qilish va DB topologiyasidagi o‘zgarishlar alohida-alohida hal qilinishi mumkin.
Boshqa tomondan, har bir DataSource o‘zining connection pool'ini yaratadi, shuning uchun umumiy ulanishlar budjeti servislar o‘rtasida taqsimlanishi kerak. Agar HikariCP standart sozlamalari har bir DataSource uchun o‘zgartirilmasdan qo‘llansa, integratsiyadan keyingi DB ulanishlari soni kutilganidan yuqori bo‘lishi mumkin. Shuning uchun har bir pool uchun maximumPoolSize va minimumIdle, shuningdek DB'ning max_connections qiymati butun integratsiyalangan topologiya nuqtayi nazaridan hisoblanishi kerak.
MultiDatasourcening o‘zi ulanish muammosining sababi emas. Biroq bir nechta pool birgalikda boshqarilmasa, u yangi operatsion muammoga aylanishi mumkin.
8. Tranzaksiyalarni paket asosida tanladik
Bir nechta DataSource’ni ro‘yxatdan o‘tkazishning o‘zi yetarli emas. Odatdagi @Transactional qaysi TransactionManager’dan foydalanishi kerakligini aniqlashimiz lozim.
Har bir service kodida qualifier’ni quyidagicha qayta-qayta ko‘rsatishingiz mumkin.
@Transactional("gateTransactionManager")
Biroq bu yondashuvda service kodi integratsiyalangan deployment tuzilmasidan bevosita xabardor bo‘lishi kerak. Agar servicelar yana ajratilsa yoki boshqa kombinatsiyada sozlansa, o‘zgartirish talab qilinadigan joylar soni ham ortadi.
Service paketi allaqachon barqaror mantiqiy chegara sifatida ishlatilayotgani sababli, paket asosidagi marshrutlashni tanladik.
io.vizend.gate.* → gateTransactionManager
io.vizend.guard.* → guardTransactionManager
other package → default service TransactionManager
Agar dasturchi @Transactional("customTransactionManager") kabi qualifier’ni aniq ko‘rsatsa, bu qaror saqlanib qoladi. Faqat qualifier mavjud bo‘lmaganda service va TransactionManager e’lon qiluvchi klass paketiga asoslanib bog‘lanadi.
@Primary EntityManager marshrutlash proksisi sifatida taqdim etiladi. Joriy tranzaksiyaga faqat bitta EntityManagerFactory bog‘langan bo‘lsa, u shu service uchun umumiy EntityManager’ga delegatsiya qiladi. Bir nechta tranzaksiya resursi ichma-ich joylashtirilsa, joriy tranzaksiya nomi va paket qoidalari asosida maqsadni tanlaydi. Faol tranzaksiya bo‘lmasa, sozlangan standart service’dan foydalanadi.
Bu xatti-harakatni sehrli narsa kabi yashirmaslik kerak. Paketni ko‘chirish tranzaksiyalarni marshrutlashni o‘zgartirishi mumkin, qoidalardan tashqaridagi paketlar esa standart service’ga qaytadi. Shu sababli paket konvensiyalari, qualifier ustuvorligi va fallback xatti-harakatini testlar va hujjatlar orqali qat’iy belgiladik.
Bundan tashqari, har bir service’ning JpaTransactionManager’i mustaqil lokal tranzaksiyani ifodalaydi. U ikkita DataSource’dagi o‘zgarishlarni yagona atomar tranzaksiyaga birlashtirmaydi. Agar bir nechta ma’lumotlar bazasini birgalikda o‘zgartirish kerak bo‘lsa, talablar REQUIRES_NEW, kompensatsiyalovchi amallar, outbox, qayta urinishlar va idempotentlik yoki alohida taqsimlangan tranzaksiya strategiyasi nuqtayi nazaridan qayta ko‘rib chiqilishi kerak.
9. Gate, Metro va Bro’da takrorlangan konfiguratsiyani aniqladik
Dastlab integratsiyalangan Boot MultiDatasource Config’ni bevosita boshqarardi. Talablar va istisnolar yetarlicha tekshirilmasdan uni umumiy kutubxonaga joylashtirish o‘rniga, avval zarur qoidalarni amaldagi servicelarda qo‘llab ko‘rib tasdiqlash xavfsizroq, degan qarorga keldik.
Xuddi shu integratsiya namunasi Gate, Metro va Bro’da tarqalgach, vaziyat o‘zgardi.
|
Integratsiyalangan Boot |
Birgalikda yig‘ilgan servicelar |
Qo‘llash davomida aniqlangan xususiyatlar |
|---|---|---|
|
Gate |
Gate + Guard |
Paket asosidagi tranzaksiyalarni marshrutlashning asosiy holatini tasdiqladik. |
|
Metro |
Metro + Inflo |
Servicelararo chaqiruvlar va tranzaksiyalarni uzatish chegaralaridagi xatolarni tasdiqladik. |
|
Bro |
Bro + Porto |
Ayrim servicelar o‘chirilganda, amaldagi standart service’ni qayta tanlashimiz kerak bo‘ldi. |
|
Extension PoC |
Checkpoint + Metro + Gallery + Pigeon + Vault |
Beshtadan ortiq service’ni ham xuddi shu tamoyillar asosida yig‘ish mumkinligini tasdiqladik. |
Quyidagi kod har bir integratsiyalangan Boot’da takrorlangan.
• DataSource va JPA Bean’larini izchil nomlar bilan yaratdik.
• Entity va Repository paketlarini skanerlashni sozladik.
• Service’ga xos Flyway sozlamalari va bajarilishini sozladik.
• Standart Bean alias’larini ro‘yxatdan o‘tkazdik.
• Har bir service yoqilgan yoki yoqilmagan holatni boshqardik.
• TransactionManager va EntityManager marshrutlashini sozladik.
Production muhitida servicelar kombinatsiyasi oshgani sari bir xil Config’ni nusxalashimizga to‘g‘ri keldi. Umumiy mantiqni o‘zgartirganda, har bir integratsiyalangan Boot’ni topib, bir xil o‘zgarishni qo‘llashimiz kerak edi; bundan tashqari, har bir service uni biroz boshqacha shaklda amalga oshirishi ehtimoli ham oshdi. Runtime integratsiyasi orqali Pod’larni ishlatish xarajatlarini kamaytirish o‘rniga, takroriy Java Config va service’ga xos farqlar tufayli yangi texnik xizmat ko‘rsatish xarajatlarini yuzaga keltirdik.
10. Takrorlangan qoidalarni Prologue darajasiga ko‘tardik
Gate, Metro va Bro’dagi umumiy qoidalar va istisnolarni aniqlaganimizdan so‘ng, MultiDatasource konfiguratsiyasini Prologue’ga ko‘chirdik. Bu shunchaki service kodini umumiy modulga ko‘chirish emas, balki bir nechta servicelarda qayta-qayta tekshirilgan qoidalarni platforma kontraktiga ko‘tarish jarayoni edi.
Prologue’ning MultiDatasource auto-configuration’i opt-in yondashuvidan foydalanadi. Mavjud bitta DataSource’li servicelarga ta’sir qilmaslik uchun u faqat aniq yoqilgan ilovalarda ishlaydi. Foydalanuvchi servicelar uchun esa faqat qaysi servicelarni yig‘ish va paket chegaralari qoladi.
vizend:
prologue:
multi-datasource:
enabled: true
default-service: gate
services:
gate:
base-package: io.vizend.gate
guard:
enabled: ${vizend.guard.enabled:true}
base-package: io.vizend.guard
Ulanish ma’lumotlari, HikariCP va Flyway override’lari mavjud service’ga xos datasource sozlamalarida qoladi. Topologiyaga asoslanib, Prologue har bir service uchun persistence Bean’lari va marshrutlash konfiguratsiyasini ro‘yxatdan o‘tkazadi.
3-rasm. Topologiya konfiguratsiyasini amaldagi persistence konfiguratsiyasiga aylantirishda Prologue’ning roli
Umumlashtirish chegarasi ham muhim edi. Biz har bir farqni Prologue’ga yuklamadik. Muayyan service’ning chaqiruv tuzilmasi uchun zarur bo‘lgan, masalan, kod orqali yaratilgan Store yoki nomga asoslangan TransactionTemplate kabi moslashtirishlar foydalanuvchi service’da qoldirildi.
Umumiy kutubxona har bir istisnoni o‘ziga singdiradigan joy emas. U bir nechta servicelar izchil amal qilishi kerak bo‘lgan kontraktlarni taqdim etishi, service’ga xos mas’uliyatlar esa har bir service’da qolishi kerak.
11. Prologue bilan umumlashtirgandan keyin nimalar o‘zgardi
Birinchidan, yangi integratsiyalangan Boot yaratishda nusxalanadigan Java Config kamaydi. Servicelar topologiya va ulanish ma’lumotlariga e’tibor qaratishi mumkin, Prologue esa Bean yaratish qoidalarini izchil qo‘llaydi.
Ikkinchidan, service kombinatsiyasidagi o‘zgarishlar aniq konfiguratsiyaga aylandi. Muayyan persistence topologiyasini service’ning enabled qiymati orqali chiqarib tashlash mumkin. Sozlangan standart service o‘chirilgan kombinatsiyalarni ham ko‘rib chiqdik, shunda standart service amalda faol bo‘lgan servicelar orasidan tanlanishi mumkin.
Uchinchidan, integratsiya va ajratish xarajatlari kamaydi. Service kodidagi tranzaksiya annotatsiyalari va repository implementatsiyalari deployment shaklini bevosita bilishi shart emas. Integratsiyalangan Boot’da marshrutlashni Prologue boshqaradi; mustaqil Boot’da esa faqat shu service’ning persistence konfiguratsiyasidan foydalanish kerak.
To‘rtinchidan, test qilinishi kerak bo‘lgan kontraktlar aniq bo‘ldi. Quyidagi bandlarni auto-configuration uchun asosiy tekshiruv maqsadlari sifatida tartibga soldik.
• Funksiya o‘chirilganda Prologue persistence Bean’larini yaratmasligini tekshirish.
• Har bir faol xizmat uchun zarur bo‘lgan barcha Bean’lar yaratilganini tekshiring.
• Repository to‘g‘ri persistence unit’dan foydalanayotganini tekshiring.
• Standart @Transactional paket uchun mos TransactionManager’ni tanlashini tekshiring.
• Aniq qualifier’lar saqlanib qolganini tekshiring.
• Faol xizmatlar kombinatsiyasiga muvofiq standart xizmat to‘g‘ri aniqlanishini tekshiring.
• Flyway har bir xizmatning DB’si va sxemasida ishga tushishini tekshiring.
• Bitta DataSource’ga ega xizmatlar uchun mavjud ishga tushirish usuliga ta’sir qilinmaganini tekshiring.
Standartlashtirishni faqat kod satrlari sonining kamayishi orqali baholash qiyin. Eng katta o‘zgarish shundan iborat bo‘ldiki, endi bir xil o‘zgarishlarni talab qiladigan har bir integratsiyalangan Boot’da Config’ni qayta-qayta o‘zgartirishimiz shart emas; yangi xizmat kombinatsiyalarini esa endi topologiya deklaratsiyalari orqali tasvirlash mumkin.
12. Amalga oshirish jarayonidan olingan saboqlar
12.1 Arxitekturadagi o‘zgarishlar operatsion muammolardan boshlanishi kerak
MultiDatasource maqsad emas edi. U Pod’lar va JVM’larning takroriy xarajatlarini kamaytirgan holda xizmatlar chegaralarini saqlab qolish vositasi edi. Yechilishi kerak bo‘lgan muammo aniq bo‘lgani uchun, barcha DataSource’larni bittaga birlashtirish kabi muammoga aloqador bo‘lmagan o‘zgarishlardan qochishga muvaffaq bo‘ldik.
12.2 Bajarilish chegaralari va ma’lumotlar chegaralari birgalikda ko‘chirilishi shart emas
Xizmatlarni bitta JVM’da ishga tushirish va bitta DB’dan foydalanish bir-biridan mustaqil tanlovlardir. Ushbu qarorlarni ajratish o‘zgarishlar ko‘lamini qisqartiradi va xizmatlar rivojlangach ularni yana ajratish moslashuvchanligini oshiradi.
12.3 Avval xizmatlarda tekshirish, keyin naqshni platformaga joriy etish xavfsizroq
Agar boshidanoq umumiy maqsadli auto-configuration’ni loyihalashtirganimizda, haqiqiy Bean ziddiyatlarini, faol bo‘lmagan xizmatlarni, standart xizmatning almashinishlarini va xizmatga xos moslashtirishlarni to‘liq tushunish qiyin bo‘lardi. Bir xil muammolar bir nechta xizmatlarda takrorlangandan keyingina standartlashtirishni amalga oshirganimiz sababli, Prologue’ning mas’uliyatlari va chegaralarini aniq belgilay oldik.
12.4 Marshrutlash qanchalik qulay bo‘lsa, shartnoma shunchalik aniq bo‘lishi kerak
Paketga asoslangan marshrutlash xizmat kodini ixchamlashtiradi, biroq paket qoidalari, fallback xatti-harakati va tranzaksiya cheklovlarini tushunmasdan uning xatti-harakatini oldindan aytish qiyin bo‘lishi mumkin. Qanchalik ko‘p avtomatlashtirsak, konfiguratsiya, hujjatlar va integratsion testlar shunchalik aniq bo‘lishi kerak.
12.5 Pod’lar sonining kamayishi xarajatlarni tejash imkoniyatini ko‘rsatadi va o‘lchov natijalaridan farqlanishi kerak
Xizmatlarni bitta JVM’ga birlashtirish takroriy heap va thread pool’lar hajmini kamaytirishi mumkin. Boshqa tomondan, bitta nosozlik bir nechta xizmatga ta’sir qiladigan ko‘lam kattalashadi, shuningdek, bir nechta DataSource’ning connection pool’larini boshqarish zarur bo‘lib qoladi. Yakuniy baholash haqiqiy joylashtirish muhitida CPU, heap, GC, DB ulanishlari va nosozliklar ko‘lamini birgalikda o‘lchashi kerak.
13. Xulosa
Ushbu ishda birlashtirgan narsamiz xizmatlarga egalik yoki ma’lumotlar chegaralari emas, balki bajarilish xarajati bo‘ldi.
Biz har bir xizmatning paketi, persistence unit’i va tranzaksiya chegaralarini saqlab qolgan holda Pod’lar va JVM’lar sonini kamaytirdik. Manba darajasidagi integratsion PoC orqali beshta xizmat bitta Spring Context’da ishlashi mumkinligini tasdiqladik. Keyinchalik bu naqshni asosan Gate, Metro va Bro’da qo‘llaganimizda, bir xil Config’ning takrorlanishi muammosini aniqladik va yetarlicha tasdiqlangan MultiDatasource qoidalarini Prologue’ning umumiy auto-configuration’iga kiritdik.
Ushbu tuzilma har bir xizmat uchun mos kelavermaydi. Mustaqil masshtablash, turli joylashtirish sikllari yoki kuchli nosozlik izolyatsiyasi muhim bo‘lgan xizmatlar alohida Pod’lar uchun ko‘proq mos bo‘lishi mumkin. Muhim jihat — boshidanoq bitta joylashtirish shakliga bog‘lanib qolmaslik. Xizmatlar chegaralari va ma’lumotlarga egalikni saqlab qolish orqali xizmatlarni operatsion muhitga qarab birlashtirish yoki yana ajratish mumkin.
Yaxshi arxitektura narsalarni shunchaki kichik qismlarga ajratish bilan yakunlanmaydi. U zarur chegaralarni saqlagan holda joriy operatsion ko‘lamga mos qilib yig‘ilishi va sharoitlar o‘zgarganda yana ajratilishi mumkin bo‘lishi kerak.
David