Integrate AEM and IDS via Node.js

<!-- Integrate AEM and IDS via Node.js -->

![Slide1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/abefe7a1-a8d4-cf4a-7e81-87451e3db35c.jpeg)

<!-- AEM IDS Mediator Proof Of Concept -->

[前回 Dynamic Deck を試用](https://qiita.com/o2346/items/4fbb20eac80c22623c66) しましたが、これだけでは不足に感じるようになってきました。単純に画像を抽出してはめ込んでいくだけならいいのですが、より複雑な要件に対応する POC を開発する必要が出てきた次第です。具体的にやりたいのは:

* Content Fragment(以下CF) 単位でアイテムを取得・配置していきたい
* CF に関連付けられた JCR 上の関連画像を、それぞれの CF の文脈内に配置したい
    * 画像のパスは CF がプロパティとして持っているのでそこから導出したい

つまり AEM Assets 上で、 CF と画像に形成されている親子関係をそのまま DTP でも表現したいのです（DeskTop Publishing というより Server Side Publishing ですが笑）。商品情報とその商品の画像のようなイメージですね。しかし SQL2 でそのように階層構造を保って抽出するような SELECT 文を書く方法がどうしても見当たりませんでした

そこで抜本的に異なるアーキテクチャを志向していくことにします。こんどは Dynamic Deck に依存せずに AEM/IDS 連携を実証しようというお話です

## 前提条件

* AEM6.5.9
    * (port 4502 で起動)
* IDS
    * (port 8080 で起動)
* Node.js
* curl

※すべて同一のローカルマシンで完結するものとします

### 準備: AEM 上に素材を配置しておく

AEM に次のような CF がデフォルトで入っていました。これを流用することにします

![Screen Shot 2021-08-31 at 9.52.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/d9d76a66-1e9e-3f80-82d7-bb6e3ed24f0b.png)

該当する CFs の CF Model を特定し、

![Screen Shot 2021-08-31 at 9.58.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/c30984dc-717b-a1fd-0972-46b0114e9e1a.png)

このようなプロパティを新設し

![Screen Shot 2021-08-31 at 10.00.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/348d3624-6377-b1bd-de9f-724675077d13.png)


各々の CF に値を入れていきます。画像の JCR パスは AEM Assets に実在するものであれば適当でよいです

![Screen Shot 2021-08-31 at 10.05.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/843569a1-35e1-257d-5058-bd818636d5c2.png)
![Screen Shot 2021-08-31 at 10.06.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/8c1ca948-0c96-1853-ba68-ada675bf5a75.png)

次に AEM Assets に次のフォルダを作成します
`/content/dam/we-retail/en/experiences/destination`
`/content/dam/we-retail/en/experiences/template`

![Screen Shot 2021-08-31 at 10.12.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/026d2796-38bc-749c-35cc-9fd4f4b6d2e1.png)

[InDesign のテンプレート](https://github.com/o2-ninja/aim-poc/blob/master/template.idml) を `/content/dam/we-retail/en/experiences/template/template.idml` としてアップロードする

![Screen Shot 2021-08-31 at 10.13.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/c67d7b39-a015-847e-2d09-a2b7dc30abd0.png)

AEM 側ではこのような条件を満たしているという前提で以降進めていきます

## 設計

"抜本的に異なるアーキテクチャ" と標榜しましたがこれを冒頭の画像のようにしてはどうか？　と思い立ちました。 AEM <-> IDS という経路だと未知の部分も多く、いっそ両者は疎にしたほうが円滑に行くのでは、と思ったからです。 中継に Node.js を選定したのは自分が書き慣れているから、という理由が大きいです。また IDS で走らせる ExtensScript も Javascript ですから、 採用言語が統一できている、とも言えるでしょう（一応は）

実装に向けて各ポイントを抑えていきます

### AEM から素材を取得する

[Assets HTTP API](https://experienceleague.adobe.com/docs/experience-manager-65/assets/extending/mac-api-assets.html?lang=en) を使用します。 CF は json で取れます。同 json に前工程で新設したプロパティとその値も入っているので都合が良いです。値には画像のパスが入っている（前工程で自分で入れた）ので、同じ要領でその画像も取れるという寸法
そしてこれは [http.request()](https://nodejs.org/api/http.html#http_http_request_options_callback) で普通に実現できます

### 取得した情報を元に XML を構成する

これは純粋に文字列処理となります。 AEM から配信された CF は JSON です。これを InDesign に解釈させるための専用 XML に翻訳します。この XML は次工程で InDesign の Document にインポートさせるためのコンテンツデータです

### IDS に処理命令を発行する

[SOAP クライアントを実装](https://www.npmjs.com/package/easy-soap-request) できます。これで IDS に対し、任意の ExtensScript を実行するよう命令するわけです。引数も渡せます
この ExtendScript 内で、「この template.idml に基いてこの data.xml をインポートし、artifact.indd として保存しなさい」と命令します

### IDS 側の処理終了を検知する

簡単な IPC を構成します。方法はともかく終了フラグを立てればよいわけですが、ひとまず ExtendScript 側で終了時にフラグ用のファイルを作成し、 Node.js 側でそれを見張る、ということにします
余裕があれば [Socket](https://documentation.help/InDesign-Server-CS6/pc_Socket.html) で通信しても良いでしょう

### 出来上がった .indd を AEM Assets にアップロードする

AEM as a Cloud Service であればアップロードクライアントが [npm](https://github.com/adobe/aem-upload) で用意されているようですが、 6.5.9 ではうまく動作しませんでした。不本意ですが [child_process](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback) で [アップロード用 curl コマンド](https://helpx.adobe.com/jp/experience-manager/kb/common-AEM-Curl-commands.html) を直接実行することにします

## 実装

設計段階でポイントは抑えてありますのであとはそれらを組み合わせれば OK
https://github.com/o2-ninja/aim-poc

## 試運転

`npm run dev` すると

![Screen Shot 2021-08-31 at 14.06.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/61ee6642-d7b7-023f-d512-9ee240b7bc29.png)

できました

![Screen Shot 2021-08-31 at 11.01.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486518/9283beff-d5ff-1183-a4a2-cdc6b56ae1f2.png)
※ Assets の destination フォルダにアップロードされたものをローカルに落として表示しています

template.idml は全然作り込んでおらず適当なので見栄えの点ではいまいちながら、 やりたかったことは出来ています。左 pane の "Structure" で `item` と `Image` が親子関係になっているのがお判りいただけるでしょうか。これがやりたかったのです

## 備考

"InDesign に解釈させるための専用 XML" なるものが出てきましたが、なぜこの方法にしたか簡単に触れておきます。
ExtendScript は大変万能にいろいろな事が出来ますので、わざわざ XML を生成しなくても直接 json などのデータを読み込んでコンテンツを形成していくことは可能です。しかしこれは結構大変なんですよ。いちアイテム毎にデータをパース・成形して所定の箇所にコンテンツ文字列を配置してスタイルを適用させていったりとか改行、改ページ制御とかね。これを for 文とかで愚直に書いていくことになるのですが、それじゃ開発工数もかかるしいろんな体裁上の調整を事細かにやらなくてはいけなくなってきます。当然バグが発生する可能性も書けば書くほど増えます。さらにそれを ExtendScript というマイナーな言語でやるということは専任のプログラマーへの依存度が高くなってしまい、つまり属人性が高くなってしまい、メンテ等の中長期的な影響も踏まえ好ましくない、と導けるかと思います

一方、XML を生成するスクリプトは ExtendScript で書く必要は全くありません。 Node.js だろうと他のなにかだろうと、プレーンテキストを操作できれば何でも良いわけです

あとは template.{idml,indd} ですが、これは InDesign の世界になりますので、何れにしろプログラマと言うより professional に InDesign を扱えるデザイナーの職域になるでしょう

XML を境に住み分けることで、 XML でどう構造を表現するかと、それに対応する template をどう制作するか、に注力していけば良くなるとともに、 ExtendScript 依存度を低減できるのです。なにしろ ExtendScript で書くことは、単純に言えば「XML をインポートしなさい」だけで済みますからね。
もちろん、 XML だけでは対応しきれない局面が出てくることもないわけではありませんので、そのときは遠慮せず ExtendScript を書けばよいでしょう

### 補足

この最後の話は「エンタープライズレベルで複雑または高度に DTP を全自動化したいときの話」だとご理解いただけると幸いです。それに当てはまらない日々の小規模タスク等を自動化したいのであれば、細かいことは気にせず思うようにスクリプトを書いたほうが、制作体験の向上に資する場面も多いかと存じます。