2013-03-07 10 views
7

Sto costruendo un'API REST per eseguire operazioni CRUD su un database. Il mio stack provvisorio è Jersey, Spring, Spring Data, JPA e Hibernate. Sto anche usando jersey-spring per fornire istanze della classe di risorse in modo che Spring possa autoprodurle.Come posso implementare questa API REST e rimanere DRY?

L'API supporterà operazioni CRUD su dozzine di tabelle, con le entità JPA concomitanti e DAO supportate dagli archivi Spring Data. La famiglia di interfacce DAO e DTOs relativi simile a questa:

public interface CrudService<T extends PersistedObject> { /* ... */ } 
public interface PersonService extends CrudService<Person> { /* ... */ } 

public class PersistedObject { /* ... */ } 
public class Person extends PersistedObject { /* ... */ } 

Ecco una versione semplificata di una classe di risorse JAX-RS:

@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(); 
    } 
} 

Il problema è che resto delle decine di classi di risorse sembrano quasi identici, con la sola differenza che operano su una diversa sottoclasse PersistedObject e sul relativo DAO corrispondente. Mi piacerebbe rimanere DRY avendo una classe di risorse in grado di supportare le operazioni CRUD su tutti i tipi di entità, presumibilmente attraverso il polimoprismo e l'iniezione intelligente del DAO. Potrebbe sembrare qualcosa di simile:

@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(); 
    } 
} 

Le questioni di cui ho bisogno per risolvere:

  • Se i metodi di risorse accettare e tornare PersistedObject, come sarà Jersey/Jackson sapere come serializzare/deserializzare? La variabile resourceType nel parametro path indica il tipo concreto che l'utente sta richiedendo, ma non so come maneggiarlo a vantaggio.
  • Quando Spring fornisce l'istanza della classe di risorse, in che modo saprà quale DAO deve essere iniettato?

Nel complesso, non sono sicuro di essere sulla strada giusta. È possibile implementarlo in modo generico?

risposta

2

Mi sono imbattuto in questo problema alcune volte. Puoi creare un endpoint generico e un PersistedObjectDao e dovrebbe funzionare tutto bene. In Hibernate, i metodi di sessione come persist(), merge() ed delete() non si preoccupano di ciò che ottiene finché si tratta di un oggetto gestito o può diventare un oggetto gestito (nel caso di unione()) . Dal momento che tu trovi solo id, e che dovrebbe essere gestito nella classe PersistedObject piuttosto che nella classe Person, la funzionalità di DAO funzionerà correttamente.

L'unico problema con questo approccio è che interrompe gli strumenti di documentazione come Enuncia e fa in modo che gli URL delle risorse richiedano identificatori univoci a livello globale./xxx/1 e/yyy/1 non possono coesistere. Il metodo findOne restituirà lo stesso oggetto per entrambi. Ciò significa che dovrai utilizzare @Inheritance (strategy = InheritanceType.JOINED) per evitare collisioni di id e creare una colonna id univoca globale su tutte le entità persistenti nel database.

Per questo motivo generalmente creo una classe AbstractPersistedObjectDAO e implemento persist(), unisci() ed elimina() e astratto il findOne() in una sottoclasse per evitare la necessità di eseguire il cast nel codice se mai dovessi farlo più di CRUD. Ma generalmente prendo solo il costo del boilerplate sugli endpoint in modo che possa produrre la documentazione REST con Enunciate e mi dà una lezione per intraprendere ulteriori metodi in futuro, se necessario.

2

Non sono sicuro se sei legato a JAX-RS in qualsiasi modo, ma la famiglia di progetti Spring Data viene fornita con il modulo Spring Data REST che espone automaticamente le entità gestite dagli archivi Spring Data in modo guidato dall'ipermedia. È basato su Spring MVC.

Quindi essenzialmente le operazioni CRUD sulle entità sono gratuite, i metodi di interrogazione esposti in modo trasparente e la possibilità di modificare e sintonizzarsi vicino a qualsiasi cosa in base alle proprie esigenze.

Ecco alcuni link utili si potrebbe desiderare di cercare ulteriori informazioni: