Monday, March 18, 2013

How-to: RESTful Web services with JAX-RS and Spring

When providing services in a specific technology (i.e. EJB, JAX-WS, JAX-RS), it's wise to avoid mixing the business logic of your service and the technology that you are using to expose it to the outside world. This separation facilitates easier adoption of newer technologies and makes it easier to maintain and test services.

Let's see how this will work by implementing a simple blogging system. The project is split into 3 maven modules:

service-java
Business logic of the service. Uses domain objects.
jaxrs-service-specification
Contains JAX-RS annotated service interfaces and DTO objects.
service-provider-jaxrs
Implements JAX-RS annotated interfaces.

service-java

The service module contains the business interface (illustrated below) and it's implementation. It's a POJO service and its operations can be unit tested.

public interface BlogService {
List<MyPost> getPosts();
MyPost getPost(int id);
int addPost(MyPost post);
boolean updatePost(int id, MyPost post);
boolean deletePost(int id);
}

jaxrs-service-specification

This module is service specific. It contains a JAX-RS annotated interface and DTO classes required to provide and/or consume RESTful this service.

@Path("/blog")
@Consumes("application/xml")
@Produces("application/xml")
public interface BlogRestfulService {
@GET
@Path("/posts")
List<Post> getPosts();
@GET
@Path("/post/{id}")
Post getPost(@PathParam("id") int id);
@POST
@Path("/post")
Response addPost(Post post);
@PUT
@Path("/post/{id}")
Response updatePost(@PathParam("id") int id, Post post);
@DELETE
@Path("/post/{id}")
Response deletePost(@PathParam("id") int id);
}

service-provider-jaxrs

Provider module is usually a web module. It has a dependency to both of the above modules. This module implements the JAX-RS annotated interface and delegates to the service-java layer after carrying out technology specific aspects (i.e. validation, authentication) and mapping DTO objects to domain objects. In this way, we avoid exposing the internals of our domain structure to the outside world.

@Named
@Scope("request")
public class BlogServiceImpl implements BlogRestfulService {
@Inject
private BlogService service;
@Inject
private Mapper mapper;
@Override
public List<Post> getPosts() {
List<MyPost> posts = service.getPosts();
List<Post> dtoPosts = new ArrayList<Post>(posts.size());
for (MyPost myPost : posts) {
dtoPosts.add(mapper.map(myPost, Post.class));
}
return dtoPosts;
}
@Override
public Post getPost(int id) {
MyPost post = service.getPost(id);
if (post == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return mapper.map(post, Post.class);
}
@Override
public Response addPost(Post post) {
int id = service.addPost(mapper.map(post, MyPost.class));
return Response.created(UriBuilder.fromPath(Integer.toString(id)).build()).build();
}
@Override
public Response updatePost(int id, Post post) {
boolean success = service.updatePost(id, mapper.map(post, MyPost.class));
if (!success) {
throw new WebApplicationException(Response.Status.NOT_MODIFIED);
}
return Response.ok().build();
}
@Override
public Response deletePost(int id) {
boolean success = service.deletePost(id);
if (!success) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return Response.ok().build();
}
}

But before we are ready to deploy, we have to stitch our classes together. Spring and Jersey configuration is illustrated below, though other Resteasy and Apache CXF have similar setup for integrating with Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="tutorial"/>
<bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>jersey-spring-servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-spring-servlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
view raw web.xml hosted with ❤ by GitHub

The deploy-ready source code can be downloaded at my quickstarts repository, jaxrs-tutorial and tested using any RESTful service consuming tool.

1 comment: