進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

サーバ側コントローラを使用した Salesforce への接続

学習の目的

この単元を完了すると、次のことができるようになります。
  • Aura コンポーネントコードからリモートでコールできる Apex メソッドを作成する。
  • Aura コンポーネントからリモートメソッドへのコールを実行する。
  • コールバック関数を使用してサーバ応答を非同期で処理する。
  • ストレッチ目標: 「c.」、「c:」、「c.」の違いを説明する。

サーバ側コントローラの概念

厳密にはこれまでに行ったすべての作業はクライアント側でした。経費はまだ Salesforce に保存されていません。いくつかの経費を作成して再読み込みするとどうなるのでしょうか? そうなんです。すべての経費が消えます。やったー、経費が無料になりました。

ただし、経理はこのようなことに関してユーモアのセンスを持っていないので大声で叫ぶだけでしょうね。私たちのポケットから出ていく経費を実際に払い戻してほしいわけではありません。これは問題です。Salesforce へのデータの保存は、間違いなく P0 バグになるでしょう。

冗談はさておき、ついにサーバ側コントローラをアプリケーションに追加するときがきました。基本をやり終えるまでこの作業をとっておいたんです。準備ができたので始めましょう。

といってもいくつか画像を確認するということですけどね。目的地を把握していることと、ガスタンクが満タンになっていることを確認してから出発しましょう。

まず、このモジュールに出てきた最初の図 (Lightning コンポーネントアプリケーションのアーキテクチャの概要図) を再確認しましょう。

Lightning コンポーネントのアーキテクチャの概要: クライアントビューとコントローラ、サーバ Apex コントローラとデータベース

これまで見てきたすべての要素はこの図のクライアント側にありました。(ここではコントローラとヘルパーをマージして簡略化しています)。サーバ側で定義されたカスタムオブジェクト種別 Expense__c を参照しましたが、実際に直接そのサーバを操作することはありませんでした。

異なる要素を結び付けて完全な循環を作成することについてどのような説明があったのか思い出しましたか? 最後の単元では次のような経費フォームを作成しました。

フローのクライアント側

この循環は、clickCreate アクションハンドラ (1) に結び付けられた [Create (作成)] ボタンから始まります。アクションハンドラが実行されると、フォーム項目 (2) から値を取得し、新しい経費を expenses 配列 (3) に追加します。set によって配列が更新されると、経費のリスト (4) の自動再表示がトリガされて、循環が完了します。簡単でしょう?

サーバ側のアクセスに結び付ける場合、この図は若干複雑になります。矢印、色、番号が増えます。(このすべての説明はしばらく後回しにします)。

完全なフロー: クライアント側とサーバ側

さらに、この循環には同じようなスムーズで同期的な制御フローがありません。サーバコールはコストが高く、時間が長くなる可能性があります。順調であればすぐに済みますが (ミリ秒単位)、ネットワークが混雑していればしばらくかかります (秒単位)。サーバ応答の待機中にアプリケーションが動かなくなることは好ましくありません。

待機中に応答できるようにする解決策は、サーバ応答を非同期で処理することです。つまり、[Create Expense (経費を作成)] ボタンをクリックしたときに、クライアント側コントローラでサーバ要求を送信して処理を続けます。これにより、サーバを待機しなくなるだけでなく、要求を行ったことも忘れます。

その後、サーバから要求が戻ってきたときに、コールバック関数と呼ばれる、要求でパッケージされたコードが実行されて応答が処理されます (クライアント側のデータやユーザインターフェースの更新などを含む)。

経験豊富な JavaScript プログラマであれば、非同期実行とコールバック関数は日常業務に欠かせないものでしょう。以前にこれらを操作したことがない場合、目新しく非常に変わったものに感じるかもしれませんが、非常に素晴らしいものでもあります。

Salesforce のデータのクエリ

まず、Expenses アプリケーションの起動時に既存の経費のリストを読み込むために Salesforce のデータを読み取ります。

メモ

メモ

Salesforce でいくつかの実際の経費レコードをまだ作成していない場合は、今が良いタイミングです。作成していないと、これに続く要素を実装した後で、実際に何も読み込まれていないだけなのに時間をかけてその原因をデバッグすることになる可能性があります。謙虚な著者はいつもこれに引っ掛かります。

最初のステップでは、Apex コントローラを作成します。Apex コントローラには、Lightning コンポーネントでコールできるリモートメソッドが含まれます。この場合、Salesforce の経費データを照会して受信するためのメソッドになります。

簡易版のコードを見てみましょう。開発者コンソールで、「ExpensesController」という名前の新しい Apex クラスを作成し、次のコードを貼り付けます。

public with sharing class ExpensesController {
    // STERN LECTURE ABOUT WHAT'S MISSING HERE COMING SOON
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
}

Apex コントローラについては次のセクションでもう少し詳しく説明しますが、今のところこれは非常に簡単な Apex メソッドです。SOQL クエリを実行して結果を返します。このメソッドを Lightning コンポーネントコードで使用できるようにする具体的な要素は次の 2 つのみです。

  • method 宣言の前の @AuraEnabled アノテーション。「Aura」は、Lightning コンポーネントの中核を成すフレームワークの名前です。<aura:component> などの主要なタグの名前空間で使用されていましたが、これでその由来がわかりましたね。
  • static キーワード。すべての @AuraEnabled コントローラメソッドは静的メソッドで、かつ public または global 通用範囲のいずれかである必要があります。

これらの要件で Visualforce の JavaScript Remoting 機能のリモートメソッドを思い出したのなら、それは偶然の一致ではありません。アーキテクチャの重要なポイントが非常に似ているため、要件は同じです。

もう 1 つの重要なことは、Lightning コンポーネントのデータをパッケージ化するためにメソッドで特別なことは何も行われないということです。SOQL クエリの結果を直接返すだけです。Lightning コンポーネントフレームワークにより、ほとんどの状況に伴うすべてのマーシャリング/マーシャリング解除作業が処理されます。すばらしいですね。

Salesforce のデータの読み込み

次のステップでは、expenses コンポーネントをサーバ側 Apex コントローラに結び付けます。これは非常に簡単なので有頂天になるかもしれません。次のように経費コンポーネントの <aura:component> 開始タグを変更して、Apex コントローラを指し示すようにします。

<aura:component controller="ExpensesController">

新しい部分が太字で強調表示されています。非常に簡単ですね。

ただし、Apex コントローラを指し示しても、実際にデータを読み込んだり、リモートメソッドをコールしたりするわけではありません。このように指し示すことにより、コンポーネントと (クライアント側) コントローラ間の自動的な結び付けのように、双方でお互いを「把握」できるようになります。この「把握」の形式さえも同じです (後で説明する別の値プロバイダを使用)。ただし、自動的な結び付けだけでは不十分です。巡回を完了させるには、残りの作業を私たちで行う必要があります。

この場合の巡回の完了は、次のような意味になります。

  1. expenses コンポーネントが読み込まれる。
  2. Salesforce に対して既存の経費レコードを照会する。
  3. これらのレコードを expenses コンポーネント属性に追加する。

これらの各作業を順番に行っていきます。最初の項目 (expenses コンポーネントが最初に読み込まれたときのトリガ動作) については、私たちが init ハンドラを作成する必要があります。これは、コンポーネントが最初に作成されたときに発生するコンポーネントの init イベントに結び付けられたアクションハンドラの略語です。

1 行のマークアップで、このために必要な結び付けを行うことができます。次のマークアップを expenses コンポーネントの属性定義の直下に追加します。

<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>

<aura:handler> タグを使用すると、コンポーネントで特定のイベントをうまく処理できます。この場合、コントローラの doInit アクションハンドラで init イベントを処理します。value="{!this}" を設定すると、これ自体が「値のイベント」としてマークされます。この意味は複雑すぎるため、ここでは説明しません。必ずこの属性-値ペアを init イベントに追加する必要があるということだけを覚えておいてください。

サーバ側コントローラメソッドのコール

1 つステップが終わって、残りは 2 つです。残りのステップのどちらも doInit アクションハンドラで実行されます。それでは見てみましょう。次のコードを expense コンポーネントのコントローラに追加します。

    // Load expenses from Salesforce
    doInit: function(component, event, helper) {
        // Create the action
        let action = component.get("c.getExpenses");
        // Add callback behavior for when response is received
        action.setCallback(this, function(response) {
            let state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.expenses", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
        // Send action off to be executed
        $A.enqueueAction(action);
    },

ここで出てきたすべての新しいことに戸惑う前に、これは単に別のアクションハンドラであるということに気付いてください。その形式や関数の署名は同じです。なじみのある分野ですよね。

そうは言っても、関数の署名の後はすべて新しいコード行です。後でこれらのすべてを見ていきますが、このコードの動作の概要は次のとおりです。

  1. リモートメソッドコールを作成する。
  2. リモートメソッドコールが返されたときの動作を設定する。
  3. リモートメソッドコールをキューに登録する。

非常に簡単そうですよね? コードの構造や詳細は目新しいかもしれませんが、必要な動作の基本的な要件はなじみ深いものです。

何だか本当に激励しているみたいですね。困難を通じてあなたを指導しようとしているのかもしれません。確かに、難しいことについて説明しなければなりません。問題は、関数の最初のコード行にあります。

        let action = component.get("c.getExpenses");

このコード行では、リモートメソッドコール (リモートアクション) を作成します。まず、component.get() 部分には見覚えがあると思います。これまでに何度も実行してきました。

ただし、以前は「v.something」 (v はビューの値プロバイダ) でした。今回は「c」です。そう、c は別の値プロバイダです。c 値プロバイダは、以前に press="{!c.clickCreate}"action="{!c.doInit}" などの式に含まれていました。

これらの式は、ビューのコンポーネントのマークアップにありました。今回のコントローラの c 値プロバイダは、別の何かを表します。それは、リモート Apex コントローラです。

「ちょっと待ってください。Aura コンポーネントには、クライアント側コントローラの c、デフォルトの名前空間の c、サーバ側コントローラの c のすべてがあるというのですか?」

一言でいえば、そうです。深呼吸しましょう。

正直に言いますね。もし思い通りにやり直すことができるのであれば、別の選択をするでしょう。私たちがした選択は事故というわけではありませんが、3 つの「c」が混乱をきたす可能性が高いことは間違いありません。私たちでさえも混乱します。

ただし、月並みですがそれが現実です。備えあれば憂いなしですが、これで準備ができましたね。

識別子
コンテキスト
意味
c.
コンポーネントのマークアップ
クライアント側コントローラ
c.
コントローラコード
サーバ側コントローラ
c:
マークアップ
デフォルトの名前空間

それでは、コードに戻りましょう。話しが脱線する前は、次のコードを見ていましたよね。

        let action = component.get("c.getExpenses");

以前のコードの component.get("v.something") ではビューの子コンポーネント (コンポーネントのマークアップ) への参照を返していましたが、component.get("c.whatever") ではコントローラで使用できるアクションへの参照を返します。この場合、Apex コントローラへのリモートメソッドコールを返します。これが @AuraEnabled メソッドへのコールを作成する方法です。

次の「行」の action.setCallback(…) は、リモートメソッドコールが返されるときに実行されるコードのブロックです。これは「後で」実行されるため、今のところは脇に置いておきましょう。

実際に実行されるのは次の行で、以下のようになります。

        $A.enqueueAction(action);

$A は以前に出てきましたが、説明していませんでした。これは、多数の重要な機能およびサービスを提供するフレームワークグローバル変数です。$A.enqueueAction(action) は、設定したサーバコールを Aura コンポーネントフレームワークの要求キューに追加します。これは、次の要求サイクルで他の待機中のサーバ要求と一緒にサーバに送信されます。

少し不明瞭ですね。詳細は、Aura コンポーネントの高度な用途で重要になり、興味深いのですが$A.enqueueAction(action) を理解するために今必要なことは次のとおりです。

  • サーバ要求をキューに登録する。
  • コントローラアクションに関しては、これで終了となる。
  • いつ戻ってくるのか、または戻ってくるのかどうかはわからない。

次に、脇に置いておいたコードブロックについて説明します。ただし、その前に少しポップカルチャーについてお話しします。

サーバコール、非同期実行、コールバック関数

2011 年にリリースされた Carly Rae Jepsen (カーリー・レイ・ジェプセン) のシングル「Call Me Maybe (コール・ミー・メイビー)」は、何十もの国々のヒットチャートで 1 位を飾り、商業的に大成功を収めました。現在までに世界各国で 1,800 万枚以上を売り上げ、デジタルシングル史上のベストセラーの 1 つの数えられます。コーラスの最も印象的な部分は、「Here’s my number. So call me maybe. (これ私の番号だから、よかったら電話して)」です。アップビートで危険なほどにキャッチーであることに加えて、Aura コンポーネントがサーバコールを処理する方法の隠喩になっています。

最後まで聞いてくださいね。疑似コードのアクションハンドラを見てみましょう。

    doInit: function(component, event, helper) {
        // Load expenses from Salesforce
        let action = component.get("c.getExpenses");
        action.setCallback(
            // Here’s my number,
            // Call me maybe
        );
        $A.enqueueAction(action);
    },

ん? action.setCallback() のパラメータを詳しく説明した方が良さそうですね。実際のアクションハンドラのコードでは、次のようにコールします。

        action.setCallback(this, function(response) { ... });

this はコールバックを実行する通用範囲です。今回の this はアクションハンドラ関数自体です。これは、アドレス (番号) だと考えてください。関数は、サーバ応答が返されたときにコールされる要素です。そのため、次のようになります。

        action.setCallback(scope, callbackFunction);

これが私の番号です。よかったら電話して。

全体として、要求の作成、要求が行われたときに実行される動作のコードのパッケージ化、および実行するための送信が行われます。その時点で、アクションハンドラ自体は実行を停止します。

理解できるように、別の例を次に示します。学校に行く子供たちを集めて、授業が終わって家に帰ってきたときにやってもらいたい雑用のリストを手渡します。あなたは子供たちを学校で降して出勤します。仕事中は自分の作業を行い、良い子にしている子供たちが学校から帰ってきたときにあなたが割り当てた仕事をしていることがわかっているので安心できます。自分自身でその仕事をするわけではないので、子供たちが仕事をする正確な時間はわかりません。ただし、仕事は行われます。

最後にこれをもう一度疑似コードで見てみましょう。このバージョンでは、アクションハンドラのより直線的なバージョンを示すためにコールバック関数が「ラップされていません」。

    // Not real code! Do not cut-and-paste!
    doInit: function(component, event, helper) {
        // Create server request
        let action = component.get("c.getExpenses");
        // Send server request
        $A.enqueueAction(action);
        // ... time passes ...
        // ...
        // ... Jeopardy theme plays ...
        // ...
        // ... at some point in the indeterminate future ...
        // Handle server response
        let state = action.response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", action.response.getReturnValue());
        }
    },

もう一度言います。非同期実行とコールバック関数は JavaScript プログラマにとって必需品ですが、そのような経歴がない場合は、あまりなじみがないかもしれません。これは Lightning コンポーネントを使用したアプリケーションの開発に欠かせないため、できれば現時点で完全に理解しておきましょう。

サーバ応答の処理

サーバ要求を作成する構造を完全に理解したので、コールバック関数が実際に応答を処理するしくみを詳しく見ていきましょう。コールバック関数を次に示します。

    function(response) {
        let state = response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", response.getReturnValue());
        }
    }

コールバック関数は、1 つのパラメータ response (返されるデータ (存在する場合) と要求の状況に関するさまざまな詳細を提供する非透過オブジェクト) を取ります。

この特定のコールバック関数では、次の操作を行います。

  1. 応答の状態を取得する。
  2. 状態が SUCCESS である場合 (要求が計画どおりに完了した場合)、次の操作を行う。
  3. コンポーネントの expenses 属性を応答データの値に設定する。

次のような点について疑問に思うかもしれません。

  • 応答状態が SUCCESS でない場合、どうなるのか?
  • 応答がこない場合、どうなるのか。(電話してくれるかな)?
  • どのように応答データをコンポーネント属性に割り当てることができるのか?

最初の 2 つ疑問については、残念ながらこのモジュールでは扱いません。実際のアプリケーションではこれらを理解して考慮する必要があることは確かですが、単純にそのスペースがありません。

最後の疑問は、ここで最も関連性の高いものになりますが、答えは最も簡単です。私たちは経費属性のデータ型を定義しました。

<aura:attribute name="expenses" type="Expense__c[]"/>

サーバ側コントローラアクションには、返されるデータ型を定義するメソッドの署名があります。

public static List<Expense__c> getExpenses() { ... }

型が一致するため、単純に一方を他方に割り当てるだけで済みます。詳細は、すべて Aura コンポーネントによって処理されます。あなたは確実に結果の処理を行い、アプリケーション内で他のデータに変換できます。ただし、サーバ側アクションを適切に設計している場合、この作業は不要です。

さまざまな方法で数十行のコードを見てきました。ここで質問です。アプリケーションの独自のバージョンをもう試してみましたか? Salesforce 部分からの経費の読み込みを処理したので、アプリケーションを再読み込みして、Salesforce に入力した経費が表示されているかどうか確認してみましょう。

Aura コンポーネントの Apex コントローラ

アプリケーション開発の次のステップに進む前に、Apex コントローラについてもう少し詳しく説明しましょう。新規レコードの作成と既存のレコードの [Reimbursed? (払い戻し済み?)] チェックボックスの更新を処理するために必要な次のバージョンは、以下のようになっています。

public with sharing class ExpensesController {
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Perform isAccessible() checking first, then
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
    @AuraEnabled
    public static Expense__c saveExpense(Expense__c expense) {
        // Perform isUpdateable() checking first, then
        upsert expense;
        return expense;
    }
}

以前のバージョンでは、厳しい講義になる可能性があることをお伝えしましたが、今回も同様です。ただし、まずはこの最小バージョンの詳細に注目しましょう。

第 1 に、1 つの新しい @AuraEnabled メソッド saveExpense() が追加されています。このメソッドは、経費 (Expense__c) オブジェクトを取得して更新/挿入します。そのため、これは新規レコードの作成と既存のレコードの更新の両方に使用できます。

第 2 に、with sharing キーワードでクラスが作成されています。これにより、これらのメソッドで使用できるレコードに組織の共有ルールが自動的に適用されます。たとえば、一般的にユーザは各自の経費レコードのみを確認します。複雑な SOQL ルールは、すべて Salesforce によって自動的にバックグラウンドで処理されます。

with sharing キーワードの使用は、サーバ側コントローラコードの作成時に実施する必要のあるセキュリティ対策の 1 つです。ただし、これは必要な対策ですが、十分ではありません。isAccessible()isUpdateable() のチェックの実行に関するコメントを見ましたか? with sharing だけでは不十分です。具体的には、オブジェクトレベルセキュリティと項目レベルセキュリティ (FLS のように省略されていることが多い) を自分で実装する必要があります。

たとえば、このセキュリティが最小限に実装されている getExpenses() メソッドのバージョンを次に示します。

    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Check to make sure all fields are accessible to this user
        String[] fieldsToCheck = new String[] {
            'Id', 'Name', 'Amount__c', 'Client__c', 'Date__c',
            'Reimbursed__c', 'CreatedDate'
        };
        Map<String,Schema.SObjectField> fieldDescribeTokens =
            Schema.SObjectType.Expense__c.fields.getMap();
        for(String field : fieldsToCheck) {
            if( ! fieldDescribeTokens.get(field).getDescribe().isAccessible()) {
                throw new System.NoAccessException();
            }
        }
        // OK, they're cool, let 'em through
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }

これは最初の単純な構造から相当に拡張されていますが、それでも適切な範囲と言えます。また、describe コールはコストが高くなります。アプリケーションでこのメソッドを頻繁にコールする場合、最適化する方法やユーザごとのアクセスチェックをキャッシュする方法を見つける必要があります。

SLDS と同様に、安全な Apex コーディングの詳細をすべて説明するスペースはありません。SLDS とは異なり、作成するコードのセキュリティに責任を持つ必要があります。「リソース」の安全コーディングプラクティスの記事を読んでいない場合は、後で確認してください。

<>

Salesforce へのデータの保存

ごまかさずに本当に [Add Expense (経費を追加)] フォームを実装する前に、まず新規レコードを作成することと既存のレコードを読み取ることがどのような点で異なるのかを確認しましょう。doInit() を使用して、いくつかのデータを読み取り、アプリケーションのユーザインターフェースを更新しました。Carly Rae (カーリー・レイ) を使用して説明しなければなりませんでしたが、簡単でしたよね。

新規レコードの作成はさらに複雑です。フォームから値を読み取り、新規経費レコードをローカルに作成して、サーバに保存するためにそのレコードを送信します。サーバから保存されたことが通知されたら、サーバから返されるレコードを使用してユーザインターフェースを更新します。

非常にわかりにくくなりそうですよね? 今度の説明を補足するには Rolling Stones (ローリング・ストーンズ) とアルバムの全曲が必要になるかもしれませんね。

いくつかのコードを見て、自分で判断してみましょう。

まず、Apex コントローラの更新されたバージョン (saveExpense() メソッドが含まれる前回のバージョン) が保存されていることを確認します。

フォーム送信の処理方法を説明したときのことを思い出してください。項目のうち 1 つでも無効なら、エラーメッセージが表示され、フォームは送信されません。すべての項目が有効だと、エラーメッセージはクリアされます。

新しい経費の作成のすべての詳細 (およびそのすべてのごまかし) をヘルパー createExpense() 関数に配置するため、コントローラで他の変更をする必要はありません。ここまでは、非常に簡単ですよね?

前述したこれらの複雑なことをすべて実行するために必要な作業は、ヘルパーの createExpense() 関数を変更することだけです。次にそのコードを示します。

    createExpense: function(component, expense) {
        let action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        action.setCallback(this, function(response){
            let state = response.getState();
            if (state === "SUCCESS") {
                let expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
        $A.enqueueAction(action);
    },

想像してたほど複雑でしたか? 行数は多かったですか? そうではないといいのですが。

実際、このアクションハンドラで新しいことは 1 つだけで、簡単に理解できます。コードを説明しましょう。

新しい Apex コントローラメソッドを取得する component.get("c.saveExpense") でアクションを作成することから始めます。よく知っていますよね。

次に、データペイロードをアクションに割り当てます。これが新しいことです。新しい経費のデータをサーバまで送信する必要がありますが、非常に簡単ですよ。action.setParams() を使用して、パラメータ名-パラメータ値のペアで JSON スタイルのオブジェクトを提供するだけです。重要な秘訣は、パラメータ名は、Apex メソッド宣言で使用されているパラメータ名と一致している必要があるということです。

次に、要求のコールバックを設定します。繰り返しになりますが、これはサーバから応答が返されるときに発生することです。このコールバック関数と元の createExpense ヘルパー関数を比較すると、ほとんど同じです (うんざりするようなハッキングは除く)。

前のバージョンと同じように、expenses 属性を get() し、値を push() して set() します。実際に異なるのは、新しい経費のローカルバージョンを配列に push() する代わりに、サーバの応答を push() するということだけです。

なぜこれが機能するのでしょうか? サーバ側メソッドによって (この場合は新規) レコードが更新/挿入されて ID がスタンプされ、結果レコードが返されるためです。この場合も、サーバ側とクライアント側のデータ型は一致するため、他の作業は必要ありません。

これで終了です。「Rolling Stones (ローリング・ストーンズ)」は必要ありませんでしたね。

注意事項

クライアント側の Aura コンポーネントコードとサーバ側の Apex コードを接続するために必要なことはすべて説明しましたが、いくつか注意すべき点があるので、痛い目に遭うに説明しておきます。

最初の問題は大文字と小文字の区別です。要約すると一般的に Apex と Salesforce は大文字と小文字を区別しないが、JavaScript は大文字と小文字を区別するということです。つまり、「Name」と「name」は、Apex では同じですが、JavaScript では異なります。

これにより、表面上は問題ない場合でも、まったく目に見えない非常に頭にくるバグが発生する可能性があります。特に、しばらくの間 Salesforce で Lightning コンポーネント以外のコードを操作していると、オブジェクト名、項目名、メソッドなどの大文字と小文字のことをまったく考えなくなっていることがあります。

そのため、各オブジェクト、項目、型、クラス、メソッド、エンティティ、要素などで常に厳密な API 参照名を使用することをお勧めします。関係ない場合でも、どこでも常にそのようにすることで、少なくともこの問題は発生しなくなります。

注意すべきもう 1 つの問題は、「必須」の性質です。「それを連発してるな。意味が分かってるのか?」という有名な引用句を復唱せずにはいられません。

これまでに作成してきたコードで、少なくとも 2 種類の異なる「必須」がありました。[Add Expense (経費を追加)] フォームのマークアップでは、2 通りの方法でこの言葉が使用されていました。たとえば、経費名項目では次のようになっています。

<lightning:input aura:id="expenseform"
                 label="Expense Name"
                 name="expensename"
                 value="{!v.newExpense.Name}"
                 required="true"/> 

<lightning:input> タグには、required 属性があり、true に設定されています。これらは両方とも、「項目が必須であることを示すようにこの要素のユーザインターフェースを設定する」という 1 つの必須の意味のみを表しています。つまり、これは表面的なものでしかありません。ここでは、データの品質が保護されていません。

「必須」という言葉のもう 1 つの意味は、同じ項目に対して作成した検証ロジックに示されています。

let validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
    // Displays error messages for invalid fields
    inputCmp.showHelpMessageIfInvalid();
    return validSoFar && inputCmp.get('v.validity').valid;
}, true);

「必須」という言葉はどこにもありませんが、検証ロジックでは「必須」が適用されています。経費名項目には値を設定する必要があります。

現時点では、これは優れた効果を発揮しています。あなたの経費フォームでは、名前が空の新しい経費は送信されません。ただし、バグがあることがわかっている場合や、他の一部のウィジェットで同じサーバ側コントローラを使用するが、そこまで入念にフォーム検証を行わない場合などはこの限りではありません。他にもいろいろあります。そのため、これはデータ品質をある程度保護しますが、完璧ではありません。

経費名 (この例の場合) に関するデータ整合性ルールをどのように適用するのでしょうか? サーバ側で行います。サーバ側のどこでも良いというわけではありません。ルールを項目定義に配置するか、トリガにエンコードします。または、二重対策するようなエンジニア (正常なエンジニア) である場合は両方とも行います。

「必須」の意味が必須である場合、本当のデータ整合性を確保するには、可能な限り最も低いレベルで適用します。