Singleton と Flyweight で省エネ Apex

はじめまして

はじめまして!炎の池島です。(ハートが)

今回は、先日公開勉強会でお伝えしたSingleton と Flyweight で省エネ Apexに関して、 より詳細な事例と、ちょっとしたコードサンプルを交えて紹介して参ります。

まず、「Singleton と Flyweight とは何ぞや?」というところから。 Singleton と Flyweight は、23のデザインパターンを集めた GoF デザインパターンの1つです。

Singleton

Singleton は「インスタンスを生成する」という側面からみた場合のデザインパターン。 実行環境において、そのクラスのインスタンスが唯一無二であって欲しいときに使用します。 例えば、一意の番号を発行する、別システムへのコネクション(DB の場合が多いと思います)をプールして管理する、などといった場合に使用します。

語源はトランプの札1枚1枚が唯一無二である、ということから命名されたようです。 ちなみに、私なら Fedor と命名します!(60億分の1!) なお、Salesforce は1トランザクションごとに実行環境ができるようなものなので、1トランザクション内で唯一無二を保証することとなります。

クラス図       

singleton

  1. プロパティに自分自身のインスタンスを static で持つようにする
    → これによって、一回インスタンスを生成すると上書きすることができません
  2. コンストラクタを private にする
    → これによって、このクラスからしかこのクラスのインスタンスを生成することができません
  3. インスタンスの取得は static method を介して行う
    → 他のクラスはこの static method を使って、このクラスのインスタンスを取得します

Flyweight

Flyweight は「クラスの構造」という側面から見た場合のデザインパターン。

一度生成したインスタンスを再利用することで、処理負荷を抑えることを目的にしています。 例えば、別の Webサービスを呼び出して取得しなければいけないデータがあるとします。 また、そのデータを利用しなければいけない箇所が複数存在したとします。 その際、毎回 Web サービスを呼び出してデータを取得していたら、Web サービス側の処理、及びネットワークでのデータ送受信を毎回経由することになります。 そういった場合は、一回取得したデータはキャッシュして使いまわした方が良いと思いませんか!?

そんなときは Flyweight で解決!! 語源は、軽量ということで、ボクシングの階級「フライ級」から命名されたようです。 「フライ級」より軽い階級で「ライトフライ級」や「ミニマム級」がありますが、若干遠慮して「フライ級」に落ち着いたんでしょうねw(なんて勝手に想像してます)

クラス図      

 flyweight

  1. 再利用対象のインスタンスをキャッシュするためのプロパティを持つ
  2. 取得要求時、該当のインスタンスがキャッシュになければ生成し、生成したインスタンスはキャッシュに保存する
  3. 取得を要求されたインスタンスはキャッシュから取得して返却する

Salesforce での事例

Salesforce でプログラムを開発したことがある方はご存じだと思いますが、Salesforce にはガバナ制限と言われるものが存在します。 その中で、SOQL の発行は1トランザクション当り100回まで、というものがあります。 これを打開するための作戦として、Singleton&Flyweightを使っちゃいましょう!というのが以下の適用事例です。

複数の Trigger で取得する必要のあるマスタデータ

例えば、以下のような要件があったとします。

  1. お客様の購入履歴を記録する。
    販売した商品は分類によって記載する項目が異なるため、レコードタイプで種別を区分けしている。
    特定のレコードタイプのみ売上金額を集計し、総購入金額としてお客様の項目に更新する。
    ※ 諸事情により、お客様と購入した商品は主従関係にできない
  2. 加入している会員のグレードによって記載する項目が異なるため、お客様もレコードタイプで区分けしている。
    1. の総購入金額に応じて段階的に特典を自動的に登録する。
    また、その段階は会員のグレードによって異なる。

上記のデータ構成、及びプログラムの構成が以下のようになったとします。 01

商品の登録・更新時、及びお客様の更新時に、それぞれレコードタイプのデータを参照する必要があるようになっています。 また、商品の登録・更新を行うたびに、お客様の更新が行われ、それぞれの Trigger が連鎖して動作することもわかります。 そして、このままの構成では、それぞれの Trigger でレコードタイプを SELECT してしまうこともわかります。 そんなとき!!今回のお題であるSingleton&Flyweightを適用します!! 適用したのが以下の図です。

02

このようにすると、レコードタイプを取得する SOQL の発行が1回で済みます。

この例だと、レコードタイプを読込む部分が2箇所なので、それほど省エネ化できていません。 しかし、これが3箇所、4箇所と増えていく、もしくはさらに他の要件で SOQL を発行しないといけない!! という状況になったらどうしますか!? 少しでも SOQL の発行を省略できるなら省略しておきたいではないですか!? という時のSingleton&Flyweightです。

ということで、レコードタイプを取得するクラスのコードサンプルです。 このサンプルでは、レコードタイプとして登録してあるすべてのデータを取得しています。

レコードタイプを取得するクラス recordtypedao RecordTypeDao を使用する部分 client レコードタイプはある意味定数のようなデータなので、Singleton&Flyweightが非常に効果的です!

Trigger 内で SOQL を発行しているオブジェクトのデータを大量登録

現時点のガバナ制限で、Salesforce では1回のトランザクションで最大10,000件の DML を発行できます。 しかし、ここで気を付けなければいけないことが!! Apex から発行した DML は内部でバッチサイズと言われる件数で分割されて処理されます。そして、バッチサイズは200件。つまり、10,000件を登録すると、200件ごとに分割され、実際には 50回の DML を発行することになります。 という事実を知った上で、次の要件を見てください。

  1. お客様の購入した商品の発注を行うため、商品を取り扱っている会社にパートナーとして参加してもらう
  2. 発注データの登録を行うのは社内のユーザ
  3. 発注を行った後は、パートナーが内容を参照し管理する
    ※ 諸事情により、共有設定によるアクセス権の設定は有効でない
  4. 通常は個々に発注データを登録するが、大量のお客様に向けて一括発注をかけることもある

とした時に。 発注明細登録時に、発注明細のレコードにパートナーへのアクセス権を登録する必要があります。 また、個々に登録する場合が通常なので、発注明細レコードへアクセス権を登録するロジックは、発注明細オブジェクトの Trigger で対応することが妥当なようです。 パートナーへの参照権はユーザ個々ではなく、ロールに対して登録します。 とした場合、発注の Trigger 内で、そのグループの ID を取得する必要があります。(この部分の細かい話は割愛) というわけで構成図。

03

こうした場合、一括発注登録で10,000件登録したとすると、グループIDを取得する SOQL を合計50回実行することになります。 でも、10,000件登録したとしても、発注先のパートナーは特定できるので、参照権を登録するグループは1回取得しておけば良いはず! そこでSingleton&Flyweightの登場です!

04

上記のようにSingleton&Flyweightを使うことで、50回実行する必要があった SOQL が1回の実行で事足りるようになるわけです! 本来は、参照権を登録するグループ ID を取得するためにはロールのデータを取得する必要があったりして、実際にはもっと SOQL の実行回数は増えます。 と考えたら、同じデータを使う所は1回に済ませておきたいではないですか!? という時のSingleton&Flyweightなわけです。

ということで、Group のデータを取得する部分のコードサンプルです。 基本は先ほどの RecordTypeDao と変わりません。 このサンプルでは、ロール&下位ロールに該当するグループのデータをすべて取得しています。

Groupデータを取得するクラス groupdao

まとめ

というわけで、簡単な2例をご覧なっていただいた通り、いくつかのロジックを連鎖して実行するような要件、もしくは同じロジックを何回も実行するような要件があった場合、同じデータを使う部分はSingleton&Flyweightで省エネ化することができます。

最後に

今回はSingletonFlyweightの2つを紹介しましたが、 社内ではこの他にFacade(SObject)DecoraterTemplate Methodのパターンもよく使われています。これらもまた機会があれば紹介したいと思います。

オブジェクト指向プログラミングにおいて、GoF デザインパターンの考え方というものは「基本」です。 「基本」なので、Salesforce 上での開発を始めたばかり、オブジェクト指向というものを勉強し始めた、という初心者に近い方が対象の内容です。先進的な内容も良いですが、たまにはこういった形で「基本」の偉大さというものを感じてみるのも良いかと思います。

そう。何事も基本は大事ですよ!