シーケンシャルIDジェネレーター

Prev Next

To download the Sequential ID Generator, visit: [tulip](https://<span translate=).co/library/apps/sequential-id-generator---multiple-prefix-support">Library

この記事では、当社のライブラリ開発者の一人であるタマシュが、ライブラリで利用可能な「Sequential ID Generator」ビルディングブロック・アプリの開発における判断基準について解説します。

なぜこの方式で構築したのか

このロジックブロックには、信頼性が高く、再利用が容易で、多くのアプリに迅速に展開できる方法でIDを生成するという、非常に実用的な問題を解決してほしいと考えていました。

最も重要な設計目標は、単に「次の番号を生成する」ことだけではありませんでした。 真の目標は、一度定義すれば、ほとんど手間をかけずにコピーして再利用できるものを作ることでした。ボタンを一度きちんと構築しておけば、別のアプリや別のステップに配置し、設定を調整するだけで済むはずです。アプリ開発者全員が、そのたびにロジックを再開してトリガーを書き直すようなことは避けたかったのです。

そのため、このロジックブロックはロジックよりも設定を優先するように設計されています。このロジックブロックを使用する際、主に考慮すべき点は以下の通りです:* どのようなプレフィックスが必要か* 数値部分の桁数はどれくらいにするか* ボタンはどのテーブルから読み込むべきか

プレフィックス、数値の形式、およびテーブルを定義すれば、最新のIDの読み取り、その検証、次の値の生成、そして数値が設定された長さに収まっているかどうかの確認といった残りの処理は、ロジックブロックが自動的に処理してくれます。

テーブル自体が「真実の源」であるべきです。別のカウンター(例として変数など)を使用すると、レコードが予期せぬ方法で挿入、削除、または編集された場合、実際のデータから乖離してしまう可能性があります。テーブルから読み取ることで、番号付けは実際に存在するデータに紐づけられた状態を維持できます。

もう1つの重要な決定は、中核となる計算ロジックを1か所に集約することでした。最初のID用のトリガー分岐、次のID用の別の分岐、そして後に追跡が困難になるような小さなトリガー断片が散在する状態は避けたかったのです。動作の一貫性を保ち、後で修正する人がどこを確認すればよいかを正確に把握できるように、メインのロジックを1つのexpression にまとめることを目指しました。

したがって、このロジックブロックは、実際には以下のいくつかの実用的な判断に基づいて構築されています:* コピーや再利用が容易であること* 不正なIDパターンからテーブルを保護できる十分な安全性を備えていること* 設定が簡単であること* ロジックを一元化することで、メンテナンスが管理しやすい状態を維持すること

IDの構成

生成される各IDは2つの部分で構成されます:プレフィックス + 数値シーケンス

例:* ORD00000125* MAT00000003* TEST00004567

プレフィックスは、そのレコードがどのような種類のものかを示します。数値部分は連番です。数値形式の設定については、非常にシンプルにすることにしました。「8桁」といった具体的な数字をユーザーに指定させる代わりに、ロジックブロックでは次のようなゼロパテンを使用しています:* 00000000

これなら一目で理解しやすいです。8つのゼロを見れば、数字部分は常に8文字であるべきだとすぐに理解できます。そのため、idNumberFormatを純粋な長さフィールドに置き換えるのではなく、この形式のままにしました。

例:* 00000000 → 8桁の数値* 0000 → 4桁の数値

この決定は些細なものですが、重要な意味を持ちます。これにより、技術的なパラメータの観点から考えたくない人にとっても、ロジックブロックがより親しみやすいものになります。

ビルドの構成

実際の実装では、3つのトリガーを使用しています。この構造は意図的なものです。ロジックブロックを上から下へと追うのが容易であると同時に、主要なロジックを分離して再利用可能にしておきたかったのです。 そこで、ロジックを多数の編集可能なトリガーに分散させるのではなく、3つの段階に整理しました:1. ユーザー設定:入力段階 2. マジック 3. ユーザー設定:出力段階

これにより、使いやすさと制御性のバランスがうまく取れています。* 最初のトリガーでは、入力を定義します。* 2番目のトリガーには、ロジックブロックが機能するために通常編集する必要のない内部ロジックが含まれています。* 3番目のトリガーでは、生成された結果をどう処理するかを決定します。

つまり、通常はコアロジックトリガー内部で作業する必要はありません。ユーザーは入力側と出力側を設定するだけで済みます。これが重要な設計上の決定事項の一つでした。つまり、通常変更が必要な部分のみを公開することで、ロジックブロックを再利用可能にするということです。

トリガー 1 – ユーザー設定:入力ステージ

これは、ユーザーが最初に操作するトリガーです。その目的は、IDを生成する前に、ロジックブロックが必要とする入力値を定義することです。実際には、このトリガーを使用して以下を定義します:* プレフィックス* idNumberFormat* コネクタに渡されるテーブルID例:prefix = ORDidNumberFormat = 00000000table ID = ターゲットテーブル

これらの値は通常、ユースケースごとに異なるため、このステージは意図的に編集可能になっています。

同じボタンを別のアプリやステップにコピーし、これらの設定値を更新することで、内部ロジックに触れることなくロジックブロックを再利用できます。

入力値が一度定義されれば、残りのフローは自動的に動作するため、実装の迅速化につながります。また、このトリガーは、選択されたテーブルから最新の一致するレコードを読み取るコネクタ呼び出しも実行します。

コネクタが設計の一部である理由

コネクタは単なる技術的な詳細ではありません。これは再利用戦略の一部です。私はコネクタを使用して、ターゲットテーブルから最新の既存の ID を読み取ります。 コネクタはテーブルIDを入力として受け取り、プレフィックスフィルターを適用し、レコードを**_sequenceNumberで**降順に並べ替え、最新の一致するレコードを返します。これは2つの理由から重要です。第一に、これによりロジックブロックがテーブルの実際の状態から連番を継続できるようになります。第二に、ロジックブロックの再利用がはるかに容易になります。 ボタンを別のアプリにコピーする場合でも、ロジックを書き直す必要はありません。コネクタを正しいテーブルに指定し、必要な設定を行うだけで済みます。これが、この構築の背後にある実用的なパターンです:1. ボタンを一度作成する2. コネクタでテーブル入力を設定する3. プレフィックスと番号の形式を定義する4. 必要な場所で再利用する

これは、アプリ構築の高速化を支援するための意図的な選択でした。

ロジックブロック内のコネクタはどのような処理を行うのでしょうか?

ここでは次のような処理が行われます:* コネクタはテーブルIDを受け取ります* コネクタはプレフィックスを受け取ります* コネクタはプレフィックスでレコードをフィルタリングします* コネクタは_sequenceNumberでレコードを降順にソートします* コネクタは一致する最新のIDを返します

また、コネクタは「同じテーブル内の複数のエンティティ」という非常に重要なユースケースにも対応しています。

同じテーブルに次のようなレコードが含まれている場合:ORD00000011MAT00000003、ロジックブロックはこれらを1つの共有シーケンスとして扱ってはなりません。各プレフィックスは、それ自体の最新の値からのみ継続する必要があります。

そこで、コネクタはまずプレフィックスでフィルタリングを行います。つまり、次の値は次のようになります:ORD → ORD00000012、MAT → MAT00000004

このモデルを選んだ理由は、異なるエンティティが同じテーブルに存在することはあっても、それらが同じ連番を共有すべきではないからです。

設定要件の一つとして、明確に述べておくべき点があります。コネクタには認証が必要であるため、トークンを生成し、コネクタの認証設定に追加する必要があります。私はこれをロジックブロック自体のロジックの一部ではなく、セットアップ作業として扱っていますが、それでもロジックブロックを実用的にするために重要な部分です。

これはセットアップおよびコンテキスト収集に属するため、入力ステージに配置しました。内部ロジックが動作する前に、ロジックブロックはまず、何を処理対象としているかを把握する必要があります。

トリガー 2 – MAGIC

これが中核となるロジックトリガーです。意図的にこの名前をつけました。 考え方は単純です。これは通常、編集する必要のない部分です。ロジックブロックには、すでにこのロジックが組み込まれているべきだからです。このトリガーは、設定された入力値と、前のトリガーから返された最新のIDを受け取り、内部で意思決定を行います。これは1つのトリガーですが、その内部ではいくつかの処理が行われています。

1. フォーマットの検証 – トリガーの「if」部分

最初のチェックでは、テーブルから返された最新のIDが、期待される構造と依然として一致しているかを確認します。期待される構造:**プレフィックス + 固定長数値

有効な値の例:ORD00000025無効な値の例:ORD25、ORD-00025、ABC00000025、ORD00025A

Expression

REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix + '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$', 'ok')

このexpression の機能

このexpression は、最新のIDがロジックブロックが期待する構造に正確に準拠しているかどうかを検証します。この検証は3つの部分で構成されています:*LINK(...)は、どの値を検証すべきかを決定します。 コネクタがIDを返さない場合は、代わりに2番目の部分が使用されます。この2番目の部分は設定された開始構造であり、これによりロジックブロックは新しいプレフィックスシーケンスの最初のIDを生成できます。*'^' + Variable._configPrefixは、値が設定されたプレフィックスで始まる必要があることを意味します。*'[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$' は、値が期待される桁数で正確に終わり、その後に余分な文字が一切含まれてはならないことを意味します。

値全体が期待される構造と一致する場合、結果は「ok」となります。一致しない場合、expression の結果は「ok」とは異なるままとなり、トリガーは生成フローを停止してエラーメッセージを表示します

これを選んだ理由

このチェックを追加したのは、形式が不正な ID からロジックブロックが継続することを避けたかったためです。最後に保存された値が間違っていて、ロジックがそれを受け入れてしまうと、その誤ったパターンが将来のレコードへと波及し始める可能性があります。 そこで、MAGICトリガーの第一の役割を、そのような事態を早期に阻止することとしました。フォーマットが間違っている場合:*_stateOutputがクリアされる* 生成が停止した理由を説明するメッセージが表示される

2. オーバーフローの検証

2番目のチェックでは、次の数値を確認し、それが設定された数値の長さの範囲内に収まっているかを検証します。

例:idNumberFormat = 0000

許容範囲:0000 ~ 9999

もし次の値が 10000 になった場合、設定された構造に収まらなくなります。

Expression

LEN(Variable._configIdNumberFormat) < LEN( TOTEXT( TEXTTOINTEGER( REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix, '' ) ) + 1 ))

このexpression の動作

このexpression は、次の2つの長さを比較します:* idNumberFormatから取得した、許容される数値の長さ* 現在の値に1を加えた後の次の数値の長さ

右辺の処理は次のように行われます:***LINK(...)**は、コネクタが存在する場合、そのコネクタから返された ID を使用します。コネクタが ID を返さない場合は、代わりに設定された開始構造を使用します。これにより、最初の ID の生成が可能になります。*REGEX_REPLACE(...) はプレフィックスを削除し、数値部分のみを残します*TEXTTOINTEGER(...) + 1 は次の数値を算出します***TOTEXT(...)**はその次の値を再びテキストに変換します***LEN(...)**はその次の値に必要な桁数を測定します

次の数値がフォーマットで許容される桁数を超える場合、条件が真となり、トリガーによって処理が停止されます。

この方法を選んだ理由

構造を自動的に変更するのではなく、ここで処理を停止することにしました。この決定は、誰かが明示的に設定を変更しない限り、IDの長さを一定に保ちたかったため、重要なものでした。オーバーフローが検出された場合:*_stateOutput がクリアされます* 最大長に達したことを説明するメッセージが表示されます

3. IDの生成

両方のチェックに合格した場合、トリガーは次のIDを生成します。例:ORD00000015 → ORD00000016

この同じexpression は、そのプレフィックスに対応するレコードがまだ存在しない場合にも対応します。その場合、ロジックは設定された開始構造にフォールバックし、シーケンスの最初の値を生成します。例:ORD00000001

Expression

Variable._configPrefix + RIGHT( Variable._configIdNumberFormat + TOTEXT( TEXTTOINTEGER( REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix, '' ) ) + 1 ), LEN(Variable._configIdNumberFormat))

このexpression の機能

これは、最終的なIDを生成するexpression です。処理は以下の順序で行われます:*LINK(...)は、コネクタが存在する場合、コネクタから返された最新のIDを使用します。コネクタがIDを返さない場合は、代わりに設定された初期構造を使用します。これにより、同じexpression で最初に生成されたIDも処理できるようになります。*REGEX_REPLACE(...) はプレフィックスを削除し、数値部分が処理できるようにします*TEXTTOINTEGER(...)+ 1 は数値をインクリメントします*TOTEXT(...) は新しい数値を再びテキストに変換します***Variable._configIdNumberFormat + ...**は、新しい値の先頭にゼロのパターンを追加します***RIGHT(..., LEN(Variable._configIdNumberFormat))**は、必要な桁数のみを残します*Variable._configPrefix + ...は、プレフィックスを再度追加して最終的なIDを作成します。結果は_stateOutput に格納されます。

これを選んだ理由

意図的に、これをexpression という 1 つの中央処理としてまとめました。可能であれば、最初の ID と次の ID に対して別々の内部ブランチを設けたくなかったからです。計算を 1 か所にまとめることで、再利用が容易になり、将来の変更も安全に行えます。このトリガーの終了時に、生成された値は**_stateOutput** に格納されます。

トリガー 3 – USER CONFIG: 出力ステージ

これはロジックブロック内の最後のトリガーです。その目的は、_stateOutputに有効な値が含まれた時点で、生成されたIDに対してどのような処理を行うかを定義することです。ここで重要な点は、このステージが意図的にユーザーに委ねられていることです。_stateOutputが空白でない場合、ロジックブロックが次に何を行うかを決定できます。現在の設定では、トリガーに次の手順を説明するガイダンスメッセージが表示されます:* 設定済みのテーブルをアプリに追加する* このメッセージステップを「レコード作成」トリガーに置き換える*_stateOutputを新しいレコードIDとして使用する

このように構築したのは、ユースケースによって出力の挙動が異なる可能性があるためです。すぐにレコードを作成したい場合もあれば、まず別の変数に値を格納したい場合もあるでしょう。 あるいは、テーブルに何かを書き込む前に、より大規模なワークフロー内でその値を使用したい場合もあるでしょう。そのため、1つの固定された出力挙動をハードコーディングするのではなく、出力ステージを構成可能にしておきました。これが、ユーザー向けの設定項目が両端に存在する理由です:* 入力は構成可能* 出力は構成可能* コアロジックは中央に配置

トリガーがこのように分離されている理由

この構造は、ロジックブロック全体における主要な設計上の選択の一つを反映しています。それは、「責任範囲を分離しつつ、中核となるロジックは一元化する」というものです。だからこそ、すべてを1つの巨大なトリガーにまとめなかったのです。単一の巨大なトリガーでも機能するかもしれませんが、可読性が低下し、説明もデバッグも難しくなります。一方で、実際の計算を多くの小さなパーツに分割しすぎると、かえって煩雑になってしまいます。そこで、私は中間の道を選びました:* 可読性を高めるために各ステップごとにトリガーを分離* 実際のID計算には、一元化されたexpression を使用

これにより、両方のメリットを得ることができます:* ビルドの流れが追跡しやすくなる* ロジックのメンテナンスが容易になる* 再利用が簡単になる* 将来の編集に伴うリスクが低減される

このロジックブロックにおいて、検証と保護が重要な理由

このロジックブロックは、単に次の番号を生成するだけのものではありません。テーブルを不正なデータから保護することも目的としています。主な保護ポイントは2つあります:

形式の保護

このロジックブロックは、処理を続行する前に、既存の最後のIDが期待される構造と一致しているかどうかを確認します。これにより、期待される構造に一致しないIDに基づいてシーケンスが継続されるのを防ぎます。

オーバーフロー保護

このロジックブロックは、次の番号が許容される数値の長さの範囲内に収まるかどうかを確認します。これにより、シーケンスが拡大する際に構造が予期せず変化するのを防ぎます。不正なIDデータは急速に拡散する可能性があるため、私はこれら両方を重要だと考えました。 一度、誤ったパターンがテーブルに入り込み、ジェネレータがそれを使用し始めると、その問題は将来のすべてのレコードに影響を及ぼす可能性があります。そのため、ロジックブロックは、誤った方向で黙って処理を続行するのではなく、早期に停止するように設計されています。

変数名の付け方

ロジックブロック内で使用されている変数名が特定のパターンに従っていることに気づくかもしれません。例:_configPrefix_configIdNumberFormat_stateLastId_sequenceNumber

私は、アプリ内で通常作成される変数と区別しやすい命名スタイルを意図的に採用しました。その目的は技術的なものではなく、実用的なものです。ロジックブロックをアプリにインポートまたはコピーした際、どの変数がロジックブロックに属し、どの変数がアプリのロジックに属しているかが一目でわかるようにするためです。ロジックブロックの変数は一貫した構造を採用しているため、視覚的にグループ化され、ユーザー定義の変数との競合が起こりにくくなっています。これにより、ロジックブロックの再利用が容易になり、名前の競合や混乱を招くことなく、既存のアプリに安全に統合できるようになります。

設計上の決定事項のまとめ

ロジックブロック全体を、その背後にある中核的な決定に絞り込むと、以下のようになります:* 迅速な再利用を重視したため、ロジックブロックは設定駆動型としています。* テーブルを「真実の源」としたいと考えたため、次のIDは最新の実際のレコードに基づいて生成されます。* ロジックの保守性を維持したかったため、計算は一元化されています。* 1つのテーブル内の複数のエンティティがスムーズに動作するようにしたかったため、各プレフィックスに独自のシーケンスを設定しました。* テーブルを不適切なパターンから保護したかったため、フォーマットチェックとオーバーフローチェックにより、レコード作成前に不正なデータを阻止します。* トリガーの流れを読みやすくしたかったため、責任範囲を明確なステップに分割しました。

だからこそ、このロジックブロックはこのような形になっています。これは単なるID生成器ではありません。シンプルさ、再利用性、データ保護のバランスを図った、制御されたID生成のための再利用可能なパターンなのです。

参加する

コミュニティに参加して、改善案の共有、追加ユニットの提案、問題の報告、あるいはアーキテクチャ上の決定事項について議論しましょう。