以下の詳細は、David Nuescheler 氏が表明している見解です。
同氏は、アドビが 2010 年に買収した、グローバルなコンテンツ管理およびコンテンツインフラストラクチャソフトウェアの大手プロバイダーである Day Software AG 社の共同創立者兼 CTO です。現在は、アドビのフェローであり、Enterprise Technology のバイスプレジデントです。また、コンテンツ管理の技術標準である Java コンテンツリポジトリ(JCR)アプリケーションプログラミングインターフェイス(API)、JSR-170 開発の第一人者でもあります。
詳しくは、https://wiki.apache.org/jackrabbit/DavidsModel を参照してください。
様々な話し合いの中で、コンテンツのモデル化という点では、JCR の機能に関して開発者たちが幾分不安を感じていることがわかりました。リポジトリ内のコンテンツをモデル化する方法や、コンテンツモデルの中に優劣が存在する理由については、まだ何の指針もなく、経験もほとんどありません。
リレーショナルな世界では、ソフトウェア業界にはデータのモデル化に関して多くの経験がありますが、コンテンツリポジトリに関しては、まだ初期段階です。
コンテンツをモデル化する方法について私の個人的意見を表明することにより、この空白を埋める試みを始めたいと思います。これがいずれ開発者コミュニティにとってより有意義なものへと変化し、単なる「私の意見」ではなく、より一般的なものとなることを期待しています。よって、これは私の最初の挑戦であり、今後急速に進化するものとお考えください。
免責事項:このガイドラインは、私個人の見解であり、賛否が分かれる場合もあります。内容について意見を交わし、改善することを期待しています。
ERD の意味では、宣言されているデータ構造について気にしないことをお勧めします。ただし、最初のうちは、です。
開発の際は、nt:unstructured(およびその仲間たち)を好きになるようにしてください。
これについては Stefano さんがよくまとめてくれると思います。
私の結論としては、構造はコストがかかるものであり、多くの場合、基になるストレージに対して構造を明示的に宣言することはまったく必要ありません。
アプリケーションで使用することになっている構造に関しては、暗黙の契約があります。例えば、ブログの投稿の変更日を lastModified プロパティに格納するとします。アプリケーションでは、その同じプロパティから再度変更日を読み取ることが自動的に認識されるので、明示的に宣言する必要はまったくありません。
必須などの追加のデータ制約や、タイプや値の制約は、データの整合性のために必要な場合にのみ適用します。
lastModified
日付プロパティを使った前述の例は「blog post」ノードの例であり、特別なノードタイプが必要であることを意味しているわけではありません。私は、少なくとも最初は必ず nt:unstructured
をブログ投稿ノード用に使います。私のブログアプリケーションでは、lastModified の日付を表示するだけなので(「order by」の場合もある)、それが Date であるかどうかはほとんど気にしません。私はブログを書くアプリケーションが「Date」を入れてくれることを暗黙的に信じているので、nodetype という形式で lastModified
日付の存在を宣言する必要は実際にはありません。
コンテンツ階層は非常に役立つアセットです。そのため、ただ成り行きに任せるのではなく、しっかりと設計しましょう。人間が読み取れる「良い」ノード名がない場合は、それを考え直す必要があります。任意の数字は、「良い名前」とは言えません。
既存のリレーショナルモデルをすぐに階層モデルに変換できれば非常に簡単かもしれませんが、その際は多少の配慮が必要です。
私の経験では、アクセス制御および抑制によってコンテンツ階層を駆動させるという考えが多いようです。コンテンツ階層を、自分のファイルシステムと考えてください。ファイルやフォルダーを使用して、ローカルディスク上でモデル化することも可能です。
個人的には、多くの場合、最初はノードタイピングシステム経由で階層を変換し、後からタイピングを導入する方法を好みます。
コンテンツリポジトリの構造化の方法はパフォーマンスにも影響を及ぼす可能性があります。最適なパフォーマンスを確保するために、コンテンツリポジトリ内の個々のノードに接続される子ノードの数は、通常 1,000 個以下にする必要があります。
詳しくは、 CRX で処理できるデータの量 を参照してください。
以下のような単純なブログシステムをモデル化します。この時点で使用する個々のノードタイプについて、最初は気にしてさえいないことに注目してください。
/content/myblog
/content/myblog/posts
/content/myblog/posts/what_i_learned_today
/content/myblog/posts/iphone_shipping
/content/myblog/comments/iphone_shipping/i_like_it_too
/content/myblog/comments/iphone_shipping/i_like_it_too/i_hate_it
ここで明らかになるのは、コンテンツの構造が、例に基づいて、追加説明なしで理解できることです。
「コメント(comments)」を「投稿(post)」と共に格納しないことを最初は疑問にお思いかもしれませんが、これは、アクセス制御を合理的に階層化された方法で適用したいからです。
上記のコンテンツモデルを使用すると、「匿名の」ユーザーにコメントの「作成」を簡単に許可する一方で、匿名のユーザーを残りのワークスペースで読み取り専用ベースに保つことができます。
アプリケーションで clone()
、merge()
、update()
などのメソッドも使用しない場合は、単一のワークスペースが有効な方法かもしれません。
「対応するノード」は、JCR 仕様で定義されている概念です。結局のところは、本質的に、それぞれ異なるいわゆるワークスペース内の、同じコンテンツを表すノードのことです。
JCR ではワークスペースの概念が非常に抽象的に紹介されているので、何に使用すればよいのか、開発者には不明瞭なままになっています。ワークスペースを、以下のようにテスト目的で使用することを提案します。
「対応する」ノード(基本的には同じ UUID を持つノード)が、複数のワークスペースに多数重複している場合は、ワークスペースをうまく使用している可能性が高いでしょう。
同じ UUID を持つノードの重複がまったくない場合は、ワークスペースの使い方が誤っている可能性があります。
ワークスペースは、アクセス制御には使用しないでください。特定のユーザーグループにコンテンツを表示することは、別々のワークスペースに分割する十分な根拠とはなりません。JCR には、このためにコンテンツリポジトリの「アクセス制御」機能が用意されています。
ワークスペースは、参照およびクエリの境界です。
ワークスペースは、次のようなものに使用します。
ワークスペースは、次のようなものには使用しないでください。
同じ名前の兄弟(SNS)は、XML 用に設計され、XML で表現されているデータ構造との互換性を得るために仕様に導入されたので、JCR にとって非常に有益ではありますが、リポジトリのオーバーヘッドが大きく、かなり複雑です。
いずれかのパスセグメントに SNS が含まれているコンテンツリポジトリのパスは安定性が非常に低くなり、1 つの SNS が削除されるか並べ替えられると、その他すべての SNS およびそれぞれの子のパスに影響します。
XML の読み込みや既存の XML とのインタラクションのために SNS が必要かつ便利な場合もありますが、私は SNS を使用したことはなく、ゼロから始めるデータモデルでは今後も使用することはありません。
使用方法
/content/myblog/posts/what_i_learned_today
/content/myblog/posts/iphone_shipping
以下の代わりに使用します。
/content/blog[1]/post[1]
/content/blog[1]/post[2]
参照は参照整合性を暗示します。参照を使用すると、参照整合性を管理するリポジトリのコストが増加するだけでなく、コンテンツの柔軟性という点からもコストがかかることを理解することが重要です。
個人的には、定まっていない参照をどうしても処理できない場合にのみ参照を使用し、それ以外の場合はパス、名前、文字列 UUID のいずれかを使用して別のノードを参照するようにしています。
ドキュメント(a)からドキュメント(b)への「参照」を許可すると仮定しましょう。参照プロパティを使用してこの関係をモデル化する場合、2 つのドキュメントはリポジトリレベルでリンクされるということです。参照プロパティのターゲットが存在しない可能性があるので、ドキュメント(a)の個別の書き出しや読み込みはできません。統合、更新、復元、クローンなど、その他の操作も同様に影響を受けます。
したがって、私はこれらの参照を「弱い参照」(JCR v1.0 では、結局のところターゲットノードの UUID を含む文字列プロパティ)としてモデル化するか、単純にパスを使用します。そもそも、パスのほうが有意である場合もあります。
参照が定まっていないとシステムが機能しない場合があると思いますが、私の実体験からは、十分に「現実的」で、かつシンプルな例は、思いつきません。
コンテンツモデルがファイルまたはフォルダーのように感じられるものを見せている場合、私は nt:file
、nt:folder
、nt:resource
の使用(または拡張)を試みます。
私の経験では、多くの汎用アプリケーションで、nt:folder および nt:files とのインタラクションが暗黙的に許可されており、メタ情報が追加された場合にイベントを処理して表示する方法が認識されています。例えば、JCR をベースとしている CIFS や WebDAV のようなファイルサーバー実装との直接のインタラクションは暗黙となります。
私は経験則として、ファイル名と MIME タイプを保存する必要がある場合は、 nt:file
/ nt:resource
がとても良いマッチだと思います。複数の「ファイル」がある場合は、nt:folder は格納場所に適しています。
リソースのメタ情報を追加する必要がある場合(例えば、「author」プロパティや「description」プロパティなど)、nt:file
ではなく、nt:resource
を拡張します。nt:file を拡張することはほとんどなく、nt:resource
を頻繁に拡張します。
誰かが以下のブログに画像をアップロードしたいと仮定します。
/content/myblog/posts/iphone_shipping
最初の直感的反応は、画像を含むバイナリプロパティを追加することになるでしょう。
バイナリプロパティだけを使用する良い使用例ももちろんありますが(名前は重要でなく、MIME タイプは暗黙であるとします)、この場合、私のブログ例には、以下の構造をお勧めします。
/content/myblog/posts/iphone_shipping/attachments [nt:folder]
/content/myblog/posts/iphone_shipping/attachments/front.jpg [nt:file]
/content/myblog/posts/iphone_shipping/attachments/front.jpg/jcr:content [nt:resource]
リレーショナルデータベースでは、ID は関係を表すのに必要な手段なので、コンテンツモデルでも ID が使用される傾向があります。ただし、多くは誤った理由によるものです。
コンテンツモデルがプロパティでいっぱいで、「ID」で終わっている場合は、階層が適切に活用されていない可能性があります。
確かに、一部のノードでは、ライフサイクル全体に渡って安定した識別が必要です。ただし、そのようなノードはそれほど多くありません。mix:referenceable によって、このようなメカニズムがリポジトリに組み込まれるので、安定した方法でノードを識別する方法を追加で考え出す必要はありません。
項目はパスによっても識別できることも心に留めておいてください。UNIX ファイルシステムで「シンボリックリンク」が多くのユーザーにとってハードリンクよりはるかに大きな意味を持つのと同様に、ターゲットノードを参照する場合は、ほとんどのアプリケーションでパスが意味を持ちます。
さらに重要なことは、mix:referenceable であるということです。つまり、本当に参照する必要がある時点でノードに適用できるということです。
よって、タイプが「ドキュメント」であるノードを参照可能にしたいからといって、「ドキュメント」ノードタイプを静的な方法で mix:referenceable から拡張しなければならないということにはなりません。「ドキュメント」の任意のインスタンスに動的に追加できるからです。
使用方法:
/content/myblog/posts/iphone_shipping/attachments/front.jpg
以下の代わりに使用します。
[Blog]
-- blogId
-- author
[Post]
-- postId
-- blogId
-- title
-- text
-- date
[Attachment]
-- attachmentId
-- postId
-- filename
+ resource (nt:resource)