Skip to content
This repository has been archived by the owner on Sep 28, 2021. It is now read-only.

Commit

Permalink
Merge pull request #89 from rouzwawi/entity
Browse files Browse the repository at this point in the history
Add apollo-entity middleware
  • Loading branch information
pettermahlen committed Apr 13, 2016
2 parents b1cbb16 + b49865e commit f4cf3d6
Show file tree
Hide file tree
Showing 14 changed files with 1,440 additions and 5 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Apollo
======

[![Circle Status](https://circleci.com/gh/spotify/apollo.svg?style=shield&circle-token=5a9eb086ae3cec87e62fc8b6cdeb783cb318e3b9)](https://circleci.com/gh/spotify/apollo)
[![Coverage Status](https://coveralls.io/repos/spotify/apollo/badge.svg?branch=master&service=github)](https://coveralls.io/github/spotify/apollo?branch=master)
[![Maven Central](https://img.shields.io/maven-central/v/com.spotify/apollo-parent.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.spotify%22%20apollo*)
[![License](https://img.shields.io/github/license/spotify/apollo.svg)](LICENSE.txt)

Apollo
======

Apollo is a set of Java libraries that we use at Spotify when writing microservices. Apollo includes modules such as an HTTP server and a URI routing system, making it trivial to implement restful API services.

Apollo has been used in production at Spotify for a long time. As a part of the work to release version 1.0.0 we are moving the development of Apollo into the open. Please note that the API and documentation might change prior to the stable 1.0.0 open source release.
Expand Down
12 changes: 11 additions & 1 deletion apollo-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@
<artifactId>apollo-extra</artifactId>
<version>1.0.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.spotify</groupId>
<artifactId>apollo-entity</artifactId>
<version>1.0.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.spotify</groupId>
<artifactId>apollo-http-service</artifactId>
Expand Down Expand Up @@ -136,6 +141,11 @@
<artifactId>config</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>io.javaslang</groupId>
<artifactId>javaslang</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
Expand Down Expand Up @@ -170,7 +180,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
<version>1.7.16</version>
</dependency>

<dependency>
Expand Down
141 changes: 141 additions & 0 deletions apollo-entity/README.md
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
86 changes: 86 additions & 0 deletions apollo-entity/pom.xml
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>
Loading

0 comments on commit f4cf3d6

Please sign in to comment.