JavaScript上級

上級 JavaScriptで学ぶマイクロサービス|練習問題編

導入

マイクロサービスアーキテクチャは、現代のソフトウェア開発において非常に重要な手法となっています。特に、各サービスが独立してデプロイ可能であることは、開発の効率を大きく向上させます。しかし、複数のサービス間でデータをやり取りする際のエラー処理や、サービス間の通信の最適化は、しばしば開発者にとって難題となります。この記事では、JavaScriptを用いたマイクロサービスの実装における具体的なシチュエーションを通じて、実務に役立つ知識を深めることを目的とします。

教科書レベルの解説(マイクロサービス)

重要な概念の整理

マイクロサービスは、各サービスが特定の機能を持ち、APIを介して通信する構造です。このアプローチでは、サービスの独立性が重視され、スケーラビリティや保守性が向上します。ただし、サービス間の依存関係やデータ整合性の問題が発生することがあります。特に、非同期処理やエラーハンドリングが課題となることが多いです。これらを効果的に管理するためには、適切な設計パターンやツールを選定することが求められます。

コード例(JavaScript)


// マイクロサービス間の通信を管理するサンプルコード
const axios = require('axios');

async function fetchDataFromServiceA() {
    try {
        const response = await axios.get('http://service-a/api/data');
        return response.data;
    } catch (error) {
        console.error('Service Aからのデータ取得に失敗しました:', error);
        throw new Error('データ取得エラー');
    }
}

async function fetchDataFromServiceB() {
    try {
        const response = await axios.get('http://service-b/api/data');
        return response.data;
    } catch (error) {
        console.error('Service Bからのデータ取得に失敗しました:', error);
        throw new Error('データ取得エラー');
    }
}

async function aggregateData() {
    try {
        const [dataA, dataB] = await Promise.all([fetchDataFromServiceA(), fetchDataFromServiceB()]);
        return { dataA, dataB };
    } catch (error) {
        console.error('データ集約に失敗しました:', error);
        return null;
    }
}

aggregateData().then(result => console.log(result));

コードの行ごとの解説

  1. まず、axiosライブラリをインポートし、HTTPリクエストを簡単に扱えるようにします。
  2. fetchDataFromServiceA関数では、Service Aからデータを取得し、エラーハンドリングを行います。
  3. fetchDataFromServiceB関数も同様に、Service Bからデータを取得します。
  4. aggregateData関数では、Promise.allを使用して、両方のサービスからのデータ取得を並行して行います。
  5. エラーが発生した場合は、適切にログを出力し、nullを返すことで、呼び出し元での処理を可能にします。

練習問題編

以下の練習問題に取り組んでみましょう。各問題には模範解答と解説があります。

  1. 問題1: fetchDataFromServiceA関数を改良し、HTTPステータスコードに応じたエラーハンドリングを追加してください。

    模範解答:

    
        async function fetchDataFromServiceA() {
            try {
                const response = await axios.get('http://service-a/api/data');
                if (response.status !== 200) {
                    throw new Error('不正なステータスコード: ' + response.status);
                }
                return response.data;
            } catch (error) {
                console.error('Service Aからのデータ取得に失敗しました:', error);
                throw new Error('データ取得エラー');
            }
        }
        

    解説: ステータスコードが200以外の場合にエラーを投げることで、より具体的なエラーハンドリングが可能になります。

  2. 問題2: aggregateData関数で、データの集約に失敗した場合の処理を追加してください。

    模範解答:

    
        async function aggregateData() {
            try {
                const [dataA, dataB] = await Promise.all([fetchDataFromServiceA(), fetchDataFromServiceB()]);
                return { dataA, dataB };
            } catch (error) {
                console.error('データ集約に失敗しました:', error);
                // 失敗時の処理を追加
                return { error: 'データ集約に失敗しました' };
            }
        }
        

    解説: 失敗時にエラーメッセージを返すことで、呼び出し元でのエラーハンドリングが容易になります。

  3. 問題3: 並行処理の代わりに直列処理を行うようにaggregateData関数を修正してください。

    模範解答:

    
        async function aggregateData() {
            try {
                const dataA = await fetchDataFromServiceA();
                const dataB = await fetchDataFromServiceB();
                return { dataA, dataB };
            } catch (error) {
                console.error('データ集約に失敗しました:', error);
                return null;
            }
        }
        

    解説: 直列処理では、各サービスからのデータ取得が順番に行われるため、エラーが発生した場合のデバッグが容易になります。

まとめ

  • マイクロサービス間の通信は、エラーハンドリングが重要な要素である。
  • 非同期処理を適切に管理することで、サービスの信頼性が向上する。
  • 実際の業務では、エラー処理やデータ整合性に注意を払いながら設計を行う必要がある。