Skip to content

andrewoma/restless

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Restless Overview

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)

Build Status

Example

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.

Call echo:
exampleService.echo("Andrew", "Hello")

Request:

POST /example/echo
{"name":"Andrew","message":"Hello"}

Response:

Status: 200
"Hello Andrew! You said 'Hello'"
Create a Foo:
exampleService.createFoo(new Foo("myBar", 50))

Request:

POST /example/create-foo
{"foo":{"bar":"myBar","baz":50}}

Response:

Status: 200
1
Find a Foo:
Foo foo = exampleService.getFoo(1L);

Request:

POST /example/get-foo
{"id":1}

Response:

Status: 200
{"bar":"myBar","baz":50}
Find a Foo that doesn't exist:

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"}
Attempt to create an invalid Foo:

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"
    }
  ]
}

Status

Proof of concept, under development

Roadmap

  • 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)

License

This project is licensed under a MIT license.

About

An RPC framework for the JVM

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages