Selles postituses tahaksime jagada huvitavat viisi hajutatud sĂŒsteemi konfigureerimisega tegelemiseks.
Konfiguratsioon on esitatud otse Scala keeles tĂŒĂŒbikindlal viisil. Rakenduse nĂ€idet kirjeldatakse ĂŒksikasjalikult. Arutatakse ettepaneku erinevaid aspekte, sealhulgas mĂ”ju ĂŒldisele arendusprotsessile.

()
Sissejuhatus
Tugevate hajutatud sĂŒsteemide loomine nĂ”uab kĂ”igis sĂ”lmedes Ă”ige ja sidusa konfiguratsiooni kasutamist. TĂŒĂŒpiline lahendus on kasutada tekstilist juurutuse kirjeldust (terraform, ansible vĂ”i midagi sarnast) ja automaatselt genereeritud konfiguratsioonifaile (sageli iga sĂ”lme/rolli jaoks pĂŒhendatud). Samuti sooviksime kasutada igas suhtlussĂ”lmes samade versioonide samu protokolle (muidu tekiks kokkusobimatuse probleeme). JVM-i maailmas tĂ€hendab see, et vĂ€hemalt sĂ”numiteek peaks kĂ”igis suhtlussĂ”lmedes olema sama versiooniga.
Aga sĂŒsteemi testimine? Loomulikult peaks meil enne integratsioonitestide juurde asumist olema kĂ”igi komponentide jaoks ĂŒhikutestid. Testitulemuste kĂ€itusajal ekstrapoleerimiseks peaksime tagama, et kĂ”igi teekide versioonid oleksid nii kĂ€itus- kui ka testimiskeskkondades identsed.
Integratsioonitestide kÀitamisel on sageli palju lihtsam, kui kÔigil sÔlmedel on sama klassitee. Peame lihtsalt veenduma, et juurutamisel kasutatakse sama klassiteed. (Erinevates sÔlmedes on vÔimalik kasutada erinevaid klassiteid, kuid seda konfiguratsiooni on keerulisem esitada ja Ôigesti juurutada.) Nii et asjade lihtsaks hoidmiseks kÀsitleme kÔigis sÔlmedes ainult identseid klassiteid.
Konfiguratsioon kipub arenema koos tarkvaraga. Tavaliselt kasutame erinevate tuvastamiseks versioone
tarkvara evolutsiooni etapid. Tundub mĂ”istlik katta konfiguratsioon versioonihalduse alla ja tuvastada erinevad konfiguratsioonid teatud siltidega. Kui tootmises on ainult ĂŒks konfiguratsioon, vĂ”ime kasutada identifikaatorina ĂŒhte versiooni. MĂ”nikord vĂ”ib meil olla mitu tootmiskeskkonda. Ja iga keskkonna jaoks vĂ”ib vaja minna eraldi konfiguratsiooniharu. Seega vĂ”idakse konfiguratsioonid mĂ€rgistada haru ja versiooniga, et eri konfiguratsioone unikaalselt tuvastada. Iga haru silt ja versioon vastab iga sĂ”lme hajutatud sĂ”lmede, portide, vĂ€liste ressursside ja klassitee teegi versioonide ĂŒhele kombinatsioonile. Siin kĂ€sitleme ainult ĂŒhte haru ja tuvastame konfiguratsioonid kolmekomponendilise kĂŒmnendversiooni jĂ€rgi (1.2.3), samamoodi nagu muud artefaktid.
Kaasaegsetes keskkondades konfiguratsioonifaile enam kÀsitsi ei muudeta. Tavaliselt genereerime
konfiguratsioonifailid juurutamise ajal ja pĂ€rast. Seega vĂ”iks kĂŒsida, miks me ikkagi kasutame konfiguratsioonifailide jaoks tekstivormingut? MĂ”istlik vĂ”imalus on paigutada konfiguratsioon kompileerimisĂŒksusesse ja saada kasu kompileerimisaja konfiguratsiooni valideerimisest.
Selles postituses uurime ideed sÀilitada konfiguratsioon koostatud artefaktis.
Kompileeritav konfiguratsioon
Selles jaotises kĂ€sitleme staatilise konfiguratsiooni nĂ€idet. Kaks lihtsat teenust - kajateenus ja kajateenuse klient on seadistamisel ja juurutamisel. SeejĂ€rel luuakse kaks erinevat hajutatud sĂŒsteemi mĂ”lema teenusega. Ăks on mĂ”eldud ĂŒhe sĂ”lme konfigureerimiseks ja teine ââââkahe sĂ”lme konfigureerimiseks.
TĂŒĂŒpiline hajutatud sĂŒsteem koosneb mĂ”nest sĂ”lmest. SĂ”lme saab tuvastada mĂ”ne tĂŒĂŒbi abil:
sealed trait NodeId
case object Backend extends NodeId
case object Frontend extends NodeIdvÔi lihtsalt
case class NodeId(hostName: String)vÔi isegi
object Singleton
type NodeId = Singleton.typeNeed sĂ”lmed tĂ€idavad erinevaid rolle, kĂ€itavad mĂ”ningaid teenuseid ja peaksid suutma suhelda teiste sĂ”lmedega TCP/HTTP ĂŒhenduste kaudu.
TCP-ĂŒhenduse jaoks on vaja vĂ€hemalt pordi numbrit. Samuti tahame veenduda, et klient ja server rÀÀgivad samast protokollist. SĂ”lmedevahelise ĂŒhenduse modelleerimiseks deklareerime jĂ€rgmise klassi:
case class TcpEndPoint[Protocol](node: NodeId, port: Port[Protocol])kus Port on lihtsalt Int lubatud vahemikus:
type PortNumber = Refined[Int, Closed[_0, W.`65535`.T]]Rafineeritud tĂŒĂŒbid
nĂ€gema raamatukogu. LĂŒhidalt, see vĂ”imaldab lisada kompileerimisaja piiranguid teistele tĂŒĂŒpidele. Sel juhul Int lubatud on ainult 16-bitised vÀÀrtused, mis vĂ”ivad tĂ€histada pordi numbrit. Selle konfiguratsioonimeetodi jaoks pole seda teeki vaja kasutada. Tundub, et see sobib lihtsalt vĂ€ga hĂ€sti.
HTTP (REST) ââjaoks vĂ”ime vajada ka teenuse teed:
type UrlPathPrefix = Refined[String, MatchesRegex[W.`"[a-zA-Z_0-9/]*"`.T]]
case class PortWithPrefix[Protocol](portNumber: PortNumber, pathPrefix: UrlPathPrefix)FantoomtĂŒĂŒp
Protokolli tuvastamiseks kompileerimise ajal kasutame Scala funktsiooni tĂŒĂŒbiargumendi deklareerimiseks Protocol mida klassis ei kasutata. See on nn fantoomtĂŒĂŒp. KĂ€itusajal vajame harva protokolli identifikaatori eksemplari, seetĂ”ttu me seda ei salvesta. Koostamise ajal annab see fantoomtĂŒĂŒp tĂ€iendavat tĂŒĂŒpi turvalisust. Me ei saa vale protokolliga porti lĂ€bida.
Ăks enim kasutatavaid protokolle on REST API koos Json-serialiseerimisega:
sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]kus RequestMessage on pĂ”hitĂŒĂŒp sĂ”numid, mida klient saab saata serverisse ja ResponseMessage on vastussĂ”num serverilt. Muidugi vĂ”ime luua muid protokollikirjeldusi, mis tĂ€psustavad sideprotokolli soovitud tĂ€psusega.
Selle postituse jaoks kasutame protokolli lihtsamat versiooni:
sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]Selles protokollis lisatakse pÀringusÔnum URL-ile ja vastusesÔnum tagastatakse tavalise stringina.
Teenuse konfiguratsiooni saab kirjeldada teenuse nime, portide kogumi ja mĂ”ne sĂ”ltuvusega. KĂ”igi nende elementide esitamiseks Scalas on mĂ”ned vĂ”imalused (nĂ€iteks HList, algebralised andmetĂŒĂŒbid). Selle postituse jaoks kasutame koogi mustrit ja esindame kombineeritavaid tĂŒkke (mooduleid) tunnustena. (Selle kompileeritava konfiguratsiooni lĂ€henemisviisi puhul ei ole koogi muster nĂ”utav. See on vaid ĂŒks idee vĂ”imalik teostus.)
SÔltuvusi saab esitada, kasutades kooki mustrit teiste sÔlmede lÔpp-punktidena:
type EchoProtocol[A] = SimpleHttpGetRest[A, A]
trait EchoConfig[A] extends ServiceConfig {
def portNumber: PortNumber = 8081
def echoPort: PortWithPrefix[EchoProtocol[A]] = PortWithPrefix[EchoProtocol[A]](portNumber, "echo")
def echoService: HttpSimpleGetEndPoint[NodeId, EchoProtocol[A]] = providedSimpleService(echoPort)
}Echo teenus vajab ainult konfigureeritud porti. Ja me teatame, et see port toetab kajaprotokolli. Pange tÀhele, et me ei pea praegu konkreetset porti mÀÀrama, kuna tunnus vÔimaldab abstraktseid meetodeid deklareerida. Kui kasutame abstraktseid meetodeid, nÔuab kompilaator konfiguratsioonieksemplaris rakendamist. Siin oleme pakkunud rakenduse (8081) ja seda kasutatakse vaikevÀÀrtusena, kui jÀtame selle konkreetses konfiguratsioonis vahele.
Saame deklareerida sÔltuvuse kajateenuse kliendi konfiguratsioonis:
trait EchoClientConfig[A] {
def testMessage: String = "test"
def pollInterval: FiniteDuration
def echoServiceDependency: HttpSimpleGetEndPoint[_, EchoProtocol[A]]
}SĂ”ltuvusel on sama tĂŒĂŒp kui echoService. EelkĂ”ige nĂ”uab see sama protokolli. Seega vĂ”ime olla kindlad, et kui ĂŒhendame need kaks sĂ”ltuvust, töötavad need Ă”igesti.
Teenuste juurutamine
Teenus vajab kĂ€ivitamiseks ja sujuvaks sulgemiseks funktsiooni. (Teenuse sulgemise vĂ”imalus on testimise jaoks ĂŒlioluline.) JĂ€llegi on mĂ”ned vĂ”imalused sellise funktsiooni mÀÀramiseks antud konfiguratsiooni jaoks (nĂ€iteks vĂ”ime kasutada tĂŒĂŒbiklasse). Selle postituse jaoks kasutame taas koogimustrit. Saame esindada teenust kasutades cats.Resource mis juba pakub kahveldust ja ressursside vabastamist. Ressursi hankimiseks peaksime esitama konfiguratsiooni ja teatud kĂ€itusaja konteksti. Seega vĂ”ib teenuse kĂ€ivitamise funktsioon vĂ€lja nĂ€ha jĂ€rgmine:
type ResourceReader[F[_], Config, A] = Reader[Config, Resource[F, A]]
trait ServiceImpl[F[_]] {
type Config
def resource(
implicit
resolver: AddressResolver[F],
timer: Timer[F],
contextShift: ContextShift[F],
ec: ExecutionContext,
applicative: Applicative[F]
): ResourceReader[F, Config, Unit]
}kus
Configâ konfiguratsiooni tĂŒĂŒp, mida see teenuse kĂ€ivitaja nĂ”uabAddressResolverâ kĂ€itusobjekt, millel on vĂ”imalus hankida teiste sĂ”lmede tegelikke aadresse (ĂŒksikasjade saamiseks lugege edasi).
teised tĂŒĂŒbid pĂ€rinevad cats:
F[_]- efekti tĂŒĂŒp (lihtsamal juhulF[A]vĂ”iks olla lihtsalt() => A. Selles postituses kasutamecats.IO.)Reader[A,B]â on enam-vĂ€hem funktsiooni sĂŒnonĂŒĂŒmA => Bcats.Resourceâ omab omandamise ja vabastamise viiseTimerâ vĂ”imaldab magada/aega mÔÔtaContextShift- analoogExecutionContextApplicativeâ kehtivate funktsioonide ĂŒmbris (peaaegu monaad) (vĂ”ime selle lĂ”puks asendada millegi muuga)
Seda liidest kasutades saame rakendada mÔningaid teenuseid. NÀiteks teenus, mis ei tee midagi:
trait ZeroServiceImpl[F[_]] extends ServiceImpl[F] {
type Config <: Any
def resource(...): ResourceReader[F, Config, Unit] =
Reader(_ => Resource.pure[F, Unit](()))
}(Vt muude teenuste juurutamise jaoks â ,
ja .)
SĂ”lm on ĂŒks objekt, mis kĂ€itab mĂ”nda teenust (ressursside ahela kĂ€ivitamise lubab Cake Pattern):
object SingleNodeImpl extends ZeroServiceImpl[IO]
with EchoServiceService
with EchoClientService
with FiniteDurationLifecycleServiceImpl
{
type Config = EchoConfig[String] with EchoClientConfig[String] with FiniteDurationLifecycleConfig
}Pange tĂ€hele, et sĂ”lmes mÀÀrame tĂ€pse konfiguratsioonitĂŒĂŒbi, mida see sĂ”lm vajab. Kompilaator ei lase meil ehitada ebapiisava tĂŒĂŒbiga objekti (Cake), kuna iga teenusetunnus deklareerib piirangu Config tĂŒĂŒp. Samuti ei saa me sĂ”lme kĂ€ivitada ilma tĂ€ielikku konfiguratsiooni esitamata.
SÔlme aadressi eraldusvÔime
Ăhenduse loomiseks vajame iga sĂ”lme jaoks tĂ”elist hostiaadressi. See vĂ”ib olla teada hiljem kui konfiguratsiooni muud osad. SeetĂ”ttu vajame viisi sĂ”lme ID ja selle tegeliku aadressi vahelise vastendamiseks. See kaardistamine on funktsioon:
case class NodeAddress[NodeId](host: Uri.Host)
trait AddressResolver[F[_]] {
def resolve[NodeId](nodeId: NodeId): F[NodeAddress[NodeId]]
}Sellise funktsiooni rakendamiseks on mitu vÔimalikku viisi.
- Kui me teame tegelikke aadresse enne juurutamist, sÔlme hostide kÀivitamise ajal, saame genereerida Scala koodi tegelike aadressidega ja kÀivitada pÀrast seda ehitust (mis kontrollib kompileerimise aega ja kÀivitab seejÀrel integratsioonitestide komplekti). Sel juhul on meie kaardistamisfunktsioon staatiliselt tuntud ja seda saab lihtsustada nÀiteks a
Map[NodeId, NodeAddress]. - MÔnikord saame tegelikud aadressid alles hiljem, kui sÔlm on tegelikult kÀivitatud, vÔi meil pole veel kÀivitamata sÔlmede aadresse. Sel juhul vÔib meil olla avastamisteenus, mis kÀivitatakse enne kÔiki teisi sÔlme ja iga sÔlm vÔib selles teenuses oma aadressi reklaamida ja sÔltuvusi tellida.
- Kui saame muuta
/etc/hosts, saame kasutada eelmÀÀratletud hostinimesid (ntmy-project-main-nodejaecho-backend) ja seostage see nimi juurutamise ajal IP-aadressiga.
Selles postituses me neid juhtumeid ĂŒksikasjalikumalt ei kĂ€sitle. Tegelikult on meie mĂ€nguasja nĂ€ites kĂ”igil sĂ”lmedel sama IP-aadress - 127.0.0.1.
Selles postituses kĂ€sitleme kahte hajutatud sĂŒsteemi paigutust:
- Ăhe sĂ”lme paigutus, kus kĂ”ik teenused on paigutatud ĂŒhele sĂ”lmele.
- Kahe sÔlme paigutus, kus teenus ja klient asuvad erinevates sÔlmedes.
Konfiguratsioon a paigutus on jÀrgmine:
Ăhe sĂ”lme konfiguratsioon
object SingleNodeConfig extends EchoConfig[String]
with EchoClientConfig[String] with FiniteDurationLifecycleConfig
{
case object Singleton // identifier of the single node
// configuration of server
type NodeId = Singleton.type
def nodeId = Singleton
/** Type safe service port specification. */
override def portNumber: PortNumber = 8088
// configuration of client
/** We'll use the service provided by the same host. */
def echoServiceDependency = echoService
override def testMessage: UrlPathElement = "hello"
def pollInterval: FiniteDuration = 1.second
// lifecycle controller configuration
def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 requests, not 9.
}Siin loome ĂŒhe konfiguratsiooni, mis laiendab nii serveri kui ka kliendi konfiguratsiooni. Samuti konfigureerime elutsĂŒkli kontrolleri, mis tavaliselt lĂ”petab kliendi ja serveri pĂ€rast seda lifetime intervall möödub.
Sama teenuse juurutuste ja konfiguratsioonide komplekti saab kasutada kahe eraldi sĂ”lmega sĂŒsteemi paigutuse loomiseks. Peame lihtsalt looma vastavate teenustega:
Kahe sÔlme konfiguratsioon
object NodeServerConfig extends EchoConfig[String] with SigTermLifecycleConfig
{
type NodeId = NodeIdImpl
def nodeId = NodeServer
override def portNumber: PortNumber = 8080
}
object NodeClientConfig extends EchoClientConfig[String] with FiniteDurationLifecycleConfig
{
// NB! dependency specification
def echoServiceDependency = NodeServerConfig.echoService
def pollInterval: FiniteDuration = 1.second
def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 request, not 9.
def testMessage: String = "dolly"
}Vaadake, kuidas me sĂ”ltuvust mÀÀrame. Mainime teise sĂ”lme pakutavat teenust praeguse sĂ”lme sĂ”ltuvusena. SĂ”ltuvustĂŒĂŒpi kontrollitakse, kuna see sisaldab protokolli kirjeldavat fantoomtĂŒĂŒpi. Ja kĂ€itusajal on meil Ă”ige sĂ”lme ID. See on kavandatud konfiguratsioonimeetodi ĂŒks olulisi aspekte. See annab meile vĂ”imaluse mÀÀrata port ainult ĂŒks kord ja veenduda, et viitame Ă”igele pordile.
Kahe sÔlme rakendamine
Selle konfiguratsiooni jaoks kasutame tĂ€pselt samu teenuste rakendusi. Ei mingeid muudatusi ĂŒldse. Loome aga kaks erinevat sĂ”lmerakendust, mis sisaldavad erinevaid teenuseid:
object TwoJvmNodeServerImpl extends ZeroServiceImpl[IO] with EchoServiceService with SigIntLifecycleServiceImpl {
type Config = EchoConfig[String] with SigTermLifecycleConfig
}
object TwoJvmNodeClientImpl extends ZeroServiceImpl[IO] with EchoClientService with FiniteDurationLifecycleServiceImpl {
type Config = EchoClientConfig[String] with FiniteDurationLifecycleConfig
}Esimene sĂ”lm rakendab serverit ja see vajab ainult serveripoolset konfiguratsiooni. Teine sĂ”lm rakendab klienti ja vajab konfiguratsiooni teist osa. MĂ”lemad sĂ”lmed nĂ”uavad teatud eluea spetsifikatsiooni. Selle postiteenuse sĂ”lmel on lĂ”pmatu eluiga, mida saab kasutades lĂ”petada SIGTERM, samas kui kajaklient lĂ”peb pĂ€rast konfigureeritud piiratud kestust. Vaata ĂŒksikasjad.
Ăldine arendusprotsess
Vaatame, kuidas see lÀhenemisviis muudab konfiguratsiooniga töötamise viisi.
Konfiguratsioon koodina kompileeritakse ja see loob artefakti. Tundub mÔistlik eraldada konfiguratsiooniartefaktid muudest koodiartefaktidest. Sageli vÔib meil olla palju konfiguratsioone samal koodibaasil. Ja loomulikult vÔib meil olla erinevatest konfiguratsiooniharudest mitu versiooni. Konfiguratsioonis saame valida konkreetsed teekide versioonid ja see jÀÀb konstantseks alati, kui seda konfiguratsiooni juurutame.
Konfiguratsioonimuudatusest saab koodimuutus. Seega peaks see olema hÔlmatud sama kvaliteedi tagamise protsessiga:
Pilet -> PR -> ĂŒlevaade -> ĂŒhendamine -> pidev integreerimine -> pidev kasutuselevĂ”tt
Sellel lÀhenemisel on jÀrgmised tagajÀrjed:
- Konfiguratsioon on konkreetse sĂŒsteemi eksemplari jaoks ĂŒhtne. Tundub, et sĂ”lmede vahel ei saa kuidagi valesti ĂŒhendust luua.
- Konfiguratsiooni muutmine ainult ĂŒhes sĂ”lmes pole lihtne. Tundub ebamĂ”istlik sisse logida ja mĂ”nda tekstifaili muuta. Seega muutub konfiguratsiooni triiv vĂ€hem vĂ”imalikuks.
- VĂ€ikseid konfiguratsioonimuudatusi pole lihtne teha.
- Enamik konfiguratsioonimuudatusi jĂ€rgib sama arendusprotsessi ja see lĂ€bib mĂ”ne ĂŒlevaatuse.
Kas vajame tootmise seadistamiseks eraldi hoidlat? Tootmiskonfiguratsioon vĂ”ib sisaldada tundlikku teavet, mida sooviksime hoida paljudele inimestele kĂ€ttesaamatus kohas. Seega tasub hoida eraldi piiratud juurdepÀÀsuga hoidlat, mis sisaldab tootmiskonfiguratsiooni. VĂ”ime konfiguratsiooni jagada kaheks osaks â ĂŒks, mis sisaldab tootmise kĂ”ige avatumaid parameetreid, ja teine, mis sisaldab konfiguratsiooni salajast osa. See vĂ”imaldaks enamikule arendajatest juurdepÀÀsu enamikule parameetritele, piirates samas juurdepÀÀsu tĂ”eliselt tundlikele asjadele. Seda on lihtne saavutada parameetrite vaikevÀÀrtustega vahepealsete tunnuste abil.
Variatsioonid
Vaatame pakutud lÀhenemisviisi plusse ja miinuseid vÔrreldes teiste konfiguratsioonihaldusmeetoditega.
KÔigepealt loetleme mÔned alternatiivid pakutud konfiguratsiooni kÀsitlemise viiside erinevatele aspektidele:
- Tekstifail sihtmasinas.
- Tsentraliseeritud vÔtmevÀÀrtuste salvestus (nt
etcd/zookeeper). - Alamprotsessi komponendid, mida saab ilma protsessi taaskĂ€ivitamata ĂŒmber konfigureerida/taaskĂ€ivitada.
- Konfiguratsioon vÀljaspool artefakti ja versioonikontrolli.
Tekstifail annab ad hoc paranduste osas mĂ”ningast paindlikkust. SĂŒsteemi administraator saab sihtsĂ”lme sisse logida, teha muudatusi ja lihtsalt teenuse taaskĂ€ivitada. Suuremate sĂŒsteemide jaoks ei pruugi see nii hea olla. Muudatusest ei jÀÀ jĂ€lgi. Muudatust ei vaata ĂŒle teine ââsilmapaar. VĂ”ib olla raske vĂ€lja selgitada, mis muutuse pĂ”hjustas. Seda ei ole testitud. Hajutatud sĂŒsteemi vaatenurgast vĂ”ib administraator lihtsalt unustada mĂ”ne muu sĂ”lme konfiguratsiooni vĂ€rskendamise.
(Btw, kui lĂ”puks on vaja hakata kasutama teksti konfiguratsioonifaile, peame lisama ainult parseri + validaatori, mis suudavad sama luua Config tĂŒĂŒp ja sellest piisaks tekstikonfiguratsioonide kasutamise alustamiseks. See nĂ€itab ka, et kompileerimisaja konfiguratsiooni keerukus on veidi vĂ€iksem kui tekstipĂ”histe konfiguratsioonide keerukus, kuna tekstipĂ”hises versioonis vajame lisakoodi.)
Tsentraliseeritud vĂ”tmevÀÀrtuste salvestus on hea mehhanism rakenduse metaparameetrite levitamiseks. Siin peame mĂ”tlema, mida peame konfiguratsioonivÀÀrtusteks ja mis on lihtsalt andmed. Antud funktsioon C => A => B nimetame tavaliselt harva muutuvaid vÀÀrtusi C "konfiguratsioon", samas kui andmeid muudetakse sageli A - lihtsalt sisestage andmed. Funktsiooni konfiguratsioon tuleks anda varem kui andmed A. Seda ideed arvestades vĂ”ime öelda, et konfiguratsiooniandmete eristamiseks lihtsalt andmetest saab kasutada eeldatavat muutuste sagedust. Samuti pĂ€rinevad andmed tavaliselt ĂŒhest allikast (kasutaja) ja konfiguratsioon teisest allikast (administraator). Parameetritega tegelemine, mida saab pĂ€rast initsialiseerimisprotsessi muuta, suurendab rakenduse keerukust. Selliste parameetrite puhul peame kĂ€sitlema nende edastamismehhanismi, sĂ”elumist ja valideerimist ning valede vÀÀrtuste kĂ€sitlemist. SeetĂ”ttu peaksime programmi keerukuse vĂ€hendamiseks vĂ€hendama parameetrite arvu, mis vĂ”ivad kĂ€itusajal muutuda (vĂ”i isegi kĂ”rvaldada need tĂ€ielikult).
Selle postituse vaatenurgast peaksime eristama staatilisi ja dĂŒnaamilisi parameetreid. Kui teenindusloogika nĂ”uab mĂ”ne parameetri harva muutmist kĂ€itusajal, siis vĂ”ime neid nimetada dĂŒnaamilisteks parameetriteks. Vastasel juhul on need staatilised ja neid saab kavandatud lĂ€henemisviisi abil konfigureerida. DĂŒnaamilise ĂŒmberkonfigureerimise jaoks vĂ”ib vaja minna teisi lĂ€henemisviise. NĂ€iteks vĂ”ib sĂŒsteemi osi taaskĂ€ivitada uute konfiguratsiooniparameetritega sarnaselt hajutatud sĂŒsteemi eraldi protsesside taaskĂ€ivitamisele.
(Minu tagasihoidlik arvamus on vĂ€ltida kĂ€itusaja ĂŒmberseadistamist, kuna see muudab sĂŒsteemi keerukamaks.
VÔib-olla oleks lihtsam protsesside taaskÀivitamisel lihtsalt OS-i toele loota. Kuigi see ei pruugi alati vÔimalik olla.)
Ăks staatilise konfiguratsiooni kasutamise oluline aspekt, mis mĂ”nikord paneb inimesed (ilma muude pĂ”hjusteta) dĂŒnaamilist konfigureerimist kaaluma, on teenuse seisak konfiguratsiooni vĂ€rskendamise ajal. TĂ”epoolest, kui peame staatilist konfiguratsiooni muutma, peame sĂŒsteemi taaskĂ€ivitama, et uued vÀÀrtused muutuksid tĂ”husaks. NĂ”uded seisakuajale on erinevate sĂŒsteemide puhul erinevad, seega ei pruugi see olla nii kriitiline. Kui see on kriitiline, peame sĂŒsteemi taaskĂ€ivitamist ette planeerima. NĂ€iteks vĂ”iksime rakendada . Selle stsenaariumi korral kĂ€ivitame iga kord, kui meil on vaja sĂŒsteemi taaskĂ€ivitada, paralleelselt sĂŒsteemi uue eksemplari, seejĂ€rel lĂŒlitame sellele ELB-i, laseme samal ajal vanal sĂŒsteemil olemasolevate ĂŒhenduste teenindamise lĂ”pule viia.
Aga konfiguratsiooni hoidmine versioonistatud artefakti sees vĂ”i vĂ€ljaspool? Konfiguratsiooni hoidmine artefakti sees tĂ€hendab enamikul juhtudel, et see konfiguratsioon on lĂ€binud sama kvaliteedi tagamise protsessi nagu teised artefaktid. Seega vĂ”ib olla kindel, et konfiguratsioon on kvaliteetne ja usaldusvÀÀrne. Vastupidi, konfiguratsioon eraldi failis tĂ€hendab, et pole jĂ€lgi selle kohta, kes ja miks selles failis muudatusi tegi. Kas see on oluline? Usume, et enamiku tootmissĂŒsteemide jaoks on parem stabiilne ja kvaliteetne konfiguratsioon.
Artefakti versioon vÔimaldab teada saada, millal see loodi, milliseid vÀÀrtusi see sisaldab, millised funktsioonid on lubatud/keelatud, kes vastutas iga konfiguratsioonimuudatuse tegemise eest. Konfiguratsiooni hoidmine artefakti sees vÔib nÔuda pingutusi ja see on disaini valik.
Plussid Miinused
Siinkohal tahaksime vÀlja tuua mÔned pakutud lÀhenemisviisi eelised ja arutada mÔningaid puudusi.
Eelised
TĂ€ieliku hajutatud sĂŒsteemi kompileeritava konfiguratsiooni omadused:
- Konfiguratsiooni staatiline kontroll. See annab kĂ”rge kindlustunde, et konfiguratsioon on tĂŒĂŒbipiiranguid arvestades Ă”ige.
- Rikkalik konfiguratsioonikeel. Tavaliselt piirduvad muud konfiguratsioonimeetodid maksimaalselt muutuva asendusega.
Scalat kasutades saab konfiguratsiooni paremaks muutmiseks kasutada laias valikus keelefunktsioone. NĂ€iteks vĂ”ime kasutada tunnuseid vaikevÀÀrtuste pakkumiseks, objekte erineva ulatuse mÀÀramiseks, millele saame viidatavals on defineeritud ainult ĂŒks kord vĂ€lissfÀÀris (DRY). VĂ”imalik on kasutada sĂ”nasĂ”nalisi jadasid vĂ”i teatud klasside esinemisjuhte (Seq,MapJne). - DSL. Scalal on korralik tugi DSL-i kirjutajatele. Nende funktsioonide abil saab luua mugavama ja lĂ”ppkasutajasĂ”bralikuma konfiguratsioonikeele, nii et lĂ”plik konfiguratsioon on vĂ€hemalt domeeni kasutajatele loetav.
- SĂ”lmede terviklikkus ja sidusus. Ăks kogu hajutatud sĂŒsteemi ĂŒhes kohas konfigureerimise eeliseid on see, et kĂ”ik vÀÀrtused mÀÀratletakse rangelt ĂŒks kord ja seejĂ€rel kasutatakse neid uuesti kĂ”igis kohtades, kus neid vajame. Sisestage ka turvalise pordi deklaratsioonid, mis tagavad, et sĂŒsteemi sĂ”lmed rÀÀgivad kĂ”igis vĂ”imalikes Ă”igetes konfiguratsioonides sama keelt. SĂ”lmede vahel on selged sĂ”ltuvused, mis muudab mĂ”ne teenuse pakkumise unustamise raskeks.
- Muudatuste kĂ”rge kvaliteet. Ăldine lĂ€henemine konfiguratsioonimuutuste lĂ€bimisele tavapĂ€rase PR-protsessi kaudu kehtestab kĂ”rged kvaliteedistandardid ka konfiguratsioonis.
- Samaaegsed konfiguratsioonimuudatused. Kui teeme konfiguratsioonis muudatusi, tagab automaatne juurutamine, et kÔiki sÔlme vÀrskendatakse.
- Rakenduse lihtsustamine. Rakendus ei pea konfiguratsiooni sĂ”eluma ja kinnitama ega kĂ€sitlema valesid konfiguratsioonivÀÀrtusi. See lihtsustab ĂŒldist rakendust. (Teatud keerukus on konfiguratsioonis endas, kuid see on teadlik kompromiss ohutuse suunas.) Tavakonfiguratsiooni juurde naasmine on ĂŒsna lihtne â lihtsalt lisage puuduvad osad. Lihtsam on alustada kompileeritud konfiguratsiooniga ja lĂŒkata tĂ€iendavate osade juurutamine mĂ”nele hilisemale ajale.
- Versioonitud konfiguratsioon. Kuna konfiguratsioonimuudatused jĂ€rgivad sama arendusprotsessi, saame tulemuseks ainulaadse versiooniga artefakti. See vĂ”imaldab meil vajadusel konfiguratsiooni tagasi lĂŒlitada. Saame isegi juurutada konfiguratsiooni, mida kasutati aasta tagasi ja see töötab tĂ€pselt samamoodi. Stabiilne konfiguratsioon parandab hajutatud sĂŒsteemi prognoositavust ja töökindlust. Konfiguratsioon on kompileerimise ajal fikseeritud ja seda ei saa tootmissĂŒsteemis kergesti rikkuda.
- Modulaarsus. Kavandatav raamistik on modulaarne ja mooduleid saab kombineerida mitmel viisil
toetada erinevaid konfiguratsioone (seadistusi/paigutusi). EelkĂ”ige on vĂ”imalik kasutada vĂ€ikesemahulist ĂŒhe sĂ”lme paigutust ja suuremahulist mitme sĂ”lme seadistust. On mĂ”istlik kasutada mitut tootmispaigutust. - Testimine. Testimise eesmĂ€rgil vĂ”ib rakendada nĂ€idisteenust ja kasutada seda sĂ”ltuvusena tĂŒĂŒbikindlal viisil. Samaaegselt saab sĂ€ilitada mĂ”nda erinevat testimispaigutust, kus erinevad osad on asendatud pilkadega.
- Integratsiooni testimine. MĂ”nikord on hajutatud sĂŒsteemides integratsioonitestide kĂ€ivitamine keeruline. Kasutades kirjeldatud lĂ€henemisviisi tervikliku hajutatud sĂŒsteemi turvalise konfiguratsiooni tippimiseks, saame juhtida kĂ”iki hajutatud osi ĂŒhes serveris juhitaval viisil. Olukorda on lihtne jĂ€ljendada
kui ĂŒks teenustest muutub kĂ€ttesaamatuks.
Puudused
Koostatud konfiguratsiooni lÀhenemisviis erineb "tavalisest" konfiguratsioonist ja see ei pruugi vastata kÔikidele vajadustele. Siin on mÔned koostatud konfiguratsiooni puudused:
- Staatiline konfiguratsioon. See ei pruugi kĂ”igi rakenduste jaoks sobida. MĂ”nel juhul on konfiguratsioon tootmises vaja kiiresti fikseerida, jĂ€ttes kĂ”rvale kĂ”ik ohutusmeetmed. See lĂ€henemine muudab selle keerulisemaks. Kompileerimine ja ĂŒmberpaigutamine on vajalik pĂ€rast konfiguratsiooni muutmist. See on nii omadus kui ka koormus.
- Konfiguratsiooni genereerimine. Kui konfiguratsiooni genereerib mĂ”ni automatiseerimistööriist, nĂ”uab see lĂ€henemine hilisemat kompileerimist (mis vĂ”ib omakorda ebaĂ”nnestuda). Selle tĂ€iendava sammu integreerimine ehitussĂŒsteemi vĂ”ib nĂ”uda tĂ€iendavaid jĂ”upingutusi.
- Instrumendid. TÀnapÀeval on kasutusel palju tööriistu, mis pÔhinevad tekstipÔhistel konfiguratsioonidel. MÔned neist
ei kehti konfiguratsiooni koostamisel. - Vaja on mÔtteviisi muutust. Arendajad ja DevOps tunnevad tekstikonfiguratsioonifaile. Konfiguratsiooni koostamise idee vÔib neile tunduda kummaline.
- Enne kompileeritava konfiguratsiooni kasutuselevÔttu on vajalik kvaliteetne tarkvara arendusprotsess.
Rakendatud nÀitel on mÔned piirangud:
- Kui pakume lisakonfiguratsiooni, mida sÔlme rakendamine ei nÔua, ei aita kompilaator meil puuduvat rakendust tuvastada. Seda saab lahendada kasutades
HListvÔi ADT-d (juhtumiklassid) sÔlmede konfigureerimiseks tunnuste ja koogimustri asemel. - Peame konfiguratsioonifailis esitama mÔne katlaplaadi: (
package,import,objectdeklaratsioonid;
override def's parameetrite jaoks, millel on vaikevÀÀrtused). Seda vĂ”ib osaliselt lahendada DSL-i abil. - Selles postituses me ei kĂ€sitle sarnaste sĂ”lmede klastrite dĂŒnaamilist ĂŒmberkonfigureerimist.
JĂ€reldus
Selles postituses oleme arutanud ideed esitada konfiguratsioon otse lĂ€htekoodis tĂŒĂŒbikindlal viisil. Seda lĂ€henemisviisi saab kasutada paljudes rakendustes xml- ja muude tekstipĂ”histe konfiguratsioonide asendajana. Vaatamata sellele, et meie nĂ€idet on Scalas rakendatud, saab seda tĂ”lkida ka teistesse kompileeritavatesse keeltesse (nt Kotlin, C#, Swift jne). Seda lĂ€henemist vĂ”iks proovida mĂ”nes uues projektis ja kui see hĂ€sti ei sobi, minna ĂŒle vanamoodsale.
Loomulikult nÔuab kompileeritav konfiguratsioon kvaliteetset arendusprotsessi. Vastutasuks lubab see pakkuda sama kvaliteetset ja tugevat konfiguratsiooni.
Seda lÀhenemisviisi saab laiendada mitmel viisil:
- Makrosid saab kasutada konfiguratsiooni valideerimiseks ja Àriloogika piirangute tÔrgete korral kompileerimise ajal ebaÔnnestuda.
- DSL-i saab rakendada konfiguratsiooni esitamiseks domeeni kasutajasÔbralikul viisil.
- DĂŒnaamiline ressursside haldamine koos automaatsete konfiguratsiooni kohandamisega. NĂ€iteks kui kohandame klastri sĂ”lmede arvu, vĂ”iksime soovida, et (1) sĂ”lmed saaksid veidi muudetud konfiguratsiooni; (2) klastrihaldur uute sĂ”lmede teabe saamiseks.
tÀnan
Tahaksin tÀnada Andrei Saksonovit, Pavel Popovit ja Anton Nehajevit inspireeriva tagasiside eest selle postituse mustandile, mis aitas mul seda selgemaks teha.
Allikas: www.habr.com
