May 2, 2020
Estimated Post Reading Time ~

Invoke REST Services in AEM, The Right Way

Any web developer should at some point have had to invoke a RESTful web service, in at least 3 different languages, leveraging the APIs built into that language or by using 3rd party APIs. Now you are here trying to figure out how to do it in Adobe Experience Manager. When faced with this new challenge we will all run to Google and search for “aem json web service”. Oh look, the top result will probably be this Adobe tutorial on how to do just that. You could just cut and paste from that article straight into the IDE, say a prayer, compile and run. Or you can read the entire article, store the relevant points into your short term memory, and keep on searching. After about the 3rd or 4th how-to article, a possible solution will take shape in your head and then you are ready to implement.

Such was the case when I first had to invoke a RESTful web service in AEM. The Apache HttpClient is a decent library. It comes to OOTB in AEM. It is a pretty basic tool. It's great for one-offs (i.e. I just want to grep a web page and parse things out) but it can quickly become cumbersome if you are invoking multiple services with different content types, SSL, authentication, cookies, headers or encodings. Your code can quickly become a patchwork of hacks to address all those things.

OpenFeign
I first used OpenFeign when I wrote my first RESTful client in Java years ago. To be honest, I don’t know much about the genesis of this project. I know it started as a Netflix OSS project but now it does not seem associated with Netflix anymore. I don’t know the authors, and there does not seem to be an official project website or wiki, other than the extensive README file. You need support? Go look at the source. Why do I like it? Its pluggable, flexible, and lets me focus on working with the results from a REST service and not having to flip a bazillion switches to get an Apache HTTP request just right.

Start A New AEM Project
To get started, I am creating a new AEM project using the Lazybones template, with pretty much all the defaults. If you have an existing project, look carefully, and adapt.

lazybones create aem-multimodule-project aem-feign

Adding Dependencies
Apache HttpClient comes with AEM OOTB, great! Feign does not. To get it in the box means getting the OSGi bundle into the OSGi container, Apache Felix. This can be done in several ways. You can upload straight to the Felix console, you can embed them in a content package, or you can inline them into your own bundle. Lucky for us feign-core is distributed as an OSGi bundle, so we are ready to rock-n-roll. The easiest way for this tutorial is to embed it into the project’s ui.apps package.

Depending on your AEM version, you may or may not have Jackson or Gson. In 6.4, both are included OOTB so we are going to add the feign-jackson dependency to do our parsing.

In the root pom.xml, add the following to the dependencyManagement section

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <version>9.7.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
    <version>9.7.0</version>
    <scope>provided</scope>
</dependency>

In the ui.apps/pom.xml add the following to the dependencies section
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
</dependency>

and to the list of embedded decencies of the content-package-maven-plugin configuration

<embedded>
    <groupId>io.github.openfeign</groupId>
    <target>/apps/my-aem-project/install</target>
</embedded>

Feign will now get into the OSGi container, but to actually start using it you’ll need to update the dependencies of your own bundle under core/pom.xml by adding the following to the dependencies section

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
</dependency>

Setting Up A Mock Service
If you already have a web service you want to invoke, then you can skip this part. Otherwise, we will take a little detour and set up a mock REST service so we can test. We like Docker, so let set something up with a docker-compose.yml file

version: "3.3"
services:
    wiremock:
        image: rodolpheche/wiremock:2.23.2-alpine
        ports:
            - 8080:8080
        volumes:
            - ./wiremock:/home/wiremock

You’ll also need wiremock/__files/list-items.json

[
    { "name": "foo" },
    { "name": "bar" }
]

and wiremock/mappings/list-items.json
{
    "request": {
        "method": "GET",
        "url": "/list-items",
        "basicAuth": {
            "username": "bill",
            "password": "lumbergh"
        }
    },
    "response": {
        "status": 200,
        "bodyFileName": "list-items.json"
    }
}

Fire up the docker container with docker-compose up -d and access the WireMock admin endpoint at http://localhost:8080/__admin where you’ll see the configured request. If you set up a PostMan request with the configured basic authentication, you should be able to get a response from http://localhost:8080/list-items.

Invoking The Service
Via HttpClient
For comparison, here is the HttpClient implementation. When I was writing this up, I ran into issues right away with getting the basic auth set up, and deserializing the response stream.

final CredentialsProvider credentialProvider = new BasicCredentialsProvider();
final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(this.username, this.password);
credentialProvider.setCredentials(AuthScope.ANY, credentials);

final HttpHost targetHost = HttpHost.create(this.host);

final AuthCache authCache = new BasicAuthCache();
authCache.put(targetHost, new BasicScheme());

final HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credentialProvider);
context.setAuthCache(authCache);

final HttpClient client = HttpClientBuilder.create()
                                           .build();
final HttpResponse httpResponse = client.execute(new HttpGet(this.host + "/list-items"), context);
final int statusCode = httpResponse.getStatusLine()
                                   .getStatusCode();

List<Item> listItems = Collections.emptyList();
if (statusCode == HttpStatus.SC_OK) {
    final InputStream content = httpResponse.getEntity()
                                            .getContent();
    final Item[] items = new ObjectMapper().readValue(content, Item[].class);
    listItems = Arrays.asList(items);
}

Via Feign
You’ll need to define an interface and set the annotations as necessary, a pretty trivial task

interface ItemService {

    @RequestLine("GET /list-items")
    List<Item> listItems();
}

and create an instance of the client based on your contract

final ItemService api = Feign.builder()
                             .decoder(new JacksonDecoder())
                             .requestInterceptor(new BasicAuthRequestInterceptor(this.username, this.password))
                             .target(ItemService.class, this.host);
List<Item> listItems = api.listItems();

Thats a lot less code to read, write, maintain and unit test.

Conclusion
HttpClient is a good API but can sometimes be cumbersome. You wind up doing a lot more plumbing. Feign offers all good API to take care of all that plumbing but it is OSS. It is also limited to text-based APIs. Depending on your needs/constraints it may make sense to switch to an API like Feign. The complete project can be found here.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.