はてなダイアリーAtomPubレビュー: その3 設計編
さて、このシリーズも今回で最後です。遅れに遅れて申し訳ありません…ちなみに昨年の12/22付けで仕様書が改訂され、私が指摘したところが直っていました。どうもありがとうございます>中の人
最後は設計編です。
今回、私がはてなダイアリーAtomPubの仕様を見ていて、設計面で疑問に思ったのは次の2点です。
- X-HATENA-PUBLISH HTTP ヘッダ
- エントリ文書の対称性
以下ではそれぞれについて具体的に検討してみます。
X-HATENA-PUBLISHヘッダ
いくつかのブログでも指摘されていますが、はてなダイアリーAtomPubではapp:draft 要素を利用しません。
はてなダイアリーAtomPub仕様書によると、以下のような理由があるそうです。
はてなダイアリーAtomPubでは、AtomPubで規定されているapp:draft要素を使用しません。はてなダイアリーでは日記エントリと下書きエントリを別々に管理しています。そのため、日記エントリと下書きエントリを一つのコレクションとして扱い、app:draft要素によって下書きかどうかを区別するような構成は不自然になります。むしろ、下書きエントリを日記エントリとして公開するという操作を行う仕組みであるほうが自然であると考え、以下のような方法をとっています。
なるほど、コレクションを分ける際の実装上の都合なわけですね。
これは、これで一つの設計判断だと考えます。コレクション間のエントリのコピーや移動は AtomPub では定義されていませんので、何らかの拡張仕様が必要になります。WebDAV の MOVE を使うなど、マニアックな方法も考えられますが、独自ヘッダは悪くない判断だと思います。
ただし2点、よくない点があります。
1点目は、X-HATENA-PUBLISH の値の仕様です。このヘッダが取りうる値は「1」なのですが、いったいこれは何を意味するのでしょうか。おそらく内部的に Perl の “1″ としてそのまま実装されているのでしょうが、これはたとえば “true” にするなど、もう少し可読性を考慮した方がよいでしょう。
また、”1″ 以外の値を値を入れたときの挙動も不明です。たとえば “111″, “01″, “hoge” などを入れたらどうなるのでしょうか。
2点目は、PUT メソッドの挙動です。
はてなダイアリーAtomPubの仕様では、下書きを公開するときは既存の下書きエントリに対して X-HATENA-PUBLISH を 1 にした PUT を送ります。このリクエストが成功すると、レスポンスとして 201 Created が返り、Location ヘッダに公開されたブログコレクションのエントリが入ります。これは少し違和感を感じるところです。
HTTP/1.1 における PUT の仕様は以下のとおりです。
PUT メソッドは、同封されたエンティティを供給される Request-URI の元に保存するように要求する。 Request-URI が既に存在するリソースを参照している場合は、同封されるエンティティはオリジンサーバにあるそれの修正版とみなされるべきである。 Request-URI が既存のリソースを指していない場合に、その URI がリクエストしているユーザエージェントによって新しいリソースとして定義する事ができる時は、オリジンサーバはその URI にリソースを作成できる。新しいリソースが作成された場合、オリジンサーバは 201 (Created) レスポンスをもってユーザエージェントに知らせなければならない。既存のリソースが更新された場合は、リクエストが成功し終了した事を示すために 200 (OK) か 204 (No Content) のいずれかのレスポンスコードを送るべきである。
つまり PUT のレスポンスは
- その URI が存在しない場合はリソースを新規作成して 201 を返す
- その URI が存在する場合は、リソースを変更して 200 か 204 を返す
となります。これは SHALL (べきである)なので、絶対に従わなければならないことはありませんが、いずれにせよ既存の URI に対しての PUT で 201 が返るというのは、仕様上のグレーゾーンでクライアントを混乱させる可能性があります。
私だったらどうするか
もし私がこの機能を設計するなら以下のように考えます。
下書き機能は AtomPub の app:draft 要素に非常に近い(というかほぼ同じ)機能であるため、いかにこれを活かすかを考えます。今回の問題はドラフト状態を公開すると URI が変更になってしまうところなのですが、これをいかに違和感なくクライアントに教えるか、が課題です。
PUT すると URI が変更される(リソースが移動するイメージ)というのがクライアントからの一番の違和感だと思うので、そこをどうにかするようにしたいですね。そのためには、ドラフトの URI を残しつつ、そのリソースに別の URI (エイリアス)を付与するのが良いように思います。
これには HTTP に標準で付いてくる Content-Location ヘッダを使うことで解決できそうです。この方式を採用したときのリクエスト・レスポンスは以下のようになります。
PUT /yohei/atom/draft/12345678 HTTP/1.1
Host: d.hatena.ne.jp
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:app="http://www.w3.org/2007/app">
<title>テスト</title>
<app:control>
<app:draft>yes</app:draft>
</app:control>
...
HTTP/1.1 200 OK Content-Location: http://d.hatena.ne.jp/yohei/20081224/abcdefg
/draft 以下の URI も依然として有効ですが(おそらく作者だけがアクセスできるリソースになるでしょう)、ダイアリーの方のリソースも別名で有効になります。
もちろん、サーバ側の実装の都合で、ドラフトエントリを公開するとドラフトコレクションからは必ず消さないといけないのかもしれません。上記のような理想形で設計したとおりに実装できるわけではないですが、一つのヒントとして提案してみます。
エントリ文書の対称性
2点目はエントリ文書の対称性についてです。
現在の仕様では、ブログコレクションで日記エントリを更新するとき、以下の
ようなリクエストを送ります。
PUT /yohei/atom/blog/20090203/xxx HTTP/1.1 Host: d.hatena.ne.jp Content-Type: application/atom+xml; type=entry; charset=utf-8 <entry xmlns="http://www.w3.org/2005/Atom"> <updated>2009-02-03T00:00:00Z</updated> <id>tag:d.hatena.ne.jp,2008:diary-yohei-20081226-p1 <author><name>yohei</name></author> <title>test</title> <content type="text">this is a test</content> </entry>
これに対して、以下のようなレスポンスが返ってきます。
HTTP/1.0 200 OK
Date: Wed, 04 Feb 2009 04:13:59 GMT
Server: Apache/2.2.3 (CentOS)
Content-Length: 694
Content-Type: application/atom+xml;type=entry; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
<updated>2008-12-26T00:00:00+09:00</updated>
<published>2008-12-26T00:00:00+09:00</published>
<app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-26T00:00:00+09:00</app:edited>
<id>tag:d.hatena.ne.jp,2008:diary-yohei-20081226-p1</id>
<link rel="edit" href="http://d.hatena.ne.jp/yohei/atom/blog/20081226/p1"/>
<link rel="alternate" type="text/html" href="http://d.hatena.ne.jp/yohei/20081226/p1"/>
<author>
<name>yohei</name>
</author>
<title>test</title>
<content type="html">
<div class="section">
<p>this is a test</p>
</div>
</content>
</entry>
このリソースを再び GET してみると以下のようになります。
GET http://d.hatena.ne.jp/yohei/atom/blog/20081226/p1 HTTP/1.1 Host: d.hatena.ne.jp
HTTP/1.0 200 OK
Date: Wed, 04 Feb 2009 05:13:52 GMT
Server: Apache/2.2.3 (CentOS)
Content-Length: 793
Content-Type: application/atom+xml;type=entry; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom">
<updated>2008-12-26T00:00:00+09:00</updated>
<published>2008-12-26T00:00:00+09:00</published>
<app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-26T00:00:00+09:00</app:edited>
<id>tag:d.hatena.ne.jp,2008:diary-yohei-20081226-p1
<link rel="edit" href="http://d.hatena.ne.jp/yohei/atom/blog/20081226/p1"/>
<link rel="alternate" type="text/html" href="http://d.hatena.ne.jp/yohei/20081226/p1"/>
<author>
<name>yohei</name>
</author>
<title>test</title>
<content type="html">
<div class="section">
<p>this is a test</p>
</div>
</content>
<hatena:syntax xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#">this is a test</hatena:syntax>
</entry>
ブログコレクションのエントリを編集するときは、以下のことに気をつける必要があります。
- 追加・更新を行うときは /entry/content/@type は “text” ではてな記法を入れる
- サーバから取得できる /entry/content は @type=”html” ではてな記法がHTML にレンダリングされたものが入り、/entry/hatena:syntax にはてな記法が入る
これはクライアント側ではてな記法を入れる要素について注意しながら、はてな独自のロジックを実装しなければならない点で、あまり良い設計ではないと言えるでしょう。
この問題を解決する方法はいくつか考えられますが、完璧なソリューションは存在しないと思います。私が即興で考えたソリューションのそれぞれのメリット・デメリットを挙げておきます。
/entry/content に常にはてな記法を入れる
- Pros
- 既存の AtomPub クライアントをほぼそのまま利用可能
- Cons
- 既存のブログクライアントのユーザははてな記法を使うことを理解しなければならない
- フィードをフィードリーダーで見たときにはてな記法が出てしまう
/entry/hatena:syntax を常に使うようにする
- Pros
- content を HTML にできる
- Cons
- AtomPub クライアントが hatena:syntax を理解しなければ更新ができない
まとめ
先日、会社の超先輩エンジニア(ソフトだけじゃなくてエレキよりの設計も含めて全般されてきた方です)の方と話をしたのですが、そのときに「設計はバランスだよ」という言葉をもらいました。
今回の件でもわかるとおり、設計というのはあちらを立てればこちらが立たず、ということが多く、全てを満足させることは不可能です。
私はよく「REST 的に理想的な設計にするにはどうしたらいいんでしょう」というような質問を受けるのですが、正直なところ理想ばかり追いかけても仕方がないだろう、と思います。REST はあくまでも設計のためのヒントなのであり、それ自体が目的になってしまったら本末転倒です。
高い理想をバックグラウンドに、現実的でバランスの取れた設計をするのが優れたエンジニアなのではないでしょうか。
