Proxy パターン

オブジェクトへのアクセスを代理オブジェクトが仲介し、アクセス制御やキャッシュなどの付加機能を提供するパターン

設計パターン基礎

Proxy パターンとは

Proxy パターンは、本物のオブジェクト (Real Subject) の前に代理オブジェクト (Proxy) を配置し、アクセス制御、遅延初期化、キャッシュ、ログ記録などの付加機能を透過的に提供する GoF デザインパターンである。クライアントは Proxy と Real Subject を区別せずに使える。

Proxy の種類

種類の呼び名は「何を代理するか」で決まる。所在 (リモート)、生成のタイミング (仮想)、権限 (保護)、記録 (ログ)、結果の再利用 (キャッシュ) のどれを引き受けるかの違いであり、Real Subject と同じインターフェースを保つ点はどの種類でも共通している。

種類目的例
キャッシュプロキシ結果をキャッシュして高速化API レスポンスのキャッシュ
保護プロキシアクセス権限をチェック認証 / 認可の検証
仮想プロキシ重いオブジェクトの遅延初期化画像の遅延読み込み
ログプロキシアクセスを記録API 呼び出しのログ
リモートプロキシリモートオブジェクトをローカルに見せるgRPC クライアント

TypeScript での実装

キャッシュプロキシを例に、Real Subject と同じインターフェースを実装する形を示す。実装で判断が要るのは「見つからなかった結果 (null) もキャッシュするか」である。キャッシュしなければ存在しない ID への問い合わせが毎回 Real Subject に到達し、キャッシュすればその ID のデータが後から作られても TTL が切れるまで見えない。

interface UserRepository {
  findById(id: string): Promise<User | null>;
}

// Real Subject
class DynamoDBUserRepo implements UserRepository {
  async findById(id: string): Promise<User | null> {
    const res = await ddb.send(new GetCommand({ TableName: 'Users', Key: { id } }));
    return (res.Item as User | undefined) ?? null;
  }
}

// キャッシュプロキシ
class CachedUserRepo implements UserRepository {
  private cache = new Map<string, { user: User | null; expiry: number }>();

  constructor(private real: UserRepository, private ttlMs = 60000) {}

  async findById(id: string) {
    const cached = this.cache.get(id);
    if (cached && Date.now() < cached.expiry) return cached.user;

    const user = await this.real.findById(id);
    this.cache.set(id, { user, expiry: Date.now() + this.ttlMs });
    return user;
  }
}

// 使用: クライアントは Proxy と Real Subject を区別しない
const repo: UserRepository = new CachedUserRepo(new DynamoDBUserRepo());

JavaScript の Proxy オブジェクト

JavaScript には言語レベルの Proxy オブジェクトがあり、プロパティアクセスやメソッド呼び出しをインターセプトできる。

const handler: ProxyHandler<any> = {
  get(target, prop) {
    console.log(`Accessing ${String(prop)}`);
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
    console.log(`Setting ${String(prop)} = ${value}`);
    return Reflect.set(target, prop, value);
  },
};

const user = new Proxy({ name: 'Alice' }, handler);
user.name;        // "Accessing name"
user.name = 'Bob'; // "Setting name = Bob"

Vue 3 のリアクティビティは、reactive で包んだオブジェクトを Proxy で追跡し、ref は getter / setter で追跡する構成になっている (Vue 公式ガイド「Reactivity in Depth」)。

Decorator パターンとの違い

実装が最も似ていて混同されやすいのは Decorator である。

パターン目的インターフェース
Proxyアクセス制御、遅延初期化Real Subject と同じ
Decorator機能の追加 (重ねがけ可能)Real Subject と同じ

Proxy は「アクセスを制御する」、Decorator は「機能を追加する」。実装は似ているが意図が異なる。インターフェースを別のものへ変換するのは Adapter、複数のクラスをまとめて単純な窓口を用意するのは Facade であり、どちらも「Real Subject と同じインターフェースを保つ」という Proxy の前提から外れる。

実務での活用

  • API Gateway: Lambda の前段に配置される Proxy (認証、レート制限、キャッシュ)
  • CloudFront: オリジンサーバーの前段に配置されるキャッシュプロキシ
  • RDS Proxy: Lambda と RDS の間に配置される接続プーリングプロキシ

Proxy パターンの理解を深めるには関連書籍が参考になる。

共有:

この記事は役に立ちましたか?

関連用語

関連する記事