この機能は、イベント駆動方式でデータを処理するという新しいパラダイムを推し進めます。4D 21 は、データベース操作 (保存または削除) に関連する一連のイベントを提供します。
ORDAイベントはトリガーを置き換えることができ、さらに多くの利点を提供します。より制御しやすく、ビジネスロジック (請求書の印刷や外部データの保存など、時間のかかるジョブを含む) をORDAデータクラス関数内に直接コーディングすることができます。これらは、作成・変更・保存・削除 (CRUD) のようなデータレベルのイベントに応答します。
ORDAイベントは正確な粒度と洗練されたエラーハンドリングを提供し、強力な データ整合性とより優れたコード構成をもたらします。
保存や削除処理の各ステップにおいて、適切なビジネスロジックを実装する方法を見ていきましょう。
ORDAイベント – ワルツは一度のみならず
前回のブログ記事では、長いシリーズの最初のイベントとして touchedイベントを紹介しました。
新しいデータベース操作イベントは 、Thisキーワードで扱われるエンティティを通してORDAレイヤーで使用されるように設計されています。これはトリガーでは不可能なことです。
トリガーと比較すると、これらのイベントは、特定のデータベース操作ステップを検出するためのより細かい粒度と、必要に応じて処理を停止できる洗練されたエラーハンドリングを提供します。
トリガーとは異なり、イベントは保存/削除操作の前に処理を実装できるだけではありません。操作の最中や後にも処理を実行できます。
エラーハンドリングは強力で、異なる深刻度やエンドユーザー向けの詳細情報を含む、カスタマイズされたエラーを返すことができます。
以前は、これを行うためにいくつかの関数を実装できましたが、必要な場所すべてで手動で呼び出す必要がありました。
現在は、この機能は完全なイベント駆動ロジックを提供し、 適切なタイミングで自動的にトリガーされます。一度実装すれば、実行時に 4Dエンジンがそれを処理します:
- 不整合なデータは、永続化レイヤーに関わる前に、事前に拒否することができます。
- 外部システムとのやりとりは、4Dデータが永続化されるその時に正確に行うことができます。
- 保存/削除が失敗したときに、処理をトリガーできます。
そして朗報として、トリガーとは異なり、ORDA はエンティティを保存/削除している間、データクラスの基盤となるテーブル全体をロックしません。異なるエンティティ (つまりレコード) に関わる限り、複数のイベントを並行して実行できます。
一意のシーケンス番号などを処理するためにテーブル全体のフルロックに依存している場合は、シングルトンなどを使用して独自に処理する必要があります。これにより、必要なときだけロックし、このプロセスを完全に制御できるようになります。
データベース操作の異なるフェーズ
保存や削除操作が実行されるとき、これらのイベントによって、操作の3つの異なるフェーズでビジネスロジックを実装できます:
- 保存または削除操作の検証: この操作に関するデータの整合性をチェックするのに役立ちます。例:
- 削除されようとしているエンティティのステータスは “削除対象” に設定されているか?
- 予約エンティティにおいて、出発日<到着日になっているか?
このイベントのおかげで、保存/削除操作を中止し、エンドユーザーにエラーを返すことができます。これにより、保存/削除操作に入る前に、データが整合しており、準備が整っていることが保証されます。
- 保存中/削除中: データベース操作を外部システムと同期させる必要がある場合などに使用します。例:
- エンティティを保存するときに、ディスクにデータの書き込む、Googleドライブにドキュメントを作成するなど。
このイベントでは、失敗した場合にエラーを返して操作を停止できます。これらのエラーは、後述する after イベントで処理できます。
- 保存または削除操作の後: ここでは、保存/削除処理中に発生した可能性のあるエラーに対して措置を講じることができます。例:
- 削除処理中にエラーが発生したため、商品を “要確認” としてマークする。
これらのイベントのおかげで、保存または削除操作の各ステップが、独自の専用ビジネスロジックを持つことができます。
これらのイベントは、ORDA または RESTサーバーを使用してデータベース操作がトリガーされるとすぐに実行されます。
以下の動画をご覧ください。HDI について、より詳細に説明しています。
実際に例を見てみましょう
イベントを実装する場所
ORDA イベントは eventキーワードを使用してEntityクラス内に実装する必要があります。
validate イベント
これらのイベントは、保存または削除操作の前にトリガーされ、エラーを返すことで操作を停止できます。この場合、後述する他のイベント (after イベントを除く) はトリガーされません。
これらは以下のように実装できます:
- 属性レベルで: 特定の属性の生合成をチェックするため
- エンティティレベルで: エンティティの内容をグローバルにチェックしたり、属性を比較したりするため
例題:
この例では、ProductsEntity クラスにおいて、margin 属性に対して validateSaveイベントが実装されています。ユーザーは、利益率が 50% 未満の商品を保存することはできません。この場合、保存処理を停止し、エンドユーザーにフィードバックが返されます:
// ProductsEntity クラス
//
// 属性レベルの validateSave イベント
Function event validateSave margin($event : Object) : Object
var $result : Object
// ユーザーは、利益率が 50% 未満の商品を作成できません
If (This.margin<50)
$result:={errCode: 1; message: "商品の検証に失敗しました。"; \
extraDescription: {info: "この商品の利益率 ("+String(This.margin)+") が 50% に達していません。"}; seriousError: False}
End if
return $result
同じロジックが validateDropイベントにも適用されます。たとえば、ステータスが “削除対象” ではない商品の削除を禁止することができます。
SAVING / DROPPING イベント
これらのイベントは、保存または削除操作の最中に正確にトリガーされ、問題が発生した場合にはエラーを返すことができます。これらは以下のように実装できます:
- 属性レベルで: 特定の属性値の変更を監視するため
- エンティティレベルで: エンティティの内容をグローバルにチェックしたり、属性同士を比較したりするため
例題:
この例では、ProductsEntity クラスにおいて、userManualPath 属性に対して saving イベントが実装されています。商品が保存される際、ユーザーマニュアルがディスク上に作成され、そのパスが userManualPath 属性に格納されます。これら 2つのアクションは整合している必要があります。
ディスク上でのユーザーマニュアル作成中にエラーが発生した場合、フィードバックとしてエラーが返され、後述する次のイベント (afterSave) で処理されます。
※ファイルのコンテンツ生成は時間がかかる可能性があるため、saving イベントの外で事前に生成されていることに注意してください。
// ProductsEntity クラス
// 属性レベルの saving イベント
Function event saving userManualPath($event : Object) : Object
var $result : Object
var $userManualFile : 4D.File
var $fileCreated : Boolean
If (This.userManualPath#"")
$userManualFile:=File(This.userManualPath)
// ユーザーマニュアルがディスク上に作成されます
// ディスク容量が不足している場合、この処理は失敗することがあります
Try
// ファイルの内容は事前に生成され、マップ (Storage.docMap) に保存されています
$docInfo:=Storage.docMap.query("name = :1"; This.name).first()
$userManualFile.setContent($docInfo.content)
Catch
// 例: ディスク容量の不足
$result:={errCode: 1; message: "商品の保存中にエラーが発生しました。"; extraDescription: {info: "ディスク容量が不足しているため、ユーザーマニュアルを保存できません。"}}
End try
End if
return $result
同じロジックが droppingイベントにも適用されます。たとえば、製品を削除する際にユーザーマニュアルも削除することができます。
AFTER イベント
これらのイベントは保存または削除処理の後にトリガーされます。処理自体は完了しているため、エラーを返すことはできません。主な目的は、直前のイベント中にエラーが発生した場合にビジネスロジックをトリガーすることです。
エラーが発生しなかった場合は、成功後のロジック (確認メールの送信など) を処理することもできます。
これらのイベントはエンティティレベルでのみ実装されます。
例題:
この例では、ProductsEntity クラスにおいて、afterSaveイベントが実装されています。もし userManualPath 属性がこれまでに正しく保存されなかった場合、その値はリセットされ、ディスク上にユーザーマニュアルが存在しない事実と一致するようになります。
// ProductsEntity クラス
Function event afterSave($event : Object)
If (($event.status.success=False) && ($event.status.errors=Null))
// $event.status.errors は validateSave イベントで発生したエラーを格納しています
// userManualPath 属性が正しく保存されませんでした
// 属性値がリセットします
If ($event.savedAttributes.indexOf("userManualPath")=-1)
This.userManualPath:=""
This.status:="KO"
End if
End if
同じロジックが afterDropイベントにも適用されます。たとえば、商品の削除に失敗した場合、そのインシデントをログに記録できます。
注記: イベント内では、長い操作はできるだけ避けなければなりません。可能な限り、そのような操作は別の場所で実装されるべきです。
強力なデータ整合性を実現するためにも、イベントの実装を先延ばしにしないでください。
現在、この投稿へのコメント機能は利用できません。