2016-12-30 4 views
0

Ich denke, das ist eine generische Java-Frage, aber ich werde erklären, was ich versuche zu tun und hoffentlich kann mir jemand den richtigen Weg zeigen;Dropwizard Zusammenfassung Ressource Design

Ich versuche eine generische abstrakte Klasse zu erstellen, von der alle meine Ressourcen profitieren können.

Die abstrakte Klasse hat grundlegende CRUD Implementierungen für die Standard-Sachen

@Produces("application/vnd.api+json") 
@Consumes("application/vnd.api+json") 
public abstract class AbstractResource { 

    static final Logger LOGGER = LoggerFactory.getLogger(AbstractResource.class); 

    AbstractRepository repository; 

    AbstractResource(AbstractRepository repository) { 
     this.repository = repository; 
    } 

    @GET 
    public Response getAll(@Auth User user, @QueryParam("query") String query) { 
     String result = query != null ? repository.getByQuery(query) : repository.getAll(); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @GET 
    @Path("/{id}") 
    public Response getById(@Auth User user, @PathParam("id") String id) { 
     String result = repository.getById(id); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @POST 
    public Response save(@Auth User user, String payload) { 
     String result = repository.save(payload); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @PATCH 
    @Path("/{id}") 
    public Response update(@Auth User user, @PathParam("id") String id, String payload) { 
     String result = repository.update(payload); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @DELETE 
    @Path("/{id}") 
    public Response delete(@Auth User user, @PathParam("id") String id) { 
     repository.delete(id); 
     return Response.status(Response.Status.NO_CONTENT).build(); 
    } 

} 

Ich kann dies einfach ohne Probleme verwenden

@Path("/movies") 
public class MovieResource extends AbstractResource { 
    public MovieResource(MovieRepository repository) { 
     super(repository); 
    } 
} 

tun und ich kann nun alle Methoden zugreifen und außer Kraft setzen, wie erforderlich .

Wo ich auf Probleme stoße ist, wenn ich eine Methode überladen muss. Nehmen Sie die erste getAll Methode von der abstrakten Klasse als Beispiel möchte ich in die Parameter ändern, dass nur die Movie.class

@Path("/movies") 
public class MovieResource extends AbstractResource { 

    public MovieResource(MovieRepository repository) { 
     super(repository); 
    } 

    @GET 
    public Response getAll(@Auth User user, @QueryParam("query") String query, @QueryParam("limit") String limit, @QueryParam("page") String page) { 
     String result = repository.getPaginated(limit, page); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

} 

So ist die getAll Methode hat einen anderen Satz von Parametern in genau der Movie.class. Dies bewirkt, dass Jersey sprengen mit

[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public javax.ws.rs.core.Response space.cuttlefish.domain.resources.MovieResource.getAll(space.cuttlefish.domain.model.User,java.lang.String,java.lang.String,java.lang.String) and public javax.ws.rs.core.Response space.cuttlefish.domain.resources.AbstractResource.getAll(space.cuttlefish.domain.model.User,java.lang.String) at matching regular expression /movies. These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='[email protected]'] 

Da die ursprünglichen getAll Methode der abstrakten bereits die @GET Annotation hat.

Also, wie gehe ich über die Lösung dieses Problems?

Lösche ich alle Anmerkungen aus der abstrakten Klasse und muss dann die Anmerkungen in jeder Ressource überschreiben und erneut hinzufügen? Das scheint einfach chaotisch und fehleranfällig ... Hier muss es eine bessere Lösung geben?

Gibt es etwas blendend offensichtliche, das ich gerade übersehen habe?

Ich würde gerne etwas Hilfe!

+0

Wenn Sie nicht alle Subklassen wollen die getAll Methode mit der kürzeren Signatur haben, sollte es nicht in der abstrakten Basisklasse sein. Die geerbte Methode verursacht hier sicherlich Probleme. Möglicherweise möchten Sie der Frage ein Jersey- oder JAX-RS-Tag hinzufügen. – JayK

+0

Danke für den Tag Tipp; Hmm, das ist, was ich im Moment gemacht habe, scheint albern, obwohl ich den gleichen Code in 20 verschiedene Klassen kopiere, um den Fall zu lösen, wo nur eine Klasse die Dinge anders behandelt. –

+0

Ein Mixin-Mechanismus wäre sicher schön. Eine sehr vage Antwort wäre "Komposition über Vererbung", aber da ich JAX-RS bisher noch nie benutzt habe, weiß ich nicht, ob dieses Mantra hier auf eine nette Art und Weise angewendet werden kann. – JayK

Antwort

1

Ich empfehle die Verwendung von Generika.

Wir haben eine ähnliche, aber ziemlich komplexe Version davon erreicht.Es war ein bisschen schwierig, es am Anfang richtig zu machen, aber wir hatten maximale Code-Wiederverwendbarkeit (mit Java) und einfach zu lesen/beitragen Code.

public abstract class AbstractResource<T extends AbstractObject, K extends AbstractObjectDto> { 

    static final Logger LOGGER = LoggerFactory.getLogger(AbstractResource.class); 

    AbstractRepository<T> repository; 
    // We have used modelmapper library to automatically convert DTO objects to database objects. But you can come up with your own solution for that. I.E implementing conversion logic on each DTO and database classes. 
    ModelMapper modelMapper = new ModelMapper(); 

    // With Java Generics, one cannot access the class type directly by simply calling 'K.class'. So you need to pass the class types explicitly as well. That is if you're using modelmapper. 
    private final Class<T> objectClass; 
    private final Class<K> objectDtoClass; 

    AbstractResource(AbstractRepository<T> repository, Class<T> objectClass, Class<K> objectDtoClass) { 
     this.repository = repository; 
     this.objectClass = objectClass; 
     this.objectDtoClass = objectDtoClass; 
    } 

    ... 

    @POST 
    public K save(@Auth User user, @Valid K payload) { 
     T databaseObject = modelmapper.map(payload, objectClass); 
     T result = repository.save(databaseObject); 
     K resultDto = modelMapper.map(result, objectDtoClass); 
     retun resultDto; 
    } 
    ... 
} 

Dann müssen Sie ein Repository-Klasse erstellen, die die notwendigen Methoden wie save hat, getPaginated usw. für jeden Objekttyp, zwingende AbstractRepository. Und natürlich sollte MovieAbstractObject Klasse erweitern, und MovieDto sollte AbstractObjectDto Klasse erweitern.

public class MovieRepository extends AbstractRepository<Movie> { 
    .... 
    Movie save(Movie movie) {...} 
} 

Und der Rest ist so einfach wie diese:

@Path("/movies") 
public class MovieResource extends AbstractResource<Movie, MovieDto> { 

    public MovieResource(MovieRepository repository) { 
     super(repository, Movie.class, MovieDto.class); 
    } 
} 
+0

Danke, das ist, was ich gehofft hatte, um möglich zu sein –

0

Der Grund, warum es für Sie fehlschlägt, ist, dass in Ihrem Beispiel mehrere Methoden demselben URL-Pfad zugeordnet sind. Aber wenn Sie nur eine Methode außer Kraft setzen Jersey wird sich nicht beschweren.

ich generische Methoden in Ihrem Abstract haben würde empfehlen, wo man entweder @Context UriInfo uriInfo auf Ihre Methode übergeben und seine Abfrage params in einem allgemeinen Dienstprogramm Methode analysieren, oder Parameter so etwas wie Matrix über

@Path("/{segment: .*}") 
@GET 
@Produces("application/json") 
public Response getAll(@PathParam("segment") PathSegment segment) 
... 

verwenden und analysieren sie wieder über eine generische Standardmethode oder eine Kombination von beiden.

Auf diese Weise können Sie in vielen Fällen den gemeinsamen Endpunkt festlegen oder eine benutzerdefinierte Vorverarbeitung durchführen und an typische Analysemethoden für typische Anwendungsfälle delegieren.

Wenn ich Sie recht etwas bekam, wie man wollte im folgenden Projekt versucht wurde: https://github.com/researchgate/restler (Disclaimer: Ich bin ein Beitrag dort)

Verwandte Themen