ViennaNET: sekumpulan perpustakaan untuk backend. Bagian 2

Komunitas pengembang Raiffeisenbank .NET terus meninjau secara singkat konten ViennaNET. Tentang bagaimana dan mengapa kita sampai pada hal ini, Anda dapat membaca bagian pertama.

Dalam artikel ini, kita akan membahas pustaka yang belum dipertimbangkan untuk bekerja dengan transaksi terdistribusi, antrian, dan database, yang dapat ditemukan di repositori GitHub kami (sumber ada di sini), dan Paket Nuget di sini.

ViennaNET: sekumpulan perpustakaan untuk backend. Bagian 2

ViennaNET.Saga

Ketika sebuah proyek beralih ke DDD dan arsitektur layanan mikro, maka ketika logika bisnis didistribusikan ke berbagai layanan, muncul masalah terkait kebutuhan untuk menerapkan mekanisme transaksi terdistribusi, karena banyak skenario sering kali memengaruhi beberapa domain sekaligus. Anda dapat mengenal mekanisme tersebut secara lebih rinci, misalnya, dalam buku "Pola Layanan Mikro", Chris Richardson.

Dalam proyek kami, kami telah menerapkan mekanisme yang sederhana namun berguna: sebuah saga, atau lebih tepatnya saga berbasis orkestrasi. Esensinya adalah sebagai berikut: ada skenario bisnis tertentu di mana perlu untuk melakukan operasi secara berurutan di layanan yang berbeda, dan jika ada masalah yang muncul pada langkah mana pun, maka perlu untuk memanggil prosedur rollback untuk semua langkah sebelumnya, di mana itu adalah asalkan. Jadi, di akhir kisah ini, terlepas dari keberhasilannya, kami menerima data yang konsisten di semua domain.

Implementasi kami masih dalam bentuk dasar dan tidak terikat pada penggunaan metode interaksi apa pun dengan layanan lain. Ini tidak sulit untuk digunakan: cukup buat turunan dari kelas abstrak dasar SagaBase<T>, di mana T adalah kelas konteks Anda di mana Anda dapat menyimpan data awal yang diperlukan agar saga tersebut berfungsi, serta beberapa hasil antara. Contoh konteks akan diteruskan ke semua langkah selama eksekusi. Saga sendiri merupakan kelas stateless, sehingga instance dapat ditempatkan di DI sebagai Singleton untuk mendapatkan dependensi yang diperlukan.

Contoh iklan:

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

Contoh panggilan:

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

Contoh lengkap implementasi yang berbeda dapat dilihat di sini dan dalam perakitan dengan tes.

ViennaNET.Orm.*

Satu set perpustakaan untuk bekerja dengan berbagai database melalui Nhibernate. Kami menggunakan pendekatan DB-First menggunakan Liquibase, jadi hanya ada fungsionalitas untuk bekerja dengan data dalam database yang sudah jadi.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – rakitan utama yang masing-masing berisi antarmuka dasar dan implementasinya. Mari kita lihat isinya lebih detail.

Antarmuka. IEntityFactoryService dan implementasinya EntityFactoryService adalah titik awal utama untuk bekerja dengan database, karena Unit Kerja, repositori untuk bekerja dengan entitas tertentu, serta pelaksana perintah dan kueri SQL langsung dibuat di sini. Terkadang lebih mudah untuk membatasi kemampuan suatu kelas untuk bekerja dengan database, misalnya, untuk menyediakan kemampuan hanya membaca data. Untuk kasus seperti itu IEntityFactoryService ada leluhur - antarmuka IEntityRepositoryFactory, yang hanya mendeklarasikan metode untuk membuat repositori.

Untuk mengakses database secara langsung digunakan mekanisme penyedia. Setiap DBMS yang kami gunakan di tim kami memiliki implementasinya sendiri: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Pada saat yang sama, beberapa penyedia dapat didaftarkan dalam satu aplikasi pada saat yang sama, yang memungkinkan, misalnya, dalam kerangka satu layanan, tanpa biaya untuk memodifikasi infrastruktur, untuk melakukan migrasi langkah demi langkah dari satu DBMS ke DBMS lainnya. Mekanisme untuk memilih koneksi yang diperlukan dan, oleh karena itu, penyedia untuk kelas entitas tertentu (di mana pemetaan ke tabel database ditulis) diimplementasikan melalui pendaftaran entitas di kelas BoundedContext (berisi metode untuk mendaftarkan entitas domain) atau penerusnya ApplicationContext (berisi metode untuk mendaftarkan entitas aplikasi, permintaan dan perintah langsung), di mana pengidentifikasi koneksi dari konfigurasi diterima sebagai argumen:

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

Contoh Konteks Aplikasi:

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

Jika ID koneksi tidak ditentukan, maka koneksi bernama “default” akan digunakan.

Pemetaan langsung entitas ke tabel database diimplementasikan menggunakan alat NHibernate standar. Anda dapat menggunakan deskripsi melalui file xml dan melalui kelas. Untuk memudahkan penulisan repositori rintisan di Unit test, terdapat perpustakaan ViennaNET.TestUtils.Orm.

Contoh lengkap penggunaan ViennaNET.Orm.* dapat ditemukan di sini.

ViennaNET.Pesan.*

Satu set perpustakaan untuk bekerja dengan antrian.

Untuk bekerja dengan antrian, pendekatan yang sama dipilih seperti pada berbagai DBMS, yaitu pendekatan terpadu semaksimal mungkin dalam hal bekerja dengan perpustakaan, terlepas dari manajer antrian yang digunakan. Perpustakaan ViennaNET.Messaging justru bertanggung jawab atas penyatuan ini, dan ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue berisi implementasi adaptor untuk IBM MQ, RabbitMQ, dan Kafka.

Saat bekerja dengan antrian, ada dua proses: menerima pesan dan mengirimnya.

Pertimbangkan untuk menerima. Ada 2 pilihan di sini: untuk mendengarkan terus menerus dan untuk menerima satu pesan. Untuk terus mendengarkan antrean, Anda harus menjelaskan terlebih dahulu kelas prosesor yang diwarisinya IMessageProcessor, yang akan bertanggung jawab untuk memproses pesan masuk. Selanjutnya harus “ditautkan” ke antrian tertentu, hal ini dilakukan melalui registrasi IQueueReactorFactory menunjukkan pengidentifikasi antrian dari konfigurasi:

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

Contoh mulai mendengarkan:

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

Kemudian, ketika layanan dimulai dan metode dipanggil untuk mulai mendengarkan, semua pesan dari antrian yang ditentukan akan masuk ke prosesor yang sesuai.

Untuk menerima satu pesan dalam antarmuka pabrik IMessagingComponentFactory ada metode CreateMessageReceiveryang akan membuat penerima menunggu pesan dari antrian yang ditentukan padanya:

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

Untuk mengirim pesan Anda perlu menggunakan hal yang sama IMessagingComponentFactory dan buat pengirim pesan:

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

Ada tiga opsi siap pakai untuk membuat serialisasi dan deserialisasi pesan: hanya teks, XML, dan JSON, tetapi jika perlu, Anda dapat dengan mudah membuat implementasi antarmuka sendiri IMessageSerializer и IMessageDeserializer.

Kami telah mencoba mempertahankan kemampuan unik dari setiap pengelola antrean, misalnya. ViennaNET.Messaging.MQSeriesQueue memungkinkan Anda mengirim tidak hanya pesan teks, tetapi juga pesan byte, dan ViennaNET.Messaging.RabbitMQQueue mendukung routing dan antrian on-the-fly. Pembungkus adaptor kami untuk RabbitMQ juga mengimplementasikan beberapa kemiripan RPC: kami mengirim pesan dan menunggu respons dari antrian sementara khusus, yang dibuat hanya untuk satu pesan respons.

di sini adalah contoh penggunaan antrian dengan nuansa koneksi dasar.

ViennaNET.Konteks Panggilan

Kami menggunakan antrian tidak hanya untuk integrasi antar sistem yang berbeda, tetapi juga untuk komunikasi antar layanan mikro dari aplikasi yang sama, misalnya, dalam sebuah saga. Hal ini menyebabkan kebutuhan untuk mengirimkan bersama dengan pesan data tambahan seperti login pengguna, pengidentifikasi permintaan untuk logging end-to-end, alamat IP sumber dan data otorisasi. Untuk mengimplementasikan penerusan data ini, kami mengembangkan perpustakaan ViennaNET.CallContext, yang memungkinkan Anda menyimpan data dari permintaan yang memasuki layanan. Dalam hal ini, bagaimana permintaan dibuat, melalui antrian atau melalui Http, tidak menjadi masalah. Kemudian, sebelum mengirim permintaan atau pesan keluar, data diambil dari konteksnya dan ditempatkan di header. Dengan demikian, layanan berikutnya menerima data tambahan dan mengelolanya dengan cara yang sama.

Terima kasih atas perhatian Anda, kami menantikan komentar dan permintaan tarik Anda!

Sumber: www.habr.com

Tambah komentar