AEM開発者ブログ by YAMATO

アドビ社のデリバリーパートナー大和株式会社のAEM開発者ブログです。

【AEMバックエンド開発】使うべきはJCRかSlingかAEMか

AEM Developerの皆様、どうも。大和株式会社の狩野です。

今回はAEMのバックエンド開発時のAPI選択(JCRかSlingかAEMか)についての記事をお届けします。

結論

結論から言うと、開発時のAPIにはAEMを使うのが望ましいです。
package名でいうとcom.day.cq.wcm.apiにあるクラスから使用を検討しましょう。

書こうと思った動機

元は JCR, Sling or AEM? Which API should I use and when? という記事に書いてある内容なのですが、今確認すると数ページめくるたびに長時間広告が表示されるようになっており、見るに堪えなかったので書こうと決意しました。

なんの話か

バックエンドのJava開発を行う時、同じ操作を行うにしても使うAPIによって書き方が複数存在します。
以下に具体例を挙げます。

@Model(adaptables = {SlingHttpServletRequest.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialClass {
    @SlingObject
    private ResourceResolver resourceResolver;

    public String title;

    @PostConstruct
    public void init() {
        // 本来はcurrentPageを取得するのにこんな手順は不要で、Annotationで宣言可能だが、pathが下と同じであると明示するためにこう書いている
        Page currentPage = resourceResolver.adaptTo(PageManager.class).getPage("/content/path/to/page");
        title = currentPage.getTitle();
    }
}
@Model(adaptables = {SlingHttpServletRequest.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialClass2 {
    // currentPageのパスと同様
    @ResourcePath(path = "/content/path/to/page")
    private Resource resource;

    public String title;

    @PostConstruct
    public void init() {
        Node node = resource.adaptTo(Node.class);
        title = node.getChild("jcr:content").getProperty("jcr:title").getString();
    }
}

この2つのコードは行っている操作自体は全く同じく「現在のページのタイトルを取得する」というものです。
JCRとAEMを比較するためにかなり無理やりなコードを書いてしまったのですが、大きな違いは以下です。

title = currentPage.getTitle();
title = currentNode.getChild("jcr:content").getProperty("jcr:title").getString();

今回の記事では、この違いについてや何故そうなるのかについて話していきます。

何がJCRで何がSlingで何がAEMなのか

ここで、タイトルの3つの概念が実際どれに当たるのかを説明します。

JCR

javax.jcrというpackageに入っているクラス群を指します。

javadoc: https://developer.adobe.com/experience-manager/reference-materials/spec/jsr170/javadocs/jcr-1.0/javax/jcr/package-summary.html

Sling

org.apache.sling.api.resourceというpackageに入っているクラス群を指します。

javadoc: https://sling.apache.org/apidocs/sling7/org/apache/sling/api/resource/package-summary.html

AEM

com.day.cq.wcm.apiというpackageに入っているクラス群を指します。

javadoc: https://developer.adobe.com/experience-manager/reference-materials/6-5/javadoc/com/day/cq/wcm/api/package-summary.html

どう違うのか

1つ目のコードはPageクラスの getTitle() というメソッドを利用してタイトルを取得しています。
2つ目のコードはノード名やプロパティ名を指定してタイトルを取得しています。

さらに、使ってるAPIのレベルの高さが違います。 Pageクラスは高レベルで、Nodeクラスは低レベルです。

Pageクラスを提供しているpackageであるcom.day.cq.wcm.apiパッケージは、AEM固有の機能にアクセスするための高レベルのAPIを提供しています。
対して、Nodeクラスを提供しているpackageであるjavax.jcrパッケージはJCRデータを扱うための低レベルのAPIを提供しています。

この「高レベル」「低レベル」というのは、一般的なプログラミング用語的には「機械語に近いかどうか」という意味ですが、この場合はCRXDEで見られるノード構造に近いかどうかという意味になります。

すごく大雑把なたとえですが、Pageクラスでの取得はsitesコンソールからタイトルを見てるのと同じで、Nodeクラスでの取得はCRXDEからjcr:contentノードを直接覗いているのと同じといった具合です。

なぜ、高LVのAPIを使うのが良いのか

わかりやすい弊害の1つを説明すると、この処理はタイトルを取得するだけではありますが、コードが長くなるような処理を作る場合のバグの起きづらさに差があります。
Pageクラスの getTitle() メソッドは入力の際にエディタのコード補完の恩恵を受けられたり、タイプミスしたままAEMにデプロイしようとしたとしても、Mavenコマンドのデプロイ時に気づけます。
ですが、Nodeクラスを使って取得しようとした場合、 jcr:content のノード名をタイプミスした場合、エディタの静的解析はもちろん、Mavenコマンドのデプロイをも通り抜けてしまい、実際にページを確認した時にやっとミスに気づけます。その上、どこに問題があるかを探すのも一苦労です。

もちろん、Pageクラスではどうしてもできない処理はNodeクラスを使うことにはなりますが、まずは高レベルなAPIを使うところから検討をスタートしましょう。

以上を踏まえて…

上で挙げた例では差がわかりにくい上に、Slingレベルのクラスを使った例が無いので、もう1つ例を書こうと思います。

例1)AEMレベル

@Model(adaptables = {SlingHttpServletRequest.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialClass {
    @ScriptVariable
    private PageManager pageManager;

    @ScriptVariable
    private Page currentPage;

    private String newPageTitle;

    @PostConstruct
    public void init() {
        Page newPage = pageManager.create(currentPage.getPath(), "newPage", "/conf/myapp/settings/wcm/templates/sometemplate", "newPage Title", true);
        newPageTitle = newPage.getTitle();
    }

    public String getNewPageTitle() {
        return newPageTitle;
    }
}

例2)Slingレベル

@Model(adaptables = {SlingHttpServletRequest.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialClass2 {
    @SlingObject
    private ResourceResolver resourceResolver;

    @ScriptVariable
    private Page currentPage;

    private String newPageTitle;

    @PostConstruct
    public void init() {
        Map<String, String> properties = new ImmutableMap.Builder<String, String>()
            .put("jcr:primaryType", "cq:Page")
            .build();
        Map<String, String> pageProperties = new ImmutableMap.Builder<String, String>()
            .put("jcr:primaryType", "cq:PageContent")
            .put("jcr:title", "newPage title")
            .build();
        Resource currentPageResource = resourceResolver.getResource(currentPage.getPath());
        Resource newResource = resourceResolver.create(currentPageResource, "newPage", properties);
        Resource newResourceContent = resourceResolver.create(newResource, "jcr:content", pageProperties);
        newPageTitle = newResourceContent.getValueMap().get("jcr:title", "");
        resourceResolver.commit();
    }

    public String getNewPageTitle() {
        return newPageTitle;
    }
}

例3)JCRレベル

@Model(adaptables = {SlingHttpServletRequest.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialClass3 {
    @ScriptVariable
    private Page currentPage;

    private String newPageTitle;

    @PostConstruct
    public void init() {
        Node currentPageNode = currentPage.adaptTo(Node.class);
        Node childNode = currentPageNode.addNode("newPage", "cq:Page");
        Node childNodeContent = childNode.addNode("jcr:content", "cq:PageContent");
        childNodeContent.setProperty("jcr:title", "newPage title");
        currentPageNode.getSession().save();
        newPageTitle = childNodeContent.getProperty("jcr:title").getString();
    }

    public String getNewPageTitle() {
        return newPageTitle;
    }
}

上記の3つの例を比較して

上記の3つのクラスは

  1. currentPageの配下ページを作成
  2. 作成したページにタイトルを設定
  3. 設定したタイトルを取得できるgetterメソッドを作成

というクラスです。

AEMレベルのクラスを利用した場合は以下の指定だけで済みます。

  • 子ページの名前
  • 子ページのテンプレート
  • 子ページのタイトル

対して、Slingレベル、JCRレベルの場合以下を指定する必要があります。

  • 子ページの名前
  • 子ページのテンプレート
  • 子ページのタイトル
  • cq:Pageノードのjcr:PrimaryType
  • cq:PageContentノードのjcr:primaryType
  • jcr:contentノードの作成

指定する要素が多い上に、それらを全て文字列で指定する必要があり、その上煩雑な手順を踏む必要があります。

これらの例をAEMのUI上の操作でわかるように比較すると、

PageManagerによるページ作成→sitesコンソールからページを作成している
Resource, Nodeによるページ作成→CRXDEからノードを1個ずつ作成している

この同じ操作の比較においては、相当AEMを使うのが有利であるということがわかりやすいと思います。

最後に

繰り返しになりますが、まずは高レベルなAPIを使うところから検討をスタートしましょう。

最後までお読みいただき、ありがとうございました。