知見・実践
CMSのコンテンツ設計とテンプレート設計を分離する
運用中の仕様追加でテンプレートを複雑化させないための設計
CMSのコンテンツ値をそのままテンプレートの条件分岐に使うと、運用中の仕様追加によって条件が複雑化することがあります。コンテンツの意味と表示パターンを分離し、CMSとテンプレートの構造をシンプルに保つ設計を、Movable TypeとMyAdminPackを例に考えます。
CMSのコンテンツ設計とテンプレート設計を分離する
CMSで複数のページパターンを管理するとき、コンテンツに登録された値を使って、テンプレートの出力内容を切り替えることがあります。 例えば製品情報を管理するCMSで、
- パッケージ製品
- OEM製品
という「製品タイプ」を持っている場合、その値をテンプレートで判定し、それぞれ異なるモジュールを出力できます。 公開時点では、これで問題ありません。 しかし、Webサイトを運用していると、新しい要件が追加されます。 「通常とは異なるカスタムページを作りたい」 「生産終了製品では表示する情報を制限したい」 「グループ企業の製品も掲載したい」 こうした変更をコンテンツの値から直接判定し続けると、テンプレートには異なる意味を持つ条件が次々と追加されていきます。 問題は条件分岐があることではありません。 コンテンツが持つ意味と、テンプレートの表示パターンを同じ値で表現しようとすることです。
この記事の要点
- コンテンツの分類と、ページの表示パターンは必ずしも一致しません。
- コンテンツの値を直接テンプレートの分岐条件にすると、仕様追加によって異なる意味の条件が混在します。
- 表示パターンを独立して定義すると、コンテンツ本来の構造を保ったままテンプレートを設計できます。
- テンプレート側でも、どの表示パターンが存在するのかを把握しやすくなります。
- MyAdminPackでは「レイアウト」を、管理画面の入力構成とテンプレートの表示パターンをつなぐ情報として利用できます。
製品情報を例に考える
例えば、CMSで製品情報を管理するとします。 コンテンツタイプには、次のような情報があります。
基本情報
製品名、製品画像、価格、カテゴリなど。
特長
ブロックエディタや動画など。
ピックアップ機能紹介
製品の特徴的な機能を紹介する情報。
専門家のレビュー
レビュー本文や、専門家として登録された別のコンテンツデータへの参照。
詳細情報
サイズ、重量、製造年など。
技術資料ダウンロード
PDFなどのファイル。 製品によって、ページに表示する情報は異なります。 例えば「パッケージ製品」では、
基本情報
特長
ピックアップ機能紹介
専門家のレビュー
詳細情報
を表示し、「OEM製品」では、
基本情報
特長
詳細情報
技術資料ダウンロード
を表示するとします。
コンテンツの分類を使ってテンプレートを切り替える
Movable Typeであれば、「製品タイプ」というカテゴリセットを作り、その値から出力するモジュールを切り替えることができます。
<mt:ContentField content_field="製品タイプ">
<mt:If tag="CategoryLabel" eq="パッケージ">
<mt:Include module="基本情報">
<mt:Include module="特長">
<mt:Include module="ピックアップ機能紹介">
<mt:Include module="専門家のレビュー">
<mt:Include module="詳細情報">
<mt:Elseif tag="CategoryLabel" eq="OEM">
<mt:Include module="基本情報">
<mt:Include module="特長">
<mt:Include module="詳細情報">
<mt:Include module="技術資料ダウンロード">
</mt:If>
</mt:ContentField>
この時点では自然な設計に見えます。 「製品タイプ」というコンテンツの分類と、表示したいページの種類が一致しているためです。 しかし、ここには一つ前提があります。 今後追加される表示パターンも、すべて「製品タイプ」で表現できることです。 運用が始まると、この前提が崩れることがあります。
「カスタムページ」が追加されたらどうするか
運用開始後、次の要望が追加されたとします。
登録したHTMLをそのまま表示する「カスタム」というページパターンを追加したい。 「カスタム」は製品の種類ではありません。 そのため、
製品タイプ
パッケージ
OEM
カスタム
とするのは、コンテンツ設計として不自然です。 製品を分類するための「製品タイプ」に、テンプレートの表示方法が混ざってしまいます。 そこで「カスタムページ用コード」という別のフィールドを作り、入力されている場合にはカスタムページとして出力することにします。
<mt:ContentField content_field="カスタムページ用コード">
<mt:Include module="カスタムページ">
<mt:Else>
<mt:ContentField content_field="製品タイプ">
<mt:If tag="CategoryLabel" eq="パッケージ">
略
<mt:Elseif tag="CategoryLabel" eq="OEM">
略
</mt:If>
</mt:ContentField>
</mt:ContentField>
要件には対応できました。 しかし、テンプレートの構造は一段複雑になっています。
運用中には異なる意味の条件が追加される
さらに運用を続けると、次のような要望が出てくるかもしれません。
- 生産終了製品では表示する情報を制限したい
- グループ企業の製品も管理したい
- グループ企業製品でも、パッケージとOEMで表示内容を変えたい
それぞれ、コンテンツとしては正当な情報です。
「生産終了」は製品の状態です。 「グループ企業製品」は製品を提供する企業の区分です。 「パッケージ」「OEM」は製品の分類です。 そして「カスタム」はページの表示方法です。 本来は異なる意味を持っています。
ところが、それぞれの値からページの表示方法を直接決めようとすると、テンプレート側ではすべてが条件分岐になります。
<mt:ContentField content_field="カスタムページ用コード">
略
<mt:Else>
<mt:ContentField content_field="生産終了のチェックボックス">
略
<mt:Else>
<mt:ContentField content_field="グループ企業製品のチェックボックス">
略
<mt:ContentField content_field="製品タイプ">
<mt:If tag="CategoryLabel" eq="パッケージ">
略
<mt:Elseif tag="CategoryLabel" eq="OEM">
略
</mt:If>
</mt:ContentField>
<mt:Else>
<mt:ContentField content_field="製品タイプ">
<mt:If tag="CategoryLabel" eq="パッケージ">
略
<mt:Elseif tag="CategoryLabel" eq="OEM">
略
</mt:If>
</mt:ContentField>
</mt:ContentField>
</mt:ContentField>
</mt:ContentField>
一つひとつの条件を見ると、間違った実装ではありません。 問題は、仕様が増えるたびに異なる意味を持つコンテンツの値を組み合わせて、表示パターンを導き出していることです。
テンプレートが「表示パターンの計算式」になる
この構造では、テンプレートを読んだだけでは、サイトに何種類のページパターンが存在するのか簡単には分かりません。 例えば、
カスタムページ用コードがある
場合と、
カスタムページ用コードがない
かつ
生産終了ではない
かつ
グループ企業製品である
かつ
製品タイプがOEM
場合で、それぞれ異なる表示になります。 つまり、表示パターンそのものが定義されているのではなく、複数のコンテンツ値からテンプレート内で表示パターンを計算している状態です。 仕様が増えるほど、条件の組み合わせも増えます。 さらに、その条件を複数のモジュールで利用するようになると、テンプレート全体の構造を把握するために、関連するフィールドや変数、モジュールを追う必要が出てきます。 長期間運用するCMSでは、最初に実装した開発者が、その後も担当し続けるとは限りません。 別の担当者や制作会社が引き継いだとき、まず必要になるのが、 「このサイトには、どのような表示パターンが存在するのか」 を調べる作業になります。
コンテンツの意味と表示パターンを分ける
そこで、コンテンツが持つ情報とは別に、「このコンテンツをどの表示パターンで扱うか」を定義します。 例えばコンテンツ側では、
製品タイプ:OEM
状態:生産終了
企業区分:グループ企業
という情報をそのまま保持します。 これらは製品そのものを説明する情報です。 一方、表示パターンとして、
パッケージの製品
OEMの製品
カスタム
生産終了製品
パッケージのグループ企業製品
OEMのグループ企業製品
を別に定義します。 構造としては、
コンテンツの意味
↓
表示・更新パターン
↓
テンプレート
となります。 テンプレートは、「製品タイプ」「状態」「企業区分」などを組み合わせて表示方法を判断する必要がありません。 すでに決められた表示パターンを見て、必要なモジュールを出力します。
MyAdminPackの「レイアウト」を表示パターンとして使う
MyAdminPackには、コンテンツの更新パターンごとに管理画面の入力フィールドを整理する「レイアウト」機能があります。 例えば、
パッケージの製品
OEMの製品
カスタム
生産終了製品
パッケージのグループ企業製品
OEMのグループ企業製品
というレイアウトを作成し、それぞれの更新に必要なフィールドだけを表示できます。 さらに、選択されているレイアウトをMovable Typeのテンプレートから取得できます。
<mt:ContentMyFieldLayoutID>
コンテンツデータで指定されているレイアウトのIDを取得します。
<mt:ContentMyFieldLayoutTitle>
コンテンツデータで指定されているレイアウトのタイトルを取得します。 このレイアウトをテンプレート側の表示パターンとして利用します。
テンプレート側は表示パターンだけを見る
レイアウトを判定値にすると、先ほどのテンプレートは次のようにできます。
<mt:If tag="ContentMyFieldLayoutTitle" eq="パッケージの製品">
<mt:Include module="基本情報">
<mt:Include module="特長">
<mt:Include module="ピックアップ機能紹介">
<mt:Include module="専門家のレビュー">
<mt:Include module="詳細情報">
<mt:Elseif tag="ContentMyFieldLayoutTitle" eq="OEMの製品">
<mt:Include module="基本情報">
<mt:Include module="特長">
<mt:Include module="詳細情報">
<mt:Include module="技術資料ダウンロード">
</mt:If>
さらに運用中の仕様を追加しても、
<mt:If tag="ContentMyFieldLayoutTitle" eq="パッケージの製品">
略
<mt:Elseif tag="ContentMyFieldLayoutTitle" eq="OEMの製品">
略
<mt:Elseif tag="ContentMyFieldLayoutTitle" eq="カスタム">
略
<mt:Elseif tag="ContentMyFieldLayoutTitle" eq="生産終了製品">
略
<mt:Elseif tag="ContentMyFieldLayoutTitle" like="グループ企業製品">
略
<mt:If tag="ContentMyFieldLayoutTitle" eq="パッケージのグループ企業製品">
略
<mt:Elseif tag="ContentMyFieldLayoutTitle" eq="OEMのグループ企業製品">
略
</mt:If>
</mt:If>
となります。 条件分岐そのものがなくなるわけではありません。 大きな違いは、条件の意味が「どの表示パターンか」に統一されていることです。 テンプレートを見れば、
パッケージの製品
OEMの製品
カスタム
生産終了製品
グループ企業製品
という表示パターンが存在することを把握できます。 製品タイプやチェックボックス、入力値などを追いながら、「この組み合わせは何を意味しているのか」を読み解く必要がありません。
完全に分離するのではなく、間に一つの概念を置く
ここでいう「コンテンツ設計とテンプレート設計を分離する」は、両者を完全に無関係にするという意味ではありません。 間に「表示・更新パターン」という一つの概念を置きます。
コンテンツ
製品タイプ / 状態 / 企業区分
↓
表示・更新パターン
MyAdminPackのレイアウト
↓
テンプレート
表示するモジュール
コンテンツ側は、製品について何を管理するかを設計します。 テンプレート側は、どの表示パターンで何を出力するかを設計します。 MyAdminPackのレイアウトは、その二つをつなぐ役割を持ちます。 これによって、「テンプレートを動かすためにコンテンツの分類を歪める」「コンテンツの複数の値をテンプレート側で組み合わせ続ける」といった状態を避けやすくなります。
管理画面側も同じパターンで整理できる
表示パターンを明示する利点は、テンプレートだけではありません。 例えば「OEMの製品」であれば、
基本情報
特長
詳細情報
技術資料ダウンロード
だけを入力画面に表示できます。 「カスタム」であれば、カスタムページの作成に必要なフィールドだけを表示できます。 つまり、
更新担当者
「どの情報を入力するか」
↓ 同じレイアウト
テンプレート
「どの情報を表示するか」
を同じ表示・更新パターンから設計できます。 フィールド自体を減らす必要はありません。 一つのコンテンツタイプに多くのフィールドが存在していても、更新担当者には、その更新パターンで必要なものだけを見せられます。 テンプレート側でも同じレイアウトを判定できるため、管理画面と公開ページで「パターン」の意味がずれにくくなります。
仕様追加をなくすのではなく、追加されても理解できる構造にする
Webサイトを長期間運用すれば、公開時には存在しなかった要件が追加されます。 生産終了製品が必要になるかもしれません。 別会社の製品を同じCMSで管理することになるかもしれません。 新しいページ表現が必要になることもあります。 こうした変更そのものを予測して、最初からすべての条件を設計することには限界があります。 重要なのは、仕様追加をなくすことではありません。 仕様が追加されたときに、既存の構造を読み解き直さなくても、どこへ新しいパターンを追加すればよいか分かる状態にしておくことです。 表示パターンが明示されていれば、新しく開発を担当する人も、テンプレートからサイトの構造を把握しやすくなります。
まとめ
CMSのコンテンツ値をテンプレートの条件分岐へ直接利用すること自体が問題なのではありません。 コンテンツの分類と表示パターンが一致している小さな構成であれば、それが最もシンプルな場合もあります。 問題になるのは、運用中の仕様追加によって、
製品タイプ
状態
企業区分
特定フィールドへの入力有無
など、本来異なる意味を持つ値を組み合わせて表示パターンを決めるようになった場合です。 その状態では、テンプレートそのものが表示パターンを導き出すための複雑な計算式になっていきます。 コンテンツが持つ意味と、ページの表示・更新パターンを分け、その間に明示的なパターンを定義する。 MyAdminPackでは、その役割をレイアウトとして持たせることができます。 これにより、管理画面では更新パターンごとに必要なフィールドだけを表示し、テンプレートでは同じパターンを基準として出力内容を決められます。 コンテンツの意味を保ったまま、表示パターンを明示する。 CMSを長期間運用するうえでは、テンプレートを短くすること以上に、 後から見た人が「このサイトにはどのようなパターンがあるのか」を理解できる構造にしておくことが重要です。
- CMS
- Movable Type
- MyAdminPack
- CMS設計
