Bagriドキュメント指向データベースでの動的RESTサービスの開発

少し前、CNewsフィードを見て、「 健康のIT:ブレークスルーを待っている 」というカンファレンスの発表に出会いました。 「2011年以降、ロシアはヘルスケアにおける統一国家情報システム(EGISZ)を実装するための大規模な国家プロジェクトを実施しています」。 この資料をもう少し詳しく調べてみると、EHHISは、欧米で広く使用されている組織Health Language 7 (以下HL7)の標準に基づいていることがわかりました。 また、HL7標準の中心にあるのはXMLです。 Bagri文書データベース上にHL7文書処理システムのプロトタイプを作成し、プロトタイプが成功した場合、会議でそのレポートを作成したいという要望がありました。



画像








私はしばらくの間HL7文書の研究に取りかかりました。 それから、ところで、HabréはWayfarer15からこの技術に関する記事の良いサイクルを見つけました。 その過程で、この分野で積極的に開発された最新の標準は、 Fast Healthcare Interoperability Resources (以降FHIR)であることがわかりました。 FHIRはRESTテクノロジーと、RESTリソースを介したXML / JSONドキュメントの交換に基づいています。



これはBagriにどのように適用されますか? 約1か月前にBagriがRESTサポートを追加し、 RESTXQアノテーションを使用してXQueryモジュール内のRESTリソースを動的に決定する機能を追加しました。 つまり Bagriサーバーを再起動しなくても、FHIRリソースを動的に作成および公開できます。 やってみよう?



45分でFHIRサーバーのプロトタイプを作成します..



これには次のものが必要です。





Bagri設定ファイル(<bagri_home> /config/config.xml)に新しいスキームを作成し、FHIRと呼びましょう。



Bagri構成ファイルのFHIRスキーマ
<schema name="FHIR" active="true"> <version>1</version> <createdAt>2016-11-09T23:14:40.096+03:00</createdAt> <createdBy>admin</createdBy> <description>FHIR: schema for FHIR XML demo</description> <properties> <!--      --> <entry name="xdm.schema.ports.first">11000</entry> <entry name="xdm.schema.ports.last">11100</entry> <entry name="xdm.schema.members">localhost</entry> <entry name="xdm.schema.thread.pool">16</entry> <entry name="xdm.schema.query.cache">true</entry> <!--    ,     XML --> <entry name="xdm.schema.store.data.path">../data/fhir/xml</entry> <entry name="xdm.schema.store.type">File</entry> <entry name="xdm.schema.format.default">XML</entry> <entry name="xdm.schema.partition.count">271</entry> <entry name="xdm.schema.population.size">1</entry> <entry name="xdm.schema.buffer.size">64</entry> <entry name="xdm.schema.store.enabled">true</entry> <entry name="xdm.schema.data.cache">NEVER</entry> <entry name="xdm.schema.data.stats.enabled">true</entry> <entry name="xdm.schema.trans.backup.async">0</entry> <entry name="xdm.schema.trans.backup.sync">1</entry> <entry name="xdm.schema.trans.backup.read">false</entry> <entry name="xdm.schema.data.backup.read">false</entry> <entry name="xdm.schema.data.backup.async">1</entry> <entry name="xdm.schema.data.backup.sync">0</entry> <entry name="xdm.schema.dict.backup.sync">0</entry> <entry name="xdm.schema.dict.backup.async">1</entry> <entry name="xdm.schema.dict.backup.read">true</entry> <entry name="xdm.schema.query.backup.async">0</entry> <entry name="xdm.schema.query.backup.sync">0</entry> <entry name="xdm.schema.query.backup.read">true</entry> <entry name="xdm.schema.transaction.timeout">60000</entry> <entry name="xdm.schema.health.threshold.low">25</entry> <entry name="xdm.schema.health.threshold.high">0</entry> <entry name="xdm.schema.store.tx.buffer.size">2048</entry> <entry name="xdm.schema.population.buffer.size">1000000</entry> <entry name="xdm.schema.query.parallel">true</entry> <entry name="xdm.schema.partition.pool">32</entry> <entry name="xqj.schema.baseUri">file:/../data/fhir/xml/</entry> <entry name="xqj.schema.orderingMode">2</entry> <entry name="xqj.schema.queryLanguageTypeAndVersion">1</entry> <entry name="xqj.schema.bindingMode">0</entry> <entry name="xqj.schema.boundarySpacePolicy">1</entry> <entry name="xqj.schema.scrollability">1</entry> <entry name="xqj.schema.holdability">2</entry> <entry name="xqj.schema.copyNamespacesModePreserve">1</entry> <entry name="xqj.schema.queryTimeout">0</entry> <entry name="xqj.schema.defaultFunctionNamespace">http://www.w3.org/2005/xpath-functions</entry> <entry name="xqj.schema.defaultElementTypeNamespace">http://www.w3.org/2001/XMLSchema</entry> <entry name="xqj.schema.copyNamespacesModeInherit">1</entry> <entry name="xqj.schema.defaultOrderForEmptySequences">2</entry> <entry name="xqj.schema.defaultCollationUri">http://www.w3.org/2005/xpath-functions/collation/codepoint</entry> <entry name="xqj.schema.constructionMode">1</entry> </properties> <!--    Patient --> <collections> <collection id="1" name="Patients"> <version>1</version> <createdAt>2016-11-09T23:14:40.096+03:00</createdAt> <createdBy>admin</createdBy> <docType>/{http://hl7.org/fhir}Patient</docType> <description>All patient documents</description> <enabled>true</enabled> </collection> </collections> <fragments/> <!--    /Patient/id/@value     id  --> <indexes> <index name="idx_patient_id"> <version>1</version> <createdAt>2016-11-09T23:14:40.096+03:00</createdAt> <createdBy>admin</createdBy> <docType>/{http://hl7.org/fhir}Patient</docType> <path>/{http://hl7.org/fhir}Patient/{http://hl7.org/fhir}id/@value</path> <dataType xmlns:xs="http://www.w3.org/2001/XMLSchema">xs:string</dataType> <caseSensitive>true</caseSensitive> <range>false</range> <unique>true</unique> <description>Patient id</description> <enabled>true</enabled> </index> </indexes> <resources> <!--  ,  ,      http://localhost:3030/ --> <resource name="common"> <version>1</version> <createdAt>2016-11-09T23:14:40.096+03:00</createdAt> <createdBy>admin</createdBy> <path>/</path> <module>common_module</module> <description>FHIR Conformance resource exposed via REST</description> <enabled>true</enabled> </resource> <!--  ,     http://localhost:3030/Patient --> <resource name="patient"> <version>1</version> <createdAt>2016-11-09T23:14:40.096+03:00</createdAt> <createdBy>admin</createdBy> <path>/Patient</path> <module>patient_module</module> <description>FHIR Patient resource exposed via REST</description> <enabled>true</enabled> </resource> </resources> <triggers/> </schema>
      
      







ディレクトリ<bagri_home> / data / fhir / xmlのローカルドライブにテストデータを解凍します。 前の記事でBagriでJSONドキュメントを操作することについて書いたので、この例では、スペースを節約するために、XML形式のデータのみを操作することを示します。



執筆時点で、FHIR仕様は、サーバーがアクセスできる110の標準リソースを定義しました。 それらの一部は公式であり、システム自体に関する情報を提供する役割を果たし、残りは医療データを処理するアプリケーションリソースです。 サービスリソースの適合性は実装に必須であり、システムの利用可能な機能に関する情報を提供します。 他のリソースの有無とその動作は、適合性で宣言した内容によって決まります。



FHIR仕様に従って、アプリケーションリソースは次のメソッドを公開できます。



リソースレベルの操作:





リソースタイプレベルの操作:





説明のために、2つのリソースを実装します。すでに指定された適合性と患者アプリケーションリソースです。 適合性は、Patientリソースのクライアントが利用できる機能を決定します。



以下に多くの絵文字があります。 心配しないでください。これらはXQuery構文のオーバーヘッドです:)。



プロトタイプの適合性の実装は非常に簡単に見えます。新しいXQueryモジュール<bagri_home> /data/fhir/common_module.xqを作成します。 ヘッダーで、使用する言語のバージョン、モジュールの名前空間、使用する外部回路の名前空間を宣言します。



 xquery version "3.1"; module namespace conf = "http://hl7.org/fhir"; declare namespace rest = "http://www.expath.org/restxq";
      
      





次は、リソースの必要な動作を実装する関数のコードです。
 declare %rest:GET (:   HTTP,      :) %rest:path("/metadata") (:     ,   URL:) %rest:produces("application/fhir+xml") (:     XML :) %rest:query-param("_format", "{$format}") (:     _format :) function conf:get-conformance($format as xs:string?) as item() { if (exists($format) and not ($format = ("application/xml", "application/fhir+xml"))) then "The endpoint produce response in application/fhir+xml format, but [" || $format || "] specified" else <CapabilityStatement xmlns="http://hl7.org/fhir"> <id value="FhirServer"/> <url value="http://localhost:3030/metadata"/> <version value="1.1-SNAPSHOT"/> <name value="Bagri FHIR Server Conformance Statement"/> <status value="draft"/> <experimental value="true"/> <date value="{fn:current-dateTime()}"/> <publisher value="Bagri Project"/> <contact> <name value="Maxim Petrov"/> <telecom> <system value="other"/> <value value="@mfalifax"/> <use value="work"/> </telecom> </contact> <description value="Standard Conformance Statement for the open source Reference FHIR Server provided by Bagri"/> <kind value="instance"/> <instantiates value="http://hl7.org/fhir/Conformance/terminology-server"/> <software> <name value="Reference Server"/> <version value="1.1-SNAPSHOT"/> <releaseDate value="2016-11-10"/> </software> <implementation> <description value="FHIR Server running at http://localhost:3030/"/> <url value="http://localhost:3030/"/> </implementation> <fhirVersion value="1.7.0"/> <acceptUnknown value="both"/> <format value="application/fhir+xml"/> <rest> <mode value="server"/> <!--   ,   --> <resource> <type value="Patient"/> <profile> <reference value="http://fhir3.healthintersections.com.au/open/StructureDefinition/patient"/> </profile> <!--  ,   --> <interaction> <code value="read"/> </interaction> <interaction> <code value="vread"/> </interaction> <interaction> <code value="search-type"/> </interaction> <interaction> <code value="update"/> </interaction> <interaction> <code value="create"/> </interaction> <interaction> <code value="delete"/> </interaction> <readHistory value="true"/> <updateCreate value="true"/> <!-- ,       search --> <searchParam> <name value="birthdate"/> <definition value="http://hl7.org/fhir/SearchParameter/Patient-birthdate"/> <type value="date"/> <documentation value="The patient's date of birth"/> <!--    equals --> <modifier value="exact"/> </searchParam> <searchParam> <name value="gender"/> <definition value="http://hl7.org/fhir/SearchParameter/Patient-gender"/> <type value="token"/> <documentation value="Gender of the patient"/> <modifier value="exact"/> </searchParam> <searchParam> <name value="identifier"/> <definition value="http://hl7.org/fhir/SearchParameter/Patient-identifier"/> <type value="token"/> <documentation value="A patient identifier"/> <!--    contains --> <modifier value="contains"/> </searchParam> <searchParam> <name value="name"/> <definition value="http://hl7.org/fhir/SearchParameter/Patient-name"/> <type value="string"/> <documentation value="A server defined search that may match any of the string fields in the HumanName, including family, give, prefix, suffix and/or text"/> <modifier value="contains"/> </searchParam> <searchParam> <name value="telecom"/> <definition value="http://hl7.org/fhir/SearchParameter/Patient-telecom"/> <type value="token"/> <documentation value="The value in any kind of telecom details of the patient"/> <modifier value="contains"/> </searchParam> </resource> </rest> </CapabilityStatement> };
      
      







実際、これは適合リソースが構成される唯一の方法です。 そのタスクは、システムへのその他のアクセスポイントと、これらの対話で使用できるパラメーターを決定することです。



Patientアプリケーションリソース用に、別のXQueryモジュールを作成します。



<bagri_home> /data/fhir/patient_module.xq。 また、ヘッダーで使用される名前空間を宣言します。



 module namespace fhir = "http://hl7.org/fhir/patient"; declare namespace http = "http://www.expath.org/http"; declare namespace rest = "http://www.expath.org/restxq"; declare namespace bgdm = "http://bagridb.com/bagri-xdm"; declare namespace p = "http://hl7.org/fhir";
      
      





readメソッドを実装します。



 declare %rest:GET (:   HTTP,      :) %rest:path("/{id}") (:     ; id -    :) %rest:produces("application/fhir+xml") (:     :) function fhir:get-patient-by-id($id as xs:string) as element()? { collection("Patients")/p:Patient[p:id/@value = $id] };
      
      





私の意見では、非常に魅力的に見えます:必要な機能を1行で実装することです! しかし、ご存知のように、悪魔は細部に宿っています。 FHIR仕様では、基本的な動作に加えて、このような場合にサービスが返す必要がある多くの追加の状況とステータスおよびHTTPヘッダーも定義しています。 高度な要件を考慮して、上記の読み取りメソッドを書き直してみましょう。



 declare %rest:GET %rest:path("/{id}") %rest:produces("application/fhir+xml") function fhir:get-patient-by-id($id as xs:string) as element()* { let $itr := collection("Patients")/p:Patient[p:id/@value = $id] return if ($itr) then (<rest:response> <http:response status="200"> (:    ? :) {if ($itr/p:meta/p:versionId/@value) then ( (:  ETag       Patient :) <http:header name="ETag" value="W/"{$itr/p:meta/p:versionId/@value}""/>, (:  Content-Location   ,       :) <http:header name="Content-Location" value="/Patient/{$id}/_history/{$itr/p:meta/p:versionId/@value}"/> ) else ( (:   Content-Location      :) <http:header name="Content-Location" value="/Patient/{$id}"/> )} (:  Last-Modified   /    :) <http:header name="Last-Modified" value="{format-dateTime(xs:dateTime($itr/p:meta/p:lastUpdated/@value), "[FNn,3-3], [D] [MNn,3-3] [Y] [H01]:[m01]:[s01] [z,*-6]")}"/> </http:response> </rest:response>, $itr) else (:   404     id   :) <rest:response> <http:response status="404" message="Patient with id={$id} was not found."/> </rest:response> };
      
      





HTTP:応答構造は、HTTP応答のステータスとヘッダーを示すために使用されます。HTTP応答は、返されるデータのシーケンスの最初の要素で送信する必要があります。 また、要素()で戻り値の型を変更する必要がありましたか? 要素()*で、このオーバーヘッド情報をRESTサーバーに渡します。



もちろん、このような仕様要件の完全な実装は、はるかに冗長です。 しかし、FHIRの要件をよりコンパクトに満たすために使用できる言語/テクノロジーを言うことはできません。 一方、XQueryのXMLおよびデータシーケンス機能は非常に魅力的です。



以下では、考えられるすべての追加シナリオの処理に気を取られることはありません。上記の例では、追加のステータスとHTTPヘッダーをサーバーに返す方法を示しました。

vreadメソッドの基本的な実装は非常に似ています:



 declare %rest:GET %rest:path("/{id}/_history/{vid}") (:            :) %rest:produces("application/fhir+xml") function fhir:get-patient-by-id-version($id as xs:string, $vid as xs:string) as element()? { collection("Patients")/p:Patient[p:id/@value = $id and p:meta/p:versionId/@value = $vid] };
      
      





次の方法は検索です。 Conformanceリソースでは、名前、誕生日、性別、識別子、通信の5つのパラメーターで患者を検索できることを示しました。 また、modifier要素を使用して、検索パラメータの使用方法を示しました。この要素は、次の値を取ることができます。 正確な| 含む| ない| テキスト| で| ノットイン| 以下| 上| タイプ。 それらの説明と対応する検索エンジンの動作はここにあります



 declare %rest:GET %rest:produces("application/fhir+xml") %rest:query-param("identifier", "{$identifier}") (:      :) %rest:query-param("birthdate", "{$birthdate}") (:  http;     :) %rest:query-param("gender", "{$gender}") %rest:query-param("name", "{$name}") %rest:query-param("telecom", "{$telecom}") function fhir:search-patients($identifier as xs:string?, $birthdate as xs:date?, $gender as xs:string?, $name as xs:string?, $telecom as xs:string?) as element()* { (:    (),    :) let $itr := collection("Patients")/p:Patient[ (not(exists($gender)) or p:gender/@value = $gender) and (not(exists($birthdate)) or p:birthDate/@value = $birthdate) and (not(exists($name)) or contains(data(p:text), $name)) and (not(exists($identifier)) or contains(p:identifier/p:value/@value, $identifier)) and (not(exists($telecom)) or contains(string-join(p:telecom/p:value/@value, " "), $telecom))] (:     Bundle :) return <Bundle xmlns="http://hl7.org/fhir"> <id value="{bgdm:get-uuid()}" /> (:   bundle ID :) <meta> <lastUpdated value="{current-dateTime()}" /> </meta> <type value="searchset" /> <total value="{count($itr)}" /> <link> <relation value="self" /> <url value="http://bagridb.com/Patient/search?name=test" /> </link> {for $ptn in $itr return <entry> <resource>{$ptn}</resource> </entry> } </Bundle> };
      
      





reate-新しい患者リソース、または既存のリソースの新しいバージョンを作成します。



 declare %rest:POST (:      POST :) %rest:consumes("application/fhir+xml") (:           XML :) %rest:produces("application/fhir+xml") (:          :) function fhir:create-patient($content as xs:string) as element()? { let $doc := parse-xml($content) (:      XML,     :) let $uri := xs:string($doc/p:Patient/p:id/@value) || ".xml" (:  uri   :) let $uri := bgdm:store-document(xs:anyURI($uri), $content, ()) (:        uri,         2   :) let $content := bgdm:get-document-content($uri) (:    ,    ,      ,            :) let $doc := parse-xml($content) return $doc/p:Patient };
      
      





更新-既存の患者リソースの新しいバージョンを作成するか、特定の識別子を持つ患者がまだシステムに登録されていない場合は新しいリソースを作成します。



 declare %rest:PUT (:      PUT :) %rest:path("/{id}"). (:       :) %rest:consumes("application/fhir+xml") %rest:produces("application/fhir+xml") function fhir:update-patient($id as xs:string, $content as xs:string) as element()? { for $uri in fhir:get-patient-uri($id) (:        :) let $uri := bgdm:store-document($uri, $content, ()) let $content := bgdm:get-document-content($uri, ()) let $doc := parse-xml($content) return $doc/p:Patient };
      
      





削除-システムに登録されている患者リソースを削除します。



 declare %rest:DELETE (:  , ,   DELETE :) %rest:path("/{id}") function fhir:delete-patient($id as xs:string) as item()? { for $uri in fhir:get-patient-uri($id) return bgdm:remove-document($uri) (:     :) };
      
      





更新および削除機能から使用されるヘルパーメソッド:



 declare %private function fhir:get-patient-uri($id as xs:string) as xs:anyURI? { (:    XQuery :) let $query := ' declare namespace p = "http://hl7.org/fhir"; declare variable $id external; for $ptn in fn:collection("Patients")/p:Patient where $ptn/p:id/@value = $id return $ptn' (:  ,    uri ,    :) let $uri := bgdm:query-document-uris($query, ("id", $id), ()) return xs:anyURI($uri) };
      
      





ご覧のとおり、Bagriライブラリが提供するXQuery関数は、リソース管理ロジックの実装で使用されます。 以下に簡単な説明を示します。



 bgdm:get-uuid() as xs:string -    uuid bgdm:query-document-uris(xs:string, xs:anyType*, xs:anyAtomicType*) as xs:string* -  uri ,      XQuery bgdm:store-document(xs:anyURI, xs:string, xs:anyAtomicType*) as xs:anyURI -     ,      bgdm:get-document-content(xs:anyURI) as xs:string* -     bgdm:remove-document(xs:anyURI) as xs:anyURI -  
      
      





これで、FHIRリソース管理ロジックを実行するサーバーモジュールの実装が完了しました。 私たちは出会った45分後にだと思う:) 記事の次の部分では、上記で開発したリソースを実行してテストする方法を示します。 まあ、もちろん、Habrの親愛なる聴衆がこれについてどう思うかを聞くことは非常に興味深いでしょう。



All Articles