Salesforce LWCとApexの連携:@wireと命令的呼び出しの徹底解説

背景と応用シナリオ

Salesforce開発者として、私たちは常にユーザーに最高の体験を提供することを目指しています。その中心となるのが、Lightning Web Components (LWC) (ライトニングWebコンポーネント) です。LWCは、最新のWeb標準に基づいて構築された、Salesforceプラットフォーム向けの軽量でパフォーマンスの高いUIフレームワークです。しかし、優れたUIを構築するだけでは不十分です。多くの場合、コンポーネントはSalesforceデータベースと対話し、データの取得、作成、更新、削除といった操作を行う必要があります。

ここで登場するのが、サーバーサイドコントローラーであるApexです。LWCはクライアントサイド(ブラウザ)で実行され、Apexはサーバーサイドで実行されます。この2つを連携させることで、複雑なビジネスロジックを実行したり、SOQLクエリを介して大量のデータを効率的に処理したりすることが可能になります。

例えば、以下のようなシナリオが考えられます:

  • 取引先レコードページに、関連するすべての進行中の商談をリスト表示するカスタムLWCを配置する。
  • ユーザーがボタンをクリックすると、特定の条件に基づいて新しいケースレコードをバックグラウンドで作成する。
  • カスタム検索コンポーネントを作成し、ユーザーが入力したキーワードに基づいて取引先責任者を動的に検索・表示する。

これらのシナリオを実現するためには、LWCからApexメソッドを呼び出し、データを安全かつ効率的にやり取りする仕組みを理解することが不可欠です。本記事では、Salesforce開発者の視点から、LWCとApexを連携させるための2つの主要な方法、@wireサービス命令的呼び出し(Imperative Call)について、その原理、具体的な実装方法、そしてベストプラクティスを徹底的に解説します。


原理説明

LWCがApexメソッドを呼び出すためには、まずApexメソッドがLWCからアクセス可能であることを示す必要があります。これは、メソッドに @AuraEnabled アノテーションを付与することで実現します。このアノテーションが付けられたメソッドのみが、LWCやAuraコンポーネントから呼び出すことができます。

LWCから @AuraEnabled メソッドを呼び出す方法は、主に2つあります。

1. @wire サービスを利用したリアクティブなデータ取得

@wire は、LWCのプロパティまたは関数をApexメソッドに「結びつける(wire up)」ためのデコレータです。これはリアクティブ (Reactive) な性質を持っており、Salesforce開発において非常に強力な機能です。

リアクティブとは、データの変更が自動的にコンポーネントに反映されることを意味します。@wire を使用すると、コンポーネントの読み込み時にApexメソッドが自動的に呼び出され、データが取得されます。さらに、そのデータがLightning Data Service (LDS) (ライトニングデータサービス) によってキャッシュされます。もしサーバー上のデータが変更されたり、他のコンポーネントが同じデータを更新したりすると、LDSがそれを検知し、@wire で結びつけられたプロパティが自動的に更新され、コンポーネントが再レンダリングされます。これにより、開発者は手動でデータを再取得するコードを書く必要がなくなります。

@wire を使用するApexメソッドには、(cacheable=true) を指定する必要があります。これは、メソッドがデータの読み取り専用であり、副作用(DML操作など)を持たないことをフレームワークに伝えるためです。

2. 命令的呼び出し (Imperative Call)

命令的呼び出しは、開発者が任意のタイミングでApexメソッドを明示的に呼び出す方法です。@wire がコンポーネントのライフサイクルに連動して自動的に実行されるのに対し、命令的呼び出しはボタンのクリックやフォームの送信など、特定のユーザーイベントに応じて実行したい場合に使用します。

命令的にApexメソッドを呼び出すと、JavaScriptの Promise (プロミス) が返されます。Promiseは非同期処理の結果を表すオブジェクトであり、処理が成功した場合は .then() ブロックで結果を処理し、失敗した場合は .catch() ブロックでエラーを処理します。

この方法は、データの作成、更新、削除 (DML操作) を行うメソッドや、(cacheable=true) を指定できないメソッド(外部APIコールアウトなどを含む)を呼び出す場合に最適です。


示例代码

ここでは、取引先責任者(Contact)を検索し、表示する簡単なLWCを作成します。@wire を使って初期のリストを表示し、命令的呼び出しを使って特定の名前で検索する機能を実装します。

1. Apexコントローラーの作成

まず、LWCから呼び出されるApexクラスを作成します。

// ContactController.cls
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [
            SELECT Id, Name, Title, Phone, Email
            FROM Contact
            WITH SECURITY_ENFORCED
            LIMIT 10
        ];
    }

    @AuraEnabled
    public static List<Contact> findContacts(String searchKey) {
        String key = '%' + searchKey + '%';
        return [
            SELECT Id, Name, Title, Phone, Email
            FROM Contact
            WHERE Name LIKE :key
            WITH SECURITY_ENFORCED
        ];
    }
}

コードの注釈

  • Line 2: with sharing キーワードにより、このクラスが現在のユーザーの共有ルールを尊重することを保証します。セキュリティの基本です。
  • Line 3: @AuraEnabled(cacheable=true) - このメソッドがLWCから呼び出し可能で、かつ結果がクライアントサイドでキャッシュ可能であることを示します。@wire サービスで利用するために必須です。
  • Line 4: getContactList() - パラメータなしでContactのリストを返す静的メソッドです。
  • Line 7: WITH SECURITY_ENFORCED - ユーザーの項目レベルセキュリティ(FLS)とオブジェクト権限を自動的に適用します。これもセキュリティのベストプラクティスです。
  • Line 12: @AuraEnabled - こちらは cacheable=true がありません。なぜなら、このメソッドはパラメータ(searchKey)を受け取り、その結果は毎回異なる可能性があるため、必ずしもキャッシュに適しているとは限らないからです。命令的に呼び出します。
  • Line 13: findContacts(String searchKey) - 検索キーを引数として受け取るメソッドです。

2. LWCの作成 (HTML, JavaScript)

次に、Apexコントローラーを呼び出すLWCを作成します。

apexWireMethod.html

<template>
    <lightning-card title="Apex Wire Service" icon-name="custom:custom63">
        <div class="slds-m-around_medium">
            <!-- @wireで取得したデータを表示 -->
            <template if:true={contacts.data}>
                <h4>Initial Contact List (Wired)</h4>
                <template for:each={contacts.data} for:item="contact">
                    <p key={contact.Id}>{contact.Name}</p>
                </template>
            </template>

            <!-- エラーハンドリング -->
            <template if:true={contacts.error}>
                <p>Error loading contacts: {contacts.error.body.message}</p>
            </template>
        </div>
    </lightning-card>

    <lightning-card title="Apex Imperative Call" icon-name="custom:custom83">
        <div class="slds-m-around_medium">
            <!-- 検索入力フィールド -->
            <lightning-input type="search" 
                             onchange={handleKeyChange} 
                             class="slds-m-bottom_small" 
                             label="Search" 
                             value={searchKey}>
            </lightning-input>
            <lightning-button label="Search" onclick={handleSearch}></lightning-button>
            
            <!-- 命令的呼び出しで取得した結果を表示 -->
            <template if:true={imperativeContacts}>
                <h4 class="slds-m-top_medium">Search Results</h4>
                <template for:each={imperativeContacts} for:item="contact">
                    <p key={contact.Id}>{contact.Name}, {contact.Email}</p>
                </template>
            </template>

            <!-- エラーハンドリング -->
            <template if:true={error}>
                <p class="slds-m-top_medium">Error searching contacts: {error}</p>
            </template>
        </div>
    </lightning-card>
</template>

apexWireMethod.js

import { LightningElement, wire, track } from 'lwc';
// Apexメソッドをインポート
import getContactList from '@salesforce/apex/ContactController.getContactList';
import findContacts from '@salesforce/apex/ContactController.findContacts';

export default class ApexWireMethod extends LightningElement {
    // @wireでApexメソッドの結果を受け取るプロパティ
    // dataプロパティまたはerrorプロパティに結果が格納される
    @wire(getContactList)
    contacts;

    // 命令的呼び出し用のプロパティ
    searchKey = '';
    @track imperativeContacts;
    @track error;

    // 入力フィールドの値が変更されたときに呼び出されるハンドラ
    handleKeyChange(event) {
        this.searchKey = event.target.value;
    }

    // 検索ボタンがクリックされたときに呼び出されるハンドラ
    handleSearch() {
        // 命令的にApexメソッドを呼び出す
        findContacts({ searchKey: this.searchKey })
            .then((result) => {
                // 成功した場合、結果をプロパティに設定
                this.imperativeContacts = result;
                this.error = undefined; // 以前のエラーをクリア
            })
            .catch((error) => {
                // 失敗した場合、エラーをプロパティに設定
                this.error = error;
                this.imperativeContacts = undefined; // 以前の結果をクリア
            });
    }
}

コードの注釈

  • JS Line 3-4: @salesforce/apex/... という構文を使って、Apexコントローラーのメソッドをインポートします。これにより、コンパイラがメソッドの存在を検証できます。
  • JS Line 8-9: @wire(getContactList) contacts; - これが @wire サービスの核心です。getContactList メソッドを呼び出し、その結果(データまたはエラー)をリアクティブな contacts プロパティに格納します。
  • HTML Line 5-11: contacts.data が存在する場合にのみ、リストを表示します。@wire の結果は { data, error } という形式のオブジェクトで返されます。
  • HTML Line 14-16: contacts.error が存在する場合に、エラーメッセージを表示します。
  • JS Line 23: handleSearch() 関数内で、インポートした findContacts メソッドを直接呼び出しています。これが命令的呼び出しです。
  • JS Line 24: Apexメソッドに渡すパラメータは、キーがApexメソッドの引数名と一致するオブジェクトとして渡します ({ searchKey: this.searchKey })。
  • JS Line 25: .then((result) => { ... }) - Promiseが成功裏に解決された場合に実行されるコールバック関数。result にはApexメソッドの戻り値が入っています。
  • JS Line 30: .catch((error) => { ... }) - Promiseが拒否された(エラーが発生した)場合に実行されるコールバック関数。

注意事項

権限 (Permissions)

LWCからApexメソッドを呼び出すユーザーは、そのApexクラスへのアクセス権限が必要です。また、Apexクラス内で参照しているオブジェクトや項目に対しても、適切なオブジェクト権限および項目レベルセキュリティ(FLS)が付与されている必要があります。WITH SECURITY_ENFORCED を使用することで、これらの権限が強制され、セキュリティが向上します。

@AuraEnabled(cacheable=true) の制約

@wire で使用するApexメソッドは、必ず (cacheable=true) を指定する必要があります。これは、そのメソッドがDML操作(insert, update, deleteなど)を含まず、データのクエリのみを行うことを保証するためです。DML操作を行うメソッドを cacheable=true に設定しようとすると、コンパイルエラーが発生します。

エラー処理 (Error Handling)

@wire と命令的呼び出しでは、エラーの処理方法が異なります。

  • @wire: 結果オブジェクトの error プロパティをチェックします。HTMLテンプレート内で if:true={property.error} を使って条件付きでエラーメッセージを表示するのが一般的です。
  • 命令的呼び出し: 返されるPromiseの .catch() ブロックでエラーを捕捉します。ユーザーに分かりやすいエラーメッセージを表示するために、lightning/platformShowToastEvent を使ってトースト通知を表示することも有効です。

APIの制限

Apexの呼び出しはサーバーへのラウンドトリップを伴うため、パフォーマンスに影響を与える可能性があります。特に、短い間隔で何度も命令的呼び出しを行うような実装(例:キー入力ごとに検索を実行する)は避けるべきです。このような場合は、入力を少し待ってからAPIコールを行う「デバウンス(Debouncing)」というテクニックを実装することを検討してください。


まとめとベストプラクティス

LWCとApexの連携は、Salesforce上でリッチでインタラクティブなアプリケーションを構築するための基本です。@wire サービスと命令的呼び出しは、それぞれ異なるユースケースに対応するための強力なツールです。

ベストプラクティス

  1. 適切な方法を選択する:
    • コンポーネントの初期化時にデータを読み込み、表示するだけであれば @wire を使用します。LDSによるキャッシュの恩恵を受けられ、コードもシンプルになります。
    • ユーザーのアクション(ボタンクリックなど)に応じてDML操作を行ったり、動的なパラメータでデータを取得したりする場合は、命令的呼び出しを使用します。
  2. セキュリティを最優先する:
    • Apexクラスには必ず with sharing または without sharing を明示的に指定します。
    • SOQLクエリには WITH SECURITY_ENFORCED を使用して、ユーザーの権限を尊重します。
  3. サーバーコールを最小限に抑える:
    • 一度のApexコールで必要なデータをまとめて取得するように設計します。複数の @wire 呼び出しが同じApexメソッド(同じパラメータ)を呼び出した場合、LDSキャッシュが利用されるため効率的です。
    • 命令的呼び出しを頻繁に行う場合は、デバウンスなどのテクニックで不要なコールを減らします。
  4. 堅牢なエラー処理を実装する:
    • 必ずエラーパスを考慮し、ユーザーに何が起こったのかをフィードバックする仕組みを実装してください。何も表示されない画面は、ユーザー体験を著しく損ないます。

これらの原則を理解し、適切に使い分けることで、Salesforce開発者として、パフォーマンスが高く、安全で、保守しやすいLWCを構築することができるでしょう。

コメント