Best Practices for using a Java-callout policy in Edge

Is there a document that describes the guidelines or best practices for using a Java callout policy within Apigee Edge?

We are investigating the feasibility of using Java callout policy to either perform JDBC call or JMS call. Are there any reasons do it (or not to do) this is in Apigee Edge with Java Callout?

1 5 1,815
5 REPLIES 5

I don't know of a document that describes best practices for authoring Java callouts. It's probably a good idea to have an article at least. Some quick guidance:

  • allow configuration of your Java callouts via properties. For example the jdbc url should be specified in a property in the policy config.

  • You can resolve properties that are context variables, using the MessageContext.getVariable() method. This would allow something like this in the policy config:

    <JavaCallout name='Java-Callout-1'>
      <Properties>
        <Property name="staticProp">abcdefg-123</Property>
        <Property name="dynamicProp">{my_context_variable}</Property>
        ...
      </Properties>
      <ClassName>com.mycompany.apigee.callout.NameOfCalloutClassHere</ClassName>
      <ResourceURL>java://my-jar-goes-here.jar</ResourceURL>
    </JavaCallout>

    Then, in code you would extract the string {my_context_variable} and strip the curlies, then resolve the variable by that name.

  • Don't forget to write unit tests for the callout. This is just good hygiene. I use testng and jmockit, but you can use junit and other mock frameworks if you're more familiar.

  • use a try-catch wrapper around the execute method. If anything throws, capture the exception and set a context variable containing the stacktrace, at the very least. This helps you in debugging. You may want to gate this behavior on the presence of a debug property on the callout - only set the stacktrace variable in an error condition when the debug property is true.

  • I use a fixed variable prefix for all of the variables set in any one callout. The callout that does JDBC might use "jdbc_" as the prefix. I do not use dot-separated variable names. There have been some problems with that; especially how they are displayed in the Trace UI.

  • Use caching and pooling where appropriate. This will give you better performance at high concurrency. For example with JDBC you will want a connection pool. You can use the Guava LoadingCache for caching arbitrary things inside the callout. That's a terrific class and works well with Edge callouts. Remember this cache is distinct from the Edge cache, and it will be specific to each instance of the callout.

  • Consider writing system tests that automate the deployment of an API proxy to a live Edge system, and runs tests against it. With the Apigee Edge admin API, this is straightforward, and will pay off in quality.

  • When connecting to remote systems, be aware of the security issues. The callout does not use the TargetConnection TLS Settings! You need to set up TLS specifically for your callout (eg, for the JDBC queries).

You can find good examples in github. for example, this one. (or Search my github repos for "Edge") The callouts I write and publish on github are Apache-2.0 licensed, you can re-use and re-purpose any of that code without restriction.

As for whether it makes sense to produce a callout that connects with JDBC or a JMS system... normally not. Usually we'd recommend that people stand up a micro-service in front of those resources so that they can be accessed by any other authorized system. If you write a callout, then the callout, and the resource behind it, is accessible only through Apigee Edge. This may be what you want.

Ditto for everything Dino says, with some extra bits for the last paragraph.

You need to consider the network architecture before connecting directly to one of those systems. If you're using Apigee SaaS, those systems will need to be exposed to Apigee via the internet. Typically this is not done due to security reasons.

Similarly, if you're using Apigee self-managed via the OPDK, you need to consider where the Apigee components sit in the architecture. Often connecting from a DMZ directly to a DB without going through an application layer is simply not allowed.

Finally, even if you are using OPDK and have nothing in the DMZ today, consider if you ever will. It will be easier to migrate infrastructure or deploy proxies to different places if you mediate that direct connection to a db with something more easily deployed to different locations in the network architecture.

Just want to mention a pitfall on using variables in the Java Callout <Property> tag.

  • You cannot depend on Java callout class constructor to be called every time the Java callout policy is invoked. The execute method will be called every time but not necessarily the constructor. This is probably due object pooling design in the implementation.
  • So any initialization done on the constructor is one time only and you'll have to redo this on the execute method or you'll have stale values from a previous execution.
  • Especially when you use the <Property name="dynamicProp">{my_context_variable}</Property> trick mentioned above, take care to re-initialize all instance level state at the beginning of execute method. Otherwise stale variables values resolved during a previous run will still be available in the current run.
  • A bug in this case will manifest as a test which succeeds the first time but fails when run again after other tests.

Correct! The Contructor is called ONCE; if you provide a ctor that accepts a Map, then that ctor will receive the Properties configured in the policy config file.

Any implied reference to a context variable, as in

<Property name="dynamicProp">{my_context_variable}</Property>

...will be available to the constructor as is; the implied reference to a context variable within curly braces does not magically get "resolved" before being passed to the constructor.

At the time you the callout is invoked, the execute() method runs, and within there you will receive a MessageContext, with which you can resolve context variable references. Eg, you can effectively call

String foo = (String) messageContext.getVariable("my_context_variable");

There *may be* multiple instances of the callout running in an MP, and there WILL BE multiple instances of the callout running in distinct processes across the cloud. So if you have a need for synchronization or shared state, you should be aware and take the necessary steps.

If you like using gradle to build Java code and apigeetool to deploy API proxies, a template incorporating many of the best practices mentioned above is available - edge-java-callout-hello