普段はTypeScriptやJavaScript(以下TS/JS)を中心にWeb開発を行っているエンジニアが、Salesforceのバックエンド言語である Apex を学習した際の記録です。
TS/JSの知識をベースに、「ここは似ている」「ここは全然違う」というポイントをまとめました。同じようなバックグラウンドを持つ方の参考になれば幸いです。
1. 言語の雰囲気:ほぼJava、でもTS勢も読める
Apexの構文は Javaに非常に似ています。
TSを使っている人であれば、クラスベースのOOPや静的型付けには慣れていると思うので、コード自体はスラスラ読めると思います。
// Apexのクラス定義
public class HelloWorld {
public static void sayHello() {
String msg = 'Hello, Apex!';
System.debug(msg);
}
}
JS/TSとの主な違い(基本構文)
-
変数の宣言:
let/constではなく、型を明示します(型推論は基本ありません)。 -
定数:
final修飾子を使います。 - セミコロン: 必須です。ASI(自動挿入)はありません。
-
文字列: シングルクォート
'が基本です。
// TypeScript
const count: number = 10;
// Apex
Integer count = 10;
final String GREETING = 'Hello';
2. コレクション操作:List, Set, Map
JSの Array, Set, Map に相当するものが Apex にもあります。
List (配列相当)
JSの配列とほぼ同じですが、宣言時に「何が入るか」をジェネリクスで指定するのが一般的です。
// Apex
List<String> names = new List<String>{'Alice', 'Bob'};
names.add('Charlie');
// アクセスは [] も使えるが、get() もある
System.debug(names[0]);
JSの map や filter のような高階関数は、Apexには長らく存在しませんでしたが、最近のアップデートで Comparator や一部のメソッドが充実してきています。しかし、基本的には for-eachループ で処理することが多いです。
// Apexでのループ
for (String name : names) {
System.debug(name);
}
Map (オブジェクト/Map相当)
JSではオブジェクト { key: val } をMapとして使うことが多いですが、Apexでは明確に Map クラスを使います。
Map<String, Integer> scores = new Map<String, Integer>();
scores.put('Alice', 100);
Integer aliceScore = scores.get('Alice');
3. 非同期処理:Promiseはない
ここがJS勢にとって一番のカルチャーショックかもしれません。Apexには Promise や async/await はありません。
Apexは基本的には 同期的に 実行されます。
APIコールアウトや重い処理を非同期で行いたい場合は、以下の仕組みを使います。
- @future メソッド: 非同期で実行されるメソッド。
- Queueable Apex: ジョブキューのような仕組み。
- Batch Apex: 大量のレコードを処理するためのバッチ処理。
「この処理が終わったら次を実行する」というチェーン(.then())を書きたい場合、Queueableの中から次のQueueableを呼ぶ(Chaining)などの工夫が必要です。
4. ガバナ制限 (Governor Limits)
Apexはマルチテナント環境(サーバーリソースを多数の企業で共有)で動くため、リソース消費に対する厳格な制限があります。
これが「ガバナ制限」です。JSのランタイム(ブラウザやNode.js)ではメモリリークくらいしか気にしませんが、Apexでは以下のような制限と戦う必要があります。
- SOQLの発行回数: 1トランザクションにつき100回まで
- DML(DB更新)の発行回数: 150回まで
- CPU時間: 10,000ミリ秒(10秒)まで
そのため、ループの中でクエリ(SOQL)や更新(DML)を行うのは御法度です。
❌ ダメな例(アンチパターン)
List<Account> accounts = [SELECT Id FROM Account];
for (Account acc : accounts) {
// ループ内でクエリを投げている -> 制限オーバーでエラーになる可能性大
Integer contactCount = [SELECT Count() FROM Contact WHERE AccountId = :acc.Id];
}
✅ 良い例(バルク化)
「必要なデータはループの外でまとめて取る」のが鉄則です。これを 「バルク化 (Bulkification)」 と呼びます。
// IDセットを作成
Set<Id> accIds = new Set<Id>();
for (Account acc : accounts) {
accIds.add(acc.Id);
}
// まとめて取得(1回のクエリ)
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accIds];
5. デバッグとテスト
console.log ではなく System.debug
System.debug('Hello World');
出力はブラウザのコンソールではなく、Salesforce上の「デバッグログ」で確認します。
テストコードは必須
JSの世界ではテストを書くかどうかはプロジェクト次第ですが、Salesforceでは 本番環境にデプロイするために、75%以上のテストカバレッジが必須 です。
そのため、開発とテストライティングはセットです。
@isTest
private class MyTest {
@isTest static void testBehavior() {
// テストデータの作成
Account acc = new Account(Name='Test');
insert acc;
// 処理の実行とアサーション
Test.startTest();
// ...method call...
Test.stopTest();
System.assertEquals('expected', 'actual');
}
}
まとめ
TS/JS経験者にとって、Apexは「構文は懐かしいJava風、でも実行モデル(ガバナ制限・非同期)が全く違う」言語だと感じました。
特に 「ループ内でクエリを投げない(バルク化)」 という意識は、N+1問題を避ける意味でWeb開発全般に通じる良い習慣になりそうです。