InterSystems IRIS Workflow Engine によるタスクのフロー - 外部アプリケーションへの接続
少し遅れましたが、モバイルアプリケーションから接続する例を示して Workflow Engine に関する連載記事をようやく締めくくることにします。
前回の記事では、これから説明する例として、患者と担当医師の両方にとって高血圧症などの慢性病状の詳細な管理を可能にするアプリケーションを示しました。 この例では、患者は携帯電話からウェブアプリケーション(基本的に、デバイスに応答するように設計されたウェブページ)にアクセスし、ポータブル血圧計が IRIS インスタンスに送信する測定に基づく通知を受信します。
.png)
したがって、IRIS インスタンスへのアクセスは 2 つです。
- モバイルアプリケーションからのユーザーアクセス。
- 血圧の測定値を送信するデバイスアクセス。
この記事では、患者が測定値を生成するタスクを管理できる、最初のアクセスを確認します。
モバイルアプリケーションと IRIS の接続
この接続では、IRIS 内でウェブアプリケーションを構成するのが最も単純です。これを行うには、管理ポータルのシステム管理 -> セキュリティ -> アプリケーション -> ウェブアプリケーションからアクセスします。
.png)
次に、表示されるリストでアプリケーションの新規作成をクリックすると、以下のような画面が開きます。
.png)
この画面で、以下のフィールドを構成しましょう。
- 名前: IRIS にデプロイされる機能にアクセスできるように公開する URL を定義します。
- ネームスペース: ウェブアプリケーションを関連付けるネームスペースです。これによって、後で相互運用性プロダクションの機能を利用できるようになります。
- REST: 公開するものが HTTP 接続を可能にする REST API であるため、このオプションを選択します。
- ディスパッチクラス: HTTP 呼び出しを受け取り、その処理を決定する ObjectScript クラス。
- JWT 認証を使用する: このオプションをオンにすると、アプリケーションに定義した URL の /login および /logout エンドポイントが有効になるため、IRIS への呼び出しを認証するための JSON ウェブトークンを取得できるようになります。
- セキュリティ設定 -> 許可される認証方法: 呼び出しを安全にするためにパスワードを設定します。
Workflow.WS.Service クラスを確認しましょう。
Class Workflow.WS.Service Extends%CSP.REST
{
Parameter HandleCorsRequest = 0;Parameter CHARSET = "utf-8";
XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/getTasks" Method="GET" Call="GetTasks" />
<Route Url="/saveTask" Method="POST" Call="SaveTask" />
</Routes>
}
ClassMethod OnHandleCorsRequest(url As%String) As%Status
{
set url = %request.GetCgiEnv("HTTP_REFERER")
set origin = $p(url,"/",1,3) // origin = "http(s)://origin.com:port"// here you can check specific origins// otherway, it will allow all origins (useful while developing only)do%response.SetHeader("Access-Control-Allow-Credentials","true")
do%response.SetHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS")
do%response.SetHeader("Access-Control-Allow-Origin",origin)
do%response.SetHeader("Access-Control-Allow-Headers","Access-Control-Allow-Origin, Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control")
quit$$$OK
}
ClassMethod GetTasks() As%Status
{
Try {
Do##class(%REST.Impl).%SetContentType("application/json")
If '##class(%REST.Impl).%CheckAccepts("application/json") Do##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) QuitDo##class(%REST.Impl).%SetStatusCode("200")
set sql = "SELECT %Actions, %Message, %Priority, %Subject, TaskStatus_TimeCreated, ID FROM EnsLib_Workflow.TaskResponse WHERE TaskStatus_AssignedTo = ? AND TaskStatus_IsComplete = 0"set statement = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1set status = statement.%Prepare(sql)
if ($$$ISOK(status)) {
set resultSet = statement.%Execute($USERNAME)
if (resultSet.%SQLCODE = 0) {
set tasks = []
while (resultSet.%Next() '= 0) {
set task = {"actions": "", "message": "", "priority": "", "subject": "", "creation": "", "id": ""}
set task.actions = resultSet.%GetData(1)
set task.message = resultSet.%GetData(2)
set task.priority = resultSet.%GetData(3)
set task.subject = resultSet.%GetData(4)
set task.creation = resultSet.%GetData(5)
set task.id = resultSet.%GetData(6)
do tasks.%Push(task)
}
}
}
set result = {"username": ""}
set result.username = $USERNAMEDo##class(%REST.Impl).%WriteResponse(tasks)
} <span class="hljs-keyword">Catch</span> (ex) {
<span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%SetStatusCode</span>(<span class="hljs-string">"400"</span>)
<span class="hljs-keyword">return</span> ex.DisplayString()
}
<span class="hljs-keyword">Quit</span> <span class="hljs-built_in">$$$OK</span>
}
ClassMethod SaveTask() As%Status
{
Try {
Do##class(%REST.Impl).%SetContentType("application/json")
If '##class(%REST.Impl).%CheckAccepts("application/json") Do##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit// Reading the body of the http call with the person dataset dynamicBody = {}.%FromJSON(%request.Content)
<span class="hljs-keyword">set</span> task = <span class="hljs-keyword">##class</span>(EnsLib.Workflow.TaskResponse).<span class="hljs-built_in">%OpenId</span>(dynamicBody.<span class="hljs-built_in">%Get</span>(<span class="hljs-string">"id"</span>))
<span class="hljs-keyword">set</span> sc = task.CompleteTask(dynamicBody.action)
<span class="hljs-keyword">if</span> <span class="hljs-built_in">$$$ISOK</span>(sc) {
<span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%SetStatusCode</span>(<span class="hljs-string">"200"</span>)
<span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%WriteResponse</span>({<span class="hljs-string">"result"</span>: <span class="hljs-string">"success"</span>})
}
} <span class="hljs-keyword">Catch</span> (ex) {
<span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%SetStatusCode</span>(<span class="hljs-string">"400"</span>)
<span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%WriteResponse</span>({<span class="hljs-string">"result"</span>: <span class="hljs-string">"error"</span>})
}
<span class="hljs-keyword">Quit</span> <span class="hljs-built_in">$$$OK</span>
}
}
ご覧のとおり、WS から必要なすべてのロジックを解決していますが、この方法は推奨されません。ネームスペースに構成されたプロダクションに受信したリクエストを送信することで可能なトレーサビリティを失ってしまうためです。
URLMap セクションを確認すると、2 つのエンドポイントが WS で構成されていることが分かります。
- getTasks: ユーザーのすべての保留中のタスクを復旧します(使用されている SQL を見ると、ログインしたときに生成される変数から直接ユーザー名を渡しています)。
- saveTask: ユーザーのタスクへのレスポンスを受信し、EnsLib.Workflow.TaskResponse クラスの CompleteTaks メソッドを実行することで、タスクの終了に進みます。
IRIS の部分では、外部アプリケーションが接続できるようにすべてが構成済みです。
Workflow Engine で外部アプリケーションをテストする
まず、ファイル /shared/hl7/message_1_1.hl7 をパス /shared/in にコピーすることで、HL7 からプロダクションへのメッセージの送信をシミュレーションします。送信するメッセージを確認しましょう。
MSH|^~\&|HIS|HULP|EMPI||20240402111716||ADT^A08|346831|P|2.5.1
EVN|A08|20240402111716
PID|||07751332X^^^MI^NI~900263^^^HULP^PI||LÓPEZ CABEZUELA^ÁLVARO^^^||19560121|F|||PASEO MARIO FERNÁNDEZ 2581 DERECHA^^MADRID^MADRID^28627^SPAIN||555819817^PRN^^ALVARO.LOPEZ@VODAFONE.COM|||||||||||||||||N|
PV1||N
OBX|1|NM|162986007^Pulso^SNM||72|^bpm|||||F|||20240402111716
OBX|2|NM|105723007^Temperatura^SNM||37|^Celsius|||||F|||20240402111716
OBX|3|NM|163030003^Presión sanguínea sistólica^SNM||142|^mmHg|||||F|||20240402111716
OBX|4|NM|163031004^Presión sanguínea diastólica^SNM||83|^mmHg|||||F|||20240402111716前回の記事を覚えていらっしゃらない方のために説明すると、BPL は収縮期血圧が 140 を超えるか拡張期血圧が 90 を超えるとアラートが生成されるように定義していたため、このメッセージにより、DNI(スペインの身分証明書)が 07751332X である患者に対する警告タスクと、IRIS が新しい通知を受信するまで開いたままになる別の自動タスクが生成されます。
.png)
モバイルアプリケーションを確認しましょう。
.png)
2 つのタスクが生成されています。1 つは手動タスクとして定義されており、ユーザーによって終了方法が異なります。もう 1 つは自動タスクとして宣言されており、それを終了するには、ユーザーが血圧計で新しい測定を行う必要があります。 HTTP 呼び出しを見てみると、これにどのように JWT を含めているのかが分かります。
.png)
ユーザーが保留中のタスクの Accept ボタンをクリックすると、このフローは血圧計の測定値の受信を待機したままとなり、次のステップに進みません。 手動タスクが受け入れられ、血圧計からの新しい測定値を受信し、それがもう一度制限を超えている場合は、システムは患者向けのタスクと、関連する医師向けに危険な状態の可能性を警告するタスクの 2 つの新しい警告タスクを生成します。 :
.png)
.png)
完璧です! 患者通知システムは素早く簡単にセットアップ済みです。
まとめ
この連載記事でご覧になったとおり、Workflow Engine の機能と InterSystems IRIS の相互運用性機能を合わせることで、他のビジネスソリューションではほとんど実現できないビジネス プロセスの実装において、驚異的な可能性を切り開くことができます。 最大限に活用するためには、技術的な知識が必要となる可能性がありますが、実際に必要な取り組みを投じる価値はあります。