Apex入門 - Salesforceのプログラミングを始めよう

## はじめに
この記事では、Salesforceの開発言語であるApexの基本を学びます。JavaやC#の経験がある方にとって、比較的親しみやすい言語です。

## 目次
1. Apexの基本文法
2. トリガーの基礎
3. テストクラスの書き方

## 1. Apexの基本文法
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/9c51f183-8780-4170-bb56-3ca40a523c4a.png)

### 変数とデータ型
Apexで利用できる主要なデータ型を見ていきましょう。

```apex
// プリミティブ型
Integer count = 1;
String name = 'John';
Boolean isActive = true;
Decimal price = 1000.50;
Date today = Date.today();
DateTime now = DateTime.now();

// コレクション型
List<String> nameList = new List<String>();
Set<String> uniqueNames = new Set<String>();
Map<Id, Account> accountMap = new Map<Id, Account>();

// sObject型（Salesforceの標準オブジェクト）
Account acc = new Account(
    Name = 'テスト取引先',
    Industry = 'Technology'
);
```

### クラスの定義
Apexでのクラス定義の基本を見ていきましょう。

```apex
public class AccountService {
    // 定数
    private static final String DEFAULT_INDUSTRY = 'Technology';
    
    // インスタンス変数
    private String accountName;
    
    // コンストラクタ
    public AccountService(String name) {
        this.accountName = name;
    }
    
    // メソッド
    public Account createNewAccount() {
        Account acc = new Account();
        acc.Name = this.accountName;
        acc.Industry = DEFAULT_INDUSTRY;
        return acc;
    }
    
    // 静的メソッド
    public static List<Account> getActiveAccounts() {
        return [SELECT Id, Name FROM Account WHERE Active__c = true];
    }
}
```

### SOQLとDML
Salesforceのデータベース操作は、SOQLとDMLを使用します。

```apex
// SOQL（データの取得）
List<Account> accounts = [
    SELECT Id, Name, Industry
    FROM Account
    WHERE Industry = 'Technology'
    LIMIT 10
];

// DML（データの作成・更新・削除）
Account newAccount = new Account(Name = 'New Account');
insert newAccount;  // 挿入

newAccount.Industry = 'Finance';
update newAccount;  // 更新

delete newAccount;  // 削除
```

## 2. トリガーの基礎


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/82d1c8df-ace9-a653-3c0f-f47f64af6ea2.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a0018c0a-0125-9631-37e0-0826afad99e5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/2b883ab2-956a-416d-4e4a-3f0620c181ad.png)



### トリガーの実行タイミング
トリガーには以下の実行タイミングがあります：
- before insert
- after insert
- before update
- after update
- before delete
- after delete
- after undelete

### 基本的なトリガーの例

```apex
trigger AccountTrigger on Account (before insert, before update) {
    // トリガーコンテキスト変数の使用
    System.debug('Trigger.new: ' + Trigger.new);        // 新しいレコード
    System.debug('Trigger.old: ' + Trigger.old);        // 古いレコード
    System.debug('Trigger.newMap: ' + Trigger.newMap);  // 新しいレコードのマップ
    System.debug('Trigger.oldMap: ' + Trigger.oldMap);  // 古いレコードのマップ
    
    // insertの場合の処理
    if (Trigger.isBefore && Trigger.isInsert) {
        for (Account acc : Trigger.new) {
            if (String.isBlank(acc.Industry)) {
                acc.Industry = 'Technology';  // デフォルト値を設定
            }
        }
    }
    
    // updateの場合の処理
    if (Trigger.isBefore && Trigger.isUpdate) {
        for (Account acc : Trigger.new) {
            Account oldAcc = Trigger.oldMap.get(acc.Id);
            if (acc.Industry != oldAcc.Industry) {
                // 業種が変更された場合の処理
                acc.Industry_Changed__c = true;
            }
        }
    }
}
```

### トリガーのベストプラクティス
1. 一つのオブジェクトに対して1つのトリガーにまとめる
2. ビジネスロジックは別クラスに分離する
3. 再帰的な更新に注意する
4. バルク処理に対応する

```apex
// トリガーハンドラークラス
public class AccountTriggerHandler {
    private boolean isExecuting = false;
    
    public void handleBeforeInsert(List<Account> newAccounts) {
        if (isExecuting) return;
        isExecuting = true;
        
        try {
            // ビジネスロジックの実行
            for (Account acc : newAccounts) {
                // 処理
            }
        } finally {
            isExecuting = false;
        }
    }
}

// トリガー
trigger AccountTrigger on Account (before insert) {
    AccountTriggerHandler handler = new AccountTriggerHandler();
    if (Trigger.isBefore && Trigger.isInsert) {
        handler.handleBeforeInsert(Trigger.new);
    }
}
```

## 3. テストクラスの書き方

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/56d74308-0429-a1de-2942-6e2cd948469c.png)


### テストクラスの基本構造

```apex
@isTest
private class AccountServiceTest {
    // テストデータの準備
    @testSetup
    static void setup() {
        Account testAcc = new Account(Name = 'Test Account');
        insert testAcc;
    }
    
    // 正常系のテスト
    @isTest
    static void testCreateNewAccount() {
        // 準備
        String accountName = 'New Test Account';
        AccountService service = new AccountService(accountName);
        
        // 実行
        Test.startTest();
        Account result = service.createNewAccount();
        Test.stopTest();
        
        // 検証
        System.assertEquals(accountName, result.Name);
        System.assertEquals('Technology', result.Industry);
    }
    
    // 例外のテスト
    @isTest
    static void testCreateNewAccountWithError() {
        // 準備
        AccountService service = new AccountService(null);
        
        // 実行
        Test.startTest();
        try {
            Account result = service.createNewAccount();
            System.assert(false, '例外が発生するはずです');
        } catch (Exception e) {
            // 検証
            System.assert(e.getMessage().contains('Account name cannot be null'));
        }
        Test.stopTest();
    }
}
```

### テストの重要なポイント
1. **Test.startTest()とTest.stopTest()**
   - ガバナー制限をリセット
   - 非同期処理の完了を待機

2. **アサーション**
   - System.assertEquals(expected, actual)
   - System.assertNotEquals(expected, actual)
   - System.assert(condition)
   - System.assertNull(actual)
   - System.assertNotNull(actual)

3. **テストデータの作成**
   - @testSetupを使用して共通のテストデータを準備
   - TestDataFactoryクラスを作成してテストデータ作成を一元管理

```apex
@isTest
public class TestDataFactory {
    public static Account createTestAccount(String name) {
        Account acc = new Account(
            Name = name,
            Industry = 'Technology'
        );
        insert acc;
        return acc;
    }
    
    public static List<Account> createTestAccounts(Integer count) {
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < count; i++) {
            accounts.add(new Account(
                Name = 'Test Account ' + i,
                Industry = 'Technology'
            ));
        }
        insert accounts;
        return accounts;
    }
}
```

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/90e0e4cd-b02a-ac65-6adb-507f8509a5c6.png)


この記事では、Apexの基本的な部分を学びました：
1. 基本文法とデータ型
2. トリガーの仕組みと実装方法
3. テストクラスの作成方法

次のステップとしては：
- より複雑なビジネスロジックの実装
- バッチ処理やスケジュール処理の実装
- API統合の実装

## 参考資料
- [Apex Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/)
- [Apex Testing Best Practices](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_best_practices.htm)