简体   繁体   English

如何实现此REST API并保持DRY?

[英]How can I implement this REST API and stay DRY?

I'm building a REST API for performing CRUD operations on a database. 我正在构建一个REST API,用于在数据库上执行CRUD操作。 My tentative stack is Jersey, Spring, Spring Data, JPA and Hibernate. 我的暂定堆栈是Jersey,Spring,Spring Data,JPA和Hibernate。 I'm also using jersey-spring to supply instances of the resource class so Spring can autowire them. 我也使用jersey-spring来提供资源类的实例,以便Spring可以自动装配它们。

The API will support CRUD operations on dozens of tables, with concomitant JPA Entities and DAOs backed by Spring Data repositories. API将支持数十个表上的CRUD操作,伴随着由Spring Data存储库支持的JPA实体和DAO。 The family of DAO interfaces and related DTOs looks something like this: DAO接口和相关DTO系列看起来像这样:

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: 这是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();
    }
}

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. 问题是几十个资源类的其余部分看起来几乎相同,唯一的区别是它们在不同的PersistedObject子类及其相应的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. 我想通过拥有一个可以支持所有实体类型的CRUD操作的资源类来保持DRY,可能是通过多态和巧妙注入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: 我需要解决的问题:

  • If resource methods accept and return PersistedObject, how will Jersey/Jackson know how to serialize/deserialize? 如果资源方法接受并返回PersistedObject,那么Jersey / Jackson将如何知道如何序列化/反序列化? The 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. path参数中的resourceType变量指示用户请求的具体类型,但我不知道如何使用它来获得任何优势。
  • When Spring supplies the resource class instance, how will it know which DAO to inject? 当Spring提供资源类实例时,它将如何知道要注入哪个DAO?

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. 您可以创建一个通用端点和一个PersistedObjectDao,它应该都可以正常工作。 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()). 在Hibernate中,会话方法(例如persist(),merge()和delete()不关心它是什么,只要它是一个托管对象或者可以成为一个托管对象(在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. 由于您只是通过id查找,并且应该在PersistedObject类而不是Person类中进行管理,因此DAO的功能可以正常工作。

The only problem with this approach is that it breaks documentation tools such as Enunciate and makes the resource urls require globally unique ids. 这种方法的唯一问题是它打破了诸如Enunciate之类的文档工具,并使资源URL需要全局唯一ID。 /xxx/1 and /yyy/1 cannot coexist. / xxx / 1和/ yyy / 1不能共存。 The findOne method will return the same object for both. findOne方法将为两者返回相同的对象。 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. 这意味着您必须使用@Inheritance(strategy = InheritanceType.JOINED)来避免id冲突,并在数据库中的所有持久化实体上创建一个全局唯一的id列。

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. 因此,我通常创建一个AbstractPersistedObjectDAO类并实现persist(),merge()和delete()并将findOne()抽象为子类,以避免在需要执行更多操作时转换代码。 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. 但是我通常只是在端点上吃掉样板的成本,这样我就可以使用Enunciate生成REST文档,如果需要的话,它会给我一个类来在将来采用其他方法。

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. 不确定您是否以任何方式与JAX-RS绑定,但Spring Data系列项目附带Spring Data REST模块,该模块以超媒体驱动的方式自动公开由Spring Data存储库管理的实体。 It's based on Spring MVC. 它基于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. 因此,您基本上可以免费获取实体上的CRUD操作,透明地公开查询方法,以及根据您的需要调整和调整所有内容的能力。

Here are some useful links you might want to browse for further information: 以下是您可能想要浏览的一些有用链接以获取更多信息:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM