I’ve been prototyping a bit lately for the REST-* effort, specifically for BPM. I rely heavily on Link headers to pass links around. RESTEasy has become pretty decent at handling links on the client side. Here’s an example of client request/responses and link following via link headers:
InputStream jpdl = Thread.currentThread().getContextClassLoader().getResourceAsStream(file); ApacheHttpClientExecutor executor = new ApacheHttpClientExecutor(); ClientRequest request = executor.createRequest("http://localhost:8081/bpm/definitions"); request.body(mediaType, jpdl); Link definition = request.create(); Assert.assertNotNull(definition); ClientResponse response = null; response = definition.request().head(); Assert.assertEquals(200, response.getStatus()); Link instanceFactory = response.getLinkHeader().getLinkByTitle("instances"); MultipartFormDataOutput form = new MultipartFormDataOutput(); form.addFormData("order", "$199.99", MediaType.APPLICATION_XML_TYPE); response = instanceFactory.request() .body(MediaType.MULTIPART_FORM_DATA_TYPE, form) .post(); Assert.assertEquals(201, response.getStatus()); System.out.println(response.getLinkHeader().toString()); Link instance = response.getLocation(); Assert.assertNotNull(instance); Link next = response.getLinkHeader().getLinkByTitle("continue"); Assert.assertNotNull(next); Link variables = response.getLinkHeader().getLinkByTitle("variables"); Link newVariables = response.getLinkHeader().getLinkByTitle("variable-template"); response = variables.request().head(); Assert.assertEquals(200, response.getStatus()); System.out.println(response.getLinkHeader().toString()); Link order = response.getLinkHeader().getLinkByTitle("order"); String xml = order.request().getTarget(String.class); request = newVariables.request(); response = request.pathParameter("var", "customer") .body(MediaType.APPLICATION_XML_TYPE, "bill") .put(); Assert.assertEquals(201, response.getStatus()); response = request.pathParameter("var", "customer") .body(MediaType.APPLICATION_XML_TYPE, "bill burke") .put(); Assert.assertEquals(204, response.getStatus()); response = next.request().post(); Assert.assertEquals(204, response.getStatus());
The thing about this code, it is a little hard to read. Because HTTP is being treated like a messaging API, the code is a conglomeration of simple API calls. The RESTEasy client proxy framework provides a nice way to map Java method calls to HTTP requests. It also allows you to map automatically, a response body to a Java object. Unfortunately though, this doesn’t work that well for my usecases. Because I’m relying a lot on Link headers to pass information around in REST-*, I need something that can represent an HTTP response as a whole in a nice way.
A POJO Response Mapping
So, I thought, why not define (or reuse JAX-RS annotations) to map an HTTP response to a Java POJO? It would kind of be the opposite of the RESTEasy @Form feature (where form maps an incoming request to a POJO). It could look something like this:
@ResponseMapping @ExpectedCode(200) public interface MyResponse { @Body public Customer getCustomer(); @LinkHeader public Link getNext(); @LinkHeader("last") public Link getLastCustomer(); @Header("ETag") public String getHash(); }
In this example, the client code would be expecting a response code of 200 (OK), a message body converted to a Customer object, a Link header named “next”, and a HTTP response header “ETag”. Using the RESTEasy proxy framework, you could then return this as a method return value, i.e.
@Path("/customers") public interface CustomerClient { @Path("{id}") @Produces("application/xml") public MyResponse getCustomer(@PathParam("id") int custId); }
What about errors?
For responses where the response code does not match, you could define similar mappings on an exception class.
@ResponseMapping @ExpectedCode(404) public class NotFoundException extends RuntimeException {}
You’d integrate it with the RESTEasy proxy framework by putting it within the throws clause.
@Path("/customers") public interface CustomerClient { @Path("{id}") @Produces("application/xml") public MyResponse getCustomer(@PathParam("id") int custId) throws NotFoundException; }
What do you think?
Maybe I’m going a little overboard here. Maybe not? I don’t know. Let me know what you think.
Feb 19, 2010 @ 08:21:08
I think a response wrapper could be a nice idea. But why not be more explicit about it? Basically due to the chronic release connection problems with the client side resteasy proxies I reported a few months ago, I’ve stripped all resteasy client code from our system and replaced it with httpclient 4 ResponseHandlers. I ended up with less code.
Basically you implement ResponseHandler and implement the T handleResponse(..) method. Mostly you don’t actually need to return a wrapper object (actually, especially with post/put requests, I usually go for Boolean) but if you need one, you can return one.
So I think you need to go for a callback style solution as well. Secondly, I don’t like the abuse of exceptions for what are really not exceptional situations. Why not let the user specify a callback interface that you annotate with the expected status and corresponding types? Then throw an exception for unhandled responses (or better have a default handle method in the interface).
So you would get something like
@Path(..)
@ResponseHandler(MyHandler.class)
public void getCustomer(…)
Feb 22, 2010 @ 13:32:52
I hope if we fix the connection problems you’ll try it out again…
Feb 19, 2010 @ 08:28:47
Seems like a great idea. This would surely beat the procedural code you have in your example.
Feb 19, 2010 @ 21:40:20
Bill:
Gees, don’t you get a sense of deja vu? Don’t you get a sense that since you are always binding to the same corny OO methods the model is OO-based, regardless of which type of binding you use? It would be fun to see the same type of code sample with an action like Submit PO.
Don’t you think the problem is “distributed objects” not the bindings? Could we once and for all agree that OO does not provide a factoring of the business logic that can support information systems? Data Models, Processes, Tasks, Organizations… no to mention (forwards compatible) versioning.
Don’t you get a sense that the “client” in the JBPM example needs to know a think or two about the “links”? Where is the uniform interface? I don’t hear much about that lately. How do you version the client code when the response with the “links” changes?
How long will be have to wait until we put OO to rest and seriously consider the architecture of information systems? How will we have to wait until the RESTafarians quit their fantasy claims?
JJ-
Feb 22, 2010 @ 13:32:18
Actually JJ, so far, it seems to me that BPM fits *VERY* well with REST. From the prototyping I’m doing, Wait states and Variables become resources. Transitions become links. Tasks become feeds.
JJ, you need to ask yourself, why are people that have been developing middleware for decades (13 years for me) embracing REST? I’ll leave it at that…
Finally, the only fantasy claims that RESTafarians need to drop IMO, is the the idea that clients actually care about self describing messages and that we don’t need middleware.
Feb 23, 2010 @ 15:51:00
>> it seems to me that BPM fits *VERY* well with REST
Bill, please, don’t call jBPM “BPM”. Let’s talk about Events, Resource lifecycles, Human Tasks and Organizations not to forget Assemblies or Contracts, then we’ll talk about.
>> why are people that have been developing middleware for decades (13 years for me) embracing REST?
I have already answered that question. REST is the perfect CORBA. It is the way CORBA should have been designed. It is the ideal “distributed object” technology. This is why you have no problem “annotating” OO. This is why you like it so much. Just take CORBA one feature at a time, map it to how REST works. You’ll be stricken by the similarities.
People that have tried to develop middleware for 20-30 years should ask themselves whether it is sensitive to always reduce middleware to “plumbing” (ie boiler plate code that calls a method or procedure) or should there be a distributed programming model (in which OO does not fit of course) and where bi-directional interfaces, versioning, orchestrations and choreographies, assemblies… are key concepts. Then maybe then we will stop all these silly discussions.
So, sorry Bill, I am not sure you really understand “distributed programming models” (I won’t even talk about BPM) even though you might be a great “middleware” guy shoveling bits from A to B (B to A is a bit harder). There is value in what you are doing, but please, understand that it is only and will ever be “distributed OO”. This is what you have been doing for 15 years, and you will be doing 15 years from today. Your mind cannot shift from a monolithic programming model (a la OO) to a “distributed programming model”.
JJ-
Links « Beautiful Discovery
Apr 18, 2010 @ 21:41:29
Jul 07, 2010 @ 19:23:43
Hi Bill:
I bought your book, “Restful Java” – it is an mazing resource! Thank you!
Got a question on one of the recent readings off of burkecentral.com
I was following some ideas for RestEasy clients on http://www.javauc.com/java/1432
Where is the following 2 annotations defined? Or, is it something one need to write thier own? I searched every maven repository – could not find any jars with those interfaces
@ResponseMapping
@ExpectedCode(int)
For example, in the following:
@ResponseMapping
@ExpectedCode(200)
public interface MyResponse {
@Body public Customer getCustomer();
@LinkHeader public Link getNext();
@LinkHeader(“last”) public Link getLastCustomer();
@Header(“ETag”) public String getHash();
}
—
Have a Nice Day!
Suresh Bhaskaran
Consulting IT Specialist
Home
Work
Cell (310) 499 6211