MarkLogicデザインパターンは、MarkLogicアプリケーションの設計における一般的な問題に対処するための、再利用可能なソリューションです。これらのパターンは、MarkLogicアプリケーションに固有のもの、あるいはMarkLogicを念頭に置いた各業界ごとのパターンになっています。MarkLogicデザインパターンは、料理のレシピのように個別的・具体的なものではなく、一般に抽象度が高く、複数のシナリオに適用できます。
外部プロセスが利用するデータと、MarkLogicデータベースシステムを強力かつ柔軟にするためのデータを分離します。包括的なエンベロープの親要素すなわち「headers」セクションを含むオブジェクトと、「instance」データセクションを作成します。これらのセクションはドキュメント内で分かれています。これは、MarkLogicデータハブフレームワークおよびMarkLogicエンティティサービスによるエンベロープの作成方法に準拠しています。
MarkLogicでは、格納されているJSONまたはXMLドキュメントが「インデックスへのインターフェイス」になります。つまり、XMLドキュメントに要素を追加したり、ネストされたオブジェクトをJSON構造に追加したりすることで、データ、親子関係、値にインデックスが付けられます。このため、インデックスが付いたデータを追加したい場合、通常は要素やネストされたオブジェクトを追加するだけでOKです。
MarkLogic内部用に使用されるデータと、データサービスAPIが必要とするデータを分けておくと便利です。
データサービスは、次のようなデータを利用します。
一方、次のようなデータによって、MarkLogicの処理やインデックス付けが向上することがあります。
これと同じことを実現するために、データの読み込み時およびデータサービスからのアクセス時に大量の変換を行うこともできますが、外部からアクセスする「中核的な」データをそのまま格納しておいた方が効率的でわかりやすいです。
説明をシンプルにするため、ここではこのパターンをXMLの用語(ドキュメント、要素、ノード)で説明していきますが、JSONの場合でも基本的な考え方はすべて同じです。
簡単に言うと、エンベロープパターンは2つの相反する目標を達成します。
システムによっては、さらに別の目的が追加されます。たとえば、複数のAPIで形式が極端に異なるデータ(CSVとJSON、XMLとRDFなど)を利用したいことがあります。この場合、headersセクションとinstanceセクションに加えて他のセクション(「triples」、「html-preformatted」など)を追加することもあります。
このパターンは、複数の大規模データセットをすばやくMarkLogicで統合する場合によく使用されます。このアプローチでは、生データや「十分使えそう」なデータをMarkLogicに直接読み込みます。最初に「headers」セクションに少数の要素を含めて、多数のデータセットに対する統一的なインデックス付け、抽出、分析を実現します。
「instance」セクションのデータはすべて、アクセス、デフォルトレンダリングを使用したレンダリング、エクスポート、管理が可能です。まず最も価値が高いデータだけを使用することで、データにアクセスするシステムを非常に迅速に開発できます。
データハブフレームワークでは、生データをまずステージングデータベースに読みます。これにより、統一すなわちハーモナイズされたデータを「instance」セクションに追加できます。
MarkLogicプロセスしか使用しないデータとデータサービスからアクセスするデータを分けておくことにより、外部レイヤーやサブシステムに手を入れずに、データを「headers」セクションに適宜追加できます。これにより大規模プロジェクトの分析、再コーディング、テスト、調整の時間を短縮できます。
このエンベロープデザインパターンは、基本的にすべてのデザインにおいて使用してください。よほどの理由がない限りこれを使用しないことはおすすめしません。
このパターンは、次のような場合にお勧めします。
「すべてのアクセスはサービス経由」は、すべての更新で「headers」セクションが追加され、すべてのクエリでこれを削除するというパターンです。ここでは「headers」セクションは呼び出し元から見えないため、MarkLogicデータレイヤー内(MarkLogic自体の中の.jsおよび.xqyコード内)の柔軟性を維持できます。
「インデックス付け可能な」データを追加する作業と、データ形式を返す作業が分離されます。「headers」に変更を加えても、「instance」データに依存する外部のクライアントからは見えません。
エンベロープ パターンを実装する際は、次の点を考慮してください。
次のような一連のXML形式の記事があり、格納、検索、アクセスできるようにする必要があるとします。
<article>
<abstract>
<para>You can build a fence by deciding the areas to separate, and then making a barrier from wood or metal that sits between them.</para>
</abstract>
<para>It is often said that good fences make good neighbors.</para>
<para>Choosing areas to divide with your fence is the first step. Jim Smith has built a lot of fences, and says that in Paris, France, people divide garden areas from other areas, but in Cleaveland, OH, people divide chidren's play areas from the street most often</para>
<articleInfo>
<title>How to build a fence</title>
<revision>
<date>1/15/2002</date>
<revnumber>1.0</revnumber>
</revision>
<author><firstname>Nihal</firstname><surname>Jain</surname></author>
</articleInfo>
</article>図1で示されているのは、単純化されたdocBookスキーマのようなものです。ここでは、呼び出し元は、このデータが厳密にこの形式であることを必要としており、形式が異なれば無効と見なすとします。ここでレンジインデックスを使用して改訂日(revision date)で検索またはファセット作成したい場合、考慮すべき問題が2つあります。1つめは、対象データが汎用的な<date>要素に含まれているということです。このため、「date」にレンジインデックスを追加すると、別の用途で使われている<date>が他にあった場合、こちらも対象となってしまいます。2つめは、ここでのdateの形式がXML仕様のxs:dateTimeと互換性がないことです。これら2つの問題を解決するため、読み込み時に次の変換を実行します。
declare variable $article external;
declare namespace meta = "http://marklogic.com/patternExample/meta";
let $textDate := $article/articleInfo/revision/date/text()
let $xsDate := xdmp:parse-dateTime("mm/dd/yyyy", $textDate)
let $internalDate := <meta:revisionDate>{$xsDate}</meta:revisionDate>
return
<envelope
xmlns="http://marklogic.com/entity-services">
<headers>
{$internalDate}
</headers>
<instance>
{$article}
</instance>
</envelope>図2のコードは、dateの変換済み/フォーマット済みバージョンを抽出し、別の名前空間内に具体的な名前の要素を作成します(<meta:revisionDate>)。これにより明確なインデックス付けができるので、目的のxs:date値へアクセスできます。
ここで、2002年1月のすべての記事を検索するため、日付のレンジインデックスを<meta:revisionDate>に追加し、次のようなクエリを実行してみます。
declare namespace es = "http://marklogic.com/entity-services";
declare namespace meta = "http://marklogic.com/patternExample/meta";
(: generic function to query documents, including headers, but return only the instance data :)
declare function es:queryData($q) {
for $envelope in cts:search(/es:envelope, $q)
return $envelope/es:instance/element()
};
let $fromQ := cts:element-range-query(xs:QName("meta:revisionDate"),
">=", xs:date("2002-01-01"))
let $toQ := cts:element-range-query(xs:QName("meta:revisionDate"),
"<=", xs:date("2002-01-31"))
let $jan2002Q := cts:and-query(($fromQ, $toQ))
return es:queryData($jan2002Q)注意が必要なのは、関数es:queryData($q)は<es:instance>要素のあらゆる子要素を返すという点です(記事だけではありません)。
LinkedInやFacebookなどのソーシャルネットワークのプロファイルを表すデータの場合、人物のプロフィールをXMLとして表現し、その内部に関係性をRDFとして格納できます。この場合、RDFを「triples」セクションに含めることができます。
次に示すのは、架空の人物に関するソーシャルネットワークアプリケーションのプロフィールです。
declare namespace sn = "http://marklogic.com/patterns/example/social-network";
<sn:person>
<sn:name>Alfred</sn:name>
<sn:uniqueUserName>Alfred_Jones_1974</sn:uniqueUserName>
<sn:interests>
<sn:interest levelofinterest="7">Semantics</sn:interest>
<sn:interest levelofinterest="10">MarkLogic</sn:interest>
<sn:interest levelofinterest="3">Polyglot Persistence</sn:interest>
</sn:interests>
<sn:friends>
<sn:friend>Sally2227</sn:friend>
<sn:friend>MargaretTheProgrammer</sn:friend>
<sn:friend>Neeraj</sn:friend>
</sn:friends>
</sn:person>図3:プロフィールデータのサンプル
各ユーザーは、ドキュメントとしてモデル化するのが理想的です。というのもドキュメントは自己記述型で階層があるからです。ただし、ソーシャルネットワーク自体はグラフなので、関係性データはRDFトリプルでモデル化するのが理想的です。
Alfred <foaf:knows> Sally Alfred <foaf:knows> Margaret Alfred <foaf:knows> Neeraj
図3のプロフィールを、「Alfred」が属するソーシャルネットワークのセマンティックトリプルで強化します。各ドキュメントの挿入・更新時に次のコードを実行します。
let $thisPersonName := $newPerson/sn:uniqueUserName/text()
let $knowsGraph :=
for $friendName in $newPerson/sn:friends/sn:friend/text()
return sem:triple(
sem:iri($thisPersonName),
sem:iri("http://xmlns.com/foaf/0.1/knows"),
sem:iri($friendName) )
let $envelope :=
<envelope xmlns="http://marklogic.com/entity-services">
<es:triples>
{$knowsGraph}
</es:triples>
<es:instance>
{$newPerson}
</es:instance>
</es:envelope>
return $envelope図4のコードを実行すると、ここで求められている構造になりますす。「person」レコードはそのまま残り、このプロフィールから生成されたソーシャルネットワークを記述するセマンティックトリプルと併せてエンベロープにまとめられます。
<es:envelope xmlns:es="http://marklogic.com/entity-services">
<es:triples>
<sem:triple xmlns:sem="http://marklogic.com/semantics">
<sem:subject>Alfred_Jones_1974</sem:subject>
<sem:predicate>http://xmlns.com/foaf/0.1/knows</sem:predicate>
<sem:object>Sally2227</sem:object>
</sem:triple>
<sem:triple xmlns:sem="http://marklogic.com/semantics">
<sem:subject>Alfred_Jones_1974</sem:subject>
<sem:predicate>http://xmlns.com/foaf/0.1/knows</sem:predicate>
<sem:object>MargaretTheProgrammer</sem:object>
</sem:triple>
<sem:triple xmlns:sem="http://marklogic.com/semantics">
<sem:subject>Alfred_Jones_1974</sem:subject>
<sem:predicate>http://xmlns.com/foaf/0.1/knows</sem:predicate>
<sem:object>Neeraj</sem:object>
</sem:triple>
</es:triples>
<es:instance>
<sn:person xmlns:sn="http://marklogic.com/patterns/example/social-network">
<sn:name>Alfred</sn:name>
<sn:uniqueUserName>Alfred_Jones_1974</sn:uniqueUserName>
<sn:interests>
<sn:interest levelofinterest="7">Semantics</sn:interest>
<sn:interest levelofinterest="10">MarkLogic</sn:interest>
<sn:interest levelofinterest="3">Polyglot Persistence</sn:interest>
</sn:interests>
<sn:friends>
<sn:friend>Sally2227</sn:friend>
<sn:friend>MargaretTheProgrammer</sn:friend>
<sn:friend>Neeraj</sn:friend>
</sn:friends>
</sn:person>
</es:instance>
</es:envelope>この例は、triples セクションを紹介しその用途を説明するという観点から、前述の記事レポジトリの例とは少し異なっています。ここではinstanceセクションは、単なる元の「person」レコードです。
関連パターンとして、挿入されたり返される実際のドキュメントに対して外部からデータを追加するあらゆるパターンがあります。たとえば、URIスキーム、コレクション、プロパティフラグメント、RDFトリプルなどとして追加情報を格納するパターンがあります。
エンベロープパターンは、MarkLogic導入時に常に利用されるようになっています。このパターンは、MarkLogicのデータハブフレームワークにおいて中心的に活用されています。また、データ統合が含まれるMarkLogic導入のほぼすべてで利用されています。
Subscribe to get all the news, info and tutorials you need to build better business apps and sites