Thursday, May 21, 2009

Annotation-free services with CXF JAX-RS

One can hear now and then people saying : "It would it be good if I did not have to add so many annotations to a service class but well...". Many JSR specifications rely seriously on annotations and it does not seem like a reversible process really. Nearly every new enhancement leads to a new annotation type being introduced.

Annotations can be handy, they can save you a ton of time, they can help you with the injection of external resources, they can tell some great external library to do the job for you, they can be great after all. And they also can clutter your code and make it less readable or plain brittle.

Some annotations in Java-based web services frameworks enable Java classes to act as services, notably those defined in JAX-RS and JAX-WS. Here is a typical JAX-RS example :


@Path("/bookstore")
public class BookStore {

@GET
@Path("books/{id}")
@Produces("application/xml")
public Book getBook(@PathParam("id") Long id) {
return books.get(id);
}

@Path("books/{id}/chapter")
public Chapter getFirstChapterSub(
@PathParam("id") Long id) {
return books.get(id).getChapter(1);
}
}


Several annotation such as @Path, @GET, @PathParam and @Produces enable Book and Chapter classes to act as resource and subresource classes respectively.

The JAX-RS specification primarily talks about two main things : which annotations enable a given Java class to act as a service and how to ensure that the expectations expressed through these annotations are met, that is how to dispatch to a given method or populate a given parameter value, etc.

In CXF JAX-RS, we thought of letting users to write services without them having to annotate Java classes for a while and now we have done a first step in this direction, by following an example of the CXF SOAP simple frontend. One can apply an externally defined model instance to an existing resource tree, on either server or client side (proxies) and a CXF JAX-RS runtime will use this model instead of annotations.

One can apply such model in a number of ways.

One can create a number of UserResource beans, each representing either a root or sub-resource resource. For example, one can subclass a UserResource instance and populate it in its @PostConstruct method using some external information. A list of UserResource instances can then either be registered from Spring using a jaxrs:modelBeans child element or programmatically.

Or you can define a model instance either directly in a Spring beans.xml or in an external xml file and link to it, using either a jaxrs:model child element or jaxrs:modelRef attribute .

When using Spring, you often want to proxify a given service bean so we ensured you can use annotation-free proxified beans too.

All options are supported on the client and server sides, for jaxrs:client and jaxrs:server. In the client case it obviously applies to a proxy-based flavor only given that http-centric clients deal with the metadata (such as path, queries, etc) explicitly.

So lets see an example. Here are BookStoreNoAnnotations root resource and ChapterNoAnnotations sub-resource classes. Both classes actually deal with JAXB-annotated beans, ChapterNoAnnotations being one of them, but such beans have been likely generated out of schemas, but even if not, one will not have to go and start coding in JAX-RS annotations if it is what one does not want to do.

In one test we attach an externally defined model instance using this configuration :


<jaxrs:server
address='/thebooks6'
modelRef='classpath:/.../resources/resources.xml'/>

In other one we embed it directly inside jaxrs:server using a jaxrs:model, see this endpoint with id 'bookservice7'. In both cases an HTTP client code can invoke on either BookStoreNoAnnotations#getBook() or ChapterNoAnnotations#getItself(), by going through a BookStoreNoAnnotations#getBookChapter() subresource method first in the latter case.

In model one simply lists all the resource classes which may want to act either as root or subresource classes. The runtime will detect which one acts as a subresource one based on the response type of a given resource method, provided the model defines path but no HTTP verb values for this method.

The model is flexible enough to accommodate for most of the information which is otherwise available from JAX-RS annotations. It is really a combination of 3 simple beans, notably UserResource, UserOperation and Parameter, and they can grow as needed to keep up with the progress of JAX-RS.

Such model can be generated by a UI tool and simple enough to be created using an XML-editing tool.

Note the goal of this CXF extension is give to users more options when a task of enabling a given set of Java classes arises. If you are conscious about writing JAX-RS compliant code such that you can swap JAX-RS implementations under the hood and see your resource working then you will likely want to skip this option.

But then you might want to consider it when a cost of annotating a set of resource classes is deemed to be high. For example, embedding explicit path values using @Path annotations does seem quite brittle - path values and the way one embeds values into them or format requirements may change and now you are faced with the need to recompile. It may or may nor be an isolated and cheap update.

So please think about it, try it and provide the feedback.

By the way, this post can be viewed as a continuation of the previous one.
Actually, I think this approach can work quite nicely once CXF JAX-RS gets embedded in a Distributed OSGi implementation, with some DOSGi users being keen to keep the Java interfaces as transparent as possible.

2 comments:

James Strachan said...

But then you might want to consider it when a cost of annotating a set of resource classes is deemed to be high.
Adding a @GET or @Path is pretty cheap. Way cheaper than using a complex UI tool to edit some EMF model or whatever.

For example, embedding explicit path values using @Path annotations does seem quite brittle - path values and the way one embeds values into them or format requirements may change and now you are faced with the need to recompile.
Due to the web and folks ability to share links and use bookmarks, URIs should rarely change - and if they do, recompiling is not a big deal.

Give me a few simple annotations over complex, fragile eclipse plugins or hacking XML files any day

Sergey Beryozkin said...

> Adding a @GET or @Path is pretty cheap.

Adding can be cheap but may be tedious enough if we deal with say 10 resource classes or more, changing may not be that cheap.

> Way cheaper than using a complex UI tool

I guess UI should not necessarily offer users to go to every individual type and add some properties to it, though such an option should also exist. It may just ask for some regular expression like org.bar.Book*, so a user would just enter the expression and list one or two methods which are shared by all the classes. This is just one example.

> URIs should rarely change - and if they do, recompiling is not a big deal.

I added this option as I'm not convinced it is not a problem. It may or may not be. If we have a single resource class with say 2 Path annotations then it is not a big deal, but if we say have just 10 resources with 5 Path values per each one then it's becoming more expensive.

Perhaps one somewhat orthogonal case when we have an existing resource class and you can't go and change the code for whatever reasons.