* 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
* The Client class should be the factory for the objects you're going to
interact with on the client side, IMO.
* 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).
* 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);
}
Move queryParam and pathParam to the ClientRequest object. 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);
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();
With this model, it is also much simpler for the user as they only need
to know about 3 interfaces: ClientRequest, ClientResponse, and Client.
For a new user, navigating your current class hierarchy is quite
daunting and I don't see why we need the current complexity.
* 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.
* 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
--
Bill Burke
JBoss, a division of Red Hat
http://bill.burkecentral.com