Salesforce Developers Japan Blog

スケールを意識した Lightning ページの設計

(この投稿は Anil Jacob(Salesforce.com) による『Designing Lightning Pages for Scale』の翻訳です)

本記事は、アーキテクト、開発者、管理者が、スケールとパフォーマンスを意識した Lightning ページの設計や構築を行うためのガイドです。

これまで、Salesforce のアプリケーション開発は Classic UI と呼ばれる UI を前提としており、 Visualforce ページと複数のページで構成されたアプリケーションの構築を行っていました。しかし近年、Lightning UI と呼ばれるシングルページアプリケーション(SPA) の構築へと推移しており、Lightning コンポーネントを組み合わせて構築してする手法が主流となってきました。

多くの作業を一つのページで完了できる SPA では、ページ遷移がほとんど発生しません。これは、生産性向上において大きなアドバンテージであり、より良いユーザー体験に繋がります。しかし、スケールやパフォーマンスを維持するためには SPA の特性を理解し、最適な設計が求められます。

SPA は通常複数のコンポーネントから構成され、各コンポーネントは XML HTTP リクエスト(XHR) と呼ばれる通信方式でサーバーと通信します。ページがロードされると、複数のコンポーネントが一斉に描画されます。同時にそれぞれのコンポーネントが XHR によりサーバーからデータとメタデータを取得します。

多くのユーザーが利用する組織の場合、各コンポーネントの設計やタイミングによっては非常に多くのリクエストを一度に処理しなければならなくなります。つまり、スケールやパフォーマンスを維持するためには、コンポーネントとサーバー間で通信量を意識した設計が必要になるわけです。

スケールとパフォーマンスとは

スケールとは、システムに負荷がかかった時にどれだけ性能を維持できるかという指標です。システムがスケールしない場合、システム全体の性能が下がりビジネスの成長も止まります。パフォーマンスは処理が完了するのにかかる時間です。パフォーマンスの低いシステムでは UI アプリケーションの動作が遅くなり、ユーザー体験の品質低下に繋がります。

 

スケールに影響を与える要素

多くのユーザーが利用する組織で、スケールとパフォーマンスを維持するために、考慮すべき要素はいくつかあります。以下はその例です。

  • 配置されるコンポーネントの数
  • 大量のデータを表示するカスタムリストビュー
  • 関連リストとクイックリンク
  • カスタムコンポーネント
  • Apex とバックエンド
  • UI フォーム

これらについて、それぞれどのような考慮が必要かを説明していきます。

 

配置されるコンポーネントの数

あらゆるお客様が実装された画面を見てきた中で分かったことは、Lightning ページの読み込み時間を短縮するには、適切な数のコンポーネントを配置する必要があると言うことです。どのように配置するコンポーネントの数を絞れば良いのでしょうか。

解1: 遅延ロードするタブを使う

タブコンポーネントのように表示を切り替えられるコンポーネントは、効率的な描画が可能なだけでなくユーザー体験の向上にも繋がります。このようなコンポーネントの読み込み方式を遅延ロードと言います。遅延ロードでは、タブが切り替えられた時にのみ、追加で必要なコンポーネントの描画とそれに伴う通信が実行されます。必要な作業は、コンポーネントを機能毎にグループ化しそれぞれタブの中に配置するだけです。

解2: ユーザーの役割に応じた遅延ロード

さらに効率化するのであれば、ユーザーによって最初に表示するコンポーネントを出し分ける事を検討して下さい。利用ユーザーにとって、最も重要な情報だけを初めに表示する事で不要なコンポーネントのロードによる時間ロスを低減できます。

例として、保険商品の販売員の画面を見てみましょう。販売員がよく見るのが商品(Product)である事が分かっている場合、最初に商品(Product)タブを表示しておきます。その他の情報は、非活性のタブ内に配置する事で、必要になるまではコンポーネントのロードを遅らせることが出来ます。

カスタムコンポーネントでの遅延ロード

LWC では、is:true ディレクティブを使うことで、簡単に遅延ロードを実装出来ます。

以下のサンプルコードでは、showForm プロパティの値を fasle にしておくことで、 c:formcompoment は DOM に挿入されなくなります。c:formcompoment カスタムコンポーネントがただのスタブとして振る舞っている間、読み込まれる DOM 要素の数とサーバー通信を抑制でき、ページの読み込むにかかる時間を短縮出来ます。

TIP: showForm 変数の値は、ボタンなどを配置して必要な時に表示できるようにしておきましょう。

Lightning Web Component

<template is:true={showForm}>
  <c:formcomponent> - //component is not visible unless showForm == true
</template>

Aura component

<aura:if isTrue="{!v.showForm}">
  <c:formcomponent> - //component is not visible unless showForm == true
</aura:if>

大量のデータを表示するカスタムリストビュー

大量のデータを表示するようなカスタムリストビューもスケールとパフォーマンスに大きく影響を与えます。私達が行った検証によると、2000 行のデータを読み込むのにかかる時間は約 10 秒でした。その内、ほとんどの時間をリストの描画に費やしている事も分かりました。

下は 2000 行表示するリストビューですが、ブラウザのツールを利用して描画時間を計測してみると、実に9秒もの時間がかかっていることが分かります。残りの 1 秒はブラウザによるスタイリングの再計算です。

注意: これはあくまでも一つの例であり、実際の所要時間はリストの種別やその他の要素に依存します。

9 秒というは、大抵のユーザーにとって待てる時間ではありません。リストの描画時間を短縮するにはどのような手段があるのでしょうか。

解1: カスタムリストコンポーネント

LWC でカスタムリストコンポーネントを作成します。 LWC の JavaScript と HTML によって高速にリストの作成と描画を行います。LWC の ListUI モジュールを使う事で、標準リストビューで表示されるレコードの取得も容易です。カスタムリストを作成したとしても、ページングの実装は強く推奨されます。

解2: ページング

ページングはサーバーとクライアントの両方で実装します。ページングすることで、クライアント側で描画にかかる時間が大幅に短縮され、快適なユーザー体験を提供できます。いくつかの検証から 50 行以下のリストが最も高速で効率的な描画を可能にする事が分かっています。

カスタムリストのページング実装例

import { LightningElement, track, wire, api } from "lwc";
import getContacts from "@salesforce/apex/ListViewPlusDataController.getContacts";
import { getObjectInfo } from "lightning/uiObjectInfoApi";
import CONTACT_OBJECT from "@salesforce/schema/Contact";
export default class Listviewplus extends LightningElement {
  .....
  @wire(getContacts)
  wiredContacts({ error, data }) { 

    if (error) {
      this.error = error;
    } else if (data) {
      this.contactsbackup = data;
      this.contacts = data.slice(1, 50); 
    }
    .....
  @api
  pressRight(left, right) {
    this.contacts = this.contactsbackup.slice(left, right);
  }
  @api
  pressLeft(left, right) {
    this.contacts = this.contactsbackup.slice(left, right);
  }

ページングと言っても、以下のようにボタンで遷移できるだけも有効です。

リストをコントロールするための HTML コードです。

<div class="reccount slds-col slds-size_1-of-12">
  <h6 class="slds-small-size slds-section__title" >
    {leftIndex} - {rightIndex} of {numrecords}</h6>
</div>
  <div class="leftarrow slds-col slds-size_1-of-12">
  <lightning-button-icon title="Go To Previous Page Of This List"
      icon-name="utility:left" size="large" alternative-text="refresh this view"
      onclick={pressLeft} disabled={leftDisabled}></lightning-button-icon>
</div>

<div class="rightarrow slds-col slds-size_1-of-12">
  <lightning-button-Icon title="Go To Next Page Of This List"
      icon-name="utility:right" size="large"
      alternative-text="refresh this view"
      onclick={pressRight} disabled={rightDisabled}></lightning-button-Icon>
</div>

クイックリンクの最適化

クイックリンクは、即座に関連リストを確認できる素晴らしい機能です。各関連リストのレコード数の一覧に加えて、マウスオーバーによりレコードの一部を表示出来ます。

ところが、多くのユーザーが利用する組織では、クイックリンクの利用を避け、関連リストはタブコンポーネント内に配置した方がパーフォーマンスの向上に繋がります。なぜなら、ユーザーがページを読み込むたびに発生するレコード数の不要な計算を防ぎ、意図しないマウスオーバーによる無駄な通信を削減できるからです。

では、クイックリンクの代わりにタブを利用する事で、どの程度ページロードにかかる時間を短縮できるのか見てみましょう。

有効性の検証

ページの設計が、どれほどページ描画のパフォーマンスに影響を与えるか検証しました。この検証は 8 つのコンポーネントとクイックリンクを含むページで実施しています。

その後、クイックリンクを除外し、関連リストコンポーネントをそれぞれのタブに配置してみると、約 0.4 秒の短縮となりました。
これは、クイックリンクによる XHR による通信を排除したことの効果だと言えます。

注: このデータは本記事用に実施した基本的な検証に基づいてます。実際はその他の多くの要素に依存します。

スケールするコンポーネントを作成

カスタムコンポーネントを作成すると Salesforce プラットフォームの機能を活かしつつ独自のビジネスに合わせたアプリケーションを構築出来ます。カスタムコンポーネントは LWC によって簡単に開発できますが、listView や recordForm などの基本コンポーネントを活用すればさらに容易に開発できます。最も洗練されたコンポーネントは複雑なバックエンドのコードを必要とせずクライアントでデータを提供しますが、複雑なビジネス要件がある場合は Apex でのコーディングも可能です。

プラットフォーム・キャッシュを使う

初回ページロードで必ず表示したいカスタムコンポーネントを作成するのであれば、プラットフォーム・キャッシュの利用を検討しましょう。プラットフォーム・キャッシュでは、サーバー上のキャッシュ領域にアクセスできるようになり、コンポーネントによるリスクエスト毎の SOQL の発行数を削減する効果があります。

キャシュからのデータ取得は通常 10ms 以下で、コンポーネントの描画時間を短縮できるだけなく、データベースアクセス数を減らす事で大量の処理にシステムを苦められる状況を回避できます。
プラットフォーム・キャッシュの使い方を見てみましょう。

public static String getOffers(String agentId) {
  String cacheOffer = (String)Cache.Org.get(agentId); 

  //Cast to String since we serialized the value.
  if (cacheOffer != null) {
    return cacheOffer; 
  }
  else {
    return null; //If null check again later//

更に詳しい情報は、リファレンスからご確認頂けます。

クライアント・キャッシュのを使う

カスタムコンポーネントの場合、クライアント側のキャッシュ機能を利用する事で、サーバへの余計な通信を削減できるため、パフォーマンスの改善に繋がります。LWC のキャッシュを有効にするには、以下の通り Apex コードを記述します。

@AuraEnabled(cacheable=true)
public static List<String> getMetrics() { 
//your code here..

これによりクライアントが必要とするデータは、更新が必要となるまでキャッシュされ不要な XHR 通信が発生しません。

サーバー側の処理の最適化

スケールとユーザー体験は、バックエンド処理を記述する Apex に大きく依存します。サーバー側の処理の最適化にはいくつかのアプローチがあります。

  • Apex での処理を減らし、クライアントの待機時間を短縮
  • Apex との通信回数を削減
  • Apex @AuraEnabled メソッド内の処理を削減
  • 可能な限りクライアント側に処理を寄せる

例えば、クライアントでユーザー情報が必要な場合、ユーザー名などの個別データをそれぞれ取得するような設計は通信数の増加に繋がります。別々の場所にいるユーザーが、それぞれ違ったネットワーク環境からこれ実行すると一部のユーザーのページロードが遅くなりることが予想されます。そのため、必要なデータは可能な限りまとめて送信するの設計が望まれます。

まとめ

コンポーネントベースの Lightning アプリケーションはユーザーの生産性を高めてくれますが、多くがユーザーが利用するような環境ではアプリケーションや組織のスケール/パフォーマンスを意識した設計が必要となります。また、それがユーザー体験を高めくれます。

リファレンス

Improve Performance and Speed in Lightning
Platform Cache Blog Scaling Data Access With App Layer Cache

筆者について

Anil Jacob は Frontier Scale チームのプリンシパルエンジニアとして、スケールする Salesforce の実装でお客様を支援しています。Salesforce には 10 年間所属した Anil の専門分野は、アーキテクチャ、大量データボリューム、アプリケーションデザイン、 Lightning
、API と多岐に渡ります。Salesforce 入社以前は、Intuit、BEA にて Weblogic Visualization のコンサルタント、Wells Fargo Investments で活躍していました。

コメント

スケールを意識した Lightning ページの設計