Adobe Experience ManagerをヘッドレスCMSとして利用するための３ステップ

##はじめに
デジタルマーケティング領域において、この数年、「ヘッドレスCMS」が、企業が取り組むべきテーマとして注目されています。
実際に、「ヘッドレスCMSってなに？」「Adobe Experience Manager (以下、AEM)でヘッドレス配信すると何がいいの？」「 AEMでは、どうやって対応したらいいの？」といったお問わせをいただくことが、かなり増えています。

このエントリでは、<strong>AEMを使ったサイト構築の経験がある方</strong>を対象に、AEMをヘッドレスCMSとして使う上で、[利用できる機能](#AEMで利用できるヘッドレス配信機能)ならびに、[利用に際して実施すべき具体的な手順](#GraphQLを使用したヘッドレス配信のための３ステップ)を紹介していきます。

##AEMで利用できるヘッドレスCMS機能

###ヘッドレスCMSとは
ヘッドレスCMSとは、コンテンツを取得するためのAPIを提供することで、Webページやアプリなどのフロントエンドの開発を、特定の技術やツールに縛られずに、実施可能にするCMS機能を指します。従来のCMSのように、コンテンツの編集・管理からWebページの生成・配信までの一連プロセスがCMS機能に依存してしまうことがないため、フロントエンドにおけるデザインの柔軟性や開発における迅速性を確保できるのが特長です。

なお、ヘッドレスCMSに関する詳細については、以下のページの解説が参考になりますので、合わせてご確認ください。

-  [Headless CMS - @reflet](https://qiita.com/reflet/items/55665a79940a96252c68)
-  [ヘッドレスCMSとは何ですか？| アドビ用語集](https://www.adobe.com/jp/experience-cloud/glossary/headless-cms.html)
-  [ヘッドレスCMS | Adobe Experience Manager Sites 機能紹介](https://www.adobe.com/jp/marketing/experience-manager-sites/headless-cms.html)

###ヘッドレスCMSとしてのAEM
この数年で注目されている「ヘッドレスCMS」ですが、実は、AEMは、開発の初期段階から、コンテンツをAEM以外でのサイトやアプリケーションで利用可能な形式で管理、配信できるように設計されています。（話が長くなるので、この部分の説明は割愛します。）

そのため、すでにAEMを利用して管理しているサイトは、大規模な改修や追加開発をすることなく、ヘッドレス配信に対応することができるようになっています。

しかしながら、昨今のフロントエンド技術の多様化、革新の速さ、トレンドに追随するには、最適とは言えない部分もありました。この課題を解消するために、開発当初からの機能をベースとして、フロントエンド開発に対する拡張性、柔軟性を高めるための対応を進めており、現在、以下２つの仕組みが、ヘッドレスCMSに対応するために利用可能です。

- <strong>Content Services</strong>
    - AEM上で管理されているコンテンツをREST APIを使って取得可能にするための仕組み
    - AEMが提供するフレームワークを使ってAPIエンドポイントを実装する
    - AEMでのヘッドレス対応において、現状、一般的に使われている方式
- <strong>GraphQL API</strong>
    - AEM上で管理されているコンテンツをGraphQLを使って取得可能にするための仕組み
    - AEM管理画面を使って、Content Fragment Model(詳細は後述)を定義することで、APIエンドポイントが自動設定される
    - 2021年1月より利用可能

（上記2つの方式に関する、詳細な違い、それぞれの方式に適したユースケースについては、本エントリの[最後](#おまけ)にまとめています。）

今回は、上記の方式のうち、GraphQL APIを使って、AEMでヘッドレス配信を行うための手順を説明していきます。Content Servicesについては、 「[AEMオンラインドキュメント - Content Services使用の手引き](https://experienceleague.adobe.com/docs/experience-manager-learn/getting-started-with-aem-headless/content-services/overview.html?lang=ja#content-services)」をご参照ください。

##GraphQLを使用したヘッドレス配信のための３ステップ

GraphQLを使用して、AEMでヘッドレス配信を行うには、以下の３ステップを実施していきます。

Step 1. [AEM管理画面を使って、Content Fragment Modelを定義する](#1. AEM管理画面を使って、Content Fragment Modelを定義する)
Step 2. [AEM管理画面を使って、Content Fragmentを作成する](#2. AEM管理画面を使って、Content Fragmentを作成する)
Step 3. [フロントエンドアプリケーションを開発する](#3. フロントエンドアプリケーションを開発する)

上記の各ステップの詳細について、AEM専門用語の説明も含め、AEMの[チュートリアル (Getting Started with AEM Headless - GraphQL) ](https://experienceleague.adobe.com/docs/experience-manager-learn/getting-started-with-aem-headless/graphql/overview.html?lang=en#graphql)の内容を沿って説明していきます。

###Step 0. チュートリアルサイトの構成
手順の説明に入る前に、題材となる、チュートリアルサイトの構成を説明しておきます。このチュートリアルでは、旅行先でのアクティビティに関する記事を配信するサイトを、GraphQLとReactを使ってAEMから分離とはアプリを実装するための手順が説明されています。

作成するサイトは、アクティビティ紹介記事(Adventure)と記事の投稿者(Contributor)で構成され、下図はその画面構成とコンテンツ構造を表しています。

| 画面構成 | コンテンツ構造 |
|:-----------:|:------------:|
|![WKND画面構成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290145/e3eff9a1-cac4-9ad1-9ffc-0d2b25568510.png) GrapchQLとReactを使って実装| ![WKNDコンテンツ構造](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290145/60a9c1c8-23ab-7699-dfe8-6f86240f1802.png)AEM内部のコンテンツ構造|

以降の手順を実施し、コンテンツ構造へのContributorの定義追加、アプリ画面へのContributorのコンテンツ掲載を行っていきます。

###Step 1. AEM管理画面を使って、Content Fragment Modelを定義する

まず初めに、コンテンツを定型化し、再利用可能な形式で、AEM内に保存するための構造の定義情報（Content Fragment Model）を定義します。
データベースにおけるテーブルにあたる要素で、サイトやアプリケーションを通じて配信される記事や製品・サービス情報などのコンテンツの種類に応じて、異なる複数のモデルを定義可能です。

Content Fragment Modelは、下図のような画面(Content Fragment Model Editor)を使って、コーディングなど開発作業をすることなく、定義可能です。
![Content Fragment Model Editor](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290145/c30a513f-f94b-b700-e95b-d403da265a06.png "Content Fragment Model Editor")
上図では、モデル「Contributor」を定義しています。

右側のパネルで、各フィールド（入力項目）のデータタイプ（文字列、数字、日時など）および、入力ルール(必須 or 任意、文字数制限、初期値など)を指定し、左側のパネルにドラッグします。

定義したモデルを保存すると、後述の[Content Fragment 作成画面](#2. AEM管理画面を使って、Content Fragmentを作成する)と、GraphQLのオブジェクトが自動生成されます。

モデルの定義が完了した時点で、以下のContributorのリストを取得するためのGraphQLクエリを実行すると、

```graphql 
{
  contributorList {
    items {
      _path
    }
  }
}
```
以下のように、空のJSONオブジェクトが取得できます。

```json
{
  "data": {
    "contributorList": {
      "items": []
    }
  }
}
```
ちなみに、モデルが未定義の状態で、同じクエリを実行すると、以下のエラーが返ってきます。

```json
{
  "errors": [
    {
      "message": "Validation error of type FieldUndefined: Field 'contributorList' in type 'QueryType' is undefined @ 'contributorList'",
      "locations": [
        {
          "line": 33,
          "column": 3
        }
      ],
      "extensions": {
        "classification": "ValidationError"
      }
    }
  ]
}
```


###Step 2. AEM管理画面を使って、Content Fragmentを作成する

次に、Content Fragment Modelで定義された内容に基づいて、コンテンツの実体となる、Content Fragmentを作成します。データベースにおけるレコード/データにあたる要素となります。

Content Fragmentの作成には、下図のような、Content Fragment Modelの定義に沿って自動生成されたフォームを使用します。
(モデルで必須入力項目として定義されている、「投稿者名」が未入力のため、エラーが表示されています。)

![Content Fragment 作成画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290145/f6a4df53-ea99-97a6-0c47-612b4a6d85c7.png "Content Fragment 作成画面")
各入力項目は、テキスト形式、リッチテキスト形式、選択形式などに加え、登録済みの他のコンテンツの参照など、管理・作成するコンテンツに適した形式で入力が行えます。
![Content Fragment 入力画面(入力後)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290145/a455b9aa-0607-c442-c714-9b099c59163c.png "Content Fragment 作成画面(入力後)")

上図のとおりコンテンツを入力し、Content Fragmentを作成、保存した後、先程と同じリスト取得用のクエリを実行すると、

```graphql
{
  contributorList {
    items {
      _path
    }
  }
}
```
作成済みのContent Fragmentのパスを含むContributorのリストが取得できます。

```json 
{
  "data": {
    "contributorList": {
      "items": [
        {
          "_path": "/content/dam/wknd/en/contributors/jacob-wester"
        },
        {
          "_path": "/content/dam/wknd/en/contributors/stacey-roswells"
        },
        {
          "_path": "/content/dam/wknd/en/contributors/yosemite-jiro"
        }
    }
  }
}
```
また、__path以外の各フィールド名をクエリで指定すれば、

```graphql
{
  contributorList {
    items {
      _path
      fullName
      biographyText{
        markdown
      }
      pictureReference{
        ... on ImageRef{
          _path
          width
          height
        }
      }
      
    }
  }
}
```
以下の結果が取得できます。

```json
{
  "data": {
    "contributorList": {
      "items": [
        {
          "_path": "/content/dam/wknd/en/contributors/yosemite-jiro",
          "fullName": "よせみてじろう",
          "biographyText": {
            "markdown": "カリフォルニア州にある世界遺産、ヨセミテ国立公園でガイドの仕事をしながら、ライターとして活動しています。定番の人気スポットの紹介はもちろん、季節ごとの楽しみ方を紹介させていただきます。\n\n "
          },
          "pictureReference": {
            "_path": "/content/dam/wknd/en/contributors/yosemite-bear.png",
            "width": 1312,
            "height": 1400
          }
          ・ ・ ・
        }
      ]
    }
  }
}
```

以下のクエリを実行することで、リストの絞り込みも可能です。

```graphql
{
  contributorList (filter: {
    occupation: {
      _expressions: {
        value: "Writer"
      }
    }
  }){
    items{
      _path
    }
  }
}
```
また、リスト取得だけでなく、以下のクエリによって、個別のContent Fragmentを取得できます。

```
{
  contributorByPath (_path: "/content/dam/wknd/en/contributors/yosemite-jiro") {
    item {
      _path
      fullName
    }
  }
}
```

ここまでが、AEM上で必要となる設定作業となり、フロントエンドからコンテンツを取得する準備が整いました。上記のクエリで使用した、contributorList、contributorByPathは、いずれも、ステップ１が完了時点で自動生成されているため、AEM上での開発作業は行っていません。


###Step 3. フロントエンドアプリケーションを開発する

最後に、ここまでのステップで作成したコンテンツを配信する、AEM外部で動作する、フロントエンドのアプリケーションを、Reactを使って、開発します。
AEM上で管理されたコンテンツを取得するために、アプリケーション側で実装するべき処理は、以下の2点のみで、AEMに特化した対応処理を作成する必要はありません。

- AEMに対するGraphQLクエリの実行処理
- クエリ結果を成型し、表示する処理

以下の「クエリの生成」と「クエリ実行と結果出力」のコードを含む、Reactアプリを実装します。

クエリの生成：
リクエスト対象のAdventureと、それに紐づくContoributorの情報を取得するためのクエリを作成します。

```javascript
function adventureDetailQuery(_path) {
    return `{
        adventureByPath (_path: "${_path}") {
        item {
            _path
            adventureTitle
            adventureActivity
            adventureType
            adventurePrice
            adventureTripLength
            adventureGroupSize
            adventureDifficulty
            adventurePrice
            adventurePrimaryImage {
                ... on ImageRef {
                _path
                mimeType
                width
                height
                }
            }
            adventureDescription {
                html
            }
            adventureItinerary {
                html
            }
            adventureContributor {
                fullName
                occupation
                pictureReference {
                    ...on ImageRef {
                        _path
                    }
                }
            }
          }
       }
     }
    `;
}
```
クエリの実行と結果出力：
生成したクエリを実行後、取得した結果を出力します。

```react
function AdventureDetail(props) {
    const contentFragmentPath = props.location.pathname.substring(props.match.url.length);
    const { data, errorMessage } = useGraphQL(adventureDetailQuery(contentFragmentPath));
    ...
    let adventureData = data.adventureByPath.item;
    return (
        ...
         <h2>Itinerary</h2>
        <hr />
        <div className="adventure-detail-itinerary"
             dangerouslySetInnerHTML={{__html: adventureData.adventureItinerary.html}}></div>
        {/* Contributor component is instaniated and 
            is passed the adventureContributor object from the GraphQL Query results */}
        <Contributer {...adventureData.adventureContributor} />
        ...
    )
}
```

##まとめ
ここまでの手順の通り、GraphQLを使って実装されたアプリケーションに対して、コンテンツを必要となる開発作業は、フロントエンドアプリケーションのみで、AEM側での追加開発は不要で、設定のみで対応できます。そのため、今後増え続けるであろうコンテンツ配信チャネル、それらに対応するアプリケーション開発に迅速かつ柔軟に対応できると考えられます。

なお、実際のプロジェクトにおいては、コンテンツ配信に必要な業務フローの整理と関連する機能の開発などが必要となるケースも考えられるので、上記手順の設定・開発作業に加えて、それらを踏まえてプランを検討する必要があります。

## おまけ

[「AEMで利用できるヘッドレス配信機能」](#AEMで利用できるヘッドレス配信機能)で紹介したGraphQLとContent Servicesの２つの方式について、その違いと利用が適しているケースについて、現時点で分かっている内容から、整理してみました。

|  | AEM GraphQL API | AEM Content Services |
|:-----------|:------------:|:------------:|
| **リクエスト形式**<br>(アプリ -> AEM)</small>       | GraphQL        | REST         |
| **レスポンス形式**<br>(アプリ <- AEM)     | GraphQL JSON      | JSON<br/>(任意フォーマット)       |
| **AEM上でのAPIエンドポイントの作成要否**       | 不要<br>設定に従ってAEMが自動生成        | 要開発<br>テンプレート、コンポーネント、データモデル(Apalce Sling Models) を実装         |
| **AEM管理の既存サイトへの適用のしやすさ**         | 低<br>適用するコンテンツをContent Fragmentとして再定義する必要あり          | 高<br>既存のデータモデルの改修のみで対応可能           |
| **適用しやすいフロントエンドの構成**       | AEMとシンクすべきコンテンツの範囲が広い       | AEMとシンクすべきコンテンツの範囲が限定的       |
| **将来的なサイト展開への柔軟性**    | 高     | 低      |


##参考資料
 - [AEMオンラインドキュメント - Content Services使用の手引き](https://experienceleague.adobe.com/docs/experience-manager-learn/getting-started-with-aem-headless/content-services/overview.html?lang=ja#content-services)
 - [Getting Started with AEM Headless - GraphQL](https://experienceleague.adobe.com/docs/experience-manager-learn/getting-started-with-aem-headless/graphql/overview.html?lang=en#graphql-overview)
 - [Adobe Developers Live | Headless: GraphQL with Content Fragments](https://www.youtube.com/watch?v=zepOmSsZUjE&list=PLcVEYUqU7VRdVgUqehtpIzR5oO6hUN6ie&index=4)