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 パターンの理解を深めるには関連書籍が参考になる。

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

関連用語

関連する記事