Konfigurasi compilable saka sistem mbagekke

Ing kirim iki, kita pengin nuduhake cara sing menarik kanggo ngatasi konfigurasi sistem sing disebarake.
Konfigurasi kasebut dituduhake langsung ing basa Scala kanthi cara sing aman. Conto implementasine diterangake kanthi rinci. Macem-macem aspèk proposal sing dibahas, kalebu pengaruh ing proses pembangunan sakabèhé.

Konfigurasi compilable saka sistem mbagekke

(ing basa Rusia)

Pambuka

Mbangun sistem distribusi sing kuat mbutuhake panggunaan konfigurasi sing bener lan koheren ing kabeh simpul. Solusi sing khas yaiku nggunakake deskripsi penyebaran teks (terraform, ansible utawa sing padha) lan file konfigurasi kanthi otomatis (asring - khusus kanggo saben simpul / peran). Kita uga pengin nggunakake protokol sing padha karo versi sing padha ing saben simpul komunikasi (yen ora, kita bakal ngalami masalah incompatibility). Ing jagad JVM iki tegese paling ora perpustakaan olahpesen kudu dadi versi sing padha ing kabeh simpul komunikasi.

Kepiye babagan nguji sistem? Mesthi, kita kudu duwe tes unit kanggo kabeh komponen sadurunge teka menyang tes integrasi. Supaya bisa ngekstrapolasi asil tes ing runtime, kita kudu nggawe manawa versi kabeh perpustakaan tetep identik ing lingkungan runtime lan testing.

Nalika mbukak tes integrasi, asring luwih gampang duwe classpath sing padha ing kabeh simpul. Kita mung kudu nggawe manawa classpath padha digunakake ing panyebaran. (Sampeyan bisa nggunakake classpaths beda ing kelenjar beda, nanging luwih angel kanggo makili konfigurasi iki lan bener nyebarke.) Supaya supaya iku prasaja kita mung bakal nimbang classpaths podho rupo ing kabeh kelenjar.

Konfigurasi cenderung berkembang bebarengan karo piranti lunak. Kita biasane nggunakake versi kanggo ngenali macem-macem
tahapan evolusi piranti lunak. Iku misale jek cukup kanggo nutupi konfigurasi ing manajemen versi lan ngenali konfigurasi beda karo sawetara label. Yen mung ana siji konfigurasi ing produksi, kita bisa nggunakake versi siji minangka pengenal. Kadhangkala kita duwe sawetara lingkungan produksi. Lan kanggo saben lingkungan kita mbutuhake cabang konfigurasi sing kapisah. Dadi konfigurasi bisa diwenehi label karo cabang lan versi kanggo ngenali konfigurasi sing beda-beda. Saben label cabang lan versi cocog karo kombinasi siji saka simpul mbagekke, bandar, sumber external, versi perpustakaan classpath ing saben simpul. Ing kene kita mung bakal nutupi cabang siji lan ngenali konfigurasi kanthi versi desimal telung komponen (1.2.3), kanthi cara sing padha karo artefak liyane.

Ing lingkungan modern, file konfigurasi ora diowahi kanthi manual maneh. Biasane kita ngasilake
file config ing wektu panyebaran lan aja ndemek wong-wong mau sakwise. Dadi siji bisa takon kenapa kita isih nggunakake format teks kanggo file konfigurasi? Pilihan sing bisa ditindakake yaiku nyelehake konfigurasi ing unit kompilasi lan entuk manfaat saka validasi konfigurasi wektu kompilasi.

Ing kirim iki, kita bakal nliti ide kanggo njaga konfigurasi ing artefak sing dikompilasi.

Konfigurasi sing bisa dikompilasi

Ing bagean iki kita bakal ngrembug conto konfigurasi statis. Loro layanan prasaja - layanan gema lan klien layanan gema lagi dikonfigurasi lan dileksanakake. Banjur rong sistem distribusi sing beda karo loro layanan kasebut instantiated. Siji kanggo konfigurasi simpul siji lan siji kanggo konfigurasi rong simpul.

Sistem distribusi khas kasusun saka sawetara simpul. Node bisa diidentifikasi nggunakake sawetara jinis:

sealed trait NodeId
case object Backend extends NodeId
case object Frontend extends NodeId

utawa mung

case class NodeId(hostName: String)

utawa malah

object Singleton
type NodeId = Singleton.type

Node iki nindakake macem-macem peran, mbukak sawetara layanan lan kudu bisa komunikasi karo node liyane kanthi sambungan TCP/HTTP.

Kanggo sambungan TCP paling sethithik nomer port dibutuhake. Kita uga pengin nggawe manawa klien lan server ngomongake protokol sing padha. Kanggo model sambungan antarane simpul, ayo wara-wara kelas ing ngisor iki:

case class TcpEndPoint[Protocol](node: NodeId, port: Port[Protocol])

ngendi Port iku mung Int ing kisaran sing diijini:

type PortNumber = Refined[Int, Closed[_0, W.`65535`.T]]

Jinis sing ditapis

Waca olahan perpustakaan. Singkat, ngidini kanggo nambah watesan wektu kompilasi kanggo jinis liyane. Ing kasus iki Int mung diijini duwe nilai 16-bit sing bisa makili nomer port. Ora ana syarat kanggo nggunakake perpustakaan iki kanggo pendekatan konfigurasi iki. Iku mung katon pas banget.

Kanggo HTTP (REST) ​​​​kita uga mbutuhake jalur layanan:

type UrlPathPrefix = Refined[String, MatchesRegex[W.`"[a-zA-Z_0-9/]*"`.T]]
case class PortWithPrefix[Protocol](portNumber: PortNumber, pathPrefix: UrlPathPrefix)

Tipe Phantom

Kanggo ngenali protokol sajrone kompilasi, kita nggunakake fitur Scala kanggo nyatakake argumen jinis Protocol sing ora digunakake ing kelas. Iku sing diarani jinis phantom. Nalika runtime kita arang mbutuhake conto pengenal protokol, mulane ora disimpen. Sajrone kompilasi, jinis phantom iki menehi safety jinis tambahan. Kita ora bisa ngliwati port kanthi protokol sing salah.

Salah sawijining protokol sing paling akeh digunakake yaiku REST API kanthi serialisasi Json:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

ngendi RequestMessage minangka jinis pesen dhasar sing bisa dikirim klien menyang server lan ResponseMessage yaiku pesen respon saka server. Mesthi wae, kita bisa nggawe deskripsi protokol liyane sing nemtokake protokol komunikasi kanthi presisi sing dikarepake.

Kanggo tujuan kirim iki, kita bakal nggunakake versi protokol sing luwih gampang:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Ing protokol iki, pesen panyuwunan ditambahake menyang url lan pesen respon bali minangka string kosong.

Konfigurasi layanan bisa diterangake kanthi jeneng layanan, koleksi port lan sawetara dependensi. Ana sawetara cara kanggo makili kabeh unsur kasebut ing Scala (contone, HList, jinis data aljabar). Kanggo tujuan kirim iki, kita bakal nggunakake Pola Kue lan makili potongan (modul) sing bisa digabung minangka sifat. (Pola Kue ora dadi syarat kanggo pendekatan konfigurasi sing bisa dikompilasi iki. Iku mung siji implementasine ide kasebut.)

Dependensi bisa diwakili nggunakake Pola Kue minangka titik pungkasan saka simpul liyane:

  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)
  }

Layanan echo mung mbutuhake port sing dikonfigurasi. Lan kita nyatakake yen port iki ndhukung protokol echo. Elinga yen kita ora perlu nemtokake port tartamtu ing wektu iki, amarga sifat ngidini deklarasi metode abstrak. Yen kita nggunakake cara abstrak, compiler bakal mbutuhake implementasine ing conto konfigurasi. Ing kene kita wis nyedhiyakake implementasine (8081) lan bakal digunakake minangka nilai standar yen kita ngliwati konfigurasi konkrit.

Kita bisa nyatakake ketergantungan ing konfigurasi klien layanan gema:

  trait EchoClientConfig[A] {
    def testMessage: String = "test"
    def pollInterval: FiniteDuration
    def echoServiceDependency: HttpSimpleGetEndPoint[_, EchoProtocol[A]]
  }

Ketergantungan nduweni jinis sing padha karo echoService. Utamane, mbutuhake protokol sing padha. Mula, kita bisa yakin yen kita nyambungake loro dependensi kasebut bakal bisa digunakake kanthi bener.

Implementasi layanan

Layanan mbutuhake fungsi kanggo miwiti lan mateni kanthi apik. (Kemampuan kanggo mateni layanan kritis kanggo testing.) Maneh ana sawetara opsi kanggo nemtokake fungsi kuwi kanggo config tartamtu (Contone, kita bisa nggunakake kelas jinis). Kanggo kirim iki kita bakal nggunakake Pola Kue maneh. Kita bisa makili layanan nggunakake cats.Resource sing wis nyedhiyakake bracketing lan release sumber. Kanggo entuk sumber daya, kita kudu menehi konfigurasi lan sawetara konteks runtime. Dadi fungsi wiwitan layanan bisa uga katon kaya:

  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]
  }

ngendi

  • Config - jinis konfigurasi sing dibutuhake dening wiwitan layanan iki
  • AddressResolver - obyek runtime sing nduweni kemampuan kanggo njupuk alamat nyata saka kelenjar liyane (terus maca kanggo rincian).

jinis liyane teka saka cats:

  • F[_] - jinis efek (Ing kasus paling gampang F[A] bisa wae () => A. Ing kirim iki kita bakal nggunakake cats.IO.)
  • Reader[A,B] - luwih utawa kurang sinonim kanggo fungsi A => B
  • cats.Resource - duwe cara kanggo ndarbeni lan ngeculake
  • Timer - ngidini kanggo turu / ngukur wektu
  • ContextShift - analog saka ExecutionContext
  • Applicative - pambungkus fungsi ing efek (meh monad) (kita bisa uga ngganti karo liyane)

Nggunakake antarmuka iki kita bisa ngleksanakake sawetara layanan. Contone, layanan sing ora nindakake apa-apa:

  trait ZeroServiceImpl[F[_]] extends ServiceImpl[F] {
    type Config <: Any
    def resource(...): ResourceReader[F, Config, Unit] =
      Reader(_ => Resource.pure[F, Unit](()))
  }

(Waca Sumber kode kanggo implementasi layanan liyane - layanan kumandhang,
klien echo lan pengontrol umur.)

Node minangka obyek tunggal sing mbukak sawetara layanan (miwiti sumber daya diaktifake dening Pola Kue):

object SingleNodeImpl extends ZeroServiceImpl[IO]
  with EchoServiceService
  with EchoClientService
  with FiniteDurationLifecycleServiceImpl
{
  type Config = EchoConfig[String] with EchoClientConfig[String] with FiniteDurationLifecycleConfig
}

Elinga yen ing simpul kita nemtokake jinis konfigurasi sing tepat sing dibutuhake dening simpul iki. Compiler ora ngidini kita mbangun obyek (Kue) kanthi jinis sing ora cukup, amarga saben sipat layanan nyatakake kendala ing Config jinis. Uga, kita ora bakal bisa miwiti simpul tanpa menehi konfigurasi lengkap.

Resolusi alamat simpul

Kanggo nggawe sambungan, kita butuh alamat host nyata kanggo saben simpul. Bisa uga dikenal luwih cepet tinimbang bagean konfigurasi liyane. Mula, kita butuh cara kanggo nyedhiyakake pemetaan antarane id simpul lan alamat asline. Pemetaan iki minangka fungsi:

case class NodeAddress[NodeId](host: Uri.Host)
trait AddressResolver[F[_]] {
  def resolve[NodeId](nodeId: NodeId): F[NodeAddress[NodeId]]
}

Ana sawetara cara sing bisa ditindakake kanggo ngetrapake fungsi kasebut.

  1. Yen kita ngerti alamat nyata sadurunge panyebaran, sajrone instantiation host simpul, mula kita bisa ngasilake kode Scala kanthi alamat sing nyata lan mbukak bangunan kasebut (sing nindakake pamriksa wektu kompilasi lan banjur mbukak suite test integrasi). Ing kasus iki fungsi pemetaan kita dikenal statis lan bisa simplified kanggo kaya a Map[NodeId, NodeAddress].
  2. Kadhangkala kita entuk alamat sing nyata mung ing wektu sabanjure nalika simpul kasebut bener-bener diwiwiti, utawa kita ora duwe alamat simpul sing durung diwiwiti. Ing kasus iki, kita bisa uga duwe layanan panemuan sing diwiwiti sadurunge kabeh simpul liyane lan saben simpul bisa ngiklanake alamat kasebut ing layanan kasebut lan langganan dependensi.
  3. Yen kita bisa ngowahi /etc/hosts, kita bisa nggunakake jeneng host sing wis ditemtokake (kaya my-project-main-node lan echo-backend) lan mung nggandhengake jeneng iki karo alamat ip ing wektu panyebaran.

Ing kirim iki, kita ora nutupi kasus kasebut kanthi luwih rinci. Nyatane ing conto dolanan kita kabeh simpul bakal duwe alamat IP sing padha - 127.0.0.1.

Ing kirim iki, kita bakal nimbang rong tata letak sistem sing disebarake:

  1. Tata letak simpul tunggal, ing ngendi kabeh layanan diselehake ing simpul tunggal.
  2. Tata letak loro simpul, ing ngendi layanan lan klien ana ing simpul sing beda.

Konfigurasi kanggo a simpul tunggal tata letak minangka nderek:

Konfigurasi simpul tunggal

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.
}

Ing kene kita nggawe konfigurasi siji sing ngluwihi konfigurasi server lan klien. Uga kita ngatur pengontrol siklus urip sing biasane bakal mungkasi klien lan server sawise lifetime interval liwat.

Set implementasi lan konfigurasi layanan sing padha bisa digunakake kanggo nggawe tata letak sistem kanthi rong node sing kapisah. Kita mung kudu nggawe rong konfigurasi simpul sing kapisah kanthi layanan sing cocog:

Konfigurasi rong node

  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"
  }

Waca carane kita nemtokake ketergantungan. Kita nyebutake layanan sing diwenehake simpul liyane minangka ketergantungan saka simpul saiki. Jinis dependensi dicenthang amarga ngemot jinis phantom sing nggambarake protokol. Lan nalika runtime kita bakal duwe id simpul sing bener. Iki minangka salah sawijining aspek penting saka pendekatan konfigurasi sing diusulake. Iku menehi kita kemampuan kanggo nyetel port mung sapisan lan priksa manawa kita referensi port bener.

Implementasi rong node

Kanggo konfigurasi iki, kita nggunakake implementasi layanan sing padha. Ora ana owah-owahan. Nanging, kita nggawe rong implementasi simpul beda sing ngemot set layanan sing beda:

  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
  }

Node pisanan ngleksanakake server lan mung butuh konfigurasi sisih server. Simpul kapindho ngleksanakake klien lan mbutuhake bagean konfigurasi liyane. Loro-lorone node mbutuhake sawetara spesifikasi seumur hidup. Kanggo tujuan simpul layanan kirim iki bakal duwe umur tanpa wates sing bisa diakhiri nggunakake SIGTERM, nalika klien echo bakal mungkasi sawise durasi winates sing dikonfigurasi. Waca aplikasi wiwitan gamblang.

Proses pembangunan sakabèhé

Ayo ndeleng kepiye pendekatan iki ngganti cara kita nggarap konfigurasi.

Konfigurasi minangka kode bakal dikompilasi lan ngasilake artefak. Iku misale jek cukup kanggo misahake artefak konfigurasi saka artefak kode liyane. Asring kita bisa duwe akeh konfigurasi ing basis kode sing padha. Lan mesthi, kita bisa duwe sawetara versi saka macem-macem cabang konfigurasi. Ing konfigurasi, kita bisa milih versi perpustakaan tartamtu lan iki bakal tetep konstan kapan wae kita masang konfigurasi iki.

Owah-owahan konfigurasi dadi owah-owahan kode. Dadi kudu dilindhungi dening proses jaminan kualitas sing padha:

Tiket -> PR -> review -> gabung -> Integrasi terus -> penyebaran terus

Ana akibat saka pendekatan kasebut:

  1. Konfigurasi koheren kanggo conto sistem tartamtu. Iku misale jek sing ora ana cara kanggo duwe sambungan salah antarane simpul.
  2. Ora gampang ngganti konfigurasi mung ing siji simpul. Kayane ora masuk akal kanggo mlebu lan ngganti sawetara file teks. Dadi konfigurasi drift dadi kurang bisa.
  3. Owah-owahan konfigurasi cilik ora gampang ditindakake.
  4. Umume owah-owahan konfigurasi bakal ngetutake proses pangembangan sing padha, lan bakal ditinjau.

Apa kita butuh gudang sing kapisah kanggo konfigurasi produksi? Konfigurasi produksi bisa uga ngemot informasi sensitif sing pengin dijaluk saka akeh wong. Dadi bisa uga kudu nyimpen repositori sing kapisah kanthi akses sing diwatesi sing bakal ngemot konfigurasi produksi. Kita bisa mbagi konfigurasi dadi rong bagean - siji sing ngemot parameter produksi sing paling mbukak lan siji sing ngemot bagean rahasia konfigurasi. Iki bakal ngaktifake akses menyang umume pangembang menyang mayoritas paramèter nalika mbatesi akses menyang barang sing sensitif banget. Iku gampang kanggo ngrampungake iki nggunakake sipat penengah karo nilai parameter standar.

Variasi

Ayo ndeleng pro lan kontra saka pendekatan sing diusulake dibandhingake karo teknik manajemen konfigurasi liyane.

Kaping pisanan, kita bakal dhaptar sawetara alternatif kanggo macem-macem aspek saka cara sing diusulake kanggo ngatur konfigurasi:

  1. File teks ing mesin target.
  2. Panyimpenan nilai kunci terpusat (kayata etcd/zookeeper).
  3. Komponen subproses sing bisa dikonfigurasi ulang / diwiwiti maneh tanpa miwiti maneh proses.
  4. Konfigurasi njaba artefak lan kontrol versi.

File teks menehi sawetara keluwesan babagan perbaikan ad-hoc. Administrator sistem bisa mlebu menyang simpul target, ngganti lan miwiti maneh layanan kasebut. Iki bisa uga ora apik kanggo sistem sing luwih gedhe. Ora ana jejak sing ditinggal owah-owahan. Owah-owahan ora dideleng dening sepasang mata liyane. Bisa uga angel ngerteni apa sing nyebabake owah-owahan kasebut. Iku durung dites. Saka perspektif sistem sing disebarake, administrator mung bisa lali nganyari konfigurasi ing salah sawijining simpul liyane.

(Btw, yen pungkasane kudu miwiti nggunakake file konfigurasi teks, kita mung kudu nambah parser + validator sing bisa ngasilake sing padha. Config ketik lan bakal cukup kanggo miwiti nggunakake konfigurasi teks. Iki uga nuduhake yen kerumitan konfigurasi wektu kompilasi luwih cilik tinimbang kerumitan konfigurasi basis teks, amarga ing versi basis teks kita butuh sawetara kode tambahan.)

Panyimpenan nilai kunci terpusat minangka mekanisme sing apik kanggo nyebarake paramèter meta aplikasi. Ing kene kita kudu mikir babagan apa sing dianggep minangka nilai konfigurasi lan mung data. Diwenehi fungsi C => A => B kita biasane nelpon arang ngganti nilai C "konfigurasi", nalika data asring diganti A - mung input data. Konfigurasi kudu diwenehake menyang fungsi luwih awal tinimbang data A. Given idea iki kita bisa ngomong sing samesthine frekuensi owah-owahan apa bisa digunakake kanggo mbedakake data konfigurasi saka mung data. Uga data biasane asale saka siji sumber (user) lan konfigurasi asale saka sumber liyane (admin). Nangani paramèter sing bisa diganti sawise proses initialization ndadékaké kanggo nambah kerumitan aplikasi. Kanggo paramèter kasebut, kita kudu nangani mekanisme pangiriman, parsing lan validasi, nangani nilai sing salah. Mula, kanggo nyuda kerumitan program, luwih becik kita nyuda jumlah parameter sing bisa diganti nalika runtime (utawa malah ngilangi kabeh).

Saka perspektif kirim iki, kita kudu mbedakake antarane paramèter statis lan dinamis. Yen logika layanan mbutuhake owah-owahan langka saka sawetara paramèter ing runtime, banjur kita bisa nelpon paramèter dinamis. Yen ora, iku statis lan bisa dikonfigurasi nggunakake pendekatan sing diusulake. Kanggo konfigurasi ulang dinamis, pendekatan liyane bisa uga dibutuhake. Contone, bagean saka sistem bisa diwiwiti maneh karo paramèter konfigurasi anyar kanthi cara sing padha kanggo miwiti maneh proses kapisah saka sistem sing disebarake.
(Mratelakake panemume sing sederhana yaiku supaya ora konfigurasi ulang runtime amarga nambah kerumitan sistem.
Bisa uga luwih gampang mung ngandelake dhukungan OS kanggo miwiti maneh proses. Sanadyan, bisa uga ora mesthi bisa.)

Salah siji aspek penting nggunakake konfigurasi statis sing kadhangkala ndadekake wong nganggep konfigurasi dinamis (tanpa alasan liyane) yaiku downtime layanan sajrone nganyari konfigurasi. Pancen, yen kita kudu ngowahi konfigurasi statis, kita kudu miwiti maneh sistem supaya nilai anyar dadi efektif. Syarat kanggo downtime beda-beda kanggo sistem sing beda-beda, saengga bisa uga ora kritis. Yen kritis, mula kita kudu ngrancang luwih dhisik kanggo miwiti maneh sistem. Contone, kita bisa ngleksanakake AWS ELB sambungan draining. Ing skenario iki saben kita kudu miwiti maneh sistem, kita miwiti Kayata anyar saka sistem ing podo karo, banjur ngalih ELB menyang, nalika ngidini sistem lawas kanggo ngrampungake layanan sambungan ana.

Apa babagan tetep konfigurasi ing artefak versi utawa ing njaba? Tetep konfigurasi ing artefak tegese ing umume kasus konfigurasi iki wis ngliwati proses jaminan kualitas sing padha karo artefak liyane. Dadi siji bisa uga yakin manawa konfigurasi kasebut kualitas apik lan bisa dipercaya. Kosok baline, konfigurasi ing file sing kapisah tegese ora ana jejak sapa lan apa sing nggawe owah-owahan ing file kasebut. Apa iki penting? Kita yakin manawa kanggo umume sistem produksi, luwih becik duwe konfigurasi sing stabil lan berkualitas.

Versi artefak kasebut ngidini sampeyan ngerteni nalika digawe, apa isine, fitur apa sing diaktifake / dipateni, sing tanggung jawab kanggo nggawe saben owah-owahan ing konfigurasi. Bisa uga mbutuhake upaya kanggo njaga konfigurasi ing artefak lan minangka pilihan desain sing kudu ditindakake.

Pro & kontra

Ing kene kita pengin nyorot sawetara kaluwihan lan ngrembug sawetara kekurangan saka pendekatan sing diusulake.

Kaluwihan

Fitur konfigurasi kompilasi sistem distribusi lengkap:

  1. Priksa konfigurasi statis. Iki menehi tingkat dhuwur saka kapercayan, sing konfigurasi bener diwenehi jinis alangan.
  2. Basa sugih konfigurasi. Biasane pendekatan konfigurasi liyane diwatesi ing substitusi variabel.
    Nggunakake Scala siji bisa nggunakake sawetara saka sudhut fitur basa kanggo nggawe konfigurasi luwih. Kayata, kita bisa nggunakake sipat kanggo nyedhiyani nilai gawan, obyek kanggo nyetel orane katrangan beda, kita bisa deleng vals ditetepake mung sapisan ing orane katrangan njaba (GARING). Sampeyan bisa nggunakake urutan literal, utawa conto saka kelas tartamtu (Seq, Map, Dll).
  3. DSL. Scala duwe dhukungan sing cocog kanggo panulis DSL. Siji bisa nggunakake fitur iki kanggo netepake basa konfigurasi sing luwih trep lan pangguna pungkasan, supaya konfigurasi final paling ora bisa diwaca dening pangguna domain.
  4. Integritas lan koherensi antarane simpul. Salah sawijining keuntungan duwe konfigurasi kanggo kabeh sistem sing disebarake ing sak panggonan yaiku kabeh nilai ditetepake kanthi ketat sapisan lan banjur digunakake maneh ing kabeh papan sing kita butuhake. Ketik uga deklarasi port aman mesthekake yen ing kabeh konfigurasi sing bener, simpul sistem bakal nganggo basa sing padha. Ana dependensi sing jelas ing antarane simpul sing nggawe angel lali nyedhiyakake sawetara layanan.
  5. Kualitas dhuwur saka owah-owahan. Pendekatan sakabèhé kanggo ngganti konfigurasi liwat proses PR normal netepake standar kualitas dhuwur uga ing konfigurasi.
  6. Owah-owahan konfigurasi simultaneous. Saben kita nggawe owah-owahan ing konfigurasi penyebaran otomatis mesthekake yen kabeh kelenjar dianyari.
  7. Simplifikasi aplikasi. Aplikasi ora perlu ngurai lan validasi konfigurasi lan nangani nilai konfigurasi sing salah. Iki nyederhanakake aplikasi sakabèhé. (Sawetara Tambah kerumitan ing konfigurasi dhewe, nanging iku sadar trade-off menyang safety.) Iku cukup suta bali menyang konfigurasi biasa - mung nambah bêsik ilang. Iku luwih gampang kanggo miwiti karo konfigurasi nyawiji lan nundha implementasine saka bêsik tambahan kanggo sawetara wektu mengko.
  8. Konfigurasi versi. Amarga kasunyatan manawa owah-owahan konfigurasi ngetutake proses pangembangan sing padha, minangka asil kita entuk artefak kanthi versi unik. Iki ngidini kita ngalih konfigurasi maneh yen perlu. Kita malah bisa masang konfigurasi sing digunakake setahun kepungkur lan bakal bisa digunakake kanthi cara sing padha. Konfigurasi stabil nambah prediksi lan linuwih saka sistem sing disebarake. Konfigurasi tetep ing wektu kompilasi lan ora bisa gampang dirusak ing sistem produksi.
  9. Modularitas. Rangka kerja sing diusulake yaiku modular lan modul bisa digabungake kanthi macem-macem cara
    ndhukung konfigurasi beda (persiyapan / tata letak). Utamane, sampeyan bisa duwe tata letak simpul tunggal skala cilik lan setelan multi node skala gedhe. Iku cukup kanggo duwe sawetara tata produksi.
  10. Testing. Kanggo tujuan testing siji bisa ngleksanakake layanan mock lan nggunakake minangka ketergantungan ing jinis cara aman. A sawetara tata letak testing beda karo macem-macem bagean diganti dening mock bisa maintained bebarengan.
  11. Pengujian Integrasi. Kadhangkala ing sistem sing disebarake angel kanggo nganakake tes integrasi. Nggunakake pendekatan diterangake kanggo ngetik konfigurasi aman saka sistem mbagekke lengkap, kita bisa mbukak kabeh bagean mbagekke ing server siji ing cara controllable. Iku gampang kanggo niru kahanan
    nalika salah sawijining layanan ora kasedhiya.

cacat

Pendekatan konfigurasi sing dikompilasi beda karo konfigurasi "normal" lan bisa uga ora cocog karo kabeh kabutuhan. Mangkene sawetara kekurangan konfigurasi sing dikompilasi:

  1. Konfigurasi statis. Bisa uga ora cocog kanggo kabeh aplikasi. Ing sawetara kasus ana perlu kanggo cepet ndandani konfigurasi ing produksi bypassing kabeh langkah safety. Pendekatan iki ndadekake luwih angel. Kompilasi lan redeployment dibutuhake sawise nggawe owah-owahan ing konfigurasi. Iki minangka fitur lan beban.
  2. Generasi konfigurasi. Nalika config digawe dening sawetara alat otomatis, pendekatan iki mbutuhake kompilasi sakteruse (sing bisa uga gagal). Bisa uga mbutuhake upaya tambahan kanggo nggabungake langkah tambahan iki menyang sistem mbangun.
  3. Instrumen. Ana akeh alat sing digunakake saiki sing gumantung ing konfigurasi adhedhasar teks. Sawetara wong
    ora bakal ditrapake nalika konfigurasi dikompilasi.
  4. A shift ing pola pikir dibutuhake. Pangembang lan DevOps kenal karo file konfigurasi teks. Gagasan nyusun konfigurasi bisa uga katon aneh kanggo dheweke.
  5. Sadurunge ngenalake konfigurasi kompilasi, proses pangembangan piranti lunak berkualitas tinggi dibutuhake.

Ana sawetara watesan saka conto sing ditindakake:

  1. Yen kita nyedhiyakake konfigurasi ekstra sing ora dituntut dening implementasi simpul, kompiler ora bakal mbantu ndeteksi implementasine sing ora ana. Iki bisa ditanggulangi kanthi nggunakake HList utawa ADTs (kelas cilik) kanggo konfigurasi simpul tinimbang sipat lan Pola Cake.
  2. Kita kudu nyedhiyani sawetara boilerplate ing file config: (package, import, object pranyatan;
    override def's kanggo paramèter sing nduweni nilai standar). Iki bisa uga diatasi kanthi nggunakake DSL.
  3. Ing kirim iki kita ora nutupi reconfiguration dinamis kluster saka kelenjar padha.

kesimpulan

Ing kirim iki, kita wis ngrembug ide kanggo makili konfigurasi langsung ing kode sumber kanthi cara sing aman. Pendekatan kasebut bisa digunakake ing akeh aplikasi minangka panggantos kanggo xml- lan konfigurasi basis teks liyane. Sanajan conto kita wis dileksanakake ing Scala, bisa uga diterjemahake menyang basa kompilasi liyane (kayata Kotlin, C #, Swift, lsp.). Siji bisa nyoba pendekatan iki ing proyek anyar lan, yen ora pas, pindhah menyang cara lawas.

Mesthi, konfigurasi compilable mbutuhake proses pembangunan kualitas dhuwur. Ing bali iku janji bakal nyedhiyani konfigurasi kuat kualitas merata dhuwur.

Pendekatan iki bisa ditambah kanthi macem-macem cara:

  1. Siji bisa nggunakake makro kanggo nindakake validasi konfigurasi lan gagal ing wektu kompilasi yen ana kegagalan alangan logika bisnis.
  2. DSL bisa dileksanakake kanggo makili konfigurasi kanthi cara sing ramah pangguna domain.
  3. Manajemen sumber daya dinamis kanthi pangaturan konfigurasi otomatis. Contone, nalika kita nyetel jumlah kelenjar kluster kita bisa uga pengin (1) kelenjar diwenehi konfigurasi rada dipunéwahi; (2) manager cluster kanggo nampa info simpul anyar.

Thanks

Aku arep ngucapake matur nuwun kanggo Andrey Saksonov, Pavel Popov, Anton Nehaev amarga menehi saran inspirasi babagan rancangan kiriman iki sing mbantu aku nggawe luwih jelas.

Source: www.habr.com