I'm building a REST API for performing CRUD operations on a database. My tentative stack is Jersey, Spring, Spring Data, JPA and Hibernate. I'm also using jersey-spring to supply instances of the resource class so Spring can autowire them.
The API will support CRUD operations on dozens of tables, with concomitant JPA Entities and DAOs backed by Spring Data repositories. The family of DAO interfaces and related DTOs looks something like this:
public interface CrudService<T extends PersistedObject> { /* ... */ }
public interface PersonService extends CrudService<Person> { /* ... */ }
public class PersistedObject { /* ... */ }
public class Person extends PersistedObject { /* ... */ }
Here's a simplified version of a JAX-RS resource class:
@Component
@Path("/people")
public class PersonResource {
@Autowired
private PersonService personService;
@Path("/{id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Person get(@PathParam("id") String id) {
return personService.findOne(Long.valueOf(id));
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response post(Person person) {
personService.save(person);
return Response.created().build();
}
}
The problem is that remainder of the dozens of resource classes look almost identical, the only difference being they operate on a different PersistedObject subclass and its corresponding DAO. I'd like to stay DRY by having one resource class that can support the CRUD operations on all entity types, presumably via polymoprhism and clever injection of the DAO. It might look something like this:
@Component
@Path("/{resourceType}")
public class CrudResource {
@Autowired
private CrudService crudService;
@Path("/{id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
public PersistedObject get(@PathParam("id") String id) {
return crudService.findOne(Long.valueOf(id));
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response post(PersistedObject entity) {
crudService.save(entity);
return Response.created().build();
}
}
The issues I need to solve:
resourceType
variable in the path parameter indicates the concrete type the user is requesting, but I don't know how to wield this to any advantage.Overall, I'm not sure that I'm on the right path. Is it possible to implement this in a generic way?
I've run into this problem myself a few times. You can create a generic endpoint and a PersistedObjectDao and it should all work just fine. In Hibernate, session methods such as persist(), merge(), and delete() don't care about what it gets so long as it is a managed object or can become a managed object (in the case of merge()). Since you're only finding by id, and that should be managed in the PersistedObject class rather than the Person class, the functionality of the DAO will work just fine.
The only problem with this approach is that it breaks documentation tools such as Enunciate and makes the resource urls require globally unique ids. /xxx/1 and /yyy/1 cannot coexist. The findOne method will return the same object for both. This means that you will have to use @Inheritance(strategy = InheritanceType.JOINED) to avoid id collisions and create a globally unique id column across all persisted entities in the database.
Because of this I generally create an AbstractPersistedObjectDAO class and implement persist(), merge(), and delete() and abstract the findOne() to a subclass to avoid the need to cast in code if I ever need to do more than CRUD. But I generally just eat the cost of the boilerplate on the endpoints so that I can produce the REST documentation with Enunciate and it gives me a class to take on additional methods in the future if needed.
Not sure if you're tied to JAX-RS in any way but the Spring Data family of projects comes with the Spring Data REST module that automatically exposes entities managed by Spring Data repositories in a hypermedia-driven way. It's based on Spring MVC.
So you essentially get CRUD operations on the entities for free, query methods exposed transparently and the ability to tweak and tune close to everything according to your needs.
Here are some useful links you might want to browse for further information:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With