ViennaNET: ένα σύνολο βιβλιοθηκών για το backend. Μέρος 2ο

Η κοινότητα προγραμματιστών Raiffeisenbank .NET συνεχίζει να εξετάζει εν συντομία τα περιεχόμενα του ViennaNET. Σχετικά με το πώς και γιατί καταλήξαμε σε αυτό, μπορείτε να διαβάσετε το πρώτο μέρος.

Σε αυτό το άρθρο, θα περάσουμε από βιβλιοθήκες που δεν έχουν ακόμη εξεταστεί για εργασία με κατανεμημένες συναλλαγές, ουρές και βάσεις δεδομένων, τις οποίες μπορείτε να βρείτε στο αποθετήριο GitHub (οι πηγές είναι εδώ), και Πακέτα Nuget εδώ.

ViennaNET: ένα σύνολο βιβλιοθηκών για το backend. Μέρος 2ο

ViennaNET.Sagas

Όταν ένα έργο μεταβαίνει σε αρχιτεκτονική DDD και microservice, τότε όταν η επιχειρηματική λογική κατανέμεται σε διαφορετικές υπηρεσίες, προκύπτει ένα πρόβλημα που σχετίζεται με την ανάγκη εφαρμογής ενός μηχανισμού κατανεμημένων συναλλαγών, επειδή πολλά σενάρια συχνά επηρεάζουν πολλούς τομείς ταυτόχρονα. Μπορείτε να εξοικειωθείτε με τέτοιους μηχανισμούς με περισσότερες λεπτομέρειες, για παράδειγμα, στο βιβλίο «Microservices Patterns», Chris Richardson.

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

Η εφαρμογή μας εξακολουθεί να γίνεται στη βασική της μορφή και δεν συνδέεται με τη χρήση οποιωνδήποτε μεθόδων αλληλεπίδρασης με άλλες υπηρεσίες. Δεν είναι δύσκολο να το χρησιμοποιήσετε: απλώς δημιουργήστε έναν απόγονο της βασικής αφηρημένης κλάσης SagaBase<T>, όπου το T είναι η κλάση περιβάλλοντος στην οποία μπορείτε να αποθηκεύσετε τα αρχικά δεδομένα που είναι απαραίτητα για να λειτουργήσει το έπος, καθώς και ορισμένα ενδιάμεσα αποτελέσματα. Το στιγμιότυπο περιβάλλοντος θα μεταφερθεί σε όλα τα βήματα κατά την εκτέλεση. Το ίδιο το Saga είναι μια τάξη ανιθαγενών, επομένως το παράδειγμα μπορεί να τοποθετηθεί στο DI ως Singleton για να ληφθούν οι απαραίτητες εξαρτήσεις.

Παράδειγμα διαφήμισης:

public class ExampleSaga : SagaBase<ExampleContext>
{
  public ExampleSaga()
  {
    Step("Step 1")
      .WithAction(c => ...)
      .WithCompensation(c => ...);
	
    AsyncStep("Step 2")
      .WithAction(async c => ...);
  }
}

Παράδειγμα κλήσης:

var saga = new ExampleSaga();
var context = new ExampleContext();
await saga.Execute(context);

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

ViennaNET.Orm.*

Ένα σύνολο βιβλιοθηκών για εργασία με διάφορες βάσεις δεδομένων μέσω του Nhibernate. Χρησιμοποιούμε την προσέγγιση DB-First χρησιμοποιώντας το Liquibase, επομένως υπάρχει μόνο λειτουργικότητα για εργασία με δεδομένα σε έτοιμη βάση δεδομένων.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – κύρια συγκροτήματα που περιέχουν βασικές διεπαφές και τις υλοποιήσεις τους, αντίστοιχα. Ας δούμε το περιεχόμενό τους με περισσότερες λεπτομέρειες.

διεπαφή IEntityFactoryService και την εφαρμογή του EntityFactoryService αποτελούν το κύριο σημείο εκκίνησης για την εργασία με τη βάση δεδομένων, αφού εδώ δημιουργούνται η Unit of Work, αποθετήρια για εργασία με συγκεκριμένες οντότητες, καθώς και εκτελεστές εντολών και άμεσα ερωτήματα SQL. Μερικές φορές είναι βολικό να περιορίζονται οι δυνατότητες μιας κλάσης για εργασία με μια βάση δεδομένων, για παράδειγμα, να παρέχεται η δυνατότητα μόνο ανάγνωσης δεδομένων. Για τέτοιες περιπτώσεις IEntityFactoryService υπάρχει πρόγονος - διεπαφή IEntityRepositoryFactory, το οποίο δηλώνει μόνο μια μέθοδο για τη δημιουργία αποθετηρίων.

Για την άμεση πρόσβαση στη βάση δεδομένων, χρησιμοποιείται ο μηχανισμός παροχής. Κάθε DBMS που χρησιμοποιούμε στις ομάδες μας έχει τη δική του υλοποίηση: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Ταυτόχρονα, πολλοί πάροχοι μπορούν να εγγραφούν ταυτόχρονα σε μία εφαρμογή, γεγονός που επιτρέπει, για παράδειγμα, στο πλαίσιο μιας υπηρεσίας, χωρίς κανένα κόστος για την τροποποίηση της υποδομής, να πραγματοποιήσει μια βήμα προς βήμα μετάβαση από το ένα DBMS στο άλλο. Ο μηχανισμός για την επιλογή της απαιτούμενης σύνδεσης και, επομένως, του παρόχου για μια συγκεκριμένη κλάση οντοτήτων (για την οποία είναι γραμμένη η αντιστοίχιση σε πίνακες βάσης δεδομένων) υλοποιείται μέσω της εγγραφής της οντότητας στην κλάση BoundedContext (περιέχει μια μέθοδο για την καταχώριση οντοτήτων τομέα) ή του διαδόχου της ApplicationContext (περιέχει μεθόδους για την καταχώριση οντοτήτων εφαρμογής , άμεσες αιτήσεις και εντολές), όπου το αναγνωριστικό σύνδεσης από τη διαμόρφωση γίνεται δεκτό ως όρισμα:

"db": [
  {
    "nick": "mssql_connection",
    "dbServerType": "MSSQL",
    "ConnectionString": "...",
    "useCallContext": true
  },
  {
    "nick": "oracle_connection",
    "dbServerType": "Oracle",
    "ConnectionString": "..."
  }
],

Παράδειγμα ApplicationContext:

internal sealed class DbContext : ApplicationContext
{
  public DbContext()
  {
    AddEntity<SomeEntity>("mssql_connection");
    AddEntity<MigratedSomeEntity>("oracle_connection");
    AddEntity<AnotherEntity>("oracle_connection");
  }
}

Εάν το αναγνωριστικό σύνδεσης δεν έχει καθοριστεί, τότε θα χρησιμοποιηθεί η σύνδεση με το όνομα "προεπιλογή".

Η απευθείας αντιστοίχιση οντοτήτων σε πίνακες βάσεων δεδομένων υλοποιείται χρησιμοποιώντας τυπικά εργαλεία NHibernate. Μπορείτε να χρησιμοποιήσετε την περιγραφή τόσο μέσω αρχείων xml όσο και μέσω κλάσεων. Για εύκολη εγγραφή αποθετηρίων στέλεχος σε δοκιμές μονάδας, υπάρχει μια βιβλιοθήκη ViennaNET.TestUtils.Orm.

Μπορείτε να βρείτε πλήρη παραδείγματα χρήσης του ViennaNET.Orm.* εδώ.

ViennaNET.Messaging.*

Ένα σύνολο βιβλιοθηκών για εργασία με ουρές.

Για την εργασία με ουρές, επιλέχθηκε η ίδια προσέγγιση με διάφορα DBMS, δηλαδή η μέγιστη δυνατή ενοποιημένη προσέγγιση όσον αφορά την εργασία με τη βιβλιοθήκη, ανεξάρτητα από τον διαχειριστή ουράς που χρησιμοποιείται. Βιβλιοθήκη ViennaNET.Messaging είναι ακριβώς υπεύθυνος για αυτή την ενοποίηση, και ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue περιέχουν υλοποιήσεις προσαρμογέων για τα IBM MQ, RabbitMQ και Kafka, αντίστοιχα.

Όταν εργάζεστε με ουρές, υπάρχουν δύο διαδικασίες: η λήψη ενός μηνύματος και η αποστολή του.

Σκεφτείτε να λάβετε. Υπάρχουν 2 επιλογές εδώ: για συνεχή ακρόαση και για λήψη ενός μόνο μηνύματος. Για να ακούτε συνεχώς την ουρά, πρέπει πρώτα να περιγράψετε την κλάση επεξεργαστή που κληρονομήθηκε IMessageProcessor, το οποίο θα είναι υπεύθυνο για την επεξεργασία του εισερχόμενου μηνύματος. Στη συνέχεια, πρέπει να «συνδεθεί» σε μια συγκεκριμένη ουρά, αυτό γίνεται μέσω εγγραφής στο IQueueReactorFactory υποδεικνύοντας το αναγνωριστικό ουράς από τη διαμόρφωση:

"messaging": {
    "ApplicationName": "MyApplication"
},
"rabbitmq": {
    "queues": [
      {
        "id": "myQueue",
        "queuename": "lalala",
        ...
      }
    ]
},

Παράδειγμα έναρξης ακρόασης:

_queueReactorFactory.Register<MyMessageProcessor>("myQueue");
var queueReactor = queueReactorFactory.CreateQueueReactor("myQueue");
queueReactor.StartProcessing();

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

Για να λάβετε ένα μόνο μήνυμα σε μια εργοστασιακή διεπαφή IMessagingComponentFactory υπάρχει μέθοδος CreateMessageReceiverπου θα δημιουργήσει έναν παραλήπτη που περιμένει ένα μήνυμα από την ουρά που έχει καθοριστεί σε αυτόν:

using (var receiver = _messagingComponentFactory.CreateMessageReceiver<TestMessage>("myQueue"))
{
    var message = receiver.Receive();
}

Για να στείλετε ένα μήνυμα πρέπει να χρησιμοποιήσετε το ίδιο IMessagingComponentFactory και δημιουργήστε έναν αποστολέα μηνύματος:

using (var sender = _messagingComponentFactory.CreateMessageSender<MyMessage>("myQueue"))
{
    sender.SendMessage(new MyMessage { Value = ...});
}

Υπάρχουν τρεις έτοιμες επιλογές για τη σειριοποίηση και την αποσειροποίηση ενός μηνύματος: μόνο κείμενο, XML και JSON, αλλά εάν είναι απαραίτητο, μπορείτε εύκολα να κάνετε τις δικές σας εφαρμογές διεπαφής IMessageSerializer и IMessageDeserializer.

Προσπαθήσαμε να διατηρήσουμε τις μοναδικές δυνατότητες κάθε διαχειριστή ουράς, π.χ. ViennaNET.Messaging.MQSeriesQueue σας επιτρέπει να στέλνετε όχι μόνο μηνύματα κειμένου, αλλά και byte, και ViennaNET.Messaging.RabbitMQQueue υποστηρίζει τη δρομολόγηση και την on-the-fly ουρά. Το περιτύλιγμα προσαρμογέα μας για το RabbitMQ εφαρμόζει επίσης κάποια ομοιότητα του RPC: στέλνουμε ένα μήνυμα και περιμένουμε μια απάντηση από μια ειδική προσωρινή ουρά, η οποία δημιουργείται μόνο για ένα μήνυμα απόκρισης.

Εδώ ένα παράδειγμα χρήσης ουρών με βασικές αποχρώσεις σύνδεσης.

ViennaNET.CallContext

Χρησιμοποιούμε ουρές όχι μόνο για ενοποίηση μεταξύ διαφορετικών συστημάτων, αλλά και για επικοινωνία μεταξύ μικροϋπηρεσιών της ίδιας εφαρμογής, για παράδειγμα, μέσα σε ένα έπος. Αυτό οδήγησε στην ανάγκη μετάδοσης μαζί με το μήνυμα βοηθητικών δεδομένων όπως η σύνδεση χρήστη, το αναγνωριστικό αιτήματος για καταγραφή από άκρο σε άκρο, η διεύθυνση IP προέλευσης και τα δεδομένα εξουσιοδότησης. Για την υλοποίηση της προώθησης αυτών των δεδομένων, αναπτύξαμε μια βιβλιοθήκη ViennaNET.CallContext, το οποίο σας επιτρέπει να αποθηκεύετε δεδομένα από ένα αίτημα που εισέρχεται στην υπηρεσία. Σε αυτήν την περίπτωση, το πώς έγινε το αίτημα, μέσω ουράς ή μέσω Http, δεν έχει σημασία. Στη συνέχεια, πριν από την αποστολή του εξερχόμενου αιτήματος ή μηνύματος, τα δεδομένα λαμβάνονται από το περιβάλλον και τοποθετούνται στις κεφαλίδες. Έτσι, η επόμενη υπηρεσία λαμβάνει τα βοηθητικά δεδομένα και τα διαχειρίζεται με τον ίδιο τρόπο.

Σας ευχαριστούμε για την προσοχή σας, περιμένουμε τα σχόλιά σας και τα αιτήματά σας!

Πηγή: www.habr.com

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