投稿者

インターシステムズジャパン
記事 Toshihiko Minamoto · 1月 26 5m read

プロセスを一定間隔またはスケジュールで実行する方法

InterSystems IRIS、特にInteroperabilityを使い始めたころ、最初によく思っていた疑問の一つは「処理を一定間隔やスケジュールで実行するにはどうすればいいのか」でした。 このトピックでは、この問題に対処する2つのシンプルなクラスをご紹介します。 似たようなクラスがEnsLibに見当たらないことに、驚いています。 十分に検索しなかったのかもしれません。 いずれにせよ、このトピックでは複雑な作業を扱うつもりはなく、初心者向けの簡単なスニペットを少し紹介します。

では、「APIからデータを取得して、外部データベースに挿入する」というタスクがあると想定しましょう。 このタスクを解決するには、以下が必要です。

  1. Ens.BusinessProcess:これにはデータフローのアルゴリズムが含まれます。データ取得のためのリクエストをどのように準備するのか、APIレスポンスをDBへのリクエストにどのように変換するのか、データフローのライフサイクル全体でエラーやその他のイベントをどのように処理するのか、などです
  2. EnsLib.REST.OperationEnsLib.HTTP.OutboundAdapterを使用してAPIにHTTPリクエストを行うためのものです
  3. Ens.BusinessOperationEnsLib.SQL.OutboundAdapterを使用してJDBC接続経由で外部データベースにデータを挿入します

これらのビジネスホストの実装についての詳細はこの記事の範囲外であるため、既にプロセスと2つの操作が設定されているとしましょう。 では、どのように実行するのでしょうか? プロセスはインバウンドリクエストによってのみ実行できます。つまり、Initiatorが必要です! これは、一定間隔で実行され、プロセスにダミーリクエストを送信します。

initiatorクラスは次のとおりです。 少し追加機能を加えました。同期または非同期呼び出しを使用し、複数ホストがターゲットの場合、エラー発生時に処理を停止または継続します。 しかし、主にここで扱うのはターゲットリストです。 このリストの各項目(ビジネスホスト)に対してリクエストが送信されます。 OnGetConnectionsイベントに注意してください。Production UIで正しいリンクを構築するために必要です。

/// Call targets by interval
Class Util.Service.IntervalCall Extends Ens.BusinessService
{

/// List of targets to call Property TargetConfigNames As Ens.DataType.ConfigName;

/// If true, calls are made asynchronously (SendRequestAsync) Property AsyncCall As %Boolean;

/// If true, and the target list contains more than one target, the process will stop after the first error Property BreakOnError As %Boolean [ InitialExpression = 1 ];

Property Adapter As Ens.InboundAdapter;

Parameter ADAPTER = "Ens.InboundAdapter";

Parameter SETTINGS = "TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},AsyncCall,BreakOnError";

Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject, ByRef pHint As %String) As %Status { Set tSC = $$$OK Set targets = $LISTFROMSTRING(..TargetConfigNames)

<span class="hljs-keyword">Quit</span>:<span class="hljs-built_in">$LISTLENGTH</span>(targets)=<span class="hljs-number">0</span> <span class="hljs-built_in">$$$ERROR</span>(<span class="hljs-built_in">$$$GeneralError</span>, <span class="hljs-string">"TargetConfigNames are not defined"</span>)

<span class="hljs-keyword">For</span> i=<span class="hljs-number">1</span>:<span class="hljs-number">1</span>:<span class="hljs-built_in">$LISTLENGTH</span>(targets) {
    <span class="hljs-keyword">Set</span> target = <span class="hljs-built_in">$LISTGET</span>(targets, i)
    <span class="hljs-keyword">Set</span> pRequest = <span class="hljs-keyword">##class</span>(Ens.Request).<span class="hljs-built_in">%New</span>()

    <span class="hljs-keyword">If</span> <span class="hljs-built_in">..AsyncCall</span> {
        <span class="hljs-keyword">Set</span> tSC = <span class="hljs-built_in">..SendRequestAsync</span>(target, pRequest)
    } <span class="hljs-keyword">Else</span>  {
        <span class="hljs-keyword">Set</span> tSC = <span class="hljs-built_in">..SendRequestSync</span>(target, pRequest, .pResponse)
    }
    <span class="hljs-keyword">Quit</span>:(<span class="hljs-built_in">$$$ISERR</span>(tSC)&amp;&amp;<span class="hljs-built_in">..BreakOnError</span>)
}

<span class="hljs-keyword">Quit</span> tSC

}

ClassMethod OnGetConnections(Output pArray As %String, pItem As Ens.Config.Item) { If pItem.GetModifiedSetting("TargetConfigNames", .tValue) { Set targets = $LISTFROMSTRING(tValue) For i=1:1:$LISTLENGTH(targets) Set pArray($LISTGET(targets, i)) = "" } }

}

あとは、このクラスをProductionに追加して、TargetConfigNamesの設定でビジネスプロセスを指定するだけです。 

しかし、要件が変更された場合はどうなるでしょうか? 今度は、データ取得プロセスを毎週月曜日午前8時に実行する必要があります。 最適な方法は、Task Managerを使用することです。 これを行うには、Initiatorをプログラムで実行するカスタムタスクを作成する必要があります。 このタスクのサンプルコードは次のとおりです。

/// Launch selected business service on schedule
Class Util.Task.ScheduleCall Extends %SYS.Task.Definition
{

Parameter TaskName = "Launch On Schedule";

/// Business Service to launch Property ServiceName As Ens.DataType.ConfigName;

Method OnTask() As %Status { #dim tService As Ens.BusinessService Set tSC = ##class(Ens.Director).CreateBusinessService(..ServiceName, .tService) Quit:$$$ISERR(tSC) tSC

<span class="hljs-keyword">Set</span> pRequest = <span class="hljs-keyword">##class</span>(Ens.Request).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">Quit</span> tService.ProcessInput(pRequest, .pResponse)

}

}

ここで重要な点は2つあります。

  • Call Intervalによる実行を妨げるために、Initiator Business ServiceのPool Sizeを0に設定する必要があります(Call Intervalオプションは、クリアにしても、そのままでも問題ありません。Pool Sizeが0の場合、使用されません)

             

  • Task Managerでタスクを作成して、タスクタイプとして「Launch On Schedule」を選択し(ネームスペースをチェックすることを忘れないでください)、ServiceNameパラメーターにInitiator Business Service名を設定して、希望するスケジュールを設定します。 次を参照ください:System Operation > Task Manager > New Task

おまけ

Production環境で、オンデマンドで処理を実行する必要がある場面に、私はよく直面しました。 もちろん、CSPでカスタムUIを作成することも可能ですが、既存の仕組みを一から作り直すのは効率的ではありません。 管理ポータルの標準UIを使う方がいいと思います。 というわけで、先ほど作成した同じタスクは手動で動かすこともできます。 タスク実行タイプをOn Demandに変更するだけです。 オンデマンドのタスクリストは、System > Task Manager > On-demand Tasksで確認できます。Runボタンがあります。 さらに、すべての種類のタスクに対してRunボタン(手動による実行)を利用できます。

以上です。 これで、ビジネスホスト向けの相互運用性のしっかりしたアーキテクチャができました。 また、データ取得処理を実行する方法としては、一定間隔、スケジュール、手動の3つがあります。