はじめに
突然ですが、Heroku connectを使って、SalesforceとHeroku Postgresと同期する際の話です。
特に、Heroku ---> Salesforce という方向の連携が発生する時の話です。
この場合、Heroku Connectの設定画面でユニークキーを指定することが推奨されています。
すなわち、連携するオブジェクト側で外部キーとして定義されているフィールドを指定する、という具合ですね。
Herokuリファレンス / Mapping Configuration Options
このとき、Salesforce側からもレコードを作成することがあるならば、トリガやフローなどでユニークなキーを発行する必要が出てきます。
SalesforceのオブジェクトIDを流用しても良いですが、ユニークなキーといえばUUIDを思い浮かべる人も多いと思います。
今回、UUID以外のユニークキーとして、ULIDというものもあるので、それを適用してみたいと思います。
ULIDとは
ULIDはUniversally Unique Lexicographically Sortable Identifier
を略したもので、このリポジトリに仕様が記載されております。
ulid/spec
また、リポジトリのREADMEを見ると、様々な言語で実装されているようです。
(実装されている言語の例)
- C#
- Go
- Haskell
- Java
- JavaScript
- Kotlin
- .NET
- PHP
- Python
- Ruby
- Scala
- (etc)
ULIDは、__タイムスタンプ部__と__ランダム部__の2つから構成されています。
- タイムスタンプ部
- 48ビット整数
- UNIX時間(ミリ秒)から生成
- 西暦10889年まで使用可能 --> 我々が生きてる間は大丈夫そう
- ランダム部
- 80ビット
- 可能であれば、暗号論的乱数を使う
タイムスタンプと組み合わせることでソート可能となる、という具合です。また、1ミリ秒内に 2^80 以上作成しない限りは一意性を担保してくれることでしょう。Salesforceで試す限りは、そんなハードな状況はなかなかなさそうです。
ソート可能であるという点は、UUIDと違って使い勝手が良いかもしれません。
Apexでも試してみる
タイムスタンプ部の生成
要はUNIX時間(ミリ秒)を32進数に変換する、という感じです。
単純に、UNIX時間(ミリ秒)を起点にして以下の処理を10回繰り返します。
- 受け取った数値を32で割って、余りの部分を以下の配列のインデックスとして指定して文字を決定する。文字はタイムスタンプ部の前方に追加する。
- 商の部分を「1.」の処理に回す
0123456789ABCDEFGHJKMNPQRSTVWXYZ
※仕様上、紛らわしい文字 I
,L
,O
,U
を除くようになってます。
ちなみに、しばらくの間タイムスタンプ部は0
から始まります。1
から始まるようになるまで、1000年以上かかるので、気長に待つにも程があります。
ランダム部の生成
※もう少し良いやり方がないかな、と検討中です...
こちらの記事を参考にしてみましたが、同じミリ秒内で生成した場合、後に生成したキーが正しい順番になるかどうかは運任せになりますね。何か良い方法がないものかな...とボンヤリと模索中です。
How do I generate a random string
要は、この文字を使って16文字のランダムな文字列を生成することを頑張りましょう。
Apexで実装する際には Crypto.getRandomLong() を利用してみました。
0123456789ABCDEFGHJKMNPQRSTVWXYZ
おわりに
実装してみた爪痕はここに残してみました。
ulid.apex
UUIDにしてもULIDにしても、Salesforceの標準機能として組み込んでくれたら良いのになぁと思いますが、こうして仕様を追いかけながら、自作してみるのも楽しいかもしれません。
そういえば本記事はSalesforce Advent Calendar 2021の6日目です。