TypeScript中級

中級 TypeScriptで学ぶマイクロサービス|アンチパターン編

導入

マイクロサービスアーキテクチャは、柔軟性やスケーラビリティを提供する一方で、特有の課題も存在します。特に、実務においては、設計や実装の段階で直面するアンチパターンが多く、これに対処することが求められます。この記事では、TypeScriptを用いたマイクロサービスにおける具体的なアンチパターンを取り上げ、その問題点と解決策を探ります。

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

重要な概念の整理

マイクロサービスは、機能を小さなサービス単位に分割し、それぞれが独立してデプロイ可能なアーキテクチャスタイルです。このアプローチにより、チームは異なる技術スタックを使用したり、異なるリリースサイクルで作業したりできます。しかし、サービス間の通信やデータの整合性を保つことが難しくなる場合があります。

コード例(TypeScript)


interface User {
    id: number;
    name: string;
}

class UserService {
    private users: User[] = [];

    public addUser(user: User): void {
        this.users.push(user);
    }

    public getUser(id: number): User | null {
        return this.users.find(user => user.id === id) || null;
    }
}

コードの行ごとの解説

  1. interface User: ユーザーのデータ構造を定義します。
  2. class UserService: ユーザー管理のロジックを持つクラスです。
  3. private users: ユーザー情報を保持する配列です。プライベートにすることで、外部からの直接アクセスを防ぎます。
  4. addUser: 新しいユーザーを配列に追加するメソッドです。
  5. getUser: ユーザーIDに基づいてユーザーを取得するメソッドです。存在しない場合はnullを返します。

アンチパターン編

上記のコードには、「状態を持つサービス」というアンチパターンが見受けられます。具体的には、UserService クラスが内部に状態(ユーザー情報)を保持しているため、スケーラビリティやテストの容易さが損なわれます。これにより、複数のインスタンスが同時に実行されると、データの整合性が失われるリスクがあります。

この問題を解決するためには、状態を持たずに外部データベースやストレージを利用する方法が考えられます。以下は、改善されたコード例です。


class UserService {
    constructor(private database: Database) {}

    public async addUser(user: User): Promise {
        await this.database.saveUser(user);
    }

    public async getUser(id: number): Promise {
        return await this.database.findUserById(id);
    }
}

このように、データベースとのインターフェースを用いて、状態を外部に委譲することで、スケーラビリティやテストの容易さが向上します。

まとめ

  • 状態を持つサービスは、スケーラビリティやデータ整合性の問題を引き起こす可能性がある。
  • 外部ストレージを利用することで、状態管理を改善し、マイクロサービスの利点を最大限に活用できる。
  • 実装においては、各サービスが独立して動作できるように設計することが重要である。