Post honetan sistema banatu baten konfigurazioari aurre egiteko modu interesgarri bat partekatu nahi dugu.
Konfigurazioa Scala hizkuntzan zuzenean adierazten da modu seguruan. Inplementazio adibide bat xehetasunez deskribatzen da. Proposamenaren hainbat alderdi eztabaidatzen dira, garapen prozesu orokorrean eragina barne.
Sarrera
Banatutako sistema sendoak eraikitzeak nodo guztietan konfigurazio zuzena eta koherentea erabiltzea eskatzen du. Soluzio tipiko bat inplementazio testualaren deskribapena (terraform, ansible edo antzeko zerbait) eta automatikoki sortutako konfigurazio-fitxategiak erabiltzea da (askotan, nodo/rol bakoitzerako dedikatuta). Komunikazio nodo bakoitzean bertsio bereko protokolo berdinak ere erabili nahi genituzke (bestela bateraezintasun arazoak izango genituzke). JVM munduan horrek esan nahi du gutxienez mezularitza-liburutegiak bertsio berekoa izan behar duela komunikazio-nodo guztietan.
Zer gertatzen da sistema probatzea? Jakina, osagai guztien unitate-probak egin beharko genituzke integrazio-probetara heldu aurretik. Exekuzio-denboran proben emaitzak estrapolatu ahal izateko, ziurtatu behar dugu liburutegi guztien bertsioak berdin mantentzen direla bai exekuzio-denboran, bai proba-inguruneetan.
Integrazio probak exekutatzen direnean, askotan askoz errazagoa da nodo guztietan klase-bide bera izatea. Inplementazioan klase bide bera erabiltzen dela ziurtatu behar dugu. (Posible da klase-bide desberdinak erabiltzea nodo desberdinetan, baina zailagoa da konfigurazio hau irudikatzea eta behar bezala zabaltzea.) Beraz, gauzak sinpleak izan daitezen nodo guztietan klase-bide berdinak bakarrik hartuko ditugu kontuan.
Konfigurazioak softwarearekin batera eboluzionatu ohi du. Normalean bertsioak erabiltzen ditugu hainbat identifikatzeko
softwarearen bilakaeraren faseak. Arrazoizkoa dirudi bertsioen kudeaketan konfigurazioa estaltzea eta etiketa batzuekin konfigurazio desberdinak identifikatzea. Ekoizpenean konfigurazio bakarra badago, bertsio bakarra erabil dezakegu identifikatzaile gisa. Batzuetan ekoizpen-ingurune anitz izan ditzakegu. Eta ingurune bakoitzerako konfigurazio-adar bereizi bat beharko genuke. Beraz, konfigurazioak adarrekin eta bertsioarekin etiketatuta egon daitezke konfigurazio desberdinak modu berezian identifikatzeko. Adarren etiketa eta bertsio bakoitza nodo bakoitzean banatutako nodoen, ataken, kanpoko baliabideen, classpath liburutegien bertsioen konbinazio bakar bati dagokio. Hemen adar bakarra estaliko dugu eta konfigurazioak hiru osagai hamartarren bertsio baten bidez identifikatuko ditugu (1.2.3), beste artefaktu batzuen modu berean.
Ingurune modernoetan konfigurazio fitxategiak ez dira eskuz aldatzen. Normalean sortzen dugu
konfigurazio fitxategiak inplementazio garaian eta
Post honetan konfigurazioa konpilatutako artefaktuan mantentzeko ideia aztertuko dugu.
Konfigurazio konpilagarria
Atal honetan konfigurazio estatikoen adibide bat eztabaidatuko dugu. Bi zerbitzu sinple: echo zerbitzua eta echo zerbitzuaren bezeroa konfiguratzen eta inplementatzen ari dira. Ondoren, bi zerbitzuak dituzten bi sistema banatu desberdin instanziatzen dira. Bata nodo bakarreko konfiguraziorako da eta beste bat bi nodoen konfiguraziorako.
Banatutako sistema tipiko batek nodo batzuk ditu. Nodoak mota batzuk erabiliz identifikatu litezke:
sealed trait NodeId
case object Backend extends NodeId
case object Frontend extends NodeId
edo besterik ez
case class NodeId(hostName: String)
edo are
object Singleton
type NodeId = Singleton.type
Nodo hauek hainbat rol betetzen dituzte, zerbitzu batzuk exekutatzen dituzte eta beste nodoekin TCP/HTTP konexioen bidez komunikatzeko gai izan behar dute.
TCP konexiorako gutxienez ataka-zenbaki bat behar da. Bezeroa eta zerbitzaria protokolo berean hitz egiten ari direla ziurtatu nahi dugu. Nodoen arteko konexio bat modelatzeko, deklara dezagun klase hau:
case class TcpEndPoint[Protocol](node: NodeId, port: Port[Protocol])
non Port
bat besterik ez da Int
baimendutako barrutian:
type PortNumber = Refined[Int, Closed[_0, W.`65535`.T]]
Mota finduak
Ikusi Int
ataka-zenbakia irudika dezaketen 16 biteko balioak soilik izatea onartzen da. Ez dago liburutegi hau erabiltzeko baldintzarik konfigurazio ikuspegi honetarako. Oso ondo moldatzen dela dirudi.
HTTP (REST) ββzerbitzuaren bide bat ere beharko genuke:
type UrlPathPrefix = Refined[String, MatchesRegex[W.`"[a-zA-Z_0-9/]*"`.T]]
case class PortWithPrefix[Protocol](portNumber: PortNumber, pathPrefix: UrlPathPrefix)
Fantasma mota
Konpilazioan protokoloa identifikatzeko Scala ezaugarria erabiltzen ari gara mota argumentua deklaratzeko Protocol
klasean erabiltzen ez dena. Hala deitzen da fantasma mota. Exekuzioan oso gutxitan behar dugu protokoloaren identifikatzailearen instantzia bat, horregatik ez dugu gordetzen. Konpilazioan zehar mota fantasma honek segurtasun mota gehigarria ematen du. Ezin dugu portua pasa protokolo oker batekin.
Gehien erabiltzen den protokoloetako bat REST APIa da Json serializazioarekin:
sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]
non RequestMessage
bezeroak zerbitzariari bidal ditzakeen oinarrizko mezu mota da eta ResponseMessage
zerbitzariaren erantzun-mezua da. Jakina, komunikazio-protokoloa nahi den zehaztasunarekin zehazten duten beste protokolo deskribapen batzuk sor ditzakegu.
Post honen helburuetarako protokoloaren bertsio sinpleago bat erabiliko dugu:
sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]
Protokolo honetan eskaera-mezua url-era gehitzen da eta erantzun-mezua kate arrunt gisa itzultzen da.
Zerbitzuaren konfigurazio bat zerbitzuaren izenak, ataka bilduma batek eta mendekotasun batzuekin deskriba liteke. Elementu horiek guztiak Scala-n irudikatzeko modu posible batzuk daude (adibidez, HList
, datu aljebraikoak). Post honen helburuetarako Cake Pattern erabiliko dugu eta konbina daitezkeen piezak (moduluak) ezaugarri gisa irudikatuko ditugu. (Pastelaren eredua ez da konfigurazio-ikuspegi konpilagarri honetarako baldintza bat. Ideia inplementazio posible bat besterik ez da.)
Mendekotasunak beste nodo batzuen amaierako puntu gisa Cake Pattern erabiliz irudikatu litezke:
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 zerbitzuak ataka bat bakarrik konfiguratu behar du. Eta ataka honek oihartzun protokoloa onartzen duela adierazten dugu. Kontuan izan momentu honetan ez dugula ataka jakin bat zehaztu behar, ezaugarriak metodo abstraktuak deklaratzeko aukera ematen duelako. Metodo abstraktuak erabiltzen baditugu, konpilatzaileak inplementazio bat eskatuko du konfigurazio instantzia batean. Hemen inplementazioa eman dugu (8081
) eta balio lehenetsi gisa erabiliko da konfigurazio konkretu batean saltatzen badugu.
Oihartzun zerbitzuaren bezeroaren konfigurazioan mendekotasuna deklara dezakegu:
trait EchoClientConfig[A] {
def testMessage: String = "test"
def pollInterval: FiniteDuration
def echoServiceDependency: HttpSimpleGetEndPoint[_, EchoProtocol[A]]
}
Mendekotasunak mota bera du echoService
. Bereziki, protokolo bera eskatzen du. Horregatik, ziur egon gaitezke bi mendekotasun hauek konektatzen baditugu ondo funtzionatuko dutela.
Zerbitzuak ezartzea
Zerbitzu batek funtzio bat behar du abiarazteko eta ondo ixteko. (Zerbitzu bat ixteko gaitasuna funtsezkoa da probak egiteko.) Berriro ere, konfigurazio jakin baterako funtzio bat zehazteko aukera batzuk daude (adibidez, mota klaseak erabil genitzake). Post honetarako Cake Pattern berriro erabiliko dugu. Zerbitzu bat erabiliz irudikatu dezakegu cats.Resource
dagoeneko hortxetzea eta baliabideen askapena eskaintzen duena. Baliabide bat eskuratzeko konfigurazio bat eta exekuzio-testuinguru bat eman beharko genuke. Beraz, zerbitzua abiarazteko funtzioak itxura hau izan dezake:
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]
}
non
Config
β Zerbitzu abiarazle honek eskatzen duen konfigurazio motaAddressResolver
β beste nodo batzuen benetako helbideak lortzeko gaitasuna duen exekuzio-denborako objektu bat (jarrai ezazu irakurtzen xehetasunetarako).
beste motak datoz cats
:
F[_]
β efektu mota (Kasurik errazeneanF[A]
justua izan liteke() => A
. Post honetan erabiliko dugucats.IO
.)Reader[A,B]
β funtzio baten sinonimoa da gutxi gorabeheraA => B
cats.Resource
β eskuratzeko eta askatzeko bideak dituTimer
β lo egiteko/denbora neurtzeko aukera ematen duContextShift
- analogikoaExecutionContext
Applicative
- Funtzioen bilgarria indarrean (ia monada bat) (azkenean beste zerbaitekin ordezkatu genezake)
Interfaze hau erabiliz zerbitzu batzuk inplementa ditzakegu. Adibidez, ezer egiten ez duen zerbitzu bat:
trait ZeroServiceImpl[F[_]] extends ServiceImpl[F] {
type Config <: Any
def resource(...): ResourceReader[F, Config, Unit] =
Reader(_ => Resource.pure[F, Unit](()))
}
(Ikus
Nodo bat zerbitzu batzuk exekutatzen dituen objektu bakarra da (baliabide-kate bat abiaraztea Cake Pattern-ek gaitzen du):
object SingleNodeImpl extends ZeroServiceImpl[IO]
with EchoServiceService
with EchoClientService
with FiniteDurationLifecycleServiceImpl
{
type Config = EchoConfig[String] with EchoClientConfig[String] with FiniteDurationLifecycleConfig
}
Kontuan izan nodoan nodo honek behar duen konfigurazio mota zehatza zehazten dugula. Konpilatzaileak ez digu utziko objektua (Cake) mota nahikorik gabe eraikitzen, zerbitzu-ezaugarri bakoitzak muga bat adierazten duelako. Config
mota. Gainera, ezingo dugu nodoa abiarazi konfigurazio osoa eman gabe.
Nodo helbidearen ebazpena
Konexio bat ezartzeko benetako ostalari helbide bat behar dugu nodo bakoitzeko. Baliteke konfigurazioaren beste atal batzuk baino beranduago ezagutzea. Hori dela eta, nodoaren id eta benetako helbidearen arteko mapa bat emateko modu bat behar dugu. Kartografia hau funtzio bat da:
case class NodeAddress[NodeId](host: Uri.Host)
trait AddressResolver[F[_]] {
def resolve[NodeId](nodeId: NodeId): F[NodeAddress[NodeId]]
}
Funtzio hori ezartzeko modu posible batzuk daude.
- Inplementatu aurretik benetako helbideak ezagutzen baditugu, nodoen ostalarien instantziazioan zehar, orduan Scala kodea sor dezakegu benetako helbideekin eta eraikitzea exekutatu ondoren (konpilazio-denboraren egiaztapenak egiten dituena eta integrazio-proba multzoa exekutatzen duena). Kasu honetan gure mapa-funtzioa estatikoki ezagutzen da eta a bezalako zerbaitetara sinplifikatu daiteke
Map[NodeId, NodeAddress]
. - Batzuetan, nodoa benetan abiarazten den une batean bakarrik lortzen ditugu benetako helbideak, edo ez ditugu oraindik hasi ez diren nodoen helbideak. Kasu honetan, beste nodo guztien aurretik abiarazten den aurkikuntza-zerbitzu bat izan dezakegu eta nodo bakoitzak bere helbidea iragar dezake zerbitzu horretan eta menpekotasunetara harpidetu.
- Aldatu ahal badugu
/etc/hosts
, aurrez definitutako ostalari-izenak erabil ditzakegu (adibidezmy-project-main-node
echo-backend
) eta lotu izen hau ip helbidearekin inplementazio garaian.
Argitalpen honetan ez ditugu kasu hauek xehetasun gehiagorekin lantzen. Izan ere, gure jostailu adibidean nodo guztiek IP helbide bera izango dute - 127.0.0.1
.
Argitalpen honetan bi sistema banatuko diseinuak hartuko ditugu kontuan:
- Nodo bakarreko diseinua, non zerbitzu guztiak nodo bakarrean kokatzen diren.
- Bi nodoen diseinua, non zerbitzua eta bezeroa nodo ezberdinetan dauden.
A-rako konfigurazioa
Nodo bakarreko konfigurazioa
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.
}
Hemen zerbitzariaren eta bezeroaren konfigurazioa hedatzen duen konfigurazio bakarra sortzen dugu. Gainera, normalean bezeroa eta zerbitzaria amaituko dituen bizi-zikloko kontroladore bat konfiguratzen dugu lifetime
tartea igarotzen da.
Zerbitzuen inplementazio eta konfigurazio multzo bera erabil daiteke sistema baten diseinua bi nodo bereiziekin sortzeko. Sortzea besterik ez dugu falta
Bi nodoen konfigurazioa
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"
}
Ikusi nola zehazten dugun mendekotasuna. Uneko nodoaren menpekotasun gisa aipatzen dugu beste nodoaren emandako zerbitzua. Mendekotasun mota egiaztatzen da protokoloa deskribatzen duen fantasma mota duelako. Eta exekuzioan nodoaren id zuzena izango dugu. Hau da proposatutako konfigurazio ikuspegiaren alderdi garrantzitsuetako bat. Portua behin bakarrik ezartzeko eta ataka zuzenari erreferentzia egiten ari garela ziurtatzeko aukera ematen digu.
Bi nodo inplementazioa
Konfigurazio honetarako zerbitzuen inplementazio berdinak erabiltzen ditugu. Ez dago batere aldaketarik. Hala ere, zerbitzu multzo desberdinak dituzten bi nodo inplementazio desberdin sortzen ditugu:
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
}
Lehen nodoak zerbitzaria inplementatzen du eta zerbitzariaren alboko konfigurazioa besterik ez du behar. Bigarren nodoak bezeroa inplementatzen du eta konfigurazioaren beste zati bat behar du. Bi nodoek bizitzako zehaztapen batzuk behar dituzte. Post-zerbitzuaren nodo honen helburuetarako, erabilera amai daitekeen bizitza infinitua izango du SIGTERM
, echo bezeroa, berriz, konfiguratutako iraupen mugatuaren ondoren amaituko da. Ikusi
Garapen prozesu orokorra
Ikus dezagun nola aldatzen duen ikuspegi honek konfigurazioarekin lan egiteko modua.
Kode gisa konfigurazioa konpilatu egingo da eta artefaktu bat sortzen du. Arrazoizkoa dirudi konfigurazio-artefaktua beste kode-artefaktuetatik bereiztea. Askotan konfigurazio ugari izan ditzakegu kode-oinarri berean. Eta noski, hainbat konfigurazio adarren bertsio anitz izan ditzakegu. Konfigurazio batean liburutegien bertsio jakin batzuk hauta ditzakegu eta hau konstante mantenduko da konfigurazio hau zabaltzen dugunean.
Konfigurazio aldaketa kode aldaketa bihurtzen da. Beraz, kalitatea bermatzeko prozesu berberarekin estali behar da:
Ticket -> PR -> berrikuspena -> batu -> etengabeko integrazioa -> etengabeko hedapena
Planteamenduaren ondorio hauek daude:
- Konfigurazioa koherentea da sistema jakin baten instantziarako. Badirudi ez dagoela modurik nodoen artean konexio okerrik izateko.
- Ez da erraza konfigurazioa nodo bakarrean aldatzea. Arrazoigabea dirudi saioa hastea eta testu fitxategi batzuk aldatzea. Beraz, konfigurazio-noraeza posibleagoa da.
- Konfigurazio aldaketa txikiak ez dira errazak egitea.
- Konfigurazio-aldaketa gehienek garapen-prozesu bera jarraituko dute, eta berrikuspen batzuk gaindituko dituzte.
Produkzioaren konfiguraziorako biltegi bereizi bat behar al dugu? Produkzio-konfigurazioak informazio sentikorra izan dezake, jende askoren eskura ez eduki nahiko genukeena. Beraz, baliteke ekoizpen-konfigurazioa edukiko duen sarbide mugatua duen biltegi bereizi bat mantentzea. Konfigurazioa bi zatitan bana dezakegu: bata ekoizpen-parametro irekienak dituena eta bestea konfigurazioaren zati sekretua duena. Honek garatzaile gehienentzat parametro gehienetarako sarbidea ahalbidetuko luke, gauza sentikorretarako sarbidea mugatuz. Erraza da parametro balio lehenetsiekin tarteko ezaugarriak erabiliz.
Aldakuntzak
Ikus ditzagun proposatutako planteamenduaren alde onak eta txarrak beste konfigurazio-kudeaketako teknikekin alderatuta.
Lehenik eta behin, proposatutako konfigurazioari aurre egiteko moduaren alderdi ezberdinen alternatiba batzuk zerrendatuko ditugu:
- Testu-fitxategia xede-makinan.
- Gako-balioen biltegiratze zentralizatua (adibidez
etcd
/zookeeper
). - Prozesua berrabiarazi gabe birkonfiguratu/berrabiarazi daitezkeen azpiprozesuko osagaiak.
- Artefaktu eta bertsio-kontroletik kanpoko konfigurazioa.
Testu-fitxategiak nolabaiteko malgutasuna ematen du ad-hoc konponketei dagokienez. Sistema baten administratzaileak xede-nodoan saioa hasi, aldaketa bat egin eta zerbitzua berrabiarazi besterik ez du egin. Baliteke hau sistema handiagoentzat ez izatea hain ona. Aldaketaren atzean ez da arrastorik geratzen. Aldaketa ez du beste begi pare batek berrikusten. Zaila izan daiteke aldaketa zerk eragin duen jakitea. Ez da probatu. Banatutako sistemaren ikuspuntutik administratzaileak beste nodoren batean konfigurazioa eguneratzea ahaztu dezake.
(Btw, azkenean testu-konfigurazio-fitxategiak erabiltzen hasi beharra egongo balitz, berdina sor dezaketen analizatzailea + balioztatzailea bakarrik gehitu beharko dugu. Config
idatzi eta nahikoa izango litzateke testu-konfigurazioak erabiltzen hasteko. Horrek ere erakusten du konpilazio garaiko konfigurazioaren konplexutasuna testuan oinarritutako konfigurazioen konplexutasuna baino apur bat txikiagoa dela, testuan oinarritutako bertsioan kode gehigarri bat behar dugulako.)
Gako-balioen biltegiratze zentralizatua mekanismo ona da aplikazioen meta-parametroak banatzeko. Hemen konfigurazio-balioak direla eta datuak besterik ez diren pentsatu behar dugu. Funtzio bat emanda C => A => B
gutxitan aldatzen diren balioei deitu ohi diegu C
"konfigurazioa", maiz aldatzen diren datuak A
- datuak sartu besterik ez. Konfigurazioa datuak baino lehenago eman behar zaio funtzioari A
. Ideia hori kontuan hartuta, esan dezakegu aldaketen maiztasuna espero dela konfigurazio datuak datuetatik bereizteko erabil daitekeena. Gainera, normalean datuak iturri batetik datoz (erabiltzailea) eta konfigurazioa beste iturri batetik dator (administratzailea). Hasierako prozesuaren ondoren alda daitezkeen parametroei aurre egiteak aplikazioaren konplexutasuna areagotzea dakar. Horrelako parametroetarako haien entrega-mekanismoa, analisia eta baliozkotzea kudeatu beharko dugu, balio okerrak maneiatzen. Horregatik, programaren konplexutasuna murrizteko, hobe genuke exekuzioan alda daitezkeen parametroen kopurua murriztea (edo guztiz ezabatzea ere).
Post honen ikuspegitik parametro estatiko eta dinamikoen arteko bereizketa egin beharko genuke. Zerbitzu-logikak exekuzio garaian parametro batzuen aldaketa arraroa eskatzen badu, parametro dinamikoak dei ditzakegu. Bestela, estatikoak dira eta proposatutako ikuspegia erabiliz konfigura daitezke. Birkonfigurazio dinamikorako beste ikuspegi batzuk beharko lirateke. Adibidez, sistemaren zatiak konfigurazio-parametro berriekin berrabiarazi daitezke sistema banatu baten prozesu bereiziak berrabiaraztearen antzera.
(Nire iritzi xumea exekuzio denboraren birkonfigurazioa saihestea da, sistemaren konplexutasuna areagotzen duelako.
Baliteke errazagoa izan liteke prozesuak berrabiarazteko sistema eragilearen laguntzan fidatzea. Hala ere, agian ez da beti posible izango.)
Batzuetan jendeak konfigurazio dinamikoa kontuan hartzen duen konfigurazio estatikoa erabiltzearen alderdi garrantzitsu bat (beste arrazoirik gabe) zerbitzuaren geldialdia da konfigurazio eguneratzean. Izan ere, konfigurazio estatikoan aldaketak egin behar baditugu, sistema berrabiarazi beharko dugu, balio berriak eraginkorrak izan daitezen. Geldialdietarako eskakizunak sistema desberdinen arabera aldatzen dira, beraz, baliteke horren kritikoa ez izatea. Kritikoa bada, sistema edozein berrabiarazi aurretik planifikatu behar dugu. Adibidez, inplementa genezake
Zer gertatzen da bertsiodun artefaktuaren barruan edo kanpoan mantentzea? Konfigurazioa artefaktu baten barruan mantentzeak esan nahi du kasu gehienetan konfigurazio honek beste artefaktuen kalitatea bermatzeko prozesu bera gainditu duela. Beraz, ziur egon liteke konfigurazioa kalitate onekoa eta fidagarria dela. Aitzitik, fitxategi bereizi batean konfigurazioak esan nahi du ez dagoela fitxategi horretan aldaketak nork eta zergatik egin dituenaren arrastorik. Garrantzitsua al da hori? Uste dugu ekoizpen-sistema gehienentzat hobe dela konfigurazio egonkorra eta kalitate handikoa izatea.
Artefaktuaren bertsioak noiz sortu den, zer balio dituen, zer funtzio gaitu/desgaitu eta konfigurazioan aldaketa bakoitza egiteaz arduratu den jakiteko aukera ematen du. Baliteke artefaktu baten barruan konfigurazioa mantentzeko ahalegina behar izatea eta diseinu-aukera bat da.
Alde onak eta txarrak
Hemen proposatzen den ikuspegiaren abantaila batzuk nabarmendu nahi ditugu eta desabantaila batzuk eztabaidatu.
Abantailak
Banatutako sistema oso baten konfigurazio konpilagarriaren ezaugarriak:
- Konfigurazioaren egiaztapen estatikoa. Honek konfiantza maila altua ematen du, konfigurazioa zuzena dela mota-murrizketak kontuan hartuta.
- Konfigurazio hizkuntza aberatsa. Normalean beste konfigurazio-ikuspegiak gehienez ordezkapen aldakor batera mugatzen dira.
Scala erabiliz hizkuntza-funtzio ugari erabil daitezke konfigurazioa hobeto egiteko. Esate baterako, ezaugarriak erabil ditzakegu balio lehenetsiak emateko, objektuak esparru desberdinak ezartzeko, erreferentzia egin dezakeguval
s behin bakarrik definitzen da kanpoko esparruan (DRY). Posible da sekuentzia literalak edo zenbait klaseren instantzien erabilera (Seq
,Map
, Eta abar). - DSL. Scalak laguntza duina dauka DSL idazleentzat. Ezaugarri hauek erabil daitezke konfigurazio-lengoaia erosoagoa eta azken erabiltzaileentzat atseginagoa dena ezartzeko, azken konfigurazioa gutxienez domeinuko erabiltzaileek irakur dezaten.
- Osotasuna eta koherentzia nodoen artean. Banatutako sistema osoaren konfigurazioa leku bakarrean edukitzearen abantailetako bat balio guztiak behin zorrozki definitzen direla da eta gero berrerabiltzen direla behar ditugun leku guztietan. Era berean, idatzi ataka seguruen adierazpenak ziurtatu ahal diren konfigurazio zuzen guztietan sistemaren nodoek hizkuntza bera hitz egingo dutela. Nodoen artean menpekotasun esplizituak daude eta horrek zaila egiten du zerbitzu batzuk eskaintzea ahaztea.
- Aldaketen kalitate handia. Konfigurazio-aldaketak PR prozesu arruntetik pasatzeko ikuspegi orokorrak kalitate estandar altuak ezartzen ditu konfigurazioan ere.
- Aldibereko konfigurazio-aldaketak. Konfigurazioan aldaketak egiten ditugunean, inplementazio automatikoak nodo guztiak eguneratzen ari direla ziurtatzen du.
- Aplikazioaren sinplifikazioa. Aplikazioak ez du zertan konfigurazioa analizatu eta balioztatu eta konfigurazio-balio okerrak kudeatu behar. Horrek aplikazio orokorra errazten du. (Konplexutasuna handitzea konfigurazioan bertan dago, baina segurtasunarekiko truke kontziente bat da.) Nahiko erraza da konfigurazio arruntera itzultzea β falta diren piezak gehitu besterik ez dago. Errazagoa da konpilatutako konfigurazioarekin hastea eta pieza gehigarrien inplementazioa geroago atzeratzea.
- Bertsiodun konfigurazioa. Konfigurazio-aldaketek garapen-prozesu bera jarraitzen dutelako, ondorioz, bertsio bakarra duen artefaktu bat lortzen dugu. Behar izanez gero, konfigurazioa atzera aldatzeko aukera ematen digu. Duela urtebete erabili zen konfigurazio bat ere zabaldu dezakegu eta modu berean funtzionatuko du. Konfigurazio egonkorrak sistema banatuaren aurreikusgarritasuna eta fidagarritasuna hobetzen ditu. Konfigurazioa konpilazio garaian finkatzen da eta ezin da erraz manipulatu ekoizpen-sistema batean.
- Modulartasuna. Proposatutako esparrua modularra da eta moduluak hainbat modutan konbina daitezke
konfigurazio desberdinak onartzen ditu (konfigurazioak/diseinuak). Bereziki, posible da eskala txikiko nodo bakarreko diseinua eta eskala handiko nodo anitzeko ezarpena izatea. Arrazoizkoa da ekoizpen diseinu anitz izatea. - Probak. Proba egiteko zerbitzu simulatu bat inplementatu eta mendekotasun gisa erabil daiteke modu seguruan. Proba-diseinu ezberdin batzuk maketak ordezkatuta hainbat piezarekin mantendu litezke aldi berean.
- Integrazio probak. Batzuetan, sistema banatuetan zaila da integrazio-probak egitea. Banatutako sistema osoaren konfigurazio segurua idazteko deskribatutako ikuspegia erabiliz, banatutako zati guztiak zerbitzari bakarrean exekutatu ditzakegu modu kontrolagarrian. Erraza da egoera imitatzea
zerbitzuren bat erabilgarri gelditzen denean.
Desabantailak
Konpilatutako konfigurazio-ikuspegia konfigurazio "normalaren" desberdina da eta beharbada ez da behar guztietara egokitzea. Hona hemen konpilatutako konfigurazioaren desabantaila batzuk:
- Konfigurazio estatikoa. Baliteke aplikazio guztietarako egokia ez izatea. Zenbait kasutan, ekoizpenean konfigurazioa azkar konpondu beharra dago segurtasun neurri guztiak alde batera utzita. Ikuspegi honek zailagoa egiten du. Konfigurazioan edozein aldaketa egin ondoren, konpilazioa eta birmoldaketa beharrezkoak dira. Hau da ezaugarria eta zama.
- Konfigurazioa sortzea. Automatizazio tresnaren batek konfigurazioa sortzen duenean hurbilketa honek ondorengo konpilazioa behar du (horrek huts egin dezake). Baliteke urrats gehigarri hau eraikuntza-sisteman integratzeko ahalegin gehigarria behar izatea.
- Instrumentuak. Testuan oinarritutako konfigurazioetan oinarritzen diren tresna ugari erabiltzen dira gaur egun. Haietako batzuk
ez da aplikagarria izango konfigurazioa konpilatzen denean. - Pentsamolde aldaketa bat behar da. Garatzaileek eta DevOps-ek testu-konfigurazio fitxategiak ezagutzen dituzte. Konfigurazioa konpilatzearen ideia arraroa iruditu daiteke.
- Konfigurazio konpilagarria sartu aurretik kalitate handiko softwarea garatzeko prozesu bat behar da.
Inplementatutako adibidearen muga batzuk daude:
- Nodoaren inplementazioak eskatzen ez duen konfigurazio gehigarria ematen badugu, konpilatzaileak ez digu lagunduko ez dagoen inplementazioa detektatzen. Erabiliz zuzendu liteke
HList
edo ADTak (kasu-klaseak) nodoen konfiguraziorako ezaugarrien eta Cake Pattern ordez. - Boilerplate batzuk eman behar ditugu konfigurazio fitxategian: (
package
,import
,object
adierazpenak;
override def
balio lehenetsiak dituzten parametroetarako). Baliteke hori partzialki DSL bat erabiliz konpontzea. - Post honetan ez dugu antzeko nodoen multzoen birkonfigurazio dinamikoa lantzen.
Ondorioa
Post honetan konfigurazioa zuzenean iturburu-kodean modu seguruan adierazteko ideia eztabaidatu dugu. Planteamendua aplikazio askotan erabil liteke xml eta testuan oinarritutako beste konfigurazio batzuen ordez. Gure adibidea Scala-n inplementatu den arren, beste hizkuntza konpilagarri batzuetara ere itzul liteke (Kotlin, C#, Swift, etab.). Ikuspegi hau proiektu berri batean probatu liteke eta, ondo moldatzen ez bada, antzinako erara jo.
Jakina, konfigurazio konpilagarriak kalitate handiko garapen prozesua behar du. Horren truke, kalitate handiko konfigurazio sendoa emango duela agintzen du.
Planteamendu hau hainbat modutara heda daiteke:
- Makroak erabil litezke konfigurazioen baliozkotzea egiteko eta konpilazio garaian huts egin dezake negozio-logikako murriztapenen hutsegiteen kasuan.
- DSL bat inplementatu liteke konfigurazioa domeinua erabiltzeko modu egokian irudikatzeko.
- Baliabideen kudeaketa dinamikoa konfigurazio doikuntza automatikoekin. Adibidez, kluster-nodoen kopurua doitzen dugunean (1) nodoek apur bat aldatutako konfigurazioa lortzea nahi dugu; (2) kluster kudeatzailea nodo berriak jasotzeko.
Eskerrik asko
Eskerrak eman nahi nizkieke Andrey Saksonov, Pavel Popov, Anton Nehaev-i, argiago uzten lagundu didaten mezu honen zirriborroari buruzko iritzi inspiratzaileak emateagatik.
Iturria: www.habr.com