# Apex の生 SOQL から、チェーンメソッドで組み立てる ORM へ

長くなりがちな SOQL 文字列、扱いにくいリレーション、遅くて壊れやすい DB 依存のテスト。Salesforce 開発者が日常的に向き合っているこれらの課題は、Apex の標準的な書き方をそのまま使っている限り、なかなか減りません。

ApexEloquent は、メソッドチェーンでクエリを宣言的に組み立て、テストでは DB に触れずに完結できる ORM を提供します。生 SOQL 中心の Apex 開発から、どこがどう変わるかを順に見ていきます。

## 生 SOQL 中心の Apex 開発で起きること

標準の SOQL は強力ですが、運用していると次のような問題が必ず顔を出します。

- ❌ **可読性の限界**: 手で連結した長い SOQL 文字列は、すぐに保守不能になる
- ❌ **動的条件の組み立てが面倒**: `if` 分岐の中で条件を追加するには、文字列操作の組み合わせを書くことになる
- ❌ **型安全性が無い**: 文字列内のフィールド名のタイポは実行時まで気付けない
- ❌ **リレーションが扱いにくい**: 親子のリレーション名 (`__r` 付きの名前など) を正確に覚えていないと書けない
- ❌ **テストが書きづらい**: ビジネスロジックが DB に強く結合し、テストごとに大量のデータを `insert` する必要が出る

これらは「生 SOQL を書く」という選択をした瞬間に、ほぼ自動的についてくる代償です。`ApexEloquent` はこの代償を払わずに同じ仕事をするために生まれました。

## ApexEloquent によるアプローチ

ApexEloquent は、Laravel の Eloquent から発想を借りた Apex 向けの ORM フレームワークで、2 つの主要コンポーネントが軸になります。

- **`Scribe`**: SOQL クエリをメソッドチェーンで組み立てる、immutable なクエリビルダー
- **`IEloquent`** (`Eloquent` / `MockEloquent`): 組み立てた `Scribe` を受け取って実行する側。本番は `Eloquent`、テストは `MockEloquent` を DI で差し替える

この「クエリ構築」と「クエリ実行」を分けて設計するアプローチには、独自の設計哲学があります。詳しい背景は [Query Delegation Pattern](/ja/apex-stem/docs/query-delegation-pattern) と、なぜ Repository を ApexEloquent に内蔵したかは [Apex における Repository パターンの試行錯誤と内蔵化](/ja/apex-stem/docs/repository-pattern-challenges-builtin-solution-apex) で扱っています。

このドキュメントでは、設計哲学はいったん置いておき、**生 SOQL からの移行で「実際にコードがどう変わるか」** を中心に見ていきます。

## 基本的な使い方

クエリの組み立ては直感的です。`Scribe` のメソッドを連ねるだけで、SELECT・WHERE・並び替えがそのまま型付きの API として書けます。

```apex
// Industry が 'Technology' のすべての Account の Id と Name を取得するクエリ
Scribe accountScribe = Scribe.of(Account.class)
    .fields(new List<String>{ 'Id', 'Name' })
    .whereEqual('Industry', 'Technology')
    .orderBy('Name');
```

これと等価な生 SOQL は次のとおりです。

```soql
SELECT Id, Name FROM Account WHERE Industry = 'Technology' ORDER BY Name
```

短い 1 行ならどちらも同程度に読めますが、条件・並び替え・サブクエリが増えるほど、`Scribe` 版は **構造のまま読めること** のメリットが効いてきます。組み立てた `Scribe` を実行する側 (`IEloquent.get(scribe)` 等) や、`Scribe` の API 全体については [Scribe でクエリを組み立てる](/ja/apex-stem/docs/apex-eloquent-scribe-guide) と [データ取得と DML、IEntry、Mock](/ja/apex-stem/docs/apex-eloquent-data-access) を参照してください。

## 動的にクエリを組み立てる

`Scribe` が immutable であることが効いてくる代表的な場面が、「ユースケースの条件によってクエリを分岐させたい」ケースです。

```apex
Scribe oppScribe = Scribe.of(Opportunity.class)
    .fields(new List<String>{ 'Name', 'StageName' })
    .whereIn('StageName', new List<String>{ 'Prospecting', 'Qualification' });

// 特定条件のときだけ WHERE 条件を追加する
if (includeHighValueDeals) {
    oppScribe = oppScribe.whereGreaterThan('Amount', 100000);
}

// 特定条件のときだけ SELECT するフィールドを追加する
if (includeAmountDetails) {
    oppScribe = oppScribe.field('Amount').field('Probability');
}
```

`Scribe` のメソッドは毎回新しい `Scribe` を返すので、元のクエリを壊さずに「条件を足す」「フィールドを足す」を同じ感覚で扱えます。

> ここでは SELECT 項目の追加も含むため `if` で書いていますが、**WHERE 条件だけを出し入れする**なら `ignoreWhen` を使うと分岐なしの 1 本のチェーンで書けます (`whereIn('Id', ids).ignoreWhen(ids.isEmpty())`)。

### 順番に依存しない構造

上の 2 つの `if` ブロックは **入れ替えても出来上がる SOQL が同じ** になります。`field` と `where` のどちらを先に追加しても、最終的に生成されるクエリの SELECT 句・WHERE 句に過不足なく反映されるからです。これは「ユースケース側の判断のしやすい順に if を並べる」自由度をそのまま許してくれます。

同じことを文字列の SOQL でやろうとすると、SELECT 句用の文字列と WHERE 句用の文字列を別の変数で保持して、最後に決まった順序で連結する、という **ビジネスロジックとは関係のない処理** が増えてきます。

```apex
// 文字列 SOQL で同じことをやる場合 (悪い例)
String selectClause = 'SELECT Name, StageName';
String whereClause = " WHERE StageName IN ('Prospecting', 'Qualification')";

if (includeHighValueDeals) {
    whereClause += ' AND Amount > 100000';
}
if (includeAmountDetails) {
    selectClause += ', Amount, Probability';
}

String soql = selectClause + ' FROM Opportunity' + whereClause;
```

SELECT 句と WHERE 句を別変数で持つ書き方が「分岐ごとにどちらの変数を触るか」を間違えるリスクを抱えますし、`' AND ...'` の先頭スペースを入れ忘れて `WHERE Foo = 'X'AND Bar = 'Y'` のように繋がってしまう **空白の入れ忘れケアレスミス** も典型的です。`Scribe` ベースの組み立てでは、これらの「クエリ文字列を組み立てるためだけのコード」が一切要らなくなります。

このメソッドチェーン方式の良さは、**「クエリを部品として組み立てる」感覚がそのままコードに乗ること** です。ユースケース固有の条件を、文字列ではなく型付きのメソッド呼び出しとして表現できます。

## リレーションも直感的に扱える

親子のリレーション名 (`__r` 付きの名前など) を正確に覚えていなくても、`Scribe` のメソッドで親子クエリを組めます。

```apex
// Account と、その配下の Contact を一緒に取得
Scribe accountScribe = Scribe.of(Account.class)
    .field('Name')
    .withChildren(
        Scribe.asChild(Contact.class)
            .fields(new List<String>{ 'LastName', 'Email' })
    )
    .whereEqual('Id', someAccountId);

IEntry accountEntry = (new Eloquent()).first(accountScribe);
List<IEntry> contacts = accountEntry.getChildren('Contact');
```

親項目を SELECT する `parentField`、親条件で絞る `parentCondition`、Junction Object を経由する多対多の `through` などについては、[親項目・子サブクエリ・多対多](/ja/apex-stem/docs/apex-eloquent-relations) で詳しく扱っています。

## DB なしテストへの入口

ApexEloquent のもう一つの大きな価値は、`IEloquent` を `MockEloquent` に DI で差し替えるだけで、**DB を介さない単体テスト** が書けることです。

```apex
// 想定するクエリ結果をメモリ上に組み立てる
IEloquent mockEloquent = new MockEloquent(new List<IEntry>{
    MockEntry.of(Opportunity.class)
        .set('StageName', 'Closed Won')
        .setParent('AccountId',
            MockEntry.of(Account.class).set('Type', 'Prospect')
        )
});

// DB に触れず、サービスクラスを直接テスト
AccountService service = new AccountService(mockEloquent);
Account updatedAccount = service.updateAccountType('006...');

Assert.areEqual('Customer', updatedAccount.Type);
```

`MockEloquent` / `MockEntry` の詳しい使い方 (Spy プロパティ、`failOn*`、親子・集計のモック、SELECT 漏れ検知など) は [データ取得と DML、IEntry、Mock](/ja/apex-stem/docs/apex-eloquent-data-access) で網羅しています。

## 生 SOQL vs ApexEloquent 比較

| 観点 | 生 SOQL | ApexEloquent |
|---|---|---|
| **可読性** | 複雑なクエリで急速に悪化 | ✅ メソッドチェーンで構造が見える |
| **動的 WHERE** | 文字列連結を手で書く | ✅ チェーンメソッドで安全に継ぎ足せる |
| **リレーション** | リレーション名を覚えて記述 | ✅ `withChildren` / `parentField` で直感的 |
| **テスト** | ❌ DML + テストデータが必須 | ✅ `MockEloquent` で DB なしテスト |
| **安全性** | フィールド名タイポは実行時エラー | ✅ テスト段階で SELECT 漏れも検知 |

## まとめ

生 SOQL を書き続けることで支払っている代償 (可読性 / 動的条件の面倒さ / 型安全性の欠如 / リレーションの扱いづらさ / テストの重さ) は、別々の問題に見えて、**「文字列でクエリを書いている」という選択そのものが根に近い** 問題です。

`Scribe` でクエリを組み立て、`IEloquent` に実行を委ねるスタイルに切り替えると、これらの代償がまとめて軽くなります。生 SOQL を 1 行も書かない、というほど極端な話ではなく、ユースケースに紐づくクエリを `Scribe` で構造的に組み立て、保管庫に置く共通クエリも `Scribe` で部品化していく、というのが現実的な移行像です。

### 関連ドキュメント

- [Query Delegation Pattern](/ja/apex-stem/docs/query-delegation-pattern): なぜクエリ構築と実行を分けるのか
- [Apex における Repository パターンの試行錯誤と内蔵化](/ja/apex-stem/docs/repository-pattern-challenges-builtin-solution-apex): Repository を内蔵した動機
- [Scribe でクエリを組み立てる](/ja/apex-stem/docs/apex-eloquent-scribe-guide): Scribe API のガイド
- [データ取得と DML、IEntry、Mock](/ja/apex-stem/docs/apex-eloquent-data-access): IEloquent / MockEloquent のガイド
- [親項目・子サブクエリ・多対多](/ja/apex-stem/docs/apex-eloquent-relations): リレーション操作の典型例
- [ApexEloquent トップ](/ja/apexeloquent): OSS の全体像
