Headless CMSのContentfulをRailsで使ってみる~概要~

# 本記事の趣旨

ContentfulをRailsで使用することを検討中の方に、
**「この先はイバラの道だ、引き返すなら今だ。」**と伝えるための記事です。

ただ、使用感を後述していますが、
**いくつか制限に対して、問題ないという場合には使用する判断もアリ**かと思います。

実際の使い方に関しては別記事へのリンクを末尾に記載しますので、
そちらを参考にされると良いかと思います。

# Contentfulとは

Headless CMS(Content Management System)と言われていますが、いまいちピンと来ないかと思います。
簡単に言うと、「ブログのデータのみを扱うサービス」というくらいのイメージです。

具体的にすると、
データモデル定義して・・・
![スクリーンショット 2021-05-13 12.49.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430603/d38197e9-b3c6-ce8e-6c35-5d65ada0c50b.png)

実際のデータを作って・・・
![スクリーンショット 2021-05-13 13.13.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430603/d9691694-8348-7e32-745b-4b3bc1ac5dd0.png)

登録したデータをAPIで受け取ることができるというものです。

```response_sample.json
{
  "metadata": {"tags": []},
  "sys": {
    "space": {
      "sys": {"type": "Link", "linkType": "Space", "id": "xxxxxxxxxxx"}
    },
    "id": "XXXXXXXXXXXXXXXXXXX",
    "type": "Entry",
    "createdAt": "2021-02-19T04:33:30.532Z",
    "updatedAt": "2021-02-19T04:33:30.532Z",
    "environment": {
      "sys": {"id": "master", "type": "Link", "linkType": "Environment"}
    },
    "revision": 1,
    "contentType": {
      "sys": {"type": "Link", "linkType": "ContentType", "id": "blog"}
    },
    "locale": "ja-JP"
  },
  "fields": {
    "title": "きょうのにっき",
    "body": {
      "data": {},
      "content": [
        {
          "data": {},
          "content": [
            {
              "data": {},
              "marks": [],
              "value": "なにもないすばらしい1日でした。",
              "nodeType": "text"}
          ],
          "nodeType": "paragraph"
        }
      ],
      "nodeType": "document"
    },
    "thumbnail": {
      "sys": {
        "type": "Link", "linkType": "Asset", "id": "XXXXXXXXXXXXXXXXX"}
      }
   }
}
```

# 用途

用途を考えると、
ブログサイトのフロントエンドを構築して、APIでContentfulから記事の情報を取得する
といったことに向いているかと思います。

ただこういう使い方もできると考えられます。
**バックエンドのデータモデルで管理画面が必要なモデルは、
Contentfulで定義すれば管理画面を開発する必要がなくて工数が浮くのではないか？** 

そしてRubyで扱えるGemも存在しているのです。

contentful
https://github.com/contentful/contentful.rb

contentful-management
https://github.com/contentful/contentful-management.rb

そういった発想からRailsでContentfulを使用することになり、
開発を通して得た知見をここにまとめていこうと思います。
（先に言っておくと、個人的にはお勧めしません。）

# ContentfulのAPIについて

公式のドキュメント
https://www.contentful.com/developers/docs/references/

いくつかAPIが存在しますが、先述したGemで使用されている主要なAPIの３つを紹介します。

* **Contentful Delivery API (CDA)**
基本となる読み取り専用のAPI。
Contentfulにはデータに対して、Publish/Unpublish/Archiveといった公開・非公開・アーカイブなどの状態を持つことができ、
このAPIでは公開されている情報のみを取得できます。

* **Contentful Preview API (CPA)**
非公開のデータも読み取ることができるAPI。
主にプレビュー・テスト環境での使用などが想定されている、と思います。

* **Contentful Management API (CMA)**
Contentfulの環境自体の管理や、データの読み込み・書き込みも可能なAPI。

# ContentfulのGemについて

先述のGem「contentful」は"CDA"と"CPA"、「contentful management」は"CMA"を扱うGemとなっているようです。

Githubに記載の内容から、
「contentful」は**クライアントにマッピングしておくことで、APIで受け取ったデータ（JSON）を、Rails上のモデルに変換してくれる機能**があるようです。
Rails上で扱うにはモデルへの変換ができるとかなり便利なので、読み取りには「contentful」を使用し、
データの書き込みは唯一「contentful management」しかできないので、こちらを使用することにしました。

# 使用感

実際に使用してみて、キビシイなと感じた点を主に記載していきます。

### Contentfulで扱うモデルの定義の難易度が高い

Githubに書いていませんが、ContentfulのデータモデルをRailsのモデルに変換する機能を使う場合、
initializerのパラメータをContentful::Entryの形式に合わせないといけないとか、制限が出てきます。

具体的になりますが、こんなかんじになります。

```blog.rb

# Contentful::Entryを継承する
class Blog < Contentful::Entry
  # アクセサを定義する
  attr_accessor :id, :title, :body, :thumbnail

  # initializerをこの形にする
  def initialize(item = {}, _configuration = {}, localized = false, includes = [], entries = {}, depth = 0, errors = [])
    # itemsにデータが入ってくるので、データ項目のマッピングは自分で書く
    self.id = item.dig('sys', 'id')
    self.title = item.dig('fields', 'title')
    ...
    super
  end
  
end

```
この辺はお作法が分かれば問題ないかと思いますが、
要するにGithubの説明だけ見れば実装できる代物ではないということです。

リレーション項目や画像項目、リッチテキスト項目は実際にAPIからデータを受けとり、
そのJSONを見てRailsのモデルに落とし込むような処理を実装する必要があるので、難易度が高いと感じました。
結局そこに工数が嵩むなら、管理画面を作ったほうが早いと思ったら、使用しない判断もありだと思います。

### OR条件検索ができない

複数回クエリを発行して、その結果を合わせれば実質OR検索になるのですが、
ページングなどの制御を入れることができなくなるので、クリティカルな制限になっています。
複雑なクエリを発行する必要がある場合は、使用を諦めた方が良さそうです。

### クエリの指定が難しい

下記がContentfulからデータを取得するためのクエリです。
ContentfulのJSON形式に合う形でフィールドを指定しなければいけないので、
一つの条件を書くのが長くなります。

```
client.entries(
  content_type: 'blog',
  'fields.title[eq]' => 'きょうのにっき',
  ...
)
```
別の記事で、クエリをRailsライクに記述できるようにした共通処理を記載するので、
多少の癖は残りますが乗り越えられる壁かと思います。

### トランザクション機能がない

APIなので当然と言えば当然ですが、
複数のモデルにわたって更新を行う場合など、整合性を担保する必要があるには、自前でトランザクション的な処理を実装する必要があります。

### CMAでデータを書き込んでからCDAで読み取る場合に、反映までタイムラグがある

contentful-managementのClientを使用してデータを更新すると、
その結果を受け取ることはできますが、JSON形式です。

モデルへ変換するにはcontentfulのGemを通す必要があります。
なので、CDAで再度読み取るクエリを発行すると、帰ってくるデータが更新前の場合があります。

幸いバージョン番号があるので、それがインクリメントされるまで再ロードを繰り返せば良いのですが、
実際に反映されたデータを受け取るまで時間がかかり、レスポンスが遅くなるなど、パフォーマンスに影響が出てきます。

これを回避したい場合はCMAで受け取ったJSONをモデルにマッピングして変換する処理を自前で実装する必要があります。（じゃあもうCDAいらないじゃないか、と思いますが、もう戻る道がなく私の場合はパフォーマンスが犠牲になりました。）

### 以上をふまえて・・・

下記のような用途であればRailsで扱っても良いかも。

* 複雑なクエリは発行しない
* 頻繁に書き込みをしない
* 複数モデルの同時更新しない

# 詳細

難易度が高いとか書いた後でなんですが、それでもRailsでContentful使おうという開発者向けに、
Contentful Gemを自分なりに使いやすくする共通処理を作ったりしたので、下記の記事に記載します。

https://qiita.com/polar_bear_tech/items/e3bd69d71e9671fb0070