我如何实现这个REST API并保持DRY



我正在构建一个REST API,用于在数据库上执行CRUD操作。我的暂定堆栈是Jersey、Spring、Spring Data、JPA和Hibernate。我还使用jersey-spring来提供资源类的实例,以便Spring可以自动连接它们。

该API将支持数十个表上的CRUD操作,并附带JPA实体和由Spring Data存储库支持的dao。DAO接口族和相关的dto看起来像这样:
public interface CrudService<T extends PersistedObject> { /* ... */  }
public interface PersonService extends CrudService<Person> { /* ... */  }
public class PersistedObject { /* ... */ }
public class Person extends PersistedObject { /* ... */ }
下面是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();
    }
}

问题是这几十个资源类的其余部分看起来几乎相同,唯一的区别是它们在不同的persisteddobject子类及其相应的DAO上操作。我希望通过拥有一个可以支持所有实体类型上的CRUD操作的资源类来保持DRY,这可能是通过多态和巧妙地注入DAO实现的。它可能看起来像这样:

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

我需要解决的问题:

  • 如果资源方法接受并返回persisteddobject, Jersey/Jackson如何知道如何序列化/反序列化?path参数中的resourceType变量指示用户请求的具体类型,但我不知道如何使用它来获得任何优势。当Spring提供资源类实例时,它如何知道要注入哪个DAO ?

总的来说,我不确定我走在正确的道路上。是否有可能以通用的方式实现这一点?

我自己也遇到过几次这个问题。您可以创建一个通用端点和一个persisteddobjectdao,它应该都能正常工作。在Hibernate中,像persist()、merge()和delete()这样的会话方法并不关心它得到了什么,只要它是一个托管对象或者可以成为一个托管对象(在merge()的情况下)。由于您只根据id进行查找,并且应该在persisteddobject类而不是Person类中进行管理,因此DAO的功能将正常工作。

这种方法的唯一问题是它破坏了文档工具,如Enunciate,并使资源url需要全局唯一的id。/xxx/1和/yyy/1不能同时存在。findOne方法将为两者返回相同的对象。这意味着您必须使用@Inheritance(strategy = InheritanceType.JOINED)来避免id冲突,并在数据库中所有持久化实体之间创建一个全局唯一的id列。

因此,我通常创建一个abstractpersiststedobjectdao类,并实现persist()、merge()和delete(),并将findOne()抽象为一个子类,以避免在需要执行CRUD以外的操作时在代码中进行强制转换。但我通常只是在端点上消耗样板的成本,这样我就可以使用Enunciate生成REST文档,并且它为我提供了一个类,以便在将来需要时采用其他方法。

不确定您是否以任何方式与JAX-RS绑定在一起,但是Spring Data系列项目附带了Spring Data REST模块,该模块以超媒体驱动的方式自动公开由Spring Data存储库管理的实体。它基于Spring MVC。

因此,您基本上可以免费获得实体上的CRUD操作,查询方法透明地公开,并且能够根据您的需要调整和调优接近所有内容。

以下是一些有用的链接,您可能需要浏览以获取更多信息:

  • 参考文档
  • Spring Data REST样例项目——摘自Spring Data书。
  • Spring RESTBucks——一个更高级的示例,展示了如何使用自定义更复杂的功能来增强导出的实体,以超媒体驱动的方式实现业务流程。

最新更新