Dao実装時にもう一手間

この記事はForce.com Advend Calender 2013 15日目エントリーです。

 

 さて、ほぼ1年間放置してたわけですが、この場所はadvent calendarを書くための場所となりましたw

 今年のadvent calendarも質の良い記事が沢山投稿されてますね。でもちょっと難しいな、と感じている初級者の方のために(多分)明日からでも使えるApexのTipsを一つ。(本当はWinter14のパイロット機能を紹介しようとしたら、一つは未実装。二つ目は有効化に間に合わなくてこうなったのは内緒ですorz)

 

 オブジェクトにアクセスするためにはDaoクラスを用意するところは多いと思います。DaoとはData access Object つまりオブジェクトにアクセスすることに特化したクラスというわけです。

 例えば取引先のDaoだと以下の感じでしょうか。

 

 

  1. /**
  2.  * 取引先のデータ・アクセスオブジェクトクラスです
  3.  */
  4. public with sharing class AccountDao {
  5.    /**
  6.     * IDをキーに取引先オブジェクトを取得します
  7.     * @param id 取引先ID
  8.     * @return 取得した取引先オブジェクト
  9.     */
  10.    public static Account selectById(ID id) {
  11.       List<Account> accList = [select Id, Name from Account where id = :id];
  12.       if (accList.size() > 0) {
  13.          return accList.get(0);
  14.       }
  15.       return null;
  16.    }
  17. }

 

 上記は取引先IDをキーに取引先を取得する例ですね。

 [select Id, Name from Account where id = :id]の返り値はSObjectクラスも返してくれるのでreturn [select Id, Name from Account where id = :id];でもよさそうですがなぜそうしないかというと、SObjectクラスで受け取る場合、select結果が0件の場合は例外が発生してしまいます。例外をtry/catchするよりはnullを返したほうが色々と便利なので、1件しか返さない前提の場合はこういう書き方をする事が多いですね。

話を元に戻して、salesforceにはアクセス権があります。この場合はクラスにwith sharingをしているのでユーザ権限ですね。実行者のユーザ権限において参照できる取引先オブジェクトが取得できます。知らない方はフレクトさんが分かりやすくまとめられていますので、URLをご紹介しておきます。

http://blog.flect.co.jp/salesforce/2010/11/with-sharingwit.html

 

 では同じSOQLをシステム管理者権限で実行したい場合はどうすればよいでしょうか? 結論として、このままではどうやってもシステム管理者権限で実行できません。新しくwithout sharingを付けたシステム管理者権限用のDaoクラスを作成し、同じロジックを記述しなくてはならないのです。同じロジックが別のクラスに分散するのはちょっと嫌ですよね? じゃあどうするか。

 ここで思い出して欲しいのは、with sharingやwithout sharingを付けないクラスは呼び出し元の設定が適用されるということです。つまり、ロジックの記述されているクラスが指定なしで、呼び出し元がwith sharingならユーザ実行権限で実行されることになります。

 以上を踏まえた上でDaoを実装すると以下になります。

 

AccountDaoクラス

  1. /**
  2.  * 取引先のデータ・アクセスオブジェクトクラスです
  3.  */
  4. public class AccountDao {
  5.    /**
  6.     * IDをキーに取引先オブジェクトを取得します
  7.     * @param id 取引先ID
  8.     * @return 取得した取引先オブジェクト
  9.     */
  10.    public static Account selectById(ID id) {
  11.       List<Account> accList = [select Id, Name from Account where id = :id];
  12.       if (accList.size() > 0) {
  13.          return accList.get(0);
  14.       }
  15.       return null;
  16.    }
  17. }

WS_AccountDaoクラス

  1. /**
  2.  * 取引先のデータ・アクセスオブジェクトクラスです(ユーザ権限実行)
  3.  */
  4. public with sharing class WS_AccountDao {
  5.    /**
  6.     * IDをキーに取引先オブジェクトを取得します
  7.     * @param id 取引先ID
  8.     * @return 取得した取引先オブジェクト
  9.     */
  10.    public static Account selectById(ID id) {
  11.       return AccountDao.selectById(id);
  12.    }
  13. }

 

WOS_AccountDaoクラス

  1. /**
  2.  * 取引先のデータ・アクセスオブジェクトクラスです(システム管理者権限実行)
  3.  */
  4. public without sharing class WOS_AccountDao {
  5.    /**
  6.     * IDをキーに取引先オブジェクトを取得します
  7.     * @param id 取引先ID
  8.     * @return 取得した取引先オブジェクト
  9.     */
  10.    public static Account selectById(ID id) {
  11.       return AccountDao.selectById(id);
  12.    }
  13. }

 どうでしょうか。実装はAccountDaoに任せ、WS_AccountDaoとWOS_AccountDaoはAccountDaoのメソッドを呼び出しているだけです。これでユーザ権限で実行したい場合はWS_AccountDao、システム管理者権限で実行したい場合はWOS_AccountDaoを使えばよいのです。テストはAccountDaoは使わないという取り決めさえあればWS_AccountDaoとWOS_AccountDaoの2つをテストすればよいでしょう。

 

 これでロジックは1つのクラスにまとめたまま、ユーザ権限実行とシステム管理者権限実行ができるようになりました。しかし、その分ユーザ権限実行のみをしたい場合でも2つのクラスが必要になってしまいました。これはちょっと、と考える人もいるでしょう。これを解決する方法はあるのかというと一つあります。

 拡張コントローラやカスタムコントローラなどのクラスを自作する場合、force.com IDEから自動生成するとwith sharingで作られますよね? まあ他にもglobalクラスとか例外はありますが、これをわざわざ外して作っているところはほとんど見たことはありません。つまり、コントローラクラスも権限指定がされているのでこれを使えばよいのです。

 呼び出し元クラスが必ずwith sharingであるという取り決めさえあれば、WS_AccountDaoは必要ありません。AccountDaoのメソッドをコントローラクラスから呼び出せば、それはユーザ管理権限実行になります。その中でシステム管理者権限で取得したい場合はWOS_AccountDaoを使えばよいのです。これで実装するクラスは増やさずに、実装を1つのクラスにまとめることができました。AccountDaoを使うことになるので、ちゃんとこのクラスのテストも行うことを忘れずに。

 ただし一つ注意点があります。AccountDaoは呼び出し元クラスの設定になるので、指定なしクラスやwithout sharingのクラスではシステム管理者権限実行となってしまいます。その場合は素直にWS_AccountDaoを実装しましょう。それしか方法はありません。

 

 いかがだったでしょうか? 今まで自分が関わってきたプロジェクトだとwith sharingでとりあえずDaoを作っておいて、必要になったらwithout sharingのDaoをもう一つ作るという事がほとんどだったのですが、ロジックの分散がいつも気になってどうにかならないかと考えてみた方法がこれです。継承を使えばもっとうまくいけそうな気もするんですが、どうにもうまくいかず結局この方法落ち着きました。もっといい方法があれば教えてください凄い人! まあ、手間の割にそれほどメリットはないかもですが、個人的にはそれなりに気に入っていますw これを読んで少しでも参考になればこれ幸いです。

 

※追記

 まるで自分1人で考えたかのように書いてしまいましたが、事の発端はtwitter上で@i_sanukiさんにwith sharing版とwithout sharing版の2つのDaoを作っていますと言ったら、もっと良い方法があるよとアドバイスを貰ったのがきっかけでした。結構昔の事なのですっかり忘れてました・・・。ここで改めて訂正を。