Μεταγλωττισμένη Διαμόρφωση Κατανεμημένου Συστήματος

Θα ήθελα να σας πω έναν ενδιαφέρον μηχανισμό για την εργασία με τη διαμόρφωση ενός κατανεμημένου συστήματος. Η διαμόρφωση αναπαρίσταται απευθείας σε μια μεταγλωττισμένη γλώσσα (Scala) χρησιμοποιώντας ασφαλείς τύπους. Αυτή η ανάρτηση παρέχει ένα παράδειγμα τέτοιας διαμόρφωσης και συζητά διάφορες πτυχές της εφαρμογής μιας μεταγλωττισμένης διαμόρφωσης στη συνολική διαδικασία ανάπτυξης.

Μεταγλωττισμένη Διαμόρφωση Κατανεμημένου Συστήματος

(Αγγλικά)

Εισαγωγή

Η οικοδόμηση ενός αξιόπιστου κατανεμημένου συστήματος σημαίνει ότι όλοι οι κόμβοι χρησιμοποιούν τη σωστή διαμόρφωση, συγχρονισμένη με άλλους κόμβους. Οι τεχνολογίες DevOps (terraform, ansible ή κάτι τέτοιο) χρησιμοποιούνται συνήθως για την αυτόματη δημιουργία αρχείων διαμόρφωσης (συχνά ειδικά για κάθε κόμβο). Θα θέλαμε επίσης να είμαστε σίγουροι ότι όλοι οι κόμβοι επικοινωνίας χρησιμοποιούν πανομοιότυπα πρωτόκολλα (συμπεριλαμβανομένης της ίδιας έκδοσης). Διαφορετικά, η ασυμβατότητα θα ενσωματωθεί στο κατανεμημένο μας σύστημα. Στον κόσμο του JVM, μια συνέπεια αυτής της απαίτησης είναι ότι η ίδια έκδοση της βιβλιοθήκης που περιέχει τα μηνύματα πρωτοκόλλου πρέπει να χρησιμοποιείται παντού.

Τι γίνεται με τη δοκιμή ενός κατανεμημένου συστήματος; Φυσικά, υποθέτουμε ότι όλα τα στοιχεία έχουν δοκιμές μονάδων πριν προχωρήσουμε στη δοκιμή ενοποίησης. (Για να μπορούμε να προεκτείνουμε τα αποτελέσματα των δοκιμών στο χρόνο εκτέλεσης, πρέπει επίσης να παρέχουμε ένα πανομοιότυπο σύνολο βιβλιοθηκών στο στάδιο της δοκιμής και κατά το χρόνο εκτέλεσης.)

Όταν εργάζεστε με δοκιμές ολοκλήρωσης, είναι συχνά πιο εύκολο να χρησιμοποιήσετε την ίδια διαδρομή τάξης παντού σε όλους τους κόμβους. Το μόνο που έχουμε να κάνουμε είναι να διασφαλίσουμε ότι χρησιμοποιείται η ίδια διαδρομή κλάσης κατά το χρόνο εκτέλεσης. (Αν και είναι απολύτως δυνατό να εκτελεστούν διαφορετικοί κόμβοι με διαφορετικές διαδρομές κλάσης, αυτό προσθέτει πολυπλοκότητα στη συνολική διαμόρφωση και δυσκολίες με τις δοκιμές ανάπτυξης και ενοποίησης.) Για τους σκοπούς αυτής της ανάρτησης, υποθέτουμε ότι όλοι οι κόμβοι θα χρησιμοποιούν την ίδια διαδρομή κλάσης.

Η διαμόρφωση εξελίσσεται με την εφαρμογή. Χρησιμοποιούμε εκδόσεις για να προσδιορίσουμε διαφορετικά στάδια εξέλιξης του προγράμματος. Φαίνεται λογικό να προσδιορίζονται επίσης διαφορετικές εκδόσεις διαμορφώσεων. Και τοποθετήστε την ίδια τη διαμόρφωση στο σύστημα ελέγχου έκδοσης. Εάν υπάρχει μόνο μία διαμόρφωση στην παραγωγή, τότε μπορούμε απλά να χρησιμοποιήσουμε τον αριθμό έκδοσης. Εάν χρησιμοποιήσουμε πολλές περιπτώσεις παραγωγής, τότε θα χρειαστούμε αρκετές
διακλαδώσεις διαμόρφωσης και μια πρόσθετη ετικέτα εκτός από την έκδοση (για παράδειγμα, το όνομα του κλάδου). Με αυτόν τον τρόπο μπορούμε να προσδιορίσουμε με σαφήνεια την ακριβή διαμόρφωση. Κάθε αναγνωριστικό διαμόρφωσης αντιστοιχεί μοναδικά σε έναν συγκεκριμένο συνδυασμό κατανεμημένων κόμβων, θυρών, εξωτερικών πόρων και εκδόσεων βιβλιοθήκης. Για τους σκοπούς αυτής της ανάρτησης θα υποθέσουμε ότι υπάρχει μόνο ένας κλάδος και μπορούμε να αναγνωρίσουμε τη διαμόρφωση με τον συνηθισμένο τρόπο χρησιμοποιώντας τρεις αριθμούς που χωρίζονται με μια τελεία (1.2.3).

Σε σύγχρονα περιβάλλοντα, τα αρχεία διαμόρφωσης σπάνια δημιουργούνται χειροκίνητα. Συχνότερα δημιουργούνται κατά την ανάπτυξη και δεν αγγίζονται πλέον (έτσι ώστε μην σπάσεις τίποτα). Τίθεται ένα φυσικό ερώτημα: γιατί εξακολουθούμε να χρησιμοποιούμε μορφή κειμένου για την αποθήκευση των ρυθμίσεων; Μια βιώσιμη εναλλακτική λύση φαίνεται να είναι η δυνατότητα χρήσης κανονικού κώδικα για τη διαμόρφωση και να επωφεληθείτε από τους ελέγχους χρόνου μεταγλώττισης.

Σε αυτήν την ανάρτηση θα διερευνήσουμε την ιδέα της αναπαράστασης μιας διαμόρφωσης μέσα σε ένα μεταγλωττισμένο τεχνούργημα.

Μεταγλωττισμένη διαμόρφωση

Αυτή η ενότητα παρέχει ένα παράδειγμα στατικής μεταγλωττισμένης διαμόρφωσης. Υλοποιούνται δύο απλές υπηρεσίες - η υπηρεσία echo και ο πελάτης υπηρεσίας echo. Με βάση αυτές τις δύο υπηρεσίες, συναρμολογούνται δύο επιλογές συστήματος. Σε μια επιλογή, και οι δύο υπηρεσίες βρίσκονται στον ίδιο κόμβο, σε μια άλλη επιλογή - σε διαφορετικούς κόμβους.

Συνήθως ένα κατανεμημένο σύστημα περιέχει αρκετούς κόμβους. Μπορείτε να αναγνωρίσετε κόμβους χρησιμοποιώντας τιμές κάποιου τύπου NodeId:

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 μεταξύ τους.

Για να περιγράψουμε μια σύνδεση TCP χρειαζόμαστε τουλάχιστον έναν αριθμό θύρας. Θα θέλαμε επίσης να απεικονίσουμε το πρωτόκολλο που υποστηρίζεται σε αυτήν τη θύρα για να διασφαλίσουμε ότι τόσο ο πελάτης όσο και ο διακομιστής χρησιμοποιούν το ίδιο πρωτόκολλο. Θα περιγράψουμε τη σύνδεση χρησιμοποιώντας την ακόλουθη κλάση:

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

όπου Port - απλώς ένας ακέραιος αριθμός Int υποδεικνύοντας το εύρος των αποδεκτών τιμών:

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

Εκλεπτυσμένοι τύποι

Δείτε βιβλιοθήκη εξευγενισμένα и μου την έκθεση. Εν ολίγοις, η βιβλιοθήκη σάς επιτρέπει να προσθέτετε περιορισμούς σε τύπους που ελέγχονται κατά το χρόνο μεταγλώττισης. Σε αυτήν την περίπτωση, οι έγκυρες τιμές αριθμού θύρας είναι ακέραιοι αριθμοί 16-bit. Για μια μεταγλωττισμένη διαμόρφωση, η χρήση της εκλεπτυσμένης βιβλιοθήκης δεν είναι υποχρεωτική, αλλά βελτιώνει την ικανότητα του μεταγλωττιστή να ελέγχει τη διαμόρφωση.

Για τα πρωτόκολλα HTTP (REST), εκτός από τον αριθμό θύρας, μπορεί να χρειαστούμε και τη διαδρομή προς την υπηρεσία:

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

Τύποι φάντασμα

Για να αναγνωρίσουμε το πρωτόκολλο κατά το χρόνο μεταγλώττισης, χρησιμοποιούμε μια παράμετρο τύπου που δεν χρησιμοποιείται στην κλάση. Αυτή η απόφαση οφείλεται στο γεγονός ότι δεν χρησιμοποιούμε μια παρουσία πρωτοκόλλου κατά το χρόνο εκτέλεσης, αλλά θα θέλαμε ο μεταγλωττιστής να ελέγξει τη συμβατότητα του πρωτοκόλλου. Καθορίζοντας το πρωτόκολλο, δεν θα μπορούμε να μεταβιβάσουμε μια ακατάλληλη υπηρεσία ως εξάρτηση.

Ένα από τα κοινά πρωτόκολλα είναι το REST API με σειριοποίηση Json:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

όπου RequestMessage - τύπος αιτήματος, ResponseMessage — τύπος απόκρισης.
Φυσικά, μπορούμε να χρησιμοποιήσουμε άλλες περιγραφές πρωτοκόλλων που παρέχουν την ακρίβεια της περιγραφής που απαιτούμε.

Για τους σκοπούς αυτής της ανάρτησης, θα χρησιμοποιήσουμε μια απλοποιημένη έκδοση του πρωτοκόλλου:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Εδώ το αίτημα είναι μια συμβολοσειρά που προσαρτάται στη διεύθυνση url και η απάντηση είναι η συμβολοσειρά που επιστρέφεται στο σώμα της απόκρισης HTTP.

Η διαμόρφωση της υπηρεσίας περιγράφεται από το όνομα της υπηρεσίας, τις θύρες και τις εξαρτήσεις. Αυτά τα στοιχεία μπορούν να αναπαρασταθούν στο Scala με διάφορους τρόπους (για παράδειγμα, HList-s, αλγεβρικοί τύποι δεδομένων). Για τους σκοπούς αυτής της ανάρτησης, θα χρησιμοποιήσουμε το μοτίβο κέικ και θα αναπαραστήσουμε ενότητες χρησιμοποιώντας trait'ov. (Το μοτίβο κέικ δεν είναι απαραίτητο στοιχείο αυτής της προσέγγισης. Είναι απλώς μια πιθανή εφαρμογή.)

Οι εξαρτήσεις μεταξύ των υπηρεσιών μπορούν να αναπαρασταθούν ως μέθοδοι που επιστρέφουν θύρες EndPoint's άλλων κόμβων:

  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, το μόνο που χρειάζεστε είναι ένας αριθμός θύρας και μια ένδειξη ότι η θύρα υποστηρίζει το πρωτόκολλο echo. Μπορεί να μην προσδιορίσουμε μια συγκεκριμένη θύρα, επειδή... Τα χαρακτηριστικά σάς επιτρέπουν να δηλώσετε μεθόδους χωρίς υλοποίηση (αφηρημένες μέθοδοι). Σε αυτήν την περίπτωση, κατά τη δημιουργία μιας συγκεκριμένης διαμόρφωσης, ο μεταγλωττιστής θα μας ζητούσε να παρέχουμε μια υλοποίηση της αφηρημένης μεθόδου και να παρέχουμε έναν αριθμό θύρας. Εφόσον έχουμε εφαρμόσει τη μέθοδο, κατά τη δημιουργία μιας συγκεκριμένης διαμόρφωσης, ενδέχεται να μην καθορίσουμε διαφορετική θύρα. Θα χρησιμοποιηθεί η προεπιλεγμένη τιμή.

Στη διαμόρφωση πελάτη δηλώνουμε μια εξάρτηση από την υπηρεσία echo:

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

Η εξάρτηση είναι του ίδιου τύπου με την εξαγόμενη υπηρεσία echoService. Συγκεκριμένα, στον πελάτη echo απαιτούμε το ίδιο πρωτόκολλο. Επομένως, όταν συνδέουμε δύο υπηρεσίες, μπορούμε να είμαστε σίγουροι ότι όλα θα λειτουργήσουν σωστά.

Υλοποίηση υπηρεσιών

Απαιτείται μια λειτουργία για την έναρξη και τη διακοπή της υπηρεσίας. (Η δυνατότητα διακοπής μιας υπηρεσίας είναι κρίσιμη για τη δοκιμή.) Και πάλι, υπάρχουν αρκετές επιλογές για την υλοποίηση μιας τέτοιας δυνατότητας (για παράδειγμα, θα μπορούσαμε να χρησιμοποιήσουμε κατηγορίες τύπων με βάση τον τύπο διαμόρφωσης). Για τους σκοπούς αυτής της ανάρτησης θα χρησιμοποιήσουμε το Μοτίβο Τούρτας. Θα αντιπροσωπεύσουμε την υπηρεσία χρησιμοποιώντας μια κλάση cats.Resource, επειδή Αυτή η κατηγορία παρέχει ήδη μέσα για την ασφαλή εγγύηση της αποδέσμευσης πόρων σε περίπτωση προβλημάτων. Για να αποκτήσουμε έναν πόρο, πρέπει να παρέχουμε διαμόρφωση και ένα έτοιμο πλαίσιο χρόνου εκτέλεσης. Η λειτουργία εκκίνησης υπηρεσίας μπορεί να μοιάζει με αυτό:

  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 — τύπος διαμόρφωσης για αυτήν την υπηρεσία
  • AddressResolver — ένα αντικείμενο χρόνου εκτέλεσης που σας επιτρέπει να μάθετε τις διευθύνσεις άλλων κόμβων (δείτε παρακάτω)

και άλλα είδη από τη βιβλιοθήκη cats:

  • F[_] — είδος αποτελέσματος (στην απλούστερη περίπτωση F[A] θα μπορούσε να είναι απλώς μια συνάρτηση () => A. Σε αυτή την ανάρτηση θα χρησιμοποιήσουμε cats.IO.)
  • Reader[A,B] - περισσότερο ή λιγότερο συνώνυμο με τη λειτουργία A => B
  • cats.Resource - ένας πόρος που μπορεί να αποκτηθεί και να κυκλοφορήσει
  • Timer — χρονόμετρο (σας επιτρέπει να κοιμηθείτε για λίγο και να μετρήσετε τα χρονικά διαστήματα)
  • ContextShift - αναλογικό ExecutionContext
  • Applicative — μια κλάση τύπου εφέ που σας επιτρέπει να συνδυάζετε μεμονωμένα εφέ (σχεδόν ένα monad). Σε πιο σύνθετες εφαρμογές φαίνεται καλύτερο να χρησιμοποιείται Monad/ConcurrentEffect.

Χρησιμοποιώντας αυτήν την υπογραφή συνάρτησης μπορούμε να υλοποιήσουμε διάφορες υπηρεσίες. Για παράδειγμα, μια υπηρεσία που δεν κάνει τίποτα:

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

(Εκ. πηγή, στο οποίο υλοποιούνται άλλες υπηρεσίες - υπηρεσία ηχούς, echo client
и ελεγκτές διάρκειας ζωής.)

Ένας κόμβος είναι ένα αντικείμενο που μπορεί να εκκινήσει πολλές υπηρεσίες (η εκκίνηση μιας αλυσίδας πόρων διασφαλίζεται από το Μοτίβο Τούρτας):

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

Λάβετε υπόψη ότι καθορίζουμε τον ακριβή τύπο διαμόρφωσης που απαιτείται για αυτόν τον κόμβο. Εάν ξεχάσουμε να καθορίσουμε έναν από τους τύπους διαμόρφωσης που απαιτούνται από μια συγκεκριμένη υπηρεσία, θα υπάρξει σφάλμα μεταγλώττισης. Επίσης, δεν θα μπορέσουμε να ξεκινήσουμε έναν κόμβο αν δεν παρέχουμε κάποιο αντικείμενο του κατάλληλου τύπου με όλα τα απαραίτητα δεδομένα.

Ανάλυση ονόματος κεντρικού υπολογιστή

Για να συνδεθείτε σε έναν απομακρυσμένο κεντρικό υπολογιστή, χρειαζόμαστε μια πραγματική διεύθυνση IP. Είναι πιθανό η διεύθυνση να γίνει γνωστή αργότερα από την υπόλοιπη διαμόρφωση. Χρειαζόμαστε λοιπόν μια συνάρτηση που αντιστοιχίζει το αναγνωριστικό κόμβου σε μια διεύθυνση:

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

Υπάρχουν διάφοροι τρόποι υλοποίησης αυτής της λειτουργίας:

  1. Εάν οι διευθύνσεις γίνουν γνωστές σε εμάς πριν από την ανάπτυξη, τότε μπορούμε να δημιουργήσουμε κώδικα Scala με
    διευθύνσεις και μετά εκτελέστε το build. Αυτό θα μεταγλωττίσει και θα εκτελέσει δοκιμές.
    Σε αυτήν την περίπτωση, η συνάρτηση θα είναι γνωστή στατικά και μπορεί να αναπαρασταθεί σε κώδικα ως αντιστοίχιση Map[NodeId, NodeAddress].
  2. Σε ορισμένες περιπτώσεις, η πραγματική διεύθυνση είναι γνωστή μόνο μετά την εκκίνηση του κόμβου.
    Σε αυτήν την περίπτωση, μπορούμε να εφαρμόσουμε μια «υπηρεσία ανακάλυψης» που εκτελείται πριν από άλλους κόμβους και όλοι οι κόμβοι θα εγγραφούν σε αυτήν την υπηρεσία και θα ζητήσουν τις διευθύνσεις άλλων κόμβων.
  3. Αν μπορούμε να τροποποιήσουμε /etc/hosts, τότε μπορείτε να χρησιμοποιήσετε προκαθορισμένα ονόματα κεντρικών υπολογιστών (όπως my-project-main-node и echo-backend) και απλώς συνδέστε αυτά τα ονόματα
    με διευθύνσεις IP κατά την ανάπτυξη.

Σε αυτήν την ανάρτηση δεν θα εξετάσουμε αυτές τις περιπτώσεις με περισσότερες λεπτομέρειες. Για τα δικά μας
σε ένα παράδειγμα παιχνιδιού, όλοι οι κόμβοι θα έχουν την ίδια διεύθυνση IP - 127.0.0.1.

Στη συνέχεια, εξετάζουμε δύο επιλογές για ένα κατανεμημένο σύστημα:

  1. Τοποθέτηση όλων των υπηρεσιών σε έναν κόμβο.
  2. Και φιλοξενία της υπηρεσίας echo και του πελάτη echo σε διαφορετικούς κόμβους.

Διαμόρφωση για ένας κόμβος:

Διαμόρφωση ενός κόμβου

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

Το αντικείμενο υλοποιεί τη διαμόρφωση τόσο του πελάτη όσο και του διακομιστή. Χρησιμοποιείται επίσης μια διαμόρφωση χρόνου ζωής, έτσι ώστε μετά το διάστημα lifetime τερματίσετε το πρόγραμμα. (Το Ctrl-C λειτουργεί επίσης και ελευθερώνει όλους τους πόρους σωστά.)

Το ίδιο σύνολο χαρακτηριστικών διαμόρφωσης και υλοποίησης μπορεί να χρησιμοποιηθεί για τη δημιουργία ενός συστήματος που αποτελείται από δύο ξεχωριστούς κόμβους:

Διαμόρφωση δύο κόμβων

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

Σπουδαίος! Παρατηρήστε πώς συνδέονται οι υπηρεσίες. Καθορίζουμε μια υπηρεσία που υλοποιείται από έναν κόμβο ως υλοποίηση της μεθόδου εξάρτησης ενός άλλου κόμβου. Ο τύπος εξάρτησης ελέγχεται από τον μεταγλωττιστή, επειδή περιέχει τον τύπο πρωτοκόλλου. Κατά την εκτέλεση, η εξάρτηση θα περιέχει το σωστό αναγνωριστικό κόμβου προορισμού. Χάρη σε αυτό το σχήμα, καθορίζουμε τον αριθμό θύρας ακριβώς μία φορά και έχουμε πάντα εγγύηση ότι αναφέρεται στη σωστή θύρα.

Υλοποίηση δύο κόμβων συστήματος

Για αυτήν τη διαμόρφωση, χρησιμοποιούμε τις ίδιες υλοποιήσεις υπηρεσιών χωρίς αλλαγές. Η μόνη διαφορά είναι ότι τώρα έχουμε δύο αντικείμενα που υλοποιούν διαφορετικά σύνολα υπηρεσιών:

  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
  }

Ο πρώτος κόμβος υλοποιεί τον διακομιστή και χρειάζεται μόνο διαμόρφωση διακομιστή. Ο δεύτερος κόμβος υλοποιεί τον πελάτη και χρησιμοποιεί ένα διαφορετικό μέρος της διαμόρφωσης. Επίσης και οι δύο κόμβοι χρειάζονται διαχείριση διάρκειας ζωής. Ο κόμβος διακομιστή λειτουργεί επ' αόριστον μέχρι να σταματήσει SIGTERM'om, και ο κόμβος πελάτη τερματίζεται μετά από κάποιο χρονικό διάστημα. Εκ. εφαρμογή εκκίνησης.

Γενική διαδικασία ανάπτυξης

Ας δούμε πώς αυτή η προσέγγιση διαμόρφωσης επηρεάζει τη συνολική διαδικασία ανάπτυξης.

Η διαμόρφωση θα μεταγλωττιστεί μαζί με τον υπόλοιπο κώδικα και θα δημιουργηθεί ένα τεχνούργημα (.jar). Φαίνεται ότι έχει νόημα να βάλουμε τη διαμόρφωση σε ένα ξεχωριστό τεχνούργημα. Αυτό συμβαίνει επειδή μπορούμε να έχουμε πολλαπλές διαμορφώσεις με βάση τον ίδιο κώδικα. Και πάλι, είναι δυνατή η δημιουργία τεχνουργημάτων που αντιστοιχούν σε διαφορετικούς κλάδους διαμόρφωσης. Οι εξαρτήσεις από συγκεκριμένες εκδόσεις βιβλιοθηκών αποθηκεύονται μαζί με τη διαμόρφωση και αυτές οι εκδόσεις αποθηκεύονται για πάντα κάθε φορά που αποφασίζουμε να αναπτύξουμε αυτήν την έκδοση της διαμόρφωσης.

Οποιαδήποτε αλλαγή διαμόρφωσης μετατρέπεται σε αλλαγή κώδικα. Και επομένως, το καθένα
η αλλαγή θα καλύπτεται από την κανονική διαδικασία διασφάλισης ποιότητας:

Εισιτήριο στο πρόγραμμα παρακολούθησης σφαλμάτων -> PR -> κριτική -> συγχώνευση με σχετικά υποκαταστήματα ->
ενσωμάτωση -> ανάπτυξη

Οι κύριες συνέπειες της εφαρμογής μιας μεταγλωττισμένης διαμόρφωσης είναι:

  1. Η διαμόρφωση θα είναι συνεπής σε όλους τους κόμβους του κατανεμημένου συστήματος. Λόγω του γεγονότος ότι όλοι οι κόμβοι λαμβάνουν την ίδια διαμόρφωση από μία μόνο πηγή.

  2. Είναι προβληματική η αλλαγή της διαμόρφωσης μόνο σε έναν από τους κόμβους. Ως εκ τούτου, η "μετατροπή διαμόρφωσης" είναι απίθανη.

  3. Γίνεται πιο δύσκολο να κάνετε μικρές αλλαγές στη διαμόρφωση.

  4. Οι περισσότερες αλλαγές διαμόρφωσης θα πραγματοποιηθούν ως μέρος της συνολικής διαδικασίας ανάπτυξης και θα υπόκεινται σε έλεγχο.

Χρειάζομαι ξεχωριστό αποθετήριο για την αποθήκευση της διαμόρφωσης παραγωγής; Αυτή η διαμόρφωση ενδέχεται να περιέχει κωδικούς πρόσβασης και άλλες ευαίσθητες πληροφορίες στις οποίες θα θέλαμε να περιορίσουμε την πρόσβαση. Με βάση αυτό, φαίνεται να έχει νόημα η αποθήκευση της τελικής διαμόρφωσης σε ξεχωριστό αποθετήριο. Μπορείτε να χωρίσετε τη διαμόρφωση σε δύο μέρη—ένα που περιέχει ρυθμίσεις διαμόρφωσης προσβάσιμες από το κοινό και ένα που περιέχει περιορισμένες ρυθμίσεις. Αυτό θα επιτρέψει στους περισσότερους προγραμματιστές να έχουν πρόσβαση σε κοινές ρυθμίσεις. Αυτός ο διαχωρισμός είναι εύκολο να επιτευχθεί χρησιμοποιώντας ενδιάμεσα χαρακτηριστικά που περιέχουν προεπιλεγμένες τιμές.

Πιθανές παραλλαγές

Ας προσπαθήσουμε να συγκρίνουμε τη μεταγλωττισμένη διαμόρφωση με μερικές κοινές εναλλακτικές:

  1. Αρχείο κειμένου στο μηχάνημα προορισμού.
  2. Κεντρικό κατάστημα αξίας κλειδιού (etcd/zookeeper).
  3. Στοιχεία διεργασίας που μπορούν να διαμορφωθούν/επανεκκινήσουν χωρίς επανεκκίνηση της διαδικασίας.
  4. Αποθήκευση διαμορφώσεων εκτός του ελέγχου τεχνουργημάτων και έκδοσης.

Τα αρχεία κειμένου παρέχουν σημαντική ευελιξία όσον αφορά τις μικρές αλλαγές. Ο διαχειριστής του συστήματος μπορεί να συνδεθεί στον απομακρυσμένο κόμβο, να κάνει αλλαγές στα κατάλληλα αρχεία και να επανεκκινήσει την υπηρεσία. Για μεγάλα συστήματα, ωστόσο, αυτή η ευελιξία μπορεί να μην είναι επιθυμητή. Οι αλλαγές που έγιναν δεν αφήνουν ίχνη σε άλλα συστήματα. Κανείς δεν εξετάζει τις αλλαγές. Είναι δύσκολο να προσδιοριστεί ποιος ακριβώς έκανε τις αλλαγές και για ποιο λόγο. Οι αλλαγές δεν ελέγχονται. Εάν το σύστημα είναι κατανεμημένο, τότε ο διαχειριστής μπορεί να ξεχάσει να κάνει την αντίστοιχη αλλαγή σε άλλους κόμβους.

(Θα πρέπει επίσης να σημειωθεί ότι η χρήση μιας μεταγλωττισμένης διαμόρφωσης δεν κλείνει τη δυνατότητα χρήσης αρχείων κειμένου στο μέλλον. Θα αρκεί να προσθέσετε έναν αναλυτή και έναν επικυρωτή που παράγει τον ίδιο τύπο με την έξοδο Configκαι μπορείτε να χρησιμοποιήσετε αρχεία κειμένου. Αμέσως προκύπτει ότι η πολυπλοκότητα ενός συστήματος με μεταγλωττισμένη διαμόρφωση είναι κάπως μικρότερη από την πολυπλοκότητα ενός συστήματος που χρησιμοποιεί αρχεία κειμένου, επειδή τα αρχεία κειμένου απαιτούν πρόσθετο κωδικό.)

Ένα κεντρικό κατάστημα κλειδιού-τιμής είναι ένας καλός μηχανισμός για τη διανομή μετα-παραμέτρων μιας κατανεμημένης εφαρμογής. Πρέπει να αποφασίσουμε τι είναι οι παράμετροι διαμόρφωσης και τι είναι απλώς δεδομένα. Ας έχουμε μια λειτουργία C => A => Bκαι τις παραμέτρους C σπάνια αλλάζει και δεδομένα A - συχνά. Σε αυτή την περίπτωση μπορούμε να πούμε ότι C - παραμέτρους διαμόρφωσης και A - δεδομένα. Φαίνεται ότι οι παράμετροι διαμόρφωσης διαφέρουν από τα δεδομένα στο ότι γενικά αλλάζουν λιγότερο συχνά από τα δεδομένα. Επίσης, τα δεδομένα συνήθως προέρχονται από μια πηγή (από τον χρήστη) και οι παράμετροι διαμόρφωσης από μια άλλη (από τον διαχειριστή του συστήματος).

Εάν πρέπει να ενημερωθούν οι παράμετροι που αλλάζουν σπάνια χωρίς επανεκκίνηση του προγράμματος, τότε αυτό μπορεί συχνά να οδηγήσει σε περιπλοκή του προγράμματος, επειδή θα χρειαστεί να παραδώσουμε με κάποιο τρόπο παραμέτρους, να αποθηκεύσουμε, να αναλύσουμε και να ελέγξουμε και να επεξεργαστούμε εσφαλμένες τιμές. Επομένως, από την άποψη της μείωσης της πολυπλοκότητας του προγράμματος, είναι λογικό να μειωθεί ο αριθμός των παραμέτρων που μπορούν να αλλάξουν κατά τη λειτουργία του προγράμματος (ή να μην υποστηρίζουν καθόλου τέτοιες παραμέτρους).

Για τους σκοπούς αυτής της ανάρτησης, θα διαφοροποιήσουμε τις στατικές και τις δυναμικές παραμέτρους. Εάν η λογική της υπηρεσίας απαιτεί αλλαγή παραμέτρων κατά τη λειτουργία του προγράμματος, τότε θα ονομάσουμε τέτοιες παραμέτρους δυναμικές. Διαφορετικά, οι επιλογές είναι στατικές και μπορούν να διαμορφωθούν χρησιμοποιώντας τη μεταγλωττισμένη διαμόρφωση. Για δυναμική αναδιαμόρφωση, μπορεί να χρειαστούμε έναν μηχανισμό για την επανεκκίνηση τμημάτων του προγράμματος με νέες παραμέτρους, παρόμοιες με τον τρόπο επανεκκίνησης των διεργασιών του λειτουργικού συστήματος. (Κατά τη γνώμη μας, συνιστάται να αποφύγετε την αναδιαμόρφωση σε πραγματικό χρόνο, καθώς αυτό αυξάνει την πολυπλοκότητα του συστήματος. Εάν είναι δυνατόν, είναι καλύτερο να χρησιμοποιήσετε τις τυπικές δυνατότητες του λειτουργικού συστήματος για την επανεκκίνηση των διαδικασιών.)

Μια σημαντική πτυχή της χρήσης στατικής διαμόρφωσης που κάνει τους ανθρώπους να εξετάζουν τη δυναμική επαναδιαμόρφωση είναι ο χρόνος που χρειάζεται για την επανεκκίνηση του συστήματος μετά από μια ενημέρωση διαμόρφωσης (χρόνος διακοπής λειτουργίας). Στην πραγματικότητα, αν χρειαστεί να κάνουμε αλλαγές στη στατική διαμόρφωση, θα πρέπει να επανεκκινήσουμε το σύστημα για να τεθούν σε ισχύ οι νέες τιμές. Το πρόβλημα του χρόνου διακοπής ποικίλλει σε σοβαρότητα για διαφορετικά συστήματα. Σε ορισμένες περιπτώσεις, μπορείτε να προγραμματίσετε μια επανεκκίνηση τη στιγμή που το φορτίο είναι ελάχιστο. Εάν χρειάζεται να παρέχετε συνεχή εξυπηρέτηση, μπορείτε να εφαρμόσετε Αποστράγγιση σύνδεσης AWS ELB. Ταυτόχρονα, όταν χρειάζεται να επανεκκινήσουμε το σύστημα, εκκινούμε μια παράλληλη παρουσία αυτού του συστήματος, αλλάζουμε τον εξισορροπητή σε αυτό και περιμένουμε να ολοκληρωθούν οι παλιές συνδέσεις. Αφού τερματιστούν όλες οι παλιές συνδέσεις, τερματίζουμε την παλιά παρουσία του συστήματος.

Ας εξετάσουμε τώρα το ζήτημα της αποθήκευσης της διαμόρφωσης μέσα ή έξω από το τεχνούργημα. Εάν αποθηκεύσουμε τη διαμόρφωση μέσα σε ένα τεχνούργημα, τότε τουλάχιστον είχαμε την ευκαιρία να επαληθεύσουμε την ορθότητα της διαμόρφωσης κατά τη συναρμολόγηση του τεχνουργήματος. Εάν η διαμόρφωση είναι έξω από το ελεγχόμενο τεχνούργημα, είναι δύσκολο να εντοπιστεί ποιος έκανε αλλαγές σε αυτό το αρχείο και γιατί. Πόσο σημαντικό είναι; Κατά τη γνώμη μας, για πολλά συστήματα παραγωγής είναι σημαντικό να υπάρχει σταθερή και υψηλής ποιότητας διαμόρφωση.

Η έκδοση ενός τεχνουργήματος σάς επιτρέπει να προσδιορίσετε πότε δημιουργήθηκε, ποιες τιμές περιέχει, ποιες λειτουργίες είναι ενεργοποιημένες/απενεργοποιημένες και ποιος είναι υπεύθυνος για οποιαδήποτε αλλαγή στη διαμόρφωση. Φυσικά, η αποθήκευση της διαμόρφωσης μέσα σε ένα τεχνούργημα απαιτεί κάποια προσπάθεια, επομένως πρέπει να λάβετε μια τεκμηριωμένη απόφαση.

Πλεονεκτήματα και μειονεκτήματα

Θα ήθελα να σταθώ στα πλεονεκτήματα και τα μειονεκτήματα της προτεινόμενης τεχνολογίας.

Πλεονεκτήματα

Παρακάτω είναι μια λίστα με τα κύρια χαρακτηριστικά μιας μεταγλωττισμένης διαμόρφωσης κατανεμημένου συστήματος:

  1. Έλεγχος στατικής διαμόρφωσης. Σας επιτρέπει να είστε σίγουροι ότι
    η διαμόρφωση είναι σωστή.
  2. Πλούσια γλώσσα διαμόρφωσης. Συνήθως, άλλες μέθοδοι διαμόρφωσης περιορίζονται σε αντικατάσταση μεταβλητής συμβολοσειράς το πολύ. Όταν χρησιμοποιείτε το Scala, είναι διαθέσιμο ένα ευρύ φάσμα γλωσσικών λειτουργιών για τη βελτίωση της διαμόρφωσής σας. Για παράδειγμα μπορούμε να χρησιμοποιήσουμε
    Χαρακτηριστικά για προεπιλεγμένες τιμές, χρησιμοποιώντας αντικείμενα για ομαδοποίηση παραμέτρων, μπορούμε να αναφερθούμε σε vals που δηλώνονται μόνο μία φορά (DRY) στο πλαίσιο που περικλείει. Μπορείτε να δημιουργήσετε οποιεσδήποτε κλάσεις απευθείας μέσα στη διαμόρφωση (Seq, Map, προσαρμοσμένες τάξεις).
  3. DSL. Το Scala διαθέτει μια σειρά από χαρακτηριστικά γλώσσας που διευκολύνουν τη δημιουργία ενός DSL. Είναι δυνατό να επωφεληθείτε από αυτές τις δυνατότητες και να εφαρμόσετε μια γλώσσα διαμόρφωσης που είναι πιο βολική για την ομάδα-στόχο των χρηστών, έτσι ώστε η διαμόρφωση να είναι τουλάχιστον ευανάγνωστη από τους ειδικούς του τομέα. Οι ειδικοί μπορούν, για παράδειγμα, να συμμετέχουν στη διαδικασία ελέγχου διαμόρφωσης.
  4. Ακεραιότητα και συγχρονισμός μεταξύ κόμβων. Ένα από τα πλεονεκτήματα της αποθήκευσης της διαμόρφωσης ενός ολόκληρου κατανεμημένου συστήματος σε ένα μόνο σημείο είναι ότι όλες οι τιμές δηλώνονται ακριβώς μία φορά και στη συνέχεια επαναχρησιμοποιούνται όπου χρειάζονται. Η χρήση τύπων φαντασμάτων για τη δήλωση θυρών διασφαλίζει ότι οι κόμβοι χρησιμοποιούν συμβατά πρωτόκολλα σε όλες τις σωστές διαμορφώσεις συστήματος. Η ύπαρξη ρητών υποχρεωτικών εξαρτήσεων μεταξύ των κόμβων διασφαλίζει ότι όλες οι υπηρεσίες είναι συνδεδεμένες.
  5. Υψηλής ποιότητας αλλαγές. Η πραγματοποίηση αλλαγών στη διαμόρφωση χρησιμοποιώντας μια κοινή διαδικασία ανάπτυξης καθιστά δυνατή την επίτευξη υψηλών προτύπων ποιότητας και για τη διαμόρφωση.
  6. Ταυτόχρονη ενημέρωση διαμόρφωσης. Η αυτόματη ανάπτυξη συστήματος μετά από αλλαγές διαμόρφωσης διασφαλίζει ότι όλοι οι κόμβοι ενημερώνονται.
  7. Απλοποίηση της εφαρμογής. Η εφαρμογή δεν χρειάζεται ανάλυση, έλεγχο διαμόρφωσης ή χειρισμό εσφαλμένων τιμών. Αυτό μειώνει την πολυπλοκότητα της εφαρμογής. (Μερική από την πολυπλοκότητα διαμόρφωσης που παρατηρείται στο παράδειγμά μας δεν είναι χαρακτηριστικό της μεταγλωττισμένης διαμόρφωσης, αλλά μόνο μια συνειδητή απόφαση που καθοδηγείται από την επιθυμία παροχής μεγαλύτερης ασφάλειας τύπου.) Είναι πολύ εύκολο να επιστρέψετε στη συνήθη διαμόρφωση - απλώς εφαρμόστε το που λείπει εξαρτήματα. Επομένως, μπορείτε, για παράδειγμα, να ξεκινήσετε με μια μεταγλωττισμένη διαμόρφωση, αναβάλλοντας την υλοποίηση περιττών εξαρτημάτων μέχρι τη στιγμή που είναι πραγματικά απαραίτητη.
  8. Επαληθευμένη διαμόρφωση. Δεδομένου ότι οι αλλαγές διαμόρφωσης ακολουθούν τη συνήθη μοίρα οποιωνδήποτε άλλων αλλαγών, το αποτέλεσμα που λαμβάνουμε είναι ένα τεχνούργημα με μια μοναδική έκδοση. Αυτό μας επιτρέπει, για παράδειγμα, να επιστρέψουμε σε μια προηγούμενη έκδοση της διαμόρφωσης εάν είναι απαραίτητο. Μπορούμε ακόμη και να χρησιμοποιήσουμε τη ρύθμιση παραμέτρων πριν από ένα χρόνο και το σύστημα θα λειτουργεί ακριβώς το ίδιο. Μια σταθερή διαμόρφωση βελτιώνει την προβλεψιμότητα και την αξιοπιστία ενός κατανεμημένου συστήματος. Δεδομένου ότι η διαμόρφωση είναι σταθερή στο στάδιο της μεταγλώττισης, είναι αρκετά δύσκολο να την παραποιήσετε στην παραγωγή.
  9. Αρθρωτότητα. Το προτεινόμενο πλαίσιο είναι αρθρωτό και οι ενότητες μπορούν να συνδυαστούν με διαφορετικούς τρόπους για τη δημιουργία διαφορετικών συστημάτων. Συγκεκριμένα, μπορείτε να διαμορφώσετε το σύστημα ώστε να εκτελείται σε έναν μόνο κόμβο σε μια υλοποίηση και σε πολλούς κόμβους σε μια άλλη. Μπορείτε να δημιουργήσετε πολλές διαμορφώσεις για στιγμιότυπα παραγωγής του συστήματος.
  10. Δοκιμές. Αντικαθιστώντας μεμονωμένες υπηρεσίες με εικονικά αντικείμενα, μπορείτε να αποκτήσετε πολλές εκδόσεις του συστήματος που είναι βολικές για δοκιμή.
  11. Δοκιμή ενσωμάτωσης. Η ύπαρξη μιας ενιαίας διαμόρφωσης για ολόκληρο το κατανεμημένο σύστημα καθιστά δυνατή την εκτέλεση όλων των στοιχείων σε ένα ελεγχόμενο περιβάλλον ως μέρος της δοκιμής ενοποίησης. Είναι εύκολο να μιμηθεί κανείς, για παράδειγμα, μια κατάσταση όπου ορισμένοι κόμβοι γίνονται προσβάσιμοι.

Μειονεκτήματα και περιορισμοί

Η μεταγλωττισμένη διαμόρφωση διαφέρει από άλλες προσεγγίσεις διαμόρφωσης και ενδέχεται να μην είναι κατάλληλη για ορισμένες εφαρμογές. Παρακάτω είναι μερικά μειονεκτήματα:

  1. Στατική διαμόρφωση. Μερικές φορές χρειάζεται να διορθώσετε γρήγορα τη διαμόρφωση στην παραγωγή, παρακάμπτοντας όλους τους προστατευτικούς μηχανισμούς. Με αυτή την προσέγγιση μπορεί να είναι πιο δύσκολο. Τουλάχιστον, η μεταγλώττιση και η αυτόματη ανάπτυξη θα εξακολουθήσουν να απαιτούνται. Αυτό είναι τόσο χρήσιμο χαρακτηριστικό της προσέγγισης όσο και μειονέκτημα σε ορισμένες περιπτώσεις.
  2. Δημιουργία διαμόρφωσης. Σε περίπτωση που το αρχείο διαμόρφωσης δημιουργείται από ένα αυτόματο εργαλείο, ενδέχεται να απαιτηθούν πρόσθετες προσπάθειες για την ενσωμάτωση του σεναρίου έκδοσης.
  3. Εργαλεία. Επί του παρόντος, τα βοηθητικά προγράμματα και οι τεχνικές που έχουν σχεδιαστεί για να λειτουργούν με τη διαμόρφωση βασίζονται σε αρχεία κειμένου. Δεν θα είναι διαθέσιμα όλα αυτά τα βοηθητικά προγράμματα/τεχνικές σε μια μεταγλωττισμένη διαμόρφωση.
  4. Απαιτείται αλλαγή νοοτροπιών. Οι προγραμματιστές και οι DevOps είναι συνηθισμένοι σε αρχεία κειμένου. Η ίδια η ιδέα της σύνταξης μιας διαμόρφωσης μπορεί να είναι κάπως απροσδόκητη και ασυνήθιστη και να προκαλέσει απόρριψη.
  5. Απαιτείται μια διαδικασία ανάπτυξης υψηλής ποιότητας. Για την άνετη χρήση της μεταγλωττισμένης διαμόρφωσης, είναι απαραίτητη η πλήρης αυτοματοποίηση της διαδικασίας κατασκευής και ανάπτυξης της εφαρμογής (CI/CD). Διαφορετικά θα είναι αρκετά άβολο.

Ας σταθούμε επίσης σε ορισμένους περιορισμούς του υπό εξέταση παραδείγματος που δεν σχετίζονται με την ιδέα μιας μεταγλωττισμένης διαμόρφωσης:

  1. Εάν παρέχουμε περιττές πληροφορίες διαμόρφωσης που δεν χρησιμοποιούνται από τον κόμβο, τότε ο μεταγλωττιστής δεν θα μας βοηθήσει να εντοπίσουμε την υλοποίηση που λείπει. Αυτό το πρόβλημα μπορεί να λυθεί εγκαταλείποντας το μοτίβο κέικ και χρησιμοποιώντας πιο άκαμπτους τύπους, για παράδειγμα, HList ή αλγεβρικούς τύπους δεδομένων (κλάσεις περίπτωσης) για την αναπαράσταση της διαμόρφωσης.
  2. Υπάρχουν γραμμές στο αρχείο διαμόρφωσης που δεν σχετίζονται με την ίδια τη διαμόρφωση: (package, import,Δηλώσεις αντικειμένων· override def's για παραμέτρους που έχουν προεπιλεγμένες τιμές). Αυτό μπορεί να αποφευχθεί εν μέρει εάν εφαρμόσετε το δικό σας DSL. Επιπλέον, άλλοι τύποι διαμόρφωσης (για παράδειγμα, XML) επιβάλλουν επίσης ορισμένους περιορισμούς στη δομή του αρχείου.
  3. Για τους σκοπούς αυτής της ανάρτησης, δεν εξετάζουμε το ενδεχόμενο δυναμικής αναδιαμόρφωσης ενός συμπλέγματος παρόμοιων κόμβων.

Συμπέρασμα

Σε αυτήν την ανάρτηση, εξερευνήσαμε την ιδέα της αναπαράστασης της διαμόρφωσης στον πηγαίο κώδικα χρησιμοποιώντας τις προηγμένες δυνατότητες του συστήματος τύπου Scala. Αυτή η προσέγγιση μπορεί να χρησιμοποιηθεί σε διάφορες εφαρμογές ως αντικατάσταση των παραδοσιακών μεθόδων διαμόρφωσης που βασίζονται σε αρχεία xml ή κειμένου. Παρόλο που το παράδειγμά μας υλοποιείται στο Scala, οι ίδιες ιδέες μπορούν να μεταφερθούν σε άλλες μεταγλωττισμένες γλώσσες (όπως Kotlin, C#, Swift, ...). Μπορείτε να δοκιμάσετε αυτήν την προσέγγιση σε ένα από τα παρακάτω έργα και, εάν δεν λειτουργεί, να προχωρήσετε στο αρχείο κειμένου, προσθέτοντας τα μέρη που λείπουν.

Φυσικά, μια μεταγλωττισμένη διαμόρφωση απαιτεί μια διαδικασία ανάπτυξης υψηλής ποιότητας. Σε αντάλλαγμα, διασφαλίζεται η υψηλή ποιότητα και η αξιοπιστία των διαμορφώσεων.

Η εξεταζόμενη προσέγγιση μπορεί να επεκταθεί:

  1. Μπορείτε να χρησιμοποιήσετε μακροεντολές για να εκτελέσετε ελέγχους χρόνου μεταγλώττισης.
  2. Μπορείτε να εφαρμόσετε ένα DSL για να παρουσιάσετε τη διαμόρφωση με τρόπο που να είναι προσβάσιμος στους τελικούς χρήστες.
  3. Μπορείτε να εφαρμόσετε δυναμική διαχείριση πόρων με αυτόματη ρύθμιση παραμέτρων. Για παράδειγμα, η αλλαγή του αριθμού των κόμβων σε ένα σύμπλεγμα απαιτεί (1) κάθε κόμβος να λάβει μια ελαφρώς διαφορετική διαμόρφωση. (2) ο διαχειριστής συμπλέγματος έλαβε πληροφορίες σχετικά με νέους κόμβους.

Ευχαριστώ

Θα ήθελα να ευχαριστήσω τους Andrei Saksonov, Pavel Popov και Anton Nekhaev για την εποικοδομητική κριτική τους στο προσχέδιο του άρθρου.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο