Interactive

Fluent API

プロンプトに「Fluent API」を含めることで、ClaudeCodeは直感的で読みやすいメソッドチェーンを生成できます。弊社では、長い引数リストを避けたい場合や、設定を段階的に構築したい場合にFluent APIパターンを使用します。

弊社の推奨ルール

  1. thisを必ず返す - メソッドチェーンを可能にする
  2. イミュータブルにする - 各操作で新しいインスタンスを生成
  3. 自然言語に近い命名 - メソッド名は動詞で統一
  4. ビルダーでは終端メソッドを明確にする - build()やexecute()で処理を確定
  5. 型安全性を保つ - チェーン中でも型情報を維持

ClaudeCodeでの利用

クエリビルダー実装

複数の条件を直感的にチェーンして構築したい場合

「Fluent APIでクエリビルダーを作って。where、orderBy、limitをチェーンできるように」

既存コードの改善

手続き的なコードを読みやすいメソッドチェーンに変換

「このクラスにFluent APIを実装」

設定オブジェクト構築

複雑な設定を段階的に組み立てて可読性を向上させる

「設定オブジェクトをFluent APIで構築できるように」

長い引数リストではなくFluent APIを使う

弊社では、4つ以上の引数がある場合や、設定が段階的に構築される場合にFluent APIを使用します。理由は可読性と保守性の向上です。

// ❌ 推奨しない:複数の関数呼び出し
const user = createUser();
setName(user, 'Alice');
setAge(user, 25);
setEmail(user, 'alice@example.com');
activate(user);

従来の手続き型アプローチでは、複数の関数を順番に呼び出す必要があり、可読性が低下します。Fluent APIでは自然言語のような流れでコードを書けます。

// ✅ 推奨:Fluent API
class User {
  constructor(
    private readonly name: string = '',
    private readonly age: number = 0,
    private readonly email: string = '',
    private readonly isActive: boolean = false
  ) {
    Object.freeze(this);
  }
  
  withName(name: string): User {
    return new User(name, this.age, this.email, this.isActive);
  }
  
  withAge(age: number): User {
    return new User(this.name, age, this.email, this.isActive);
  }
  
  withEmail(email: string): User {
    return new User(this.name, this.age, email, this.isActive);
  }
  
  activate(): User {
    return new User(this.name, this.age, this.email, true);
  }

各withメソッドが新しいインスタンスを返すことで、イミュータブルなオブジェクトを維持しながらメソッドチェーンを可能にします。

  get profile() {
    return {
      name: this.name,
      age: this.age,
      email: this.email,
      active: this.isActive
    };
  }
}

// 使用例:自然で読みやすい
const user = new User()
  .withName('Alice')
  .withAge(25)
  .withEmail('alice@example.com')
  .activate();

console.log(user.profile);

個別のセッターメソッドではなくFluent APIを使う

弊社では、HTTPクライアントのような設定が多いAPIでは、個別のセッターではなくFluent APIを採用します。統一性と使いやすさを重視するためです。

class HttpRequest {
  constructor(
    private readonly url: string,
    private readonly method: string = 'GET',
    private readonly headers: Record<string, string> = {},
    private readonly params: Record<string, string> = {},
    private readonly body?: any
  ) {
    Object.freeze(this);
  }

HTTPリクエストの設定を段階的に構築するFluent APIです。各メソッドが新しいリクエストオブジェクトを返し、設定を積み重ねていきます。

  header(key: string, value: string): HttpRequest {
    return new HttpRequest(
      this.url,
      this.method,
      { ...this.headers, [key]: value },
      this.params,
      this.body
    );
  }
  
  param(key: string, value: string): HttpRequest {
    return new HttpRequest(
      this.url,
      this.method,
      this.headers,
      { ...this.params, [key]: value },
      this.body
    );
  }
  
  json(data: any): HttpRequest {
    return new HttpRequest(
      this.url,
      'POST',
      { ...this.headers, 'Content-Type': 'application/json' },
      this.params,
      JSON.stringify(data)
    );
  }

executeメソッドは終端メソッドとして、実際のHTTPリクエストを実行します。設定の構築フェーズと実行フェーズを明確に分離します。

  async execute(): Promise<Response> {
    const url = new URL(this.url);
    for (const [key, value] of Object.entries(this.params)) {
      url.searchParams.set(key, value);
    }
    
    return fetch(url.toString(), {
      method: this.method,
      headers: this.headers,
      body: this.body
    });
  }
}

// ファクトリー関数
function http(url: string): HttpRequest {
  return new HttpRequest(url);
}

実用例では、ファクトリー関数とメソッドチェーンを組み合わせて、直感的で読みやすいAPIを提供します。設定の内容が一目で分かります。

// 使用例
const response = await http('https://api.example.com/users')
  .header('Authorization', 'Bearer token')
  .param('page', '1')
  .param('limit', '10')
  .execute();

const createResponse = await http('https://api.example.com/users')
  .header('Authorization', 'Bearer token')
  .json({ name: 'John', email: 'john@example.com' })
  .execute();

よくある問題

Fluent APIでのthis消失

メソッドチェーンでthisが返されずチェーンが切れる問題です。必ずthisまたは新しいインスタンスを返すように実装します。

// ❌ エラー:thisが消える
class Builder {
  private value = 0;
  
  setValue(val: number) {
    this.value = val;
    // return thisを忘れる
  }
  
  build() {
    return this.value;
  }
}

// const result = new Builder().setValue(10).build(); // エラー

// ✅ 解決:必ずthisを返す
class Builder {
  private value = 0;
  
  setValue(val: number): this {
    this.value = val;
    return this; // 必須
  }
  
  build(): number {
    return this.value;
  }
}

// チェーンが正常に動作
const result = new Builder().setValue(10).build();

Q: イミュータブルにする必要がある?

A: 弊社では推奨。副作用を防ぎ、デバッグしやすくなる。パフォーマンスが問題なら可変版も検討。

Q: すべてのメソッドがFluent APIである必要は?

A: 設定系や条件系のメソッドのみ。計算結果を返すメソッドは通常の戻り値で良い。

Q: 終端メソッドの命名規則は?

A: ビルダーパターンでは build()、execute()、toXxx()など。通常のFluent APIでは不要。