記事 Shintaro Kaminaka · 10月 10, 2021 10m read

FHIRリポジトリをカスタマイズしよう!パート1

開発者の皆さん、こんにちは。 IRIS for Health 2021.1がリリースされてからしばらくたちますが、多くのユーザさんにFHIRリポジトリ機能をお使いいただいています。 今日はFHIRリポジトリのサーバ側の処理をカスタマイズする機能をご紹介したいと思います。

この記事で紹介している内容のFHIRリポジトリカスタマイズに関するドキュメントマニュアルはこちらになります。

この記事はIRIS for Health 2021.1 をベースに記載しています。バージョンによってはカスタマイズに必要なクラスが異なることがあります(例えば2020.1では後述のRepoManagerクラスはまだありません。)

FHIRサーバカスタマイズの2つのアプローチ

FHIRサーバ機能をカスタマイズするには2つのアプローチがあり、1つはこの記事でご紹介する、Interactionクラス群をカスタマイズしてFHIRリポジトリを拡張する方法、もう一つは、相互運用プロダクションでロジックを実装してサーバとしての動作を変更する方法です。

後者は厳密にはFHIRリポジトリとしての実装はそのままで、リポジトリに受け渡される前、あるいはリポジトリから応答を受けた後に、そのデータを参照、変更してカスタマイズを実装する流れになります。 IRISのInteroperability機能(Ensemble)に慣れているかたはこちらのアプローチがわかりやすいかもしれません。しかし、すべてのFHIRリクエストがトレースされメッセージが保存されるため、データ容量やパフォーマンスには気を配る必要があります。 この方法にご興味がある方は、こちらの相互運用プロダクションのドキュメントマニュアルをご参照ください。

FHIRリポジトリを構築する前の作業

FHIRリポジトリカスタマイズで最も注意しなければいけない点、それはFHIRリポジトリを構築する際にカスタマイズができるように、この後記載する手順を実施しておかなければいけないという点です。つまり、デフォルト設定でFHIRリポジトリを構築し、しばらく運用してから、「こういうカスタマイズ処理を組み込みたいな」と思っても、簡単に追加することができないのです。(新しくカスタマイズ可能なリポジトリを構築し、データ移行するなどの方法は取れます)

この制限があるため、FHIRリポジトリ運用当初はカスタマイズ予定がなくても、下記の手順を実行しカスタマイズ機能を組み込んでおくという選択肢もあります。

それでは、そのカスタマイズ方法をご紹介します。 まず、以下の3つのクラスをそれぞれ継承してカスタムクラスを作成します。この記事では共通のパッケージとしてCustomFSとし、クラス名の先頭にはMyを追加しています。

ベースクラス継承して作成したクラス
HS.FHIRServer.Storage.Json.InteractionsCustomFS.MyInteractions
HS.FHIRServer.Storage.Json.InteractionsStrategyCustomFS.MyInteractionsStrategy
HS.FHIRServer.Storage.Json.RepoManagerCustomFS.MyRepoManager

継承したInteractionStrategyクラスとRepoManagerクラスはロジックを実装したりはしませんが、以下の設定を行う必要があります。

  • 両クラスの StrategyKeyパラメータに共通の一意の識別子(文字列ならなんでも)を設定する
  • IntectionStrategyのInteractionClassで自分の作成したInteractionsクラスを指定する
  • RepoManagerのStrategyClassで自分の作成したInteractionStrategyクラスを指定する

図で関連を表現するとこんな感じになります。

image

実際のカスタマイズ内容は継承したInteractionsクラスの各メソッドをオーバーライドして実装していくことになります。

カスタマイズのクイックスタート

以下の表はドキュメントマニュアルに記載されている内容そのままですが、転載します。 これらのメソッドをオーバライドしてロジックを記述し、カスタマイズを行います。

目標HS.FHIRServer.Storage.Json.Interactions のサブクラスのアクション
特定のFHIR相互作用のカスタマイズ相互作用に対応するメソッド(Add,Read,Update)をオーバーライドします。
すべての要求の処理OnBeforeRequest をオーバーライドして、ユーザに透過的なロジックを実装します。FHIR クライアントに要求の処理が異なることを認識させる場合は、カスタムの FHIR 操作を作成します。
すべての要求の後処理OnAfterRequest をオーバーライドして、ユーザに透過的なロジックを実装します。FHIR クライアントに要求の処理が異なることを認識させる場合は、カスタムの FHIR 操作を作成します。
Read 相互作用の結果の後処理PostProcessRead をオーバーライドします ()。
Search 相互作用の結果の後処理PostProcessSearch をオーバーライドします ()。
カスタムの FHIR 操作の追加OperationHandlerClass パラメータをオーバーライドして、HS.FHIRServer.Storage.BuiltInOperations のサブクラスの名前を指定します。"カスタムの FHIR 操作" を参照してください。
バンドルの処理方法のカスタマイズBatchHandlerClass パラメータをオーバーライドして、カスタム・クラスの名前を指定します。既定のハンドラ・クラスは HS.FHIRServer.DefaultBundleProcessor です。
OAuth トークンの処理方法のカスタマイズOAuth2TokenHandlerClass パラメータをオーバーライドして、カスタム・クラスの名前を指定します。既定のハンドラ・クラスは HS.FHIRServer.Util.OAuth2Token* です。

この表のリンク先ドキュメントマニュアルにもいくつか、実装例が記載されていますが、この記事ではもう少しシンプルなパターンのサンプルを記載していきたいと思います。

OnAfterRequestメソッド

まずはOnAfterRequestメソッドを題材にどんな引数を受け取るのか見てみましょう。

Method OnAfterRequest(pFHIRService As HS.FHIRServer.API.Service, pFHIRRequest As HS.FHIRServer.API.Data.Request, pFHIRResponse As HS.FHIRServer.API.Data.Response)
{

このメソッドは、3つの引数を受け取ります。主に使うのはpFHIRRequestとpFHIRResponseの二つです。 このメソッドは OnAfterRequestメソッドなので、FHIRリポジトリ上での処理が完了した後に起動されるメソッドです。そのため、FHIRリポジトリへの要求であるpFHIRRequestと、これから要求元へ返すpFHIRResponseの二つを参照できます。

FHIRリポジトリへのJSONリクエストおよびレスポンスは

pFHIRRequest.Json
pFHIRResponse.Json

に含まれます。これはJsonの構造通りにデータアクセスできる %DynamicObject型の変数なので、このようにアクセスできます。

pFHIRResponse.Json.resourceType //-> Bundleなど
pFHIRResponse.Json.total //->Bundleの場合にそのBundleに含まれる数(10など)
pFHIRResponse.Json.entry.%Get(0).resource.gender //->Bundle内の最初のPatientリソースの性別を取得

ログなどの目的でこの内容を文字列あるいはストリームに保存しておきたい場合は、%ToJSON()メソッドを使って変換します。 他にもこれらのインスタンスは

プロパティ含んでいるデータ
pFHIRResponse.Id追加・更新されたリソースの論理ID
pFHIRResponse.VId追加・更新されたリソースのバージョンID
pFHIRResponse.Status処理のステータス

など様々な情報を含んでいます。詳細はHS.FHIRServer.API.Data.RequestHS.FHIRServer.API.Data.Responseのクラスリファンレスドキュメントをご覧ください。

OnBeforeRequest/PostProcessReadメソッドを実装してみる

それでは具体的にOnBeforeRequestメソッドを使って実装してみましょう。

IRIS for Health のFHIRリポジトリでは、こちらの記事(IRIS for Health上でFHIRリポジトリ+OAuth2認可サーバ/リソースサーバ構成を構築するパート3(OAuth2スコープ編))で紹介したOAuth2アクセストークンのScopeを使う方法で、このScopeのユーザにはこのリソースの参照権限だけ与える、こちらには更新権限を与える、といったアクセスコントロールが可能です。ただ、これはあくまでもリソース単位のコントロールになるので、「このリソースにこういうデータがあるときは登録させたくない」「このリソースにこういうデータが含まれているときは応答として返したくない」などの要望の実装はこのカスタマイズを使って実現することができます。

ではまず、Patientリソースの登録時に、電話番号情報を格納するtelecom要素の最初のデータに 401,403,400 で始まる電話番号が入っていたらエラーを返す、というロジックを実装してみたいと思います。最初の3桁はエラーとして返すHTTPエラーコードと紐づけています。

以下のようなコードになります。本来はチェックロジックなども含めるべきですが、サンプルのためシンプルなコードにしています。

Method OnBeforeRequest(pFHIRService As HS.FHIRServer.API.Service, pFHIRRequest As HS.FHIRServer.API.Data.Request, pTimeout As %Integer)
{
	//POST or PUTでPatientが対象の場合
	if ((pFHIRRequest.RequestMethod="POST")||(pFHIRRequest.RequestMethod="PUT"))&&(pFHIRRequest.RequestPath["Patient") {
	
		if $IsObject(pFHIRRequest.Json) {			
			s tele=pFHIRRequest.Json.telecom.%Get(0).value
			
			if $Extract(tele,1,3)="401" $$$ThrowFHIR($$$HttpOnlyResponse(401))
			if $Extract(tele,1,3)="403" $$$ThrowFHIR($$$HttpOnlyResponse(403))
			if $Extract(tele,1,3)="400" $$$ThrowFHIR($$$GeneralError, "あなたの電話番号は登録する権利がありません。", $$$OutcomeNotSupported(400))

		}

	}

エラーとして応答を返す場合は、このサンプルのように直接Throwすることができます。

$$$ThrowFHIR($$$HttpOnlyResponse(401))

次はPostProcessReadメソッドです。 先ほどは登録時の電話番号をチェックして応答を返しましたが、今回は返すデータをチェックして40で始まっている場合は、マスクして応答するようにしたいと思います。

Method PostProcessRead(pResourceObject As %DynamicObject) As %Boolean
{
	//40で始まる電話番号を持つPatientのtelecom要素を上書きする
	if pResourceObject.resourceType="Patient" {
		if $Extract(pResourceObject.telecom.%Get(0).value,1,2)="40" {
			//return 0
			set pResourceObject.telecom.%Get(0).value="******"
		}
	}
	
	return 1
}

}

この方法ではあくまで、応答を返す際に値を上書きしているだけなので、リポジトリ上の値は変更していないことに注意してください。 また、このPostProcessReadは、リソースのIDまで指定して検索するようなケースにだけ有効です。

http://localhost:52785/csp/healthshare/customfs/fhir/r4/Patient/3

以下のように全件取得や条件取得の際は応答がBundleとして返されるので、この変換は適用されません。

http://localhost:52785/csp/healthshare/customfs/fhir/r4/Patient
http://localhost:52785/csp/healthshare/customfs/fhir/r4/Patient?gender=female

Bundleに含まれる応答をカスタマイズする場合には OnProcessSearch をカスタイズします。

まとめ

いかがでしたでしょうか? 実際のFHIRアプリケーション開発においては、標準のFHIR仕様の範囲だけで実装することが難しい場合は、今回ご紹介したカスタマイズ機能を適切に使用して、開発に役立てることができます。 次回は、カスタマイズ機能パート2として、カスタムオペレーションの実装例をご紹介したいと思います。