背景と応用シナリオ
Salesforce 開発者として、私たちは常にユーザーにとってより高速で、インタラクティブで、かつ効率的なユーザーインターフェースを構築することを目指しています。Lightning Web Components (LWC) (ライトニングウェブコンポーネント) は、最新のWeb標準に基づいて構築された Salesforce の次世代UIフレームワークであり、その目標を達成するための強力なツールです。LWCの真価は、Salesforceプラットフォームの豊富なデータとシームレスに連携できる点にあります。
例えば、以下のようなシナリオを考えてみましょう。
- 特定の条件に一致する取引先リストをリアルタイムで表示するカスタムダッシュボード
- 商品選択に応じて関連する価格表や在庫情報を動的に表示する注文作成画面
- ボタンクリックで複雑なビジネスロジックを実行し、複数のオブジェクトを更新するカスタムアクション
これらのシナリオを実現するためには、LWCコンポーネントがサーバーサイドの Apex (エイペックス) を呼び出し、データの取得や更新を行う必要があります。この記事では、Salesforce 開発者の視点から、LWCとApexを連携させるための主要な2つの方法、すなわち Wire Service (ワイヤーサービス) と Imperative Apex Call (命令的なApex呼び出し) について、その原理、具体的な実装方法、そしてベストプラクティスを徹底的に解説します。
原理説明
LWCからApexメソッドを呼び出すには、まずApexメソッドがLWCからアクセス可能であることを示す必要があります。これは @AuraEnabled アノテーションをメソッドに付与することで実現します。その上で、LWC側からの呼び出し方として、主に2つのアプローチが存在します。
1. Wire Service (@wire) を使用したリアクティブなデータ取得
Wire Serviceは、Salesforce のデータを「リアクティブに」コンポーネントに提供するための仕組みです。@wire (ワイヤーデコレーター) を使用してプロパティまたは関数を装飾することで、LWCフレームワークがデータの取得と管理を自動的に行ってくれます。
主な特徴は以下の通りです。
- リアクティブ性: サーバーからデータが返されたり、基盤となるデータが変更されたりすると、コンポーネントは自動的に再レンダリングされます。開発者が手動で更新ロジックを記述する必要はありません。
- クライアントサイドキャッシュ: Wire Serviceは Lightning Data Service (LDS) (Lightning データサービス) の一部であり、一度取得したデータはクライアント側でキャッシュされます。同じリクエストが再度発生した場合、サーバーにアクセスすることなくキャッシュからデータを返すため、パフォーマンスが大幅に向上します。
- 読み取り専用:
@wireを介して呼び出されるApexメソッドは、データを読み取ることしかできません。そのため、Apexメソッドには@AuraEnabled(cacheable=true)というアノテーションが必須となります。これにより、メソッドが副作用を持たない(DML操作などを行わない)ことが保証されます。
この方法は、コンポーネントの初期化時にデータを表示したり、特定のレコードIDに基づいて関連情報を表示したりするなど、データの読み取り操作に最適です。
2. Imperative Apex Call を使用した命令的なメソッド呼び出し
一方、ユーザーのアクション(ボタンのクリックなど)に応じて任意のタイミングでApexメソッドを呼び出したい場合や、データの作成・更新・削除 (DML: Data Manipulation Language、データ操作言語) を行いたい場合は、命令的なApex呼び出しを使用します。
主な特徴は以下の通りです。
- 能動的な制御: 開発者がJavaScriptのコード内で明示的にメソッドを呼び出すため、実行タイミングを完全に制御できます。
- DML操作のサポート:
cacheable=trueの制約がないため、レコードの作成(insert)、更新(update)、削除(delete) を行うApexメソッドを呼び出すことができます。 - Promiseベースの非同期処理: メソッド呼び出しは Promise (プロミス) オブジェクトを返します。これにより、
.then()ブロックで成功時の処理を、.catch()ブロックで失敗時の処理を記述する、標準的な非同期処理パターンを利用できます。
この方法は、フォームの送信処理、検索ボタンのクリックイベント、複雑なデータ処理のトリガーなど、能動的な操作が必要な場合に適しています。
サンプルコード
ここでは、Salesforce公式ドキュメントに基づいたコード例を用いて、両方のアプローチを具体的に見ていきましょう。
例1: @wire を使用して取引先責任者リストを取得する
まず、取引先責任者のリストを返すシンプルなApexクラスを作成します。@wire で使用するため、@AuraEnabled(cacheable=true) が必須です。
Apex Controller: 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
ORDER BY Name
LIMIT 10
];
}
}
コードの注釈:
@AuraEnabled(cacheable=true): このメソッドがLWCの@wireサービスから呼び出し可能であり、かつクライアント側でキャッシュ可能であることを示します。with sharing: クラスが現在のユーザーの共有ルールを適用することを宣言します。セキュリティのベストプラクティスです。WITH SECURITY_ENFORCED: SOQLクエリレベルで項目レベルセキュリティ(FLS)とオブジェクト権限を適用します。
LWC JavaScript: contactList.js
import { LightningElement, wire } from 'lwc';
import getContactList from '@salesforce/apex/ContactController.getContactList';
export default class ContactList extends LightningElement {
// @wireデコレーターを使用してApexメソッドを呼び出す
// getContactListメソッドからのデータは contacts プロパティに格納される
@wire(getContactList)
contacts;
// contactsプロパティは { data, error } という形式のオブジェクトを持つ
// dataプロパティが設定されると、ゲッターがtrueを返す
get hasContacts() {
return this.contacts.data;
}
}
コードの注釈:
import getContactList from '@salesforce/apex/ContactController.getContactList';: ApexメソッドをJavaScriptモジュールとしてインポートします。@wire(getContactList) contacts;:getContactListApexメソッドを呼び出し、その結果をcontactsプロパティにリアクティブに紐付けます。contactsプロパティには、成功時にはdataにデータが、失敗時にはerrorにエラー情報が自動的に設定されます。
LWC HTML: contactList.html
<template>
<lightning-card title="Apex Wire Service to Method" icon-name="custom:custom63">
<div class="slds-m-around_medium">
<template if:true={contacts.data}>
<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>
</template>
コードの注釈:
<template if:true={contacts.data}>: データが正常に取得できた場合にのみ、内部のテンプレートを描画します。<template for:each={contacts.data} for:item="contact">: 取得した取引先責任者リストをループ処理し、各レコードの名前を表示します。<template if:true={contacts.error}>: データ取得時にエラーが発生した場合にエラーメッセージを表示します。
例2: 命令的にApexを呼び出して取引先責任者を検索する
次に、ユーザーが入力した検索キーワードに基づいて取引先責任者を検索する機能を実装します。これはボタンクリックという明確なトリガーがあるため、命令的な呼び出しが適しています。
Apex Controller: ContactController.cls (メソッド追加)
public with sharing class ContactController {
// ... 既存の getContactList メソッド ...
@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
LIMIT 10
];
}
}
コードの注釈:
@AuraEnabled: このメソッドは命令的に呼び出すため、cacheable=trueは必須ではありません。ただし、検索のような読み取り専用操作の場合は、キャッシュを有効にすることでパフォーマンスが向上することもあります。String searchKey: LWCから渡される検索キーワードを受け取るための引数です。
LWC JavaScript: apexImperativeMethod.js
import { LightningElement, track } from 'lwc';
import findContacts from '@salesforce/apex/ContactController.findContacts';
export default class ApexImperativeMethod extends LightningElement {
@track searchKey = '';
@track contacts;
@track error;
handleKeyChange(event) {
this.searchKey = event.target.value;
}
// ボタンクリック時に呼び出されるハンドラ
handleSearch() {
// 命令的にApexメソッドを呼び出す
findContacts({ searchKey: this.searchKey })
.then((result) => {
this.contacts = result;
this.error = undefined;
})
.catch((error) => {
this.error = error;
this.contacts = undefined;
});
}
}
コードの注釈:
findContacts({ searchKey: this.searchKey }): Apexメソッドを関数として呼び出します。引数は、Apexメソッドの引数名をキーとするオブジェクトとして渡します。.then((result) => { ... }): Promiseが成功裏に解決された場合(Apexメソッドが正常に値を返した場合)に実行されます。resultにはApexメソッドの戻り値が格納されます。.catch((error) => { ... }): Promiseが拒否された場合(Apexメソッドでエラーが発生した場合)に実行されます。errorオブジェクトにエラー情報が格納されます。
LWC HTML: apexImperativeMethod.html
<template>
<lightning-card title="Apex Imperative Method" icon-name="custom:custom14">
<div class="slds-m-around_medium">
<lightning-input
label="Search Contact"
type="search"
onchange={handleKeyChange}
value={searchKey}
></lightning-input>
<lightning-button
label="Search"
variant="brand"
onclick={handleSearch}
class="slds-m-top_small"
></lightning-button>
</div>
<div class="slds-m-around_medium">
<template if:true={contacts}>
<template for:each={contacts} for:item="contact">
<p key={contact.Id}>{contact.Name}</p>
</template>
</template>
<template if:true={error}>
<p>{error.body.message}</p>
</template>
</div>
</lightning-card>
</template>
コードの注釈:
<lightning-input onchange={handleKeyChange}>: ユーザーが入力するたびにhandleKeyChangeが呼び出され、searchKeyプロパティが更新されます。<lightning-button onclick={handleSearch}>: 検索ボタンがクリックされると、handleSearchが呼び出され、Apexメソッドの命令的な呼び出しが実行されます。
注意事項
LWCとApexの連携を実装する際には、以下の点に注意する必要があります。
- 権限とセキュリティ: Apexクラスは
with sharingまたはwithout sharingを明示的に指定することが推奨されます。また、SOQLクエリではWITH SECURITY_ENFORCEDを使用して、ユーザーの項目レベルセキュリティ(FLS)とオブジェクト権限を強制的に適用することが、現在のベストプラクティスです。これにより、ユーザーがアクセス権を持たないデータを誤って表示してしまうリスクを防ぎます。 - APIの制限: ApexはSalesforceのガバナ制限(1トランザクションあたりのSOQLクエリ数、CPU時間など)の影響を受けます。大量のデータを扱う場合や複雑な処理を行う場合は、制限に抵触しないようにApexコードを最適化する必要があります。
- エラー処理: サーバーサイドのエラーは必ず発生しうるものです。
@wireの場合はerrorプロパティを、命令的呼び出しの場合は.catch()ブロックを必ず実装し、ユーザーに分かりやすいフィードバックを提供することが重要です。エラーメッセージをそのまま表示するのではなく、より親切なメッセージに加工することも検討しましょう。 @AuraEnabled(cacheable=true)の重要性: このアノテーションは、単にキャッシュを有効にするだけでなく、メソッドが「安全」で「冪等(べきとう)」であることをフレームワークに伝えます。つまり、何度呼び出してもシステムの状態を変更しない(DML操作を行わない)ことを意味します。この制約により、LWCフレームワークは安心してメソッドの結果をキャッシュし、パフォーマンスを最適化できます。@wireを使う場合は、このアノテーションが不可欠です。
まとめとベストプラクティス
LWCとApexの連携は、Salesforce上でリッチなカスタムUIを構築するための基本です。@wire と命令的呼び出しの特性を理解し、適切に使い分けることが、パフォーマンスと保守性の高いコンポーネントを開発する鍵となります。
最後に、開発者としてのベストプラクティスをまとめます。
- データの読み取りには
@wireを優先する: コンポーネントの初期表示など、データの読み取りが目的の場合は、キャッシュとリアクティブ性の恩恵を受けられる@wireを第一選択とします。コードも簡潔になります。 - DML操作や能動的な制御には命令的呼び出しを使用する: ユーザーのアクションをトリガーとする処理や、レコードの作成・更新・削除を行う場合は、命令的呼び出しを使用します。
- Apexのロジックを再利用可能にする: 特定のLWCに依存しない、汎用的なApexメソッドを設計しましょう。1つのApexクラスに複数の
@AuraEnabledメソッドをまとめることで、コードの管理が容易になります。 - 堅牢なエラーハンドリングを実装する: どのような場合でも、成功パスと失敗パスの両方を考慮したコードを記述します。ユーザーがエラーに直面した際に、何が起こったのか、次に何をすべきかを理解できるように導くことが重要です。
- セキュリティを常に意識する:
with sharingとWITH SECURITY_ENFORCEDをデフォルトで利用し、Salesforceプラットフォームの堅牢なセキュリティモデルを最大限に活用します。
これらの原則に従うことで、あなたはより高度で信頼性の高いLightning Web Componentsを構築できるSalesforce開発者となることができるでしょう。
コメント
コメントを投稿