Node.jsマイクロサービスにおけるモジュールフェデレーションによるシームレスなコード共有
Daniel Hayes
Full-Stack Engineer · Leapcell

はじめに
現代のソフトウェア開発の進化する状況において、マイクロサービスアーキテクチャは、独立したデプロイ可能性、スケーラビリティ、技術的多様性といった数多くの利点を提供する、主要なパターンとして登場しました。しかし、個別のサービスの普及に伴い、新たな課題が頻繁に発生します。それは、バージョン管理の課題や肥大化した依存関係につながることが多い従来のパッケージ管理戦略に頼ることなく、これらのサービス間で共通のコード、ユーティリティ、またはコンポーネントを効果的に共有するにはどうすればよいかということです。これは特にNode.js環境において重要であり、共有コードベースは、定型的なコードを大幅に削減し、一貫性を向上させ、開発サイクルを加速することができます。この課題に対処するために、当初はフロントエンドの世界で普及していたモジュールフェデレーションは、バックエンドにおいても動的なコード共有を可能にする革新的で魅力的なソリューションを提供します。この記事では、Node.jsマイクロサービス内でのモジュールフェデレーションの実用的な応用について掘り下げ、サービス間のコードコラボレーションをどのように再定義できるかを説明します。
Node.jsマイクロサービスのためのモジュールフェデレーションの理解
実装の詳細に入る前に、モジュールフェデレーションとそのNode.jsコンテキストにおける関連性に関するいくつかのコアコンセプトを明確にしましょう。
コアコンセプト
- モジュールフェデレーション: Webpackの機能で、JavaScriptアプリケーションが別のアプリケーションからコードを動的にロードし、依存関係として扱うことを可能にします。従来の依存関係管理とは異なり、フェデレートされたモジュールは、コンシューマーアプリケーション内に事前にバンドルされるわけではありません。代わりに、実行時に公開され、消費されます。この動的なリンク機能がその強力さの鍵です。
- ホスト(コンテナ): 他のアプリケーションによって公開されたモジュールを消費するアプリケーション。マイクロサービスの設定では、他のサービスから機能を使用する必要があるサービスがホストとして機能します。
- リモート(公開モジュール): 他のアプリケーションが消費するために一部のモジュールを公開するアプリケーション。共有ユーティリティまたはロジックを提供するマイクロサービスはリモートとなります。
- 共有モジュール: 複数のフェデレートされたアプリケーションに共通する依存関係(
lodash
、express
、uuid
など)。モジュールフェデレーションのshared
設定により、これらの依存関係は一度だけロードされることが保証され、同じライブラリの複数のインスタンスを回避することでパフォーマンスと一貫性が向上します。これは、Node.jsサービスが依存関係を効果的に管理するために不可欠です。
操作の原則
その核心において、モジュールフェデレーションは、「remoteEntry.js」という特別なバンドルを各リモートアプリケーションのために生成することによって機能します。このファイルはマニフェストとして機能し、公開されたモジュールに関するメタデータとそれらをロードするためのブートストラップスクリプトを含みます。ホストがリモートモジュールを消費する必要がある場合、リモートサービス(Node.js設定では通常HTTP経由)からこのremoteEntry.js
ファイルを動的に取得し、そのブートストラップロジックを実行して要求されたモジュールをロードします。その後、Webpackのランタイムグルーがこのモジュールをホストアプリケーションのモジュールグラフに統合し、ローカルにインストールされた依存関係であるかのように利用可能にします。
Node.jsマイクロサービスの場合、これは、あるサービスがユーティリティ関数のセット、APIクライアント、あるいはミドルウェアスタック全体を公開でき、他のサービスが実行時に直接それを消費できることを意味します。プライベートnpmパッケージを公開する必要もなく、共有コードのためだけに複雑なモノレポを設定する必要もありません。単に直接的な実行時の関係です。
実装と応用
実用的な例でこれを説明しましょう。2つのNode.jsマイクロサービス、「User Service
」と「Auth Service
」があると想像してください。「Auth Service
」はユーザー認証を処理し、JWTトークンを生成します。「User Service
」は、認証されたリクエストのためにこれらのトークンを検証する必要があります。トークン検証ロジックを重複させるか、内部npmライブラリにパッケージ化する代わりに、モジュールフェデレーションを使用できます。
Auth Service(リモート)
「Auth Service
」はユーティリティ関数validateJwtToken
を公開します。
まず、Node.js用にwebpack 5がインストールされ、設定されていることを確認してください。
npm install webpack webpack-cli webpack-node-externals @module-federation/node@next
Auth Service のwebpack.config.js
:
const { ModuleFederationPlugin } = require('@module-federation/node'); const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { entry: './src/index.js', target: 'node', mode: 'development', // or 'production' output: { path: path.resolve(__dirname, 'dist'), filename: 'main.js', libraryTarget: 'commonjs-static', // Important for Node.js }, externals: [nodeExternals()], // Exclude node_modules from the bundle plugins: [ new ModuleFederationPlugin({ name: 'authServiceRemote', filename: 'remoteEntry.js', // This file will be fetched by hosts exposes: { './tokenValidator': './src/utils/tokenValidator.js', // Exposing a utility }, shared: { // Shared dependencies to avoid duplication. // Webpack will ensure only one instance is loaded. jsonwebtoken: { singleton: true, requiredVersion: '^8.5.1', }, dotenv: { singleton: true, requiredVersion: '^16.0.0', }, }, }), ], };
Auth Service のsrc/utils/tokenValidator.js
:
const jwt = require('jsonwebtoken'); require('dotenv').config(); const JWT_SECRET = process.env.JWT_SECRET || 'supersecretkey'; function validateJwtToken(token) { try { const decoded = jwt.verify(token, JWT_SECRET); return { isValid: true, user: decoded }; } catch (error) { return { isValid: false, error: error.message }; } } module.exports = { validateJwtToken };
「Auth Service
」は通常、remoteEntry.js
ファイルを既知のエンドポイント(例: http://localhost:3001/remoteEntry.js
)で公開して開始します。これは通常、dist
フォルダを直接提供するか、既存のExpressアプリに統合することを意味します。
User Service(ホスト)
「User Service
」はvalidateJwtToken
関数を「Auth Service
」から消費します。
User Service のwebpack.config.js
:
const { ModuleFederationPlugin } = require('@module-federation/node'); const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { entry: './src/index.js', target: 'node', mode: 'development', output: { path: path.resolve(__dirname, 'dist'), filename: 'main.js', libraryTarget: 'commonjs-static', }, externals: [nodeExternals()], plugins: [ new ModuleFederationPlugin({ name: 'userServiceHost', remotes: { // Point to the Auth Service's remoteEntry.js authServiceRemote: 'authServiceRemote@http://localhost:3001/remoteEntry.js', }, shared: { jsonwebtoken: { singleton: true, requiredVersion: '^8.5.1', }, dotenv: { singleton: true, requiredVersion: '^16.0.0', }, express: { singleton: true, requiredVersion: '^4.17.1', }, }, }), ], };
User Service のsrc/index.js
:
const express = require('express'); const { validateJwtToken } = require('authServiceRemote/tokenValidator'); // Consuming the remote module const app = express(); app.use(express.json()); // Middleware to protect routes using the federated token validator app.use(async (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader) { return res.status(401).send('No authorization header'); } const token = authHeader.split(' ')[1]; if (!token) { return res.status(401).send('Token not provided'); } // Use the federated function! const validationResult = await validateJwtToken(token); if (validationResult.isValid) { req.user = validationResult.user; // Attach user info to request next(); } else { res.status(403).send(`Invalid token: ${validationResult.error}`); } }); app.get('/profile', (req, res) => { res.json({ message: `Welcome ${req.user.username}! This is your profile.`, user: req.user }); }); const PORT = 3000; app.listen(PORT, () => { console.log(`User Service running on port ${PORT}`); });
「User Service
」が開始されると、「authServiceRemote/tokenValidator
」をhttp://localhost:3001/remoteEntry.js
から動的にロードしようとします。これにより、「User Service
」はjsonwebtoken
またはdotenv
を二重にバンドルすることなく、また事前のtoken-validator
パッケージの公開を必要とすることなく、直接「Auth Service
」のロジックを活用できます。
利点とユースケース
- 重複の削減: 共通のロジック(例: 検証スキーマ、特定のアルゴリズム、APIクライアント)を集中化し、サービス間で共有します。
- 一貫性の向上: すべてのサービスが共有コンポーネントまたはユーティリティの同じバージョンを使用していることを保証し、「ドリフト」と異なる実装に起因する可能性のあるバグを減らします。
- 開発の高速化: 開発者はユーティリティを一度構築して公開し、他のチームはパッケージの公開や複雑な同期を待つことなく、すぐに消費できます。
- 独立したデプロイ可能性: サービスは公開モジュールを独立して更新できます。APIコントラクトが安定している限り、コンシューマーは再デプロイする必要はありません。
- 動的更新: ホストは、ホスト自体の完全な再デプロイなしに、リモートサービスの新バージョンを指すようにリモート設定を更新できる可能性があります。ただし、これにはバージョニングと破壊的変更の注意深い管理が必要です。
- モノレポの代替: サービス間でコードを共有する方法を、モノレポ設定に縛られることなく提供し、サービスを個別のリポジトリに配置できるようにします。
考慮事項と課題
- 実行時依存関係: リモートサーバーの可用性と応答性に対する実行時依存関係を導入します。リモートサービス(またはその
remoteEntry.js
)がダウンしている場合、ホストはフェデレートされたモジュールをロードできなくなる可能性があります。 - バージョニングと互換性:
shared
モジュールは役立ちますが、公開されたリモートモジュールでの破壊的変更の管理には、依然として規律が必要です。リモートモジュールのセマンティックバージョニングは不可欠です。 - **ビルドの複雑さ:**各サービスにWebpack設定のオーバーヘッドを追加します。
- デバッグ: フェデレートされたモジュールをまたぐ問題のデバッグは、従来のローカル依存関係よりも複雑になる可能性があります。
- セキュリティ: 悪意のあるコードの挿入を防ぐために、
remoteEntry.js
ファイルが安全に提供され、信頼できるソースから消費されていることを確認してください。
結論
モジュールフェデレーションは、Node.jsマイクロサービスにおける効率的なコード共有のための強力でエレガントなソリューションを提供します。個別のサービス間でモジュールを動的に実行時にロードできるようにすることで、保守性が大幅に向上し、コードの重複が削減され、開発が加速されます。実行時依存関係とバージョニングに関する新たな考慮事項を導入しますが、柔軟性とサービス間コラボレーションの利点は、現代のNode.jsバックエンドで検討する価値のある魅力的なアーキテクチャパターンとなります。本質的に、モジュールフェデレーションは、マイクロサービスが真に統合された、しかし独立してデプロイ可能なエコシステムとして機能することを可能にします。