This repository has been archived by the owner on Sep 28, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 209
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #89 from rouzwawi/entity
Add apollo-entity middleware
- Loading branch information
Showing
14 changed files
with
1,440 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
Apollo Entity | ||
============= | ||
|
||
A set of Apollo Middleware for working with entity types in route handlers. | ||
|
||
## Why | ||
|
||
A common pattern when writing route handlers is to serialize and deserialize to some API level | ||
entity types. Writing such (de)serialization code quickly becomes repetitive and always looks | ||
the same. | ||
|
||
Ideally, one would like to have endpoint handlers with straight forward signatures like | ||
|
||
```java | ||
Person updatePerson(String id, Person person) { | ||
// ... | ||
} | ||
``` | ||
|
||
With a route defined as: | ||
|
||
```java | ||
Route.sync( | ||
"PUT", "/person/<id>", | ||
rc -> person -> updatePerson(rc.pathArgs().get("id"), person)) | ||
``` | ||
|
||
However, this does not work. Theres nothing handling how the `Person` type is being read to and | ||
from a `ByteString` which is the payload type Apollo works with. | ||
|
||
At this point, you'll start writing some custom middlewares using something like Jackson. This | ||
library contains exactly those middlewares, ready to use directly with your routes. | ||
|
||
|
||
## Getting started | ||
|
||
Add Maven dependency: | ||
|
||
```xml | ||
<dependency> | ||
<groupId>com.spotify</groupId> | ||
<artifactId>apollo-entity</artifactId> | ||
<version>1.0.5</version> | ||
</dependency> | ||
``` | ||
|
||
_See the [project skeleton] docs for how to use a Maven BOM for dependency versions_ | ||
|
||
Create your Jackson `ObjectMapper` and the [`EntityMiddleware`][1] factory: | ||
|
||
```java | ||
class MyApplication { | ||
|
||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
|
||
static void init(Environment environment) { | ||
MyApplication app = new MyApplication(); | ||
EntityMiddleware entity = EntityMiddleware.forCodec(JacksonEntityCodec.forMapper(OBJECT_MAPPER)); | ||
|
||
Route<SyncHandler<Response<ByteString>>> route = Route.with( | ||
entity.direct(Person.class), | ||
"PUT", "/person/<id>", | ||
rc -> person -> app.updatePerson(rc.pathArgs().get("id"), person)); | ||
|
||
// ... | ||
} | ||
|
||
Person updatePerson(String id, Person person) { | ||
// ... | ||
} | ||
} | ||
``` | ||
|
||
[`EntityMiddleware`][1] also support custom codecs through the `forCodec(EntityCodec)` static | ||
constructor. An [`EntityCodec`][2] defines how to read and write entity types from the `ByteString` | ||
payloads that apollo handle natively. Since Jackson is so commonly used, we have a bundled | ||
implementation called [`JacksonEntityCodec`][4]. | ||
|
||
## Handler signatures | ||
|
||
The [`EntityMiddleware`][1] interface can create middlewares for several types of handler | ||
signatures. | ||
|
||
* `direct(E.class[, R.class])` middleware : `EntityHandler<E, R>` | ||
- `R handler(E)` | ||
* `response(E.class[, R.class])` middleware : `EntityResponseHandler<E, R>` | ||
- `Response<R> handler(E)` | ||
* `asyncDirect(E.class[, R.class])` middleware : `EntityAsyncHandler<E, R>` | ||
- `CompletionStage<R> handler(E)` | ||
* `asyncResponse(E.class[, R.class])` middleware : `EntityAsyncResponseHandler<E, R>` | ||
- `CompletionStage<Response<R>> handler(E)` | ||
|
||
For all of the above, if the reply entity type `R.class` is omitted it will be set to the same as `E.class`. | ||
|
||
On top of these, there's also just a serializing middleware for handlers that do not take a | ||
request entity as an input. | ||
|
||
* `serializerDirect(R.class)` middleware : `SyncHandler<R>` | ||
- `R handler()` | ||
* `serializerResponse(R.class)` middleware : `SyncHandler<Response<R>>` | ||
- `Response<R> handler()` | ||
* `asyncSerializerDirect(R.class)` middleware : `AsyncHandler<R>` | ||
- `CompletionStage<R> handler()` | ||
* `asyncSerializerResponse(R.class)` middleware : `AsyncHandler<Response<R>>` | ||
- `CompletionStage<Response<R>> handler()` | ||
|
||
### Curried form | ||
|
||
All of the `Entity*Handler<E, R>` handler signatures are in curried form with a `RequestContext` | ||
as the first argument. This give you the flexibility to add more arguments to your handlers as you | ||
see fit, while also being easy to compose. | ||
|
||
```java | ||
rc -> entity -> handler(entity, ...) | ||
``` | ||
|
||
Works well with closures around handlers: | ||
|
||
```java | ||
rc -> updatePersonWithId(rc.pathArgs().get("id")) | ||
|
||
UnaryOperator<Person> updatePersonWithId(String id) { | ||
return person -> ...; | ||
} | ||
``` | ||
|
||
In the case nothing else is needed from the request context, this reduces to: | ||
|
||
```java | ||
rc -> this::handler | ||
``` | ||
|
||
--- | ||
|
||
See [`EntityMiddlewareTest`][3] for a complete list of route options and tests. | ||
|
||
[1]: src/main/java/com/spotify/apollo/entity/EntityMiddleware.java | ||
[2]: src/main/java/com/spotify/apollo/entity/EntityCodec.java | ||
[3]: src/test/java/com/spotify/apollo/entity/EntityMiddlewareTest.java | ||
[4]: src/main/java/com/spotify/apollo/entity/JacksonEntityCodec.java | ||
[project skeleton]: https://github.com/spotify/apollo/tree/master/apollo-http-service#maven |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>com.spotify</groupId> | ||
<artifactId>apollo-parent</artifactId> | ||
<version>1.0.5-SNAPSHOT</version> | ||
<relativePath>../</relativePath> | ||
</parent> | ||
|
||
<name>Spotify Apollo Entity Middleware</name> | ||
<artifactId>apollo-entity</artifactId> | ||
<packaging>jar</packaging> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>com.spotify</groupId> | ||
<artifactId>apollo-bom</artifactId> | ||
<version>1.0.5-SNAPSHOT</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.spotify</groupId> | ||
<artifactId>apollo-api</artifactId> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>io.javaslang</groupId> | ||
<artifactId>javaslang</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-databind</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-api</artifactId> | ||
</dependency> | ||
|
||
<!-- test dependencies --> | ||
<dependency> | ||
<groupId>com.spotify</groupId> | ||
<artifactId>apollo-test</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.norberg</groupId> | ||
<artifactId>auto-matter</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.norberg</groupId> | ||
<artifactId>auto-matter-jackson</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>ch.qos.logback</groupId> | ||
<artifactId>logback-classic</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.hamcrest</groupId> | ||
<artifactId>hamcrest-library</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.jayway.jsonpath</groupId> | ||
<artifactId>json-path-assert</artifactId> | ||
<version>2.2.0</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
Oops, something went wrong.