Compilable configuration of a distributed system

У дадзеным пакоі як share interesting way of dealing with configuration of distributed system.
Наладка лічыцца наўпроста ў шкале англійскай мовы ў тыпу абароненага manner. An example implementation is described in details. У розных аспектах падобных думак з'яўляецца размова, у тым ліку influence on overall development process.

Compilable configuration of a distributed system

(на рускай)

Увядзенне

Прадпрыемствы строгай сістэмнай сістэмы неабходныя для выкарыстання значных і раўнапраўных configuration on all nodes. А тыповая рашучасць выкарыстоўваецца як тэкставая аптымізацыя (terraform, ansible or something alike) і аўтаматычна генеравана configuration files (often – выкарыстоўваецца для ўсіх node / role). Для таго, каб выкарыстоўваць тыя ж тэмы, аналагічныя нябесныя аналітыкі (іншыя мерапрыемствы не маюць згоды). У свеце JVM гэтыя метады, якія на захмараную library павінны быць падобныя на ўсе камунікацыйныя nodes.

What about testing the system? З ходу, мы павінны мець цэласныя тэсты для ўсіх элементаў перад прыняццем тэставання. Каб быць здольным да extrapolate tests results on runtime, we hould make sure that the versions of all libraries are kept identical in both runtime and testing environments.

Калі кіруюцца integration tests, гэта павінна быць вельмі напампаваць, каб мець падобныя класы на ўсе nodes. Для таго, каб патрабаваць, яна выкарыстоўвае тое ж самае classpath, якое выкарыстоўваецца для распрацоўкі. (It is possible to use different classpaths on different nodes, but it's more difficult to represent this configuration and correctly deploy it.) Так, як мяркуюць, каб сыграць things simple will by толькі consider identical classpaths on all nodes.

Configuration tends to evolve together with software. We usually use versions для identify various
stages of software evolution. It seems reasonable to cover configuration under version management and identify different configurations with some labels. Калі гэта толькі адна configuration in production, мы можам выкарыстоўвацца толькі версія як identifier. Sometimes we may have multiple production environments. І для іх навакольнага асяроддзя мы павінны патрабаваць адрознівага палачка configuration. З configurations можа быць арганізаваны з парадкам і версіяй для асаблівых identify different configurations. Яе дзяўчаты літар і версій адпавядаюць той ці адзінай combination of distributed nodes, ports, externes resources, classpath library versions on each node. Тут можна толькі пачаць цэлую дзялянку і identify configurations з трох кампанентаў сярэдняй версіі (1.2.3), у той жа час як іншыя матэрыялы.

У сучасных умовах configuration files не змяняецца manually anymore. Typically we generate
config files at deployment time and never touch them afterwards. З якою можна выканаць той спосаб, які час будзе выкарыстоўвацца тэкстам format для configuration files? Апаратная функцыя з'яўляецца месцам configuration uvnitř compilation unit and benefit from compile-time configuration validation.

У гэтым артыкуле мы павінны вызначыць думкі, якія выкарыстоўваюць configuration in compiled artifact.

Compilable configuration

У гэтым секцыі будуць разважаць пра выпадак статыстычнай configuration. Два simple service — echo service and the client of the echo service is being configured and implemented. Там дзве розныя distributed systems with both services are instantiated. Адзін з'яўляецца для аднаго модуля configuration і адзін аднаго для двух модуляў configuration.

А тыповая расшыраная сістэма складаецца з некалькіх nodes. The nodes could be identified using some type:

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

ці проста

case class NodeId(hostName: String)

ці нават

object Singleton
type NodeId = Singleton.type

Гэтыя цытаты дазваляюць розныя роллы, выконваюць некаторыя паслугі і павінны быць у камунікацыі з іншымі цытаты з дапамогай TCP/HTTP connections.

Для падключэння TCP да канца да нумераванага нумара неабходна. У тым ліку імкнучыся падумаць, што кліент і сервер размаўляюць той жа самы protocol. У агуле на model a connection between nodes let's declare the following class:

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

дзе Port гэта проста Int within the allowed range:

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

Refined types

паглядзець рафінаваны library. У шорты, гэта дае магчымасць палепшыць час забарона на іншыя тыпы. У гэтым выпадку Int is only allowed to have 16-bit values ​​that can represent port number. Там не патрабуецца гэта library для гэтага configuration approach. Гэта вельмі добра для вельмі добра.

Для HTTP (REST) ​​мы павінны таксама патрабаваць паслугі:

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

Phantom type

У ацэнцы identify protocol during compilation we are using the Scala feature of declaring type argument Protocol that is not used ў класе. It's a so called phantom type. У бягучым часе неабходна патрэба ў паняцці protocol identifier, што з'яўляецца тым, што гэта. Пры складанні гэтага тыпу тыпу дазваляе іншы тып аховы. Мы не маем патрэбы парта з беспаспяховай protocol.

Адзін з самых распаўсюджаных спосабаў protocols з'яўляецца REST API with Json serialization:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

дзе RequestMessage з'яўляецца тыпам тыпу паведамленняў, што кліент можа выдаваць на server and ResponseMessage is the response message ад server. З курсу, мы можам стварыць іншыя protocol descriptions што specify the communication protocol with desired precision.

Для значэнняў гэтай працы мы павінны выкарыстоўваць прамую версію protocol:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

У гэтым протоколе запатрабавання электроннай пошты змешчаны ў URL і зыходны ліст з'яўляецца адмененым у памеры string.

Настройка службы можа быць прызначана на сэрвісе імя, зборка портаў і некаторыя dependencies. Там ёсць некалькі магчымых спосабаў, каб паказаць, што ўсе гэтыя элементы ў Scala (for instance, HList, algebraic data types). Для прыналежнасцяў для гэтага сайта мы павінны выкарыстоўваць Cake Pattern і прадстаўляюць злучальныя ручкі (модулі) як кошты. (Cake Pattern не патрабуецца для гэтай compilable configuration approach. Гэта толькі адна магчымасць ажыццяўлення.)

Адлегласці могуць быць прадстаўлены з дапамогай тлушчу pattern jako body of nodes:

  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 service толькі патрабуе port configured. І мы мяркуем, што гэты port supports echo protocol. Памятаеце, што не трэба патрабаваць да асаблівага партыя на гэты момант, таму што вар'яцтвы дазваляюць абстрактныя метады размовы. Калі мы выкарыстоўваем абстрактныя метады, compiler будзе патрабуецца implementation in configuration instance. Цяпер мы павінны рэалізаваць (8081) and it will be used as default value if we skip it in a concrete configuration.

Мы можам заўважыць залежнасць у configuration of echo service client:

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

Адначаснасць мае той самы тып як echoService. У прыватнасці, гэта неабходнасць таго ж protocol. Hence, мы можам падумаць, што калі connect these two dependencies they will work correctly.

Services implementation

Для абслугоўвання неабходна функцыю start and gracefully shutdown. (Якасць, што плацёжная служба з'яўляецца глыбокім для тэсціравання.) У той жа час ёсць некалькі функцый, якія мяркуюць такую ​​функцыю для данага config (для гасціннасці, мы можам выкарыстоўваць тыпы лекцый). Для гэтага сайта мы павінны выкарыстоўваць Cake Pattern. We can represent a service using cats.Resource якія already provides bracketing and resource release. У адпаведнасці з патрабаваннямі крыніцы мы павінны выканаць configuration and some runtime context. З мэтай ажыццяўлення функцый можа быць так:

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

дзе

  • Config - type of configuration that is required by this service starter
  • AddressResolver - Runtime object, які мае ёмістасць, каб атрымаць рэальныя адрасы іншых дзвярэй (keep reading for details).

the other types comes from cats:

  • F[_] - effect type (У simplest case F[A] could be just () => A. In this post we'll use cats.IO.)
  • Reader[A,B] is more or less a synonym for a function A => B
  • cats.Resource - has ways to acquire and release
  • Timer - allows to sleep/measure time
  • ContextShift - analog of ExecutionContext
  • Applicative — знішчэнне функцый у эфекты (у большасці monad)

З дапамогай гэтай сувязі мы можам зрабіць некалькі паслуг. Для таго, каб абслугоўваць, што няма:

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

(Гл Зыходны код for other services implementations - echo service,
echo client і lifetime controllers.)

Node - гэта цэлы прадмет, які выконвае некалькі паслуг (запамяняецца шын рэсурсаў, адпраўляецца па пачці Pattern):

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

Адчувае, што ў node ме specify тыпу тыпу configuration, які не патрабуецца да гэтага node. Запампоўнік не мае патрэбы ў будынку object (cake) with insufficient type, because each service trait declares a constraint on the Config тып. Also we won't be able to start node without providing complete configuration.

Node address resolution

У адпаведнасці з усталяваннем для падачы электроннай пошты патрэбныя рэальныя адрасы электроннай пошты для кожнага нумара. Гэта можа быць вядомым апошнім часам, чым іншыя элементы configuration. Hence, мы павінны быць здольныя да mapping паміж node id and it's actual address. Гэта mapping з'яўляецца функцыяй:

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

Там ёсць шмат магчымасцяў для рэалізацыі так функцый.

  1. Калі мы даведаемся аб гэтым адрасах перад распараджэннем, падчас нумару hosts instantiation, яны могуць generate Scala code з actual address and run the build afterwards (which performs compile time checks and the runs integration test suite). У гэтым выпадку наша mapping function з'яўляецца статыстычна і можа быць simplified to something like a Map[NodeId, NodeAddress].
  2. Некаторые месяцы аб'екта існуючых адрасоў толькі на апошнім месцы, калі цыклы з'яўляюцца сапраўднымі, або не павінны выконваць цытаты, якія павінны быць выкананы. У дадзеным выпадку мы маем абаротную паслугу, якая лічыцца перад усімі іншымі дзяўчатамі і ўсіх дзвюх коротких advertise його address in ця служба і лічыцца залежна.
  3. If we can modify /etc/hosts, we can use predefined host names (like my-project-main-node і echo-backend) and just associate this name with ip address at deployment time.

У гэтым месцы мы не павінны пакрываць гэтыя рэчы ў больш details. In fact in our toy example all nodes will have the same IP address 127.0.0.1.

У дадзеным выпадку мы знайшлі два distributed system layouts:

  1. Адзін ланцужок гандлю, дзе ўсе паслугі знаходзяцца на адным званку.
  2. Два node layout, дзе абслугоўванне і кліента знаходзяцца на розных nodes.

The configuration for a адзіны вузел layout is as follows:

Single node configuration

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

Тут можна стварыць цэлую configuration, якая пашырае server both і client configuration. Also we configure a lifecycle controller, што будзе лічыцца normally client and server after lifetime interval passes.

Самая sada паслуг ажыццяўлення і configuration можа быць створана для сістэмы ажыццяўлення з двума незалежнымі nodes. We just need to create two separate node configs with the appropriate services:

Two nodes configuration

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

See how we specify the dependency. Мы разумеем, што іншыя node забяспечваюць паслугі, як dependency of the current node. З'яўляецца тып абароненага сыходу, які вызначаецца тыпам тыпу тыпу, які запісвае protocol. And at runtime we'll мае correct node id. Гэта адзін з важных аспектаў прапанаванага configuration approach. Гэта забяспечвае магчымасць ажыццяўлення набору толькі на адзін цыкл і робіцца, што мы адпавядаюць значным партам.

Two nodes implementation

Для гэтай configuration we use exactly the same services implementations. No changes at all. However, we create two different node implementations that contain different set of services:

  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
  }

У першую чаргу implements server and it only needs server side config. З XNUMX-га дня implements client and needs another part of config. Both nodes require некаторыя specification. Для прыналежнасці гэтага post service node will have infinite lifetime that could be terminated using SIGTERM, пры якой echo client будзе доўжыцца пасля configured finite duration. See the starter application падрабязней.

Overall development process

Давайце, як гэты спосаб змяняецца, як рабіць з configuration.

Наладка кода павінна быць compiled and produces an artifact. Існуе магчымасць выканаць асаблівую configuration artifact з іншага кода artifacts. Адной з іх можа быць шматлікая configuration on the same code base. And of course, we can have multiple versions of various configuration branches. У configuration we can select particular versions of libraries and this will remain constant whenever we deploy this configuration.

A configuration change becomes code change. So it should be covered by the same quality assurance process:

Ticket -> PR -> review -> merge -> continuous integration -> continuous deployment

There are the following consequences of the approach:

  1. configuration is coherent for a particular system's instance. Існуе, што яны не маюць ніякай патрэбы ў тым, каб не адпавядаць падтрымцы паміж nodes.
  2. Гэта не вельмі добра, каб змяніць configuration just in one node. Гэта мае няздольны да log in and change some text files. So configuration drift becomes less possible.
  3. Малыя configuration changes не могуць быць добра.
  4. Большасць configuration changes будзе выконваць тыя ж працэсы развіцця, і ён будзе прайграць некаторы review.

Як патрэбен separate рэpository для production configuration? Вытворчасць configuration мае маю адметную малітвую інфармацыю, што мы павінны здавацца, каб вырваць з усяго чалавека. Таму гэта мае вялікае абслугоўванне, каб аддзяліць адпаведнік з абмежаванай тэхнікай, што неабходна ўсталяваць наладу кіравання. Для таго, каб сфарміраваць яе ў дзве часткі — адзін, які змяшчае most open parametres of production and one, that contains the secret part of configuration. Гэтая магчымая мерапрыемства мае most developers to vast majority of parameters while restricting access to really sensitive things. Гэта вельмі зручна, каб пры гэтым з'яўляцца прамыя стаўкі з паменшанымі пунктамі ўзроўню.

Варыяцыі

Дапамажыце мадэрнізацыя тэхнікаў.

У першую чаргу, мы разгледзім некалькі альтэрнатыўных аспектаў у розных аспектах прапанаванага спосабу падзялення з configuration:

  1. Text file on the target machine.
  2. Centralized key-value storage etcd/zookeeper).
  3. Subprocess components that could be reconfigured/restarted without restarting process.
  4. Configuration outside artifact and version control.

Text файлы дазваляюць мець пэўную pružnасць у тэрмінах з-за fix. Сістэма administrator можа login для target node, выканаць змяненне і толькі restart the service. Гэта не можа быць добрым для вялікіх сістэм. Ніякія меры не перавышаюць. Змены не залежаць ад іншай часткі вачэй. Гэта можа быць вельмі цяжка, каб аднавіць тое, якое змяненне змяняецца. It has not been tested. Ад distributed system's perspective administrator можа simple forget да update the configuration in one of the other nodes.

(Btw, if eventually there will be a need to start using text config files, we'll only have to add parser + validator that could produce the same Config type and that would be enough to start using text configs. Гэта таксама ацэньвае, што цэласнасць фармацевтычнага часу з'яўляецца малым смалерам, што цэласнасць тэксту заснавана configs, таму што ў сістэмнай базавай версіі трэба некаторага дадатковага кода.)

Centralized key-value storage is good mechanism for distributing application meta parameters. Тут трэба думаць пра тое, што мы знайшлі, як configuration values ​​and what is just data. Given a function C => A => B we usually call rarely changing values C "configuration", while frequently changed data A - Just input data. Дапамогу павінны быць выкананы ў функцыі earlier than the data A. Given this idea we can say that it's expected frequency of changes what could be used to distinguish configuration data from just data. Таксама data тыпова адпраўляецца з аднаго сайта (у карыстача) і configuration з аднаго боку (адміністратар). Лічыцца з пунктамі, якія могуць змяняцца пасля працэсу пераўтварэння злучэнняў у павелічэнне application complexity. Для такіх параметраў мы павінны мець handle their delivery mechanism, parsing and validation, handling incorrect values. Hence, у адпаведнасці з рэзідэнцыяй праграмай complexity, мы павінны сціснуць нумары параметраў, якія могуць змяняцца ў ходзе (або ўвогуле знімаюцца з гэтым).

З падзеі гэтай працы мы павінны зрабіць адрозненне паміж статыстычнымі і dynamic parametres. Калі ў сістэме лагічных патрэбаў rare change of some parameters in runtime, then by me call them dynamic parameters. Otherwise маюць статыстычную і могуць быць выкарыстаны пры дапамозе approach. Для dynamic reconfiguration іншыя approaches might be needed. Для прыкладу, часткі сістэмы павінны быць адключаныя з новымі наладкамі параметраў у той жа час, каб вярнуцца да separate process of distributed system.
(Мы думкі з'яўляецца тым, што патрабуецца бягучая reconfiguration because it increases complexity of the system.
Гэта можа быць занадта вялікае для таго, каб выканаць OS для падтрымкі працэсаў restarting. Though, it might not always be possible.)

Адзін важны аспект з'яўляецца статыстычная configuration, што некаторыя людзі лічаць dynamickую configuration (без іншых рашэнняў) з'яўляецца служба нізкай працягласці configuration update. Знішчаны, калі мы павінны змяніць статыстычную configuration, мы павінны аднавіць сістэму з тым, што новы ўзровень становіцца эфектыўным. Гэтыя патрабаванні для нізкага ўзроўню для розных сістэм, таму што не могуць быць глыбокімі. If it is critical, яны павінны мець план для першай сістэмы перазагрузкі. For instance, we could implement AWS ELB connection draining. У дадзеным scénаре, калі трэба выканаць сістэму, выконваць новую схему сістэм у parallel, буде перетворено ELB на цю, коли пускається old system на загальний доступ да існуючых connections.

What about keeping configuration inside versioned artifact or outside? Keeping configuration uvnitř artifact means in most of the cases that this configuration has passed same quality assurance process as other artifacts. Таму адзін можа быць падумаць, што configuration з'яўляецца добрай якасцю і паляванне. На contrar configuration in separate file means that there no traces of who and why made changes to that file. Is this important? Вельмі важна, што для найвышэйшай сістэмы сістэмы гэта павінна быць стабільнай і высокай якасцю configuration.

Version artifact allows to find out when it was created, what values ​​it contains, what features are enabled/disabled, what was responsible for making each change in the configuration. Гэта патрэба ў пэўным сэнсе, каб выканаць функцыю ўнутранага матэрыялу і гэта дызайн майна.

Плюсы мінусы

Тут мы павінны як высокі ўзровень некаторых advantages and discusse some disadvantages of the proposed approach.

перавагі

Features of compilable configuration of complete distributed system:

  1. Static check of configuration. Гэтыя думкі з высокім узроўнем confidence, што configuration is correct given typ constraints.
  2. Rich language of configuration. Звычайна іншыя configuration approaches limited to u most variable substitution.
    Пры дапамозе аднаго разу можна выкарыстоўваць шырокую колькасць гатэляў, якія маюць на ўвазе configuration better. Для таго, каб мы здольныя трымаць у ажыццяўленні значных узроўняў, objects to set different scope, we can refer to val' defined only once in outer scope (DRY). Гэта магчыма для выкарыстання прыватных экзаменаў, або значэнняў certain classes (Seq, MapІ г.д.).
  3. DSL. Scala ha decent support for DSL writers. Один можа выкарыстоўваць гэтыя лічбы, каб стварыць configuration language, што ёсць больш зручным і канчатковым карыстачом, так што бягуча configuration is readable by domain users.
  4. Integrity and coherence across nodes. Один з benefits of configuration for whole distributed system in one place is the all values ​​are defined strictly once and then reused in all places where we need them. У той жа тып тыпу партый дэкарацыяў абавязацельстваў, што ў поўнай патрэбы правільнай configuration the systems nodes будзе праяўляць самую мову. Там explicit dependencies паміж nodes, якія робяць гэта hard to forget to provide some services.
  5. High quality of changes. Наяўныя спосабы пашыры configuration змены праз normal PR працэсы establishes high standards of quality also in configuration.
  6. Simultaneous configuration changes. Whenever we make any changes in configuration automatical deployment ensures that all nodes are being updated.
  7. Application simplification. Прыкладанне не патрабуецца для parse and validate configuration and handle incorrect configuration values. Гэтыя simplifies the overall application. (Некаторая зніжэнне складанасці з'яўляецца ў configuration itself, але it's a conscious trade-off towards safety.) It's pretty straightforward to return to ordinary configuration — just add the missing pieces. Гэта назоўнік, які атрымліваецца з кампіляцыйным configuration and postpone implementation of additional pieces to some later times.
  8. Versioned configuration. To facto, што configuration changes follow the same development process, as result we get an artifact with unique version. Гэта дазваляе сістэмна configuration back if needed. Для таго, каб пачаць працу, яна была выкарыстана ў мінулым годзе і яна будзе працаваць выключна ў той жа час. Стабільная configuration няправільна з'яўляецца разнастайнасцю і стабільнасцю distribuванага сістэм. configuration fixed at compile time and cannot be easily tampered on a production system.
  9. Modularity. Прадугледжаные framework з'яўляюцца modular and modules могуць быць змешаныя ў розных спосабах
    support different configurations (setups/layouts). У асаблівасці, гэта магчыма, каб мець маленькую малую малую квадратную клавіатуру і вялізную шырыню multi node setting. Гэта reasonable для таго, каб мець шмат разнастайных продажаў.
  10. Testing. Для выпрабавання прынаднасці XNUMX мэйл implemente mock service and use it as dependency in a typ safe way. Паколькі розныя тэставанне мерапрыемствы з некалькімі часткамі забруджаныя па маках можна было б maintained simultaneously.
  11. Integration testing. Sometimes in distributed systems it's difficult to run integration tests. Використовуючи напісаны інструмент для тыпу бяспечнай configuration of complete distributed system, we can run all distributed parts on single server in controllable way. It's easy to emulate the situation
    when one of the services becomes unavailable.

недахопы

Адпаведнае configuration approach з'яўляецца адным з "normal" configuration and might not suit all needs. Тут ёсць некаторыя з недастатковых config:

  1. Static configuration. Гэта не можа быць suitable for all applications. У некаторых выпадках яны патрабуюць дасканалага фіксавання configuration in production bypassing all safety measures. Гэты спосаб складаецца з больш difficult. Прадпрыемства і рэгенерацыя з'яўляюцца неабходнымі пасля змены ў адпаведнасці з configuration. Гэта з берага.
  2. Configuration generation. Пры config is generated by some automation tool this approach requires subsequent compilation (which might in turn fail). Існуе многае патрабаванне дадатковы эфект да ўключанага гэтага дадатковага стыпа ў сістэме пабудовы.
  3. Instruments. Там ёсць усе інструменты, якія выкарыстоўваюцца ў тым, што рэчы на ​​text-based configs. Some of them
    be applicable when configuration is compiled.
  4. У shift in mindset непатрэбна. Разработчики и DevOps з'яўляюцца знаёмымі з тэкставымі configuration files. Ідэя compiling configuration might appear strange to them.
  5. Перад утрыманнем compilable configuration з'яўляецца высокая якасць развіцця праектавання, неабходна.

Там ёсць некаторыя абмежаванні на implemented example:

  1. Калі мы выконваем дадатковыя метады, якія не маюць патрэбы ў node implementation, compiler не ўмее, каб вызначыць absent implementation. Гэта можа быць addressed by using HList or ADTs (case classes) для node configuration instead of traits and Cake Pattern.
  2. Вы павінны выканаць некаторыя boilerplate ў config file: (package, import, object declarations;
    override def's для параметраў, якія маюць default values). Гэта павінна быць звычайна addressed using a DSL.
  3. У гэтым пакоі не ўключаюць dynamic reconfiguration of clusters of similar nodes.

заключэнне

У дадзеным месцы будуць размаўляць аб думцы паняцця configuration directly in the source code in typ safe way. Прылады могуць быць выкарыстаны ў многіх мерах, якія адпавядаюць xml-і іншым тэкставым падмуркам. Выключна тое, што наш example мае быць ажыццёўлены ў Scala, ён можа быць перакладзены на іншыя камп'юляцыйныя англійцы (як Kotlin, C#, Swift, etc.). Одна можна зрабіць гэты спосаб у новым праектах і, у выпадку, калі гэта не так добра, патрабуюць old fashioned way.

З ходу, compilable configuration requires High quality development process. У адваротным яна мяркуе, што эквівалентна высокая якасць robustнага configuration.

Гэты падыход можа быць пашыраны ў розных выпадках:

  1. Адзін можна выкарыстоўваць macros для эфектыўнай configuration validation and fail at compile time in case of any business-logic constraints failures.
  2. A DSL можа быць ажыццёўлена ў адпаведнасці з configuration in domain-user-friendly way.
  3. Dynamic resource management with automatic configuration adjustments. Для таго, каб убачыць нумар cluster nodes былі might want (1) the nodes to obtain lightly modified configuration; (2) cluster manager to receive new nodes info.

дзякуй

Я павінен быць здольным казаць, што Andrey Saksonov, Pavel Popov, Anton Nehaev для вібрацыі inspirational feedback на зладзе гэтага post, які меў я clearer.

Крыніца: habr.com