jsr339-experts@jax-rs-spec.java.net

[jsr339-experts] Re: Comments on client interface

From: Marek Potociar <marek.potociar_at_oracle.com>
Date: Wed, 11 May 2011 17:57:25 +0200

Hi Bill,
Thanks for joining this conversation again!

On 05/11/2011 04:17 AM, Bill Burke wrote:
> * If I provide an alternative client API for submition, will it receive honest evaluation? In the past I've encountered
> spec leads that do not want to vary much from their already implemented "reference implementation". Since the client
> api you've given is pretty much Jersey's, I worry that too much variation from this

If you ask me, I prefer we try to work with the current proposal draft and modify it to fit our desire. I would expect
that any change, no matter how big, will receive a honest evaluation. This, of course, applies to any feature and any
proposal.

Of course, I was inspired by the current Jersey API. But it was not because I feel personally attached to it as an
implementor (FWIW, I did not participate in its implementation). I chose it as a base for my proposal, because I know
that Jersey users in general find the API useful, easy to learn & use and the API has had enough time to mature to show
it's strengts as well as weaknesses. That's why I took it as my starting point, but not necessarily the finish line. :)

> * The Client class should be the factory for the objects you're going to interact with on the client side, IMO.

I agree. In what sense is the current version of the Client class not matching that description?

> * Why have a ClientRequest builder? Seems like an unnecessary step. Instead just:
>
> ClientRequest req = client.request();
> req.method("POST")
> .accept("text/plain")
> .header("If-Match", "334234324");
>
> Less things to import, easier for vendor implementations to extend behavior (instead of having to deal with umpteen
> different builder classes).

Just to confirm - are you suggesting we remove the ClientRequest.builder() method and replace it with Client.request()
instead? If so, I like it. Is this also something you were referring to earlier when you mentioned that the Client class
should be the factory for all client-side objects?

One thing that I don't like here is that the above suggests that a request is fully mutable. I was hoping to shoot for
the full immutability instead as it seems to me less error prone and removes lot of complication from the programming
model making it more straightforward. But I don't have a strong opinion here. If EG thinks that a ClientRequest should
be fully mutable "builder" like it is in Resteasy, I would be most likely OK with it.

> * IMO, a WebResource should strictly be an extension of Client. A place where you can specify a base URL to create
> requests from, add/override interceptors and filters. I don't like the fact that you specify queryParams and path
> params on a Web Resource. These methods belong on the client request.
>
> So, IMO, it should be something like:
>
> interface WebResource implements Client {
> ...
> }
>
> Or just have a method on Client:
>
> public interface Client {
>
> Client webResource(String baseURI);
> }

I am not necessarily ready to agree here (yet). :)

Client and WebResource should remain separated IMO as they encapsulate different concepts. The idea is that the Client
is the overall API entry point. It should be customizable & extensible & configurable (in terms of general client-side
features) and it should provide the access to ANY web resource. That concept is somewhat different from the WebResource.
Web resource is an accessor for a particular resource identified by it's URI that you can use to invoke HTTP methods on
the resource or traverse down to a sub resource. We may want to see if we can come up with a better name for this
concept, but let's try to avoid names like "Proxy" or "ResourceReference".

> Move queryParam and pathParam to the ClientRequest object.

Let me have a look at that.

> Move get, post, head, etc. to the Client interface:
>
> So, a request would look like this:
>
> ClientRequest req = client.request();
> req.method("GET")
> .accept("text/plain")
> .queryParam("foo", "bar")
> .pathParam("x", "y");
>
> ClientResponse res = client.execute(req);

I don't understand the connection between moving the get, post etc. and the sample above.

I think that current proposal is similar in that it lets you build the request instance if you want and then use the
generic Client.handle(...) method to send it and get back the response. This is the absolute low-level use case and thus
it is ok to have it available in the Client class.

OTOH, if a user wants to focus on a particular resource (or subset of resources) and use a "fluent" interface to perform
HTTP method invocations, then WebResource provides that (without any need to work with ClientRequest directly). That's
also most typical use case.

> In resteasy, our Client object is basically a factory for requests in which you can set the base URI along with
> configuration parameters. Execution happens through the ClientRequest:
>
> ClientRequest req = client.request("http://foo.com/customers/{id}");
> req.accept("text/plain")
> .body("text/plain", "hello world")
> .header("custom", "header-value)
> .pathParam("id", 12345);
>
> ClientResponse<String> res = req.post(String.class);
> String str = res.getEntity();
>

Ad last two lines - why did you decide not to provider the direct access to response entity from the post method?
String res = req.post(String.class);

> With this model, it is also much simpler for the user as they only need to know about 3 interfaces: ClientRequest,
> ClientResponse, and Client.

Actually 25% simpler to be precise since we just got rid of a single class. And with that extra 25%, the last version of
my proposal provides also the async api... :)

I could however argue that >90% of the use cases can be satisfied with Client, WebResource and ClientResponse, so the
amount of classes visible at the user level is the same.

Also the proposed version does not let me write code like this:
ClientResponse res = client.request().method("GET").post();

The issue with the example above is very obvious, but imagine you instantiate and configure a request in one place and
then pass it to some closed-source API that performs the actual invocation.

I guess the point I take here for now is that we should work on hinding the fluent builder interfaces from the eyes of
the user as much as possible.

> For a new user, navigating your current class hierarchy is quite daunting and I don't see
> why we need the current complexity.

Is this comment targeted at the exposed "fluent builder" interfaces or are you also suggesting we should get rid of the
handlers, filters and other stuff too?

> * I'd like to stress again, that the Resteasy proxy framework (reusing JAX-RS annotations on the client side) is very
> popular with our user base. I know it smells like a stub, but I always get asked if this feature will be standardized.
> That being said, the above model I suggest fits in quite nice when implementing a proxy framework like Resteasy's. The
> proxy just builds a ClientRequest by extracting @PathParam, @HeaderParam, @QueryParam etc...information from an invoked
> interface method. Your current WebREsource/ClientRequest hierarchy would not work in this model very well.

I certainly don't want to ruin your proxy framework. We do have a proxy framework in Jersey too, even though it is
enabling more RESTful, more dynamic and less RPC-like concept of resource views compared to Resteasy proxies. So my
natural first guess would be that if Jersey can do much more dynamic things with the API, Resteasy should be able to do
static things with the same API even more easily.

Can you point me to your proxy code so that I can have a closer look at your use case?

> * The client interception model needs to be thought about in parallel with any server side one we may or may not come up
> with. Otherwise you end up with code that is hard to reuse on both the client and server side.
>
> * MessageBodyReader/Writer interceptors
>
> Most of the use cases I've found so far involving interception revolves around marshalling and unmarshalling. In
> Resteasy we have the concept of MessageBodyReader/Writer interceptors that can be used on both the client *AND* server
> side. It has allowed us reuse code that supports features like:
>
> content encoding/decoding (i.e. gzip)
> digital signatures: signing and verifying
> message body encryption
> header decorators
>
> It has also allowed us to easily craft new annotations that trigger some of the behavior listed above that work on both
> the client side (with our proxy framework) and on the server side.
>
> Finally, reader/writer interceptors fit seamlessly into an asynchronous model. All the interceptor examples I list
> above work both synchronous and asynchronously (both on client and server) with zero extra code for the interceptor
> developer.
>
> * It is very brittle and inflexible to have ClientFilters/Handlers be a linked list and have knowledge of the
> interceptor chain. Especially if you want different lifecycles for each filter/itnerceptor (singleton, per request,
> etc.) Instead you should have a RequestContext object that is used drive interception, i.e. (pseudo code)
>
> interface RequestContext
> {
> ClientRequest getRequest();
>
>
> class myFilter implements ClientFilter
> {
> ClientResponse handle(RequestContext ctx)
> {
> return ctx.proceed();
> }
> }
>
> This is pretty much how EJB and CDI interceptors are modeled

I hear you. Let me create a sub task and have a look at it.

Many thanks for a great feedback!

Marek