これは、「 RabbitMQを介した通信を行うNode.jsで最初のマイクロサービスを作成しています」という記事の続きであり、Habrのユーザーに好評でした。
この記事では、マイクロサービスが分離されたままになるように、マイクロサービス間で適切に通信する方法について説明します。
やってはいけない方法
マイクロサービス間で通信する必要があるのはなぜですか? 1つのデータベースを使用し、そこからあなたが望むものを読んでください-ビジネス何か!
いいえ、できません。 マイクロサービスの概念は、それらが互いに分離されていることであり、誰も(実際には)何も知らないということです。 おそらく、将来、システムが成長し始めたら、機能を拡張する必要があり、マイクロサービス間で通信する必要があります。たとえば、ユーザーが製品を購入したため、販売についての通知を販売者に送信する必要があります。
断熱効果
信頼性
いくつかのコントローラーがあるモノリシックアプリケーションがあるとします。
- 製品
- 割引き
- ブログ
- ユーザー
ある晴れた日、私たちのデータベースは落ちています。現在、製品、割引、ブログ投稿、ユーザーを入手できません。 サイトは完全に利用できず、顧客はログインできず、ビジネスは利益を失っています。
マイクロサービスアーキテクチャで何が起こりますか?
別のユニバースでは、ユーザーのマイクロサービスデータベースが同じ日に落ち、アクセスできなくなります。ユーザーはログアウト、登録、ログインできません。 すべてが悪く、ビジネスも利益を失っているように見えますが、そうではありません。潜在的な購入者は入手可能な商品を見て、会社のブログを読んで、割引を見つけることができます。
各マイクロサービスには独自のデータベースがあるため、副作用ははるかに少なくなります。
これは段階的劣化と呼ばれます。
抽象化
大規模なアプリケーションでは、いくつかの小さなミドルウェアを変更するとある種のコントローラーが破損する可能性があるため、1つのタスクに集中することは非常に困難です。 新しいクライアントをredisに使用したい-いいえ、できません。3年前に作成したコントローラーはバージョン0.1.0を使用しています。 Node.js 10の新機能を最終的に活用したいですか? それとも12? 申し訳ありませんが、モノリスはバージョン6を使用しています。
コミュニケーション方法
「ユーザーが製品を購入し、販売者に販売通知を送信する」という例について話し始めたので、それを実装します。
スキームは次のとおりです。
- ユーザーは、リンクでサービスを購入するリクエストをマイクロサービス市場に送信します/ market / buy /:id
- フラグは、製品が販売されているデータベースに書き込まれます
- マイクロサービス市場から、マイクロサービス通知にリクエストが送信され、クライアントはWebSocketを介して接続されます
- マイクロサービス通知は、物の販売に関するメッセージを売り手に送信します
MicroMQをインストールする
$ npm i micromq@1 -S
ゲートウェイを書く
const Gateway = require('micromq/gateway'); // const gateway = new Gateway({ microservices: ['market'], rabbit: { url: process.env.RABBIT_URL, }, }); // market gateway.post('/market/buy/:id', (req, res) => res.delegate('market')); // gateway.listen(process.env.PORT);
ゲートウェイは1つのエンドポイントのみで構成されていますが、これはたとえばトレーニングやトレーニングには十分です。
マイクロサービス通知の作成
const MicroMQ = require('micromq'); const WebSocket = require('ws'); // const app = new MicroMQ({ name: 'notifications', rabbit: { url: process.env.RABBIT_URL, }, }); // const ws = new WebSocket.Server({ port: process.env.PORT, }); // const clients = new Map(); // ws.on('connection', (connection) => { // connection.on('message', (message) => { // , . // try/catch, json! const { event, data } = JSON.parse(message); // 'authorize' if (event === 'authorize' && data.userId) { // clients.set(data.userId, connection); } }); }); // , // ! ws.on('close', ...); // notify, app.action('notify', (meta) => { // , 400 if (!meta.userId || !meta.text) { return [400, { error: 'Bad data' }]; } // const connection = clients.get(meta.userId); // , 404 if (!connection) { return [404, { error: 'User not found' }]; } // connection.send(meta.text); // 200 return { ok: true }; }); // app.start();
ここでは、Webソケットサーバーとマイクロサービスを同時に上げて、WebソケットとRabbitMQの両方の要求を受信します。
スキームは次のとおりです。
- ユーザーがWebソケットサーバーに接続します
- ユーザーは、内部に自分のuserIdで
authorize
イベントを送信してログインします - 通知を送信できるように、ユーザーの接続を維持します
- ユーザーに通知を送信する必要があるイベントがRabbitMQに到着します
- 受信データの有効性を確認する
- ユーザー接続を取得する
- 通知を送信
マイクロサービス市場の作成
const MicroMQ = require('micromq'); const { Items } = require('./api/mongodb'); // const app = new MicroMQ({ name: 'market', rabbit: { url: process.env.RABBIT_URL, }, }); // app.post('/market/buy/:id', async (req, res) => { const { id } = req.params; // const item = await Items.findOne({ id, isSold: false }); // , 404 if (!item) { res.status(404).json({ error: 'Item not found', }); return; } // , , await Items.updateOne({ id, }, { $set: { isSold: true, }, }); // , req.app.ask('notifications', { server: { action: 'notify', meta: { userId: item.sellerId, text: JSON.stringify({ event: 'notification', data: { text: `Item #${id} was sold!`, }, }), }, }, }) // , .catch(err => console.log('Cannot send message via notifications microservice', err)); // , res.json({ ok: true, }); }); // app.start();
スキームは次のとおりです。
- アイテムの購入に関するユーザーリクエストを受け取りました
- 適切なIDのアイテムを探しており、まだ販売されていないことを確認します
- アイテムを販売済みとしてマークする
- バックグラウンドでの販売に関する通知を販売者に送信します
- クライアントに対応します
確認する
- 3つのプロセスを開始します
- POST / market / buy / 1を送信します
- 応答
{ ok: true }
を取得し{ ok: true }
- 売り手は通知を受け取ります
$ PORT=9000 node ./src/gateway.js $ PORT=9001 node ./src/notifications.js $ MONGODB_URL=mongodb://localhost:27017/my-super-microservice node ./src/market.js