Tuesday, October 29, 2013

CMISに準拠しているとはどういうことか

こんにちは、linzhixingです。現在、aegifでの仕事をお休みしてフランスに1年間の滞在中です。もう2ヶ月ほど経ちました。日本ではずっとNemakiWareの開発をしていましたが、開発の方は社内で引き継がれています。おかげで、すでにNemakiWareはバージョン1.0GA2がリリースされており、パフォーマンスの飛躍的な改善や、CMIS1.1機能などが実装されました。

私自身もこちらで日曜大工的にちょくちょくgithubにpushしているのですが(オープンソースですし)、弊社で社内コミュニケーション用に導入されているco-meetingのおかげで円滑に進められています。もともと日本でも、(プロジェクトによりますが)わりと自宅で仕事+co-meetingというスタイルだったので、場所が変わってもとくに変わらないんですが、それが新鮮といえば新鮮です。



今回のテーマ

さて 、今回のブログのテーマは、「CMISに準拠しているとこんなことができるよ」というCMIS関連でよく聞くセールスポイントではなく、「CMISに準拠しているとはどういうことか」です。少しわかりにくいタイトルですね。CMIS仕様に準拠していると一言で言うのは簡単なものの、CMIS仕様の仕様としての特殊性から、準拠といっても様々なグラデーションが考えられたり、サーバ/クライアントがそのCMISに準拠するためにどういう戦略を取り得るか、なんていう側面から、CMIS仕様に対する理解を深めてみたいと思います。そして最後に、CMIS化する(CMISに準拠させる)ためにNemakiWareを使う、というコンセプトについて紹介します。

ですので、CMISという規格について少しは馴染みのある方向けの内容となりますが、
特にCMISをご存知ない方でも、一般に既存の仕様にあわせて新しく製品を開発するときどんなことを考えればいいか、という視点で流し読みしてくだされば幸いです(CMISを知らないと分からないような例は、なるべく「たとえば」として括り出してあります)。



CMISの沿革

まずはじめに、CMIS規格が策定された経緯から。CMISはもともと2008年にIBM、EMC、MicrosoftといったECM界のビッグネームがOASISに提出した仕様で、Content Management Interoperability Service(コンテンツ管理相互運用サービス)の略です。つまり、異なるECM製品同士でも共通のプロトコルにより操作できるようにするための規格です。
 http://www.insightnow.jp/article/2074

業界全体で既にデファクトスタンダードが成立していた、というわけではなく、既にある程度確立・乱立していたECM製品同士の間に新しく共通言語としてのCMISが定められました。もちろん、いったんCMISという規格が定められた以上、ECMとして望ましい機能を今後CMISが自主的に取り込んでいく動きはあって、たとえばタグ/カテゴリの仕様策定も検討されていたようですが、いずれにせよだいぶ先の話になりそうだという印象です。 

CMISは既存ECMを抽象化したものといえますが、NemakiWareは反対に、最初からCMIS準拠を目指してスクラッチで開発されたものなので、「CMIS準拠という制約を極力守りながら、どこまでやりたいことが自由に表現できるか」についてセンシティブにならざるを得ませんでした。それが、このブログのタイトルになっています。



CMISは最大公約数 

さて、CMISはこのような経緯から、既存のいろいろなECMの機能から最大公約数を取って定義し直したようなものになりました。(追記2013/10/29: 厳密に「最大」とはいえないかもしれませんが)

ECMが持ち得る諸機能のうち、このミニマムに含まれない部分をCMIS外で提供するのは、もちろん自由です。
  •  たとえば監査(Audit)機能などはCMIS仕様では定義されていませんが、Alfrescoなどは提供しています。また、ユーザ/グループはCMISでは定義されていません。しかしCMIS仕様自身が、権限設定やコンテンツの属性としての「作成者」などでユーザの存在を前提しています。その実装は各CMISサーバに任されているわけです。 

CMIS外で提供される機能はさておき、CMIS仕様内部にも、個々のECMサーバごとに実装方針を選択していいよ、という可変な部分があります。



CMISの可変性 

CMIS内部での可変性には2種類あります。Capabilityと呼ばれる、どの実装方針を選択したのか示す情報と、もう一つは、名前はついていませんが仕様上いくつかの選択肢がゆるされている場合です。

 

Capability 

CMISサーバではリポジトリを指定してその中のファイルやフォルダを操作しますが、リポジトリ全体のメタデータであるRepositoryInfoの中にCapabilityという項目があります。これは簡単に言えばそのサーバでどの機能がON/OFFになっているかを示すものです。
  • たとえば、OrderBy CapabilityがONなら、クライアントは名前や作成日時などでソート済みのコンテンツ一覧を要求できますが、OFFなら順序が保証されません。
  • たとえば、Multifiling / Unfiling CapabilityがONならサーバは1つのファイルを複数の親フォルダに格納したり、どのフォルダにも属さないファイルの存在を許可できます。 
クラアントは、サーバがどのCapabilityを提供しているか知ることができます。様々なCMISサーバに接続する可能性がある汎用CMISクライアントは、サーバごとに必要なCapabilityが提供されているか事前にチェックして、提供されていない場合はワークアラウンドを実行する、といった戦略が必要になってきます。 
  • たとえば、コンテンツのチェンジログをクローリングするCMISクライアントは、Changes CapabilityがONならチェンジログを取得できますが、OFFの場合はワークアラウンド(そのサーバがCMIS外でチェンジログを提供していればそれを取得するなど)を取る、など。

仕様上、複数の選択肢がある場合

Capabilityのように明示的に「このサーバはこの機能を提供すると保証します」と宣言があるわけではないけれど、仕様書で「あなたのサーバがどちらの挙動をするかはお好みで」と書かれているものがあります。 
  • たとえば、ドキュメントのメタ情報を更新するsetPropertiesメソッドやドキュメントのファイル実体を更新するsetContentStreamメソッドで、これらのメソッドを実行したらドキュメントのバージョンを上げる/上げない、はサーバが任意に実装方針を選択します。どちらを選択したか告げるCapabilityは存在しません。ちなみにNemakiWareではどちらもマイナーバージョンアップ(e.g. 1.0 → 1.1)することにしています。マイナーにするかメジャーにするか、なんてこともサーバ側の自由です。 
どういう挙動になるのかクライアントからは事前に知ることができませんが、このようなケースは仕様書上それほど多くあるわけではないですし、Capabilityほど派手に挙動が変わるものでもありません。それでも、CMISが規格として公約数をとった影響か、仕様としてカッチリ固定されていない部分がままあるという印象を受けます。



デファクトスタンダードとしてのApache Chemistry

ここまで、CMISの機能セットについて論理的な特徴を説明してきましたが、CMISに準拠した製品を作る上でデファクトスタンダードとなっているApache Chemistryライブラリについても説明したいと思います。


Apache Chemistryとは

Apache ChemistryはCMIS仕様のオープンソースな実装ライブラリです。Apacheのtop levelプロジェクトになっているようです。近々CMIS and Apache Chemistry IN ACTIONという書籍(洋書)も出るようです。

なんで"Chemistry"という名前なのかというと、CMIS(シーミス)を"Content"の音でケーミス?と呼ぶこともあるから、みたいです。

Chemistryのコミッタの多くはCMIS仕様の策定メンバと重なっているようです。仕様と実装は区別しないといけませんが、CMISの挙動でよくわからないときはChemistryのJIRAにあたってみるのもいいかもしれません。

ライブラリとしては、サーバ用とクライアント用から成っています。サーバ用はJavaによるOpenCMIS一択で、クライアント用にはJAVA/Python/PHP/.Net/Objective-Cによるライブラリが存在しています。
CMISが複数のECMに共通する仕様であるように、そのデファクトスタンダードな実装ライブラリであるChemistryも多くのECMで利用されています。Wikiにあるものを挙げると、Alfresco, Nuxeo, SAP, Open Text, Liferay……


(Chemistry固有の制約)

というのはほとんどないと思いますが(CMIS仕様あっての話なので)、稀に、「仕様によればpropertyの値がnullでも許されるが、OpenCMISではエラーになる」みたいな細かい問題はあったようです。ほとんどバグのレベルと言っていいと思いますが、一番開発が進んでいるOpenCMISでも今もときどきバグfixがあります。

また、OpenCMISライブラリだけ独自に対応してくれることもあります(Atomバインディングでは普通返されないchangeTokenをOpenCMISライブラリでは特別に返してくれる、など)。 デファクトスタンダードとはいえ、アドホックな対応になるので、どこまで依存させるかの判断になります。

なんにせよ、CMISを実装していくためにCMIS仕様上の文言を解釈するとき、OpenCMISがどう実装しているのかという視点も重要になることは間違いありません。

なお余談ですが、CMIS仕様自体に「バグ」があることもあります :)
https://tools.oasis-open.org/issues/browse/CMIS-757



いっそCMISに準拠しない

さて、このブログのテーマは「CMISに準拠しているとどう嬉しいのか」ではないので、CMISに準拠しないパターンも書いてみたいと思います。

とはいっても、もちろんCMISで定義されていない機能はCMIS外で実装すればいいだけなので、ここで述べるのは「CMISの機能のうちのいくつかを単に提供しない」のと、「CMIS機能を通じて情報をやりとりするときに、CMIS外のデータを紛れ込ませる(&そのデータに基づいてCMIS外のサービスを行う)」の二つです。


「特定の機能を提供しない」

まず、CMISの機能のうちいくつかを提供しない場合ですが、ドキュメントやフォルダはCMISで提供したいがポリシーやリレーションシップなどのオブジェクトは使わない場合、その部分のメソッドを実装しない / モックに留める、ということが考えられます。CapabilityでOFFにできるのであればOFFと宣言すればよいですが、そうでない(=CMISとして必須)場合は、もうCMIS準拠していると胸を張って言うことは出来なくなります。しかし、CMISは様々なECMから機能を抽出したものなので、用途によってはポリシーなど不要で、提供しなくても他の部分への影響はほとんどないわけです。また、CMIS1.0からCMIS1.1を追加で実装するにあたって、セカンダリオブジェクトなどは実装したけれどもバルクアップデート機能はサポートするつもりがない、というケースは今後現実的にはあり得る話だと思います。


「CMIS外のデータを紛れ込ませる」

CMISは、サーバとクライアントの実際の通信にWebServicesバインディングとAtomバインディング(とCMIS1.1ではBrowserバインディング)を定めています。データはXML(かJSON)でやり取りされるので、その中にCMIS外のデータを紛れ込ませれば、なにかと便利なときがあります。
  • たとえば、Alfrescoのaspectのような情報。これはCMIS1.1でセカンダリタイプが定義されたことで、CMISの枠内に取り込まれましたが、CMIS1.0しか対応していなければCMIS外のデータになります。
  • また、コンテンツの権限設定情報であるACLが、特定の親コンテンツから特定の子コンテンツに継承されるか否かは、実はCMISでは表現できません。なんらかの意味で「継承」されているなら、親コンテンツACLへの変更を伝播する/しないはCapabilityやメソッドの引数で調整できるよ、とは書いてあるので、「継承」概念自体は前提されているのですが……。クライアントからACL継承フラグを直接設定できるようにしたい場合、サーバがフラグをCMIS外のデータとして提供できると好都合です。
CMIS外のデータは、もちろんCMISでサポートされていませんが、サーバ側のデファクトスタンダードであるOpenCMISライブラリではCmisExtensionクラスを提供しています。これはサーバ/クライアント間がCMISとしてやり取りするxmlの中に、CMIS外のデータを埋め込むためのものです。汎用性はもちろん保証されませんが、特定の(OpenCMISベースの)CMISサーバと通信するクライアントがCmisExtensionをパースしたり送信したりするケースが考えられます。

NemakiWareでも、上述のACL継承フラグをCmisExtensionで提供しています。NemakiWareの場合、クライアントはApache Chemistryではなく、RailsベースのActiveCMISライブラリを使っているので、CmisExtnsionをパースするためのコードを追加しています。(そのうち本家にpush予定)

既存のECMサーバであれば、CMISで表現できない機能は無理にCmisExtensionで押し込まず、元々のAPIや規格でのみ提供することも考えられます。しかしNemakiWareはCMIS準拠によるメリットを発揮するため、どんな機能でもできる限りCMISだけで表現しようとしています。そしてどうしてもCMISで表現できないときには、まずCmisExtensionを検討し、それでも難しい場合には独自のREST APIを実装する、という判断をしています。



CMISとデータベースのあいだ

さて、少し話は変わりますが、CMIS仕様を一言で説明したいとき、「CMISはECMのSQLだ」ということがよく言われます。SQLがさまざまなDBを統一的に操作できるように、それと平行的に、CMISはさまざまなECMサーバを統一的に操作できる共通言語だというわけですね。

とはいえ、ドメインモデルとサービスを論理的に定めるCMIS仕様と、その背後にあってデータ実体を格納するデータベース(厳密にはInMemoryサーバとかもあります)との間には大きな開きがあります。どう大きく開いているかというと、当然CMIS仕様を満たすためにいろんな実装をしないといけないのと、その実装には可変な選択肢が多くて実装方針を決めないといけないことが多いのです。
  • 前者については、デファクトスタンダードとしてのApache Chemistryがある程度までは負担を減らしてくれます。しかしそれでも、実際にNemakiWareを実装してみた感想としては、サーバではOpenCMISライブラリとDBの間に大きな開きがなお残されています。CMISで定義されたエラーを正確に返したり、フォルダツリーを辿りながらコンテンツを削除し、途中で失敗した場合は特定の出力を返すよう実装したり……。OpenCMISライブラリは、xmlによる通信部分とそのパース、および各サービスメソッドへの振り分けやデフォルト値の設定まではやってくれますが、50個近いメソッドの中身は各サーバが実装しないといけません。しかしそれらの実装は、極端にDB依存な最適化をしない限り、誰がやってもある程度似たものになるはずです。
  • また、後者については、既存ECMが自らをCMIS化するときは選択肢が多くてやりやすかったでしょうが、新しくECMサーバを作ったり、既存のCMS製品をCMIS化したい場合には、CMISのコア機能さえ実装できてCMISクライアントと通信できれば充分で面倒くさいことは考えたくない、というニーズも考えられます。
したがって、これから新しくCMISを実装する場合、特に強い拘りやパフォーマンス上の問題がない限り、DBの単純なCRUD操作さえ書けば、あとの部分はすべてライブラリとして用意されているのが便利です。そして現在、NemakiWare(+OpenCMIS)をそのようなライブラリとして整備中です。

NemakiWareはバックエンドにCouchDBがあることが特徴の一つですが、実はデータベースをほかのもの(MySQLや、ユーザ/グループ部分だけLDAPなど)に取り換えることができる設計になっています。もっとも、キャッシュ機構なども含めるとまだ少し取り換え手順は複雑なので整備が必要ですが、機能的にCouchDBに依存していたSolr連携の部分は私の開発環境では既に解決していますので、近いうちにDB取り換え可能なNemakiWareをお見せできると思います。

「DB部分」には、DB以外のものを入れてもかまいません。たとえば、既存のECMサーバでCMISをサポートしていないものでも、コンテンツのCRUD APIをNemakiWareに繋ぐことで、比較的簡単にCMIS化できるでしょう。

また、NemakiWareはCMISで提供されていない機能であってもECMとして必要最低限と思われる機能(ゴミ箱からのリカバリや、ユーザ/グループ管理など)を独自に実装しています。CRUDを一式用意するだけで、自動的にそれらの機能も使えるようになるわけです。



おわりに

NemakiWareは、AlfrescoなどのECMサーバと異なり、最初からCMIS仕様に準拠することを目指して開発されました。いろいろな製品を「CMIS化」するモデルケースと成り得るのではないでしょうか。最後の、OpenCMISとDBのあいだを埋めるという話はまだコンセプト段階ではありますが、興味を持たれた方は、ぜひgithubのNemakiWareリポジトリを覗いてみてください。ソースコードのpushやコメントなど、いつでも歓迎しています!
https://github.com/NemakiWare/NemakiWare

No comments: