The Executable Feel Of JAX-RS 2.0 Client

JAR-RS 2.0 comes with a standardized client. Instead of directly using Jersey, CXF or RESTEasy implementation you can now use these libraries as Service Provider Interface (SPI) over a standardized API:


import java.util.List;
import javax.ws.rs.client.Client;
import static javax.ws.rs.client.Entity.entity;
import javax.ws.rs.client.Target;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ClientFactory;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;

public class ScriptingResourceTest {

    private Client client;
    private String baseURI = "http://localhost:8080/lightfish/resources/scripts";
    private Target target;
            
    @Before
    public void init(){
        this.client = ClientFactory.newClient();
        this.target = this.client.target(baseURI);
    }

    @Test
    public void crudScript(){
        String scriptName = "duke"+System.currentTimeMillis();
        Script origin = new Script(scriptName, "true",true);
        
        //PUT
        Response response = this.target.request().put(entity(origin,MediaType.APPLICATION_XML));
        assertThat(response.getStatus(),is(Status.CREATED.getStatusCode()));
        String location = response.getHeaders().getHeader("Location");
        assertTrue(location.endsWith(scriptName));
        
        //GET
        Script fetched = this.client.target(location).request(MediaType.APPLICATION_XML).get(Script.class);
        assertThat(fetched,is(origin));

        //GET (ALL)
        GenericType<List<Script>> list = new GenericType<List<Script>>() {};
        List<Script> result = this.target.request(MediaType.APPLICATION_XML).get(list);
        assertFalse(result.isEmpty());
        
        //DELETE
        response = this.target.path(scriptName).request().delete();
        assertThat(response.getStatus(),is(Status.OK.getStatusCode()));
        
        //GET
        Response notExistingEntity = this.client.target(location).request(MediaType.APPLICATION_XML).get();
        assertThat(notExistingEntity.getStatus(),is(Status.NO_CONTENT.getStatusCode()));
    }
}

The system test above remotely interacts with the following JAX-RS resource:

@Stateless
@Path("scripts")
@Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
public class ScriptingResource {
 
    
    @GET
    public List<Script> scripts(){
    }

    @GET
    @Path("{id}")
    public Script script(@PathParam("id") String id){
    }
    
    @PUT
    public Response save(Script script){
    }

    @DELETE
    @Path("{name}")
    public Response delete(@PathParam("name") String name){
    }
}

The code is executable. Jersey implementation of JAX-RS 2.0 API already resides in maven central:

<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-client</artifactId>
  <version>2.0-m02</version>
  <scope>test</scope>
</dependency>

The class ScriptingResourceTest was taken from the LightFish project and used to system test the scripting subsystem.

Comments:

Now that this exists, are there reasons to continue to use jersey or the other frameworks?

Posted by Johnny on March 30, 2012 at 09:38 PM CEST #

@Johnny,

absolutely. In the example above I'm using Jersey over JAX-RS 2.0 API.

The relation between JAX-RS 2.0 Client API and Jersey is like JDBC and MySQL,

thanks!,

adam

Posted by Adam Bien on March 31, 2012 at 06:21 AM CEST #

Personally I like REST but this is something which makes me think

Posted by Javin on March 31, 2012 at 12:30 PM CEST #

The client API is really well done, also the async parts.

Do you know much of the current (early draft) JAX-RS 2.0 spec is implemented in Jersey now?

I'm about to start a new project, and I'd really love to go ahead with JAX-RS 2.0 right away - I'd rather accept a few changes due to spec changes in the next months than start with an altogether different API.

Posted by Marius on April 10, 2012 at 03:00 PM CEST #

This API is very low level compared to RESTEasy client API.

There you need just the interface class on the client. No boiler code:

http://docs.jboss.org/resteasy/2.0.0.GA/userguide/html/RESTEasy_Client_Framework.html

Posted by Adam Walczak on April 23, 2012 at 12:12 PM CEST #

The JAX-RS 2.0 client API (which is relatively similar to the current Jersey 1.x client API) isn't "low-level" at all. It's RESTful.

The client proxy approach fakes something that simply doesn't exist for a REST client: remote method calls on objects. It tells you fairytales about concepts that aren't part of the REST API, such as method names or even Exception types. These are implementation details on the server side that you should never care about.

Once you get the hang of understanding that REST is about working with a small, fixed vocabulary on resources, it's far more straightforward to use a truly RESTful client API. Pretending your API to be local method calls is painful in the long run, because it just doesn't fit (in fact, making remote communications look like local calls has been known for a long time to be a bad idea, but that mistake keeps getting repeated through the years...).

If you truly need an RPC-like API that maps cleanly to objects and methods, you're better off using SOAP. Nothing wrong with that.

(I've been through that process - worked for several years with SOAP, then made the switch to REST services. It's not just a different transport technology.)

Posted by Marius on June 19, 2012 at 09:13 PM CEST #

Client proxies are a very thin layer around a REST API. There isn't anything less "RESTful" about them. Complex path construction via a client proxy is convenient and results in fewer errors. Automatic alignment of client and server use with type safety is important as well.

In the JAX-RS 2 sample provided, there is no additional control or flexibility beyond that provided by a proxy-oriented API.

REST client proxies are not attempting to fake remote calls. They are simply organizing the call in a type-safe manner, and treating the results in the same way. RestEasy client proxies are not concerned with server method names -- they deal only with paths. Exceptions aren't a part of the picture either, unless you deliberately insert a client-side translation mechanism.

There's a symmetry argument to be made as well. If the annotation-based approach is appropriate for servers, why is it not appropriate for clients?

Posted by Ross Judson on August 30, 2012 at 01:07 AM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
...the last 150 posts
...the last 10 comments
License