【必見】Experience CloudでLWRを採用すべき決定的な理由

【ペットクリニックサイト 制作日誌 Vol.2】Headless Identityでの自己登録ハンドラー実装とデータ処理の最適解

A high-end editorial photograph of a pet owner seamlessly interacting with a sleek, minimalist tablet device inside a bright, modern contemporary pet clinic lounge, with a healthy golden retriever sitting calmly beside them. Subtle glowing abstract light streams and floating network nodes seamlessly connect the tablet to a soft, ethereal cloud visualization overhead, representing flawless digital integration and secure data flow. The scene is bathed in warm ambient sunlight mixed with cool cyan accent light, featuring clean architectural design, shallow depth of field, high detail, and a sophisticated, modern tech-editorial aesthetic. 開発・LWC

外部Webサイトからコンバージョン率を落とさずにSalesforceへ会員登録させたい場面において、Headless Identityによるパスワードレス登録は有効な選択肢です。

第二回目である本記事では、自己登録時のデータ不整合を防ぐためのバックエンド処理(Apex)と、コミュニティユーザー特有の権限管理について解説します。

前回の記事はこちら
【ペットクリニックサイト 制作日誌 Vol.1】ReactとSalesforce Headless Identityで作るパスワードレス会員ポータルアーキテクチャ

BESPOKE ONE Point for Developers

今回の連載記事で作成したReactサイトとSalesforceコンポーネントをGitHubで公開しています。 ぜひ Clone して、あなたの組織で動かしてみてください。

GitHub - bespokeone/Pet-Clinic: https://bespokeone.site/pet-clinic-build-log/
Contribute to bespokeone/Pet-Clinic development by creating an account on GitHub.
LWRって何?どんなメリットがあるの?
そんな疑問を解決するにはまずは以下の記事をご覧ください。
Salesforce LWRとは? Experience Cloudの次世代ランタイムを徹底解説

事実(仕様に基づく登録ハンドラーの前提)

Headless Identityのセルフ登録フローでは、Auth.HeadlessSelfRegistrationHandler インターフェースを実装したApexクラスが必要です。このインターフェースの createUser メソッドは、戻り値として User オブジェクトを返却する仕様です。

実装コード(複数オブジェクトのアトミック生成)

フロントエンドから送信されたJSONデータを受け取り、個人取引先とカスタムオブジェクトを生成する実装です。

global class HeadlessRegistrationHandler implements Auth.HeadlessSelfRegistrationHandler {
    
    global User createUser(Id accountId, Id profileId, Map<String, String> registrationAttributes, String password) {
        
        // registrationAttributes内のcustomUserDataMapはJSON文字列として格納されているため、再度デシリアライズが必要です
        String customDataStr = registrationAttributes.get('customUserDataMap');
        Map<String, Object> customData = (Map<String, Object>) JSON.deserializeUntyped(customDataStr);
        
        // 処理中のエラー時にすべてをロールバックするため、Savepointを設定します
        Savepoint sp = Database.setSavepoint();
        try {
            // 個人取引先の作成
            Account a = new Account(
                LastName = registrationAttributes.get('lastName'),
                FirstName = registrationAttributes.get('firstName'),
                RecordTypeId = [SELECT Id FROM RecordType WHERE SobjectType='Account' AND DeveloperName='PersonAccount' LIMIT 1].Id
            );
            insert a;
            
            // 個人取引先作成後、Userに紐付けるためにPersonContactIdを再取得します
            Account insertedAcc = [SELECT PersonContactId FROM Account WHERE Id = :a.Id];
            
            // フロントエンドから渡された拡張パラメータを用いてカスタムオブジェクトを作成します
            Pet__c pet = new Pet__c(
                Name = (String)customData.get('petName'),
                Species__c = (String)customData.get('petSpecies'),
                Owner__c = insertedAcc.PersonContactId
            );
            insert pet;
            
            // Userオブジェクトをインスタンス化します。ここで insert u; を実行するとエラーになるため設定のみ行います
            User u = new User();
            u.ProfileId = profileId;
            u.ContactId = insertedAcc.PersonContactId;
            u.Username = registrationAttributes.get('email');
            u.Email = registrationAttributes.get('email');
            u.Alias = 'guest';
            u.TimeZoneSidKey = 'Asia/Tokyo';
            u.LocaleSidKey = 'ja_JP';
            u.EmailEncodingKey = 'UTF-8';
            u.LanguageLocaleKey = 'ja';
            
            // プラットフォーム側にDMLを委譲するため、インスタンスを返却します
            return u;
            
        } catch (Exception e) {
            Database.rollback(sp);
            throw new Auth.DiscoveryCustomErrorException('Registration failed: ' + e.getMessage());
        }
    }
}

推測・見解(設計判断とトレードオフ)

個人取引先(Person Account)の採用とパフォーマンス

B2Cモデルにおいては、AccountとContactを個別に作成するよりも個人取引先モデルが自然です。

コード内で insert a; の直後に [SELECT PersonContactId FROM Account WHERE Id = :a.Id] を発行していますが、Id(プライマリキー)指定のSOQLはインデックスが完全に効くため処理時間は数ミリ秒で完了します。フロントエンド側のレイテンシに悪影響を及ぼすことはありません。

個人取引先(Person Account)を作成した直後にUserオブジェクトへ紐付けるためには、Account自身のIdではなく裏側で作られた取引先責任者(Contact)のIdが必要です。そのため、insert 直後に必ずSOQLで取得し直す一手間が発生します。

return User を避ける

通常のApex開発の癖で insert User; と書いてしまうと MIXED_DML_OPERATION エラー(非同期オブジェクトと通常オブジェクトの同一トランザクションでのDML混在)で即エラーとなります。Headless Identityのフレームワークにインスタンスごと投げ渡し、最後の保存はSalesforceに任せるのが絶対のルールです。

トランザクション管理の重要性

最後のUserのinsertはプラットフォーム側で行われます。もしカスタムオブジェクト作成中にエラーが起きた場合、Savepoint を使ってロールバックしておかないと「ユーザーは作られなかったのに、飼い主のいないペットのデータだけがDBに残る」というゴミデータ問題を誘発します。

開発時の知見(ハマりどころ)

  • MIXED_DML_OPERATION エラー
    User オブジェクトに対して明示的に insert u; を実行すると、非同期処理とDMLの衝突によるエラーが発生します。メソッドの戻り値としてインスタンスを返す仕様に則る必要があります。
  • customUserDataMap のパース漏れ
    フロントエンドから送るJSONの customdata は registrationAttributes 内で文字列として格納されます。再度 JSON.deserializeUntyped を実行しないと値が取得できません。
  • 初期化時のNull起因エラー
    登録時にユーザー入力がない項目(予約日時など)が null になるケースを想定せず、後続のマイページ表示用クラスで比較処理を行うと、ログイン後に500エラーが発生します。データの初期化と参照時のNullチェックはセットで実装してください。
  • Without Sharing とオブジェクト権限
    Apexクラスを without sharing で定義しても、 Experience Cloud上でコミュニティユーザーが実行する際、オブジェクト自体へのアクセス権限 (CRUD / OLS) は評価されます。プロファイルや権限セットにカスタムオブジェクト(Pet__c等)の参照権限がないと sObject type 'Pet__c' is not supported エラーとなるため、権限設定とApex側での try-catch によるエラーハンドリングが必須です。

まとめ

Headless Identityにおける自己登録処理は、プラットフォーム側の仕様に合わせた正確なハンドラー実装が求められます。

Userオブジェクトは明示的にinsertせず戻り値として返却する点や、JSONデータの確実なデシリアライズなど、細かな仕様への理解が不可欠です。また、without sharingを指定したクラスであっても、Experience Cloud環境下ではコミュニティユーザーのオブジェクト権限(CRUD/OLS)が厳格に評価されるため、適切なプロファイル・権限セットの割り当てと、トランザクション全体を保護するロールバック制御をセットで実装するのがポイントです。

続いて第三弾の記事もご覧ください。

参考URL

Configure Experience Cloud Settings for Headless Registration

BESPOKE ONEの管理人

福島 瑛二

2013年にJavaエンジニアとしてのキャリアをスタート。2019年にSalesforceと出会い、Salesforceエンジニアの道へ。

デザインや UI/UX の観点からもシステムを捉え、ユーザーにとって心地よい体験を実装することにやりがいを感じています。

CRM(顧客データ)や Data 360 と連携した高度なサイトを目に見える形で表現できる Experience Cloud に大きな可能性を見出しており、バックエンドのデータ構造とフロントエンドの表現力を極めることがこれからの Salesforce エンジニアに求められるスキルだと確信しています。

Trailblazer: efukushima

福島 瑛二をフォローする

読者の声

タイトルとURLをコピーしました
B-ONE 案内エージェント