Restless is an RPC framework for the JVM.
Key features:
- Restless defines RPC interfaces in terms of standard Java interfaces and POJOs (like RMI or Spring Remoting)
- The wire format is plain JSON exposed over HTTP, allowing easy integration via the browser, other platforms or debugging
- Interceptors allow for generic implementations of cross-cutting concerns such as security, validation and logging
- Exception handling is completely customisable with a predefined set for common use cases.
- Supports binary streaming of requests and responses
- Supports streaming of POJOs (TODO)
An RPC service is defined using plain Java interfaces and objects.
@Service("example")
public interface ExampleService {
String echo(@NotNull @Named("name") String name,
@NotNull @Named("message") String message);
long createFoo(@Valid @Named("foo") Foo foo);
Foo getFoo(@NotNull @Named("id") Long id);
}
public class Foo {
@NotBlank
public final String bar;
@Min(0)
@Max(100)
public final Integer baz;
}
A service implementation is just a POJO that implements the service interface. This can be created directly or exposed via the DI framework of your choice.
public class ExampleServiceImpl implements ExampleService {
private Map<Long, Foo> foos = new ConcurrentHashMap<Long, Foo>();
private AtomicLong fooId = new AtomicLong(0);
@Override
public String echo(String name, String message) {
return "Hello " + name + "! You said '" + message + "'";
}
@Override
public long createFoo(Foo foo) {
long id = fooId.incrementAndGet();
foos.put(id, foo);
return id;
}
@Override
public Foo getFoo(Long id) {
Foo foo = foos.get(id);
if (foo == null) {
throw new NotFoundException(Contexts.get().getRequestId(), "Foo with id '" + id + "' does not exist", null);
}
return foo;
}
}
A server endpoint is exposed via RestlessServlet
that delegates to a ServerHandler
that in
turn delegates to the relevant POJO service implementations for each request.
return new ServerHandlerBuilder()
.interceptor(new ValidationInterceptor(Validation.buildDefaultValidatorFactory().getValidator()))
.service(new ExampleServiceImpl())
.build();
A client is created via a HttpClientBuilder
, creating a proxy to the given interface that invokes
the service over HTTP.
return new HttpClientBuilder()
.uri(uri)
.build(ExampleService.class);
}
Now we've defined a client and server, we can try some RPC calls.
exampleService.echo("Andrew", "Hello")
Request:
POST /example/echo
{"name":"Andrew","message":"Hello"}
Response:
Status: 200
"Hello Andrew! You said 'Hello'"
exampleService.createFoo(new Foo("myBar", 50))
Request:
POST /example/create-foo
{"foo":{"bar":"myBar","baz":50}}
Response:
Status: 200
1
Foo foo = exampleService.getFoo(1L);
Request:
POST /example/get-foo
{"id":1}
Response:
Status: 200
{"bar":"myBar","baz":50}
Returns a status of 404 and an exception payload that becomes a NotFoundException:
exampleService.getFoo(666L);
Request:
POST /example/get-foo
{"id":666}
Response:
Status: 404
{"id":"KvqDEBn5TIz4QxvKSPpr","code":"NOT_FOUND","message":"Foo with id '666' does not exist"}
Returns field level validation errors:
exampleService.createFoo(new Foo(" ", 500));
Request:
POST /example/create-foo
{"foo":{"bar":" ","baz":500}}
Response:
Status: 400
{
"id": "WaX0Iqa7lj5fFLEgdIce",
"code": "VALIDATION_ERROR",
"message": "foo.bar may not be empty; foo.baz must be less than or equal to 100",
"fieldErrors": [
{
"field": "foo.bar",
"message": "may not be empty"
},
{
"field": "foo.baz",
"message": "must be less than or equal to 100"
}
]
}
Proof of concept, under development
- General cleanup and test coverage
- Support parameters in any order for non-JVM clients
- Support inlining of request parameter fields if there is only one request parameter
- Support streaming of POJOs in addition to byte streams
- Support non-chunked encoding for requests that don't involve streaming
- Expose headers in ByteStreams
- Automatically generate Swagger API specification for the API
- Move ValidationInterceptor out of examples into a real module
- Move Shiro SecurityInterceptor out of examples into a real module
- Add a standard logging interceptor (client and server)
- Add interceptors for request and correlation ids
- Support GET requests with URL encoded JSON (and possibly O-Rison: https://github.com/Nanonid/rison)
This project is licensed under a MIT license.