Hai% habrauser%!
Hari ini saya ingin memberi tahu Anda tentang satu kerangka kerja perangkat lunak molekuler yang bagus menurut saya.

Awalnya, kerangka kerja ini ditulis dalam Node.js, tetapi kemudian muncul pada port dalam bahasa lain seperti Java, Go, Python, dan .NET dan, kemungkinan besar, implementasi lain
akan muncul dalam waktu dekat. Kami telah menggunakannya dalam produksi dalam beberapa produk selama sekitar satu tahun dan sulit untuk menggambarkan dengan kata-kata betapa diberkatinya dia bagi kami setelah menggunakan Seneca dan sepeda kami. Kami mendapatkan semua yang kami butuhkan: mengumpulkan metrik, caching, menyeimbangkan, toleransi kesalahan, pilih transport, validasi parameter, logging, deklarasi metode ringkas, beberapa cara interaksi antar-layanan, mixin, dan banyak lagi. Dan sekarang dalam rangka.
Pendahuluan
Kerangka kerja ini, pada kenyataannya, terdiri dari tiga komponen (pada kenyataannya, tidak, tetapi Anda akan belajar lebih banyak tentang ini di bawah).
Transporter
Bertanggung jawab untuk menemukan layanan dan komunikasi di antara mereka. Ini adalah antarmuka yang dapat Anda implementasikan sendiri jika diinginkan, atau Anda dapat menggunakan implementasi siap pakai yang merupakan bagian dari kerangka itu sendiri. 7 transport tersedia dari kotak: TCP, Redis, AMQP, MQTT, NATS, Streaming NATS, Kafka.
Di sini Anda dapat melihat lebih banyak. Kami menggunakan transportasi Redis, tetapi kami berencana untuk beralih ke TCP dengan keluarnya dari keadaan eksperimental.
Dalam praktiknya, saat menulis kode, kami tidak berinteraksi dengan komponen ini. Anda hanya perlu tahu siapa dia. Transport yang digunakan ditentukan dalam konfigurasi. Jadi, untuk beralih dari satu transportasi ke yang lain, cukup ubah konfigurasi. Itu saja. Sesuatu seperti ini:
Data, secara default, datang dalam format JSON. Tetapi Anda dapat menggunakan apa saja: Avro, MsgPack, Notepack, ProtoBuf, Thrift, dll.
Layanan
Kelas tempat kami mewarisi dari saat menulis layanan microser kami.
Berikut adalah layanan paling sederhana tanpa metode, yang, bagaimanapun, akan terdeteksi oleh layanan lain:
Pialang layanan
Inti dari kerangka kerja.

Membesar-besarkan, kita dapat mengatakan bahwa ini adalah lapisan antara transportasi dan layanan. Ketika suatu layanan ingin berinteraksi dengan layanan lain, layanan itu melakukannya melalui broker (contoh akan diberikan di bawah). Pialang terlibat dalam load balancing (mendukung beberapa strategi, termasuk yang khusus, secara default - round-robin), dengan mempertimbangkan layanan langsung, metode yang tersedia dalam layanan ini, dll. Untuk ini, ServiceBroker menggunakan komponen lain di bawah tenda - Registry, tapi saya tidak akan memikirkannya, kita tidak akan membutuhkannya untuk berkenalan.
Memiliki broker memberi kita hal yang sangat nyaman. Sekarang saya akan mencoba mengklarifikasi, tetapi harus minggir sedikit. Dalam konteks kerangka kerja ada yang namanya simpul. Dalam istilah sederhana, sebuah simpul adalah proses dalam sistem operasi (yaitu, apa yang terjadi ketika kita memasukkan "node index.js" di konsol, misalnya). Setiap node adalah ServiceBroker dengan satu set atau lebih layanan microser. Ya, Anda dengar benar. Kita dapat membangun tumpukan layanan sesuai keinginan kita. Kenapa ini nyaman? Untuk pengembangan, kami memulai satu simpul di mana semua layanan Microsoft diluncurkan sekaligus (masing-masing 1), hanya satu proses dalam sistem dengan kemampuan untuk menghubungkan dengan mudah hotreload, misalnya. Dalam produksi - simpul terpisah untuk setiap instance layanan. Baik, atau campuran, ketika bagian dari layanan di satu node, bagian lain, dan sebagainya (meskipun saya tidak tahu mengapa melakukan ini, hanya untuk memahami bahwa Anda juga bisa melakukan ini).
Inilah yang terlihat seperti index.js kami const { resolve } = require('path'); const { ServiceBroker } = require('moleculer'); const config = require('./moleculer.config.js'); const { SERVICES, NODE_ENV, } = process.env; const broker = new ServiceBroker(config); broker.loadServices( resolve(__dirname, 'services'), SERVICES ? `*/@(${SERVICES.split(',').map(i => i.trim()).join('|')}).service.js` : '*/*.service.js', ); broker.start().then(() => { if (NODE_ENV === 'development') { broker.repl(); } });
Dengan tidak adanya variabel lingkungan, semua layanan dari direktori dimuat, jika tidak oleh mask. Omong-omong, broker.repl () adalah fitur lain yang mudah digunakan dari framework. Ketika memulai dalam mode pengembangan, kami di sana, di konsol, memiliki antarmuka untuk memanggil metode (apa yang akan Anda lakukan, misalnya, melalui tukang pos di layanan mikro Anda yang berkomunikasi melalui http), hanya di sini jauh lebih nyaman: antarmuka berada di konsol yang sama di mana mereka mulai npm.
Interaksi antar layanan
Itu dilakukan dalam tiga cara:
panggilan
Paling umum digunakan. Membuat permintaan, menerima tanggapan (atau kesalahan).
Seperti disebutkan di atas, panggilan secara otomatis seimbang. Kami hanya meningkatkan jumlah instance layanan yang diperlukan, dan kerangka itu sendiri akan melakukan penyeimbangan.

memancarkan
Digunakan ketika kami hanya ingin memberi tahu layanan lain tentang suatu acara, tetapi kami tidak membutuhkan hasilnya.
Layanan lain dapat berlangganan acara ini, dan merespons sesuai. Secara opsional, argumen ketiga, Anda dapat secara eksplisit mengatur layanan yang tersedia untuk menerima acara ini.
Poin penting adalah bahwa acara tersebut hanya akan menerima satu contoh dari setiap jenis layanan, yaitu jika kita memiliki 10 layanan "surat" dan 5 "berlangganan" yang berlangganan acara ini, maka pada kenyataannya hanya 2 salinan yang akan menerimanya - satu "surat" dan satu "berlangganan". Secara skematis terlihat seperti ini:

disiarkan
Sama seperti memancarkan, tetapi tanpa batasan. Semua 10 surat dan 5 layanan berlangganan akan menangkap acara ini.
Validasi parameter
Secara default,
validator tercepat digunakan untuk memvalidasi parameter, tampaknya sangat cepat. Tetapi tidak ada yang mencegah menggunakan yang lain, misalnya, joi yang sama, jika Anda memerlukan validasi yang lebih maju.
Ketika kami menulis layanan, kami mewarisi dari Layanan kelas dasar, mendeklarasikan metode dengan logika bisnis di dalamnya, tetapi metode ini adalah "pribadi", mereka tidak dapat dipanggil dari luar (dari layanan lain) sampai kami secara eksplisit ingin, mendeklarasikannya dalam bagian tindakan khusus selama inisialisasi layanan (metode layanan publik dalam konteks kerangka kerja disebut tindakan).
Contoh deklarasi metode dengan validasi module.exports = class JobService extends Service { constructor(broker) { super(broker); this.parseServiceSchema({ name: 'job', actions: { update: { params: { id: { type: 'number', convert: true }, name: { type: 'string', empty: false, optional: true }, data: { type: 'object', optional: true }, }, async handler(ctx) { return this.update(ctx.params); }, }, }, }); } async update({ id, name, data }) {
Mixin
Digunakan, misalnya, untuk menginisialisasi koneksi basis data. Hindari duplikasi kode dari layanan ke layanan.
Contoh mixin untuk menginisialisasi koneksi ke Redis const Redis = require('ioredis'); module.exports = ({ key = 'redis', options } = {}) => ({ settings: { [key]: options, }, created() { this[key] = new Redis(this.settings[key]); }, async started() { await this[key].connect(); }, stopped() { this[key].disconnect(); }, });
Menggunakan mixin dalam layanan const { Service, Errors } = require('moleculer'); const redis = require('../../mixins/redis'); const server = require('../../mixins/server'); const router = require('./router'); const { REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, } = process.env; const redisOpts = { host: REDIS_HOST, port: REDIS_PORT, password: REDIS_PASSWORD, lazyConnect: true, }; module.exports = class AuthService extends Service { constructor(broker) { super(broker); this.parseServiceSchema({ name: 'auth', mixins: [redis({ options: redisOpts }), server({ router })], }); } }
Caching
Metode panggilan (tindakan) dapat di-cache dalam beberapa cara: LRU, Memory, Redis. Secara opsional, Anda dapat menentukan dengan mana panggilan kunci akan di-cache (secara default, objek hash digunakan sebagai kunci caching) dan dengan TTL yang mana.
Contoh Pernyataan Metode Cached module.exports = class InventoryService extends Service { constructor(broker) { super(broker); this.parseServiceSchema({ name: 'inventory', actions: { getInventory: { params: { steamId: { type: 'string', pattern: /^76\d{15}$/ }, appId: { type: 'number', integer: true }, contextId: { type: 'number', integer: true }, }, cache: { keys: ['steamId', 'appId', 'contextId'], ttl: 15, }, async handler(ctx) { return true; }, }, }, }); }
Metode caching diatur melalui konfigurasi ServiceBroker.
Penebangan
Di sini, bagaimanapun, semuanya juga cukup sederhana. Ada built-in logger yang cukup bagus yang menulis ke konsol, dimungkinkan untuk menentukan pemformatan kustom. Tidak ada yang mencegah untuk mencuri logger populer lainnya, baik itu winston atau bunyan. Manual terperinci ada di
dokumentasi . Secara pribadi, kami menggunakan built-in logger, formatter khusus untuk beberapa baris kode yang spam ke konsol JSON hanya dipotong di prod, setelah itu mereka masuk ke graylog menggunakan driver log buruh pelabuhan.
Metrik
Jika diinginkan, Anda dapat mengumpulkan metrik untuk setiap metode dan melacak semuanya di beberapa zipkin. Berikut
adalah daftar lengkap eksportir yang tersedia. Saat ini ada lima di antaranya: Zipkin, Jaeger, Prometheus, Elastic, Console. Ini dikonfigurasi, seperti caching, ketika mendeklarasikan metode (aksi).
Contoh visualisasi untuk bundel elasticsearch + kibana menggunakan modul
elastic-apm-node dapat dilihat di
tautan ini di Github.
Cara termudah, tentu saja, adalah dengan menggunakan opsi konsol. Ini terlihat seperti ini:

Toleransi kesalahan
Kerangka kerja ini memiliki pemutus arus bawaan, yang dikendalikan melalui pengaturan ServiceBroker. Jika ada layanan gagal dan jumlah kegagalan ini melebihi batas tertentu, maka itu akan ditandai sebagai tidak sehat, permintaan untuk itu akan sangat terbatas sampai berhenti membuat kesalahan.
Sebagai bonus, ada juga fallback yang dapat disesuaikan secara individual untuk setiap metode (tindakan), jika kami berasumsi bahwa metode tersebut mungkin gagal dan, misalnya, mengirim data cache atau sebuah rintisan.
Kesimpulan
Pengenalan kerangka kerja ini bagi saya menjadi menghirup udara segar, yang menyelamatkan sejumlah besar bunts (kecuali kenyataan bahwa arsitektur microservice adalah salah satu bunt besar) dan bersepeda, membuat penulisan microservice berikutnya sederhana dan transparan. Tidak ada yang berlebihan di dalamnya, itu sederhana dan sangat fleksibel, dan Anda dapat menulis layanan pertama dalam satu atau dua jam setelah membaca dokumentasi. Saya akan senang jika materi ini bermanfaat bagi Anda dan dalam proyek Anda berikutnya, Anda akan ingin mencoba keajaiban ini, seperti yang kami lakukan (dan belum menyesalinya). Baik untuk semua!
Juga, jika Anda tertarik dengan kerangka kerja ini, maka bergabunglah dengan obrolan di Telegram -
@moleculerchat