Plugins - Not Just For Policies Any More

· api-manager, api-gateway, plugins, development, maven
Avatar for Eric Wittmann
Co-founder of Apiman, founder of Apicurio, owner of very fluffy dog.
/ Red Hat /

As you may know, apiman has long supported custom policies provided by users. If you aren’t familiar with apiman plugins, you can find more about them by clicking here.

As of version 1.1.5.Final, plugins are now even more useful. You can provide custom implementations of various core apiman system components via plugins. This allows users to customize apiman easily, without any changes to the classpath and without rebuilding the core apiman application.

In this blog post I’ll explain how it works.

Review: What is a plugin?

First, here are some good resources you can use to learn more about apiman plugins:

No patience to read those links? That’s OK - I’ll give you a quick breakdown.

An apiman plugin is basically a WAR file with one additional required file. The additional file is META-INF/apiman/plugin.json and it contains some meta-data about the plugin. An example of a plugin.json file (from the JSONP policy plugin):

{
  "frameworkVersion" : 1.0,
  "name" : "JSONP Policy Plugin",
  "description" : "This plugin turns an endpoint into a JSONP compatible endpoint.",
  "version" : "1.1.5.Final"
}

We chose WAR as the plugin format because it allows all of the file types we need, it is a well-known structure, and it’s easy to create (e.g. via maven).

When contributing a custom apiman component via a plugin, all you need is the plugin.json file and the java class file(s) that implement the appropriate component interface. Of course, because a plugin is a WAR, you can also include any library dependencies your component might need.

What are these components are you talking about?

apiman is made up of a number of components that work together to accomplish the goal of API Management. There are two primary pieces of the apiman story:

  • API Manager

  • API Gateway

Each of these consists of its own components. For example, the API Manager is made up of the following (not necessarily an exhaustive list):

  • Storage Component

  • Query Component

  • IDM Component

  • Metrics Accessor Component (consumes metrics data)

On the other hand, the API Gateway consists of a separate set of components, such as:

  • Configuration Registry

  • Rate Limiting Component

  • Shared State Component

  • Metrics Component (produces metrics data)

By default, the apiman quickstart uses default values for all of these, resulting in a stable, working system with the following characteristics:

  • Stores API Manager data in a JDBC database

  • Records and queries metrics data via Elasticsearch

  • Stores Gateway configuration information in Infinispan

  • Uses infinispan to share rate limiting state across gateway nodes

There are alternative configurations of apiman that you can use without needing to resort to plugins. For example, we provide Elasticsearch implementations of many of the components mentioned above. So you could easily switch from Infinispan to ES in the Gateway, if you wanted. However, if you wish to provide a custom implementation of something, plugins are now the way to go!

Example Scenario

There is a lot you can do now that we support plugin components. But it’s probably easiest to explain and understand if we take a simple example scenario.

Use mongodb to store Gateway configuration information

If you download the apiman quickstart, the default configuration is to use the built in WildFly 8 infinispan subsystem to store the API Gateway configuration info. This includes all APIs published to the Gateway, and all client apps registered with it as well. Perhaps you would rather that data be stored in mongodb? Since we don’t have a mongodb implementation of the Gateway Registry, you’ll need to implement it yourself and bundle it up into a plugin!

Create a apiman-gateway-mongodb plugin

I won’t go through the entire process of creating an apiman plugin here, since it is already well documented (and linked above). You’ll need a WAR maven project with a plugin.json file in the right place, which might look something like this:

{
  "frameworkVersion" : 1.0,
  "name" : "mongodb plugin",
  "description" : "This plugin provides a mongodb implementation of the Gateway registry.",
  "version" : "1.0"
}

You will also need an implementation of the Gateway’s io.apiman.gateway.engine.IRegistry interface. Let’s call it MongoDbRegistry.java:

package org.example.apiman.gateway;

import io.apiman.gateway.engine.async.IAsyncResultHandler;
import io.apiman.gateway.engine.beans.ClientApp;
import io.apiman.gateway.engine.beans.Api;
import io.apiman.gateway.engine.beans.ApiContract;
import io.apiman.gateway.engine.beans.ApiRequest;

import java.util.Map;

/**
 * An implementation of the {@link IRegistry} interface using mongodb.
 */
public class MongoDbRegistry implements IRegistry {

    /**
     * Constructor.
     * @param config
     */
    public MongoDbRegistry(Map<String, String> config) {
        super(config);
    }

    /**
     * @see io.apiman.gateway.engine.IRegistry#getContract(io.apiman.gateway.engine.beans.ApiRequest, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void getContract(ApiRequest request, IAsyncResultHandler<ApiContract> handler) {
        // TODO Auto-generated method stub
    }

    /**
     * @see io.apiman.gateway.engine.IRegistry#publishApi(io.apiman.gateway.engine.beans.Api, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void publishApi(Api service, IAsyncResultHandler<Void> handler) {
        // TODO Auto-generated method stub
    }

    /**
     * @see io.apiman.gateway.engine.IRegistry#retireApi(io.apiman.gateway.engine.beans.Api, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void retireApi(Api service, IAsyncResultHandler<Void> handler) {
        // TODO Auto-generated method stub
    }

    /**
     * @see io.apiman.gateway.engine.IRegistry#registerClientApp(io.apiman.gateway.engine.beans.ClientApp, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void registerClientApp(ClientApp application, IAsyncResultHandler<Void> handler) {
        // TODO Auto-generated method stub
    }

    /**
     * @see io.apiman.gateway.engine.IRegistry#unregisterClientApp(io.apiman.gateway.engine.beans.ClientApp, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void unregisterClientApp(ClientApp application, IAsyncResultHandler<Void> handler) {
        // TODO Auto-generated method stub
    }

    /**
     * @see io.apiman.gateway.engine.IRegistry#getApi(java.lang.String, java.lang.String, java.lang.String, io.apiman.gateway.engine.async.IAsyncResultHandler)
     */
    @Override
    public void getApi(String organizationId, String apiId, String apiVersion,
            IAsyncResultHandler<Api> handler) {
        // TODO Auto-generated method stub
    }
}

and then add something like this:

apiman-gateway.registry=plugin:GROUP_ID:ARTIFACT_ID:VERSION/org.example.apiman.gateway.MongoDbRegistry

The format of the value of apiman-gateway.registry is very important - when using a plugin you must specify the maven information of your plugin so that apiman can locate and download it. See the apiman documentation for additional details about how plugins are loaded.

Note that you can also provide configuration parameters to your component. That will obviously be helpful since it will probably need connection details. So really your configuration might look something like this:

apiman-gateway.registry=plugin:GROUP_ID:ARTIFACT_ID:VERSION/org.example.apiman.gateway.MongoDbRegistry
apiman-gateway.registry.mongo.host=localhost
apiman-gateway.registry.mongo.port=27017
apiman-gateway.registry.mongo.username=sa
apiman-gateway.registry.mongo.password=sa123!
apiman-gateway.registry.mongo.database=apiman

These configuration options will be passed to your component in its constructor if your class has a Map<String,String> constructor.

Conclusion

This is a powerful new feature for extending and customizing apiman to better suit your needs. Of course, we will want to continue offering the most popular component implementations as a core part of apiman. However, there will always be many more options than we can easily implement and support. For this reason we wanted to provide an easy way for users (and the apiman community at large) to contribute.

/post