JSON RPC examples - apinazo/booter GitHub Wiki
JSON-RPC could be more useful than REST in inter-service communications, since it will reduce the traffic overload and gives a simple way to expose services, avoiding all the REST verbosity.
Here is an example of how to use it, as it's implemented in Booter.
I'll be using the third party library jsonrpc4j as it's easy and simple. Just add this dependency to the pom.xml
.
<dependency>
<groupId>com.github.briandilley.jsonrpc4j</groupId>
<artifactId>jsonrpc4j</artifactId>
<version>1.5.3</version>
</dependency>
First of all, let's define the service. In order for this to work, there must be both an interface and an implementations. It won't work with a single class.
The interface just define the service's methods.
public interface SampleRPCService {
String sayHello(@JsonRpcParam(value = "name") String name);
}
The @JsonRpcParam(value = "name")
gives a name to the parameter, in the JSON-RPC requests.
The implementation of the service could be this one:
@Service
public class SampleRPRCServiceImpl implements SampleRPCService {
public String sayHello(String name) {
return "Hello, " + name;
}
}
Note that the service is declared as a Spring bean
, annotated with @Service
. This is mandatory to inject it as shown later.
The configuration is a normal @Configuration
, defining a @Bean
for each service class to expose through JSON-RPC. The bean will be of type JsonServiceExporter
and will be built using the service interface declared above, so the @Service
implementation is injected.
@Configuration
public class RPCConfiguration {
@Bean(name = "/rpc/sample")
public JsonServiceExporter jsonServiceExporter(SampleRPCService sampleRPCService) {
JsonServiceExporter exporter = new JsonServiceExporter();
exporter.setService(sampleRPCService);
exporter.setServiceInterface(SampleRPCService.class);
return exporter;
}
}
The name = "/rpc/sample"
declares the path the service will have in the context.
Once the app is up, consuming the service is very similar to doing so with a REST service.
- The path is the one defined in the bean. In this example:
/rpc/sample
. - The HTTP method will always be POST.
- The payload will content the name of the remote service and its params.
Example request:
curl -v -H "Content-Type: application/json" -d "{\"id\":0, \"method\":\"sayHello\", \"params\":[\"John Doe\"]}" http://localhost:8001/rpc/sample
Same request, using named params:
curl -i -H "Content-Type: application/json" -d "{\"id\":0, \"method\":\"sayHello\", \"params\":{\"name\":\"John Doe\"} }" http://localhost:8001/rpc/sample
The response to the request will be something like this:
{"jsonrpc":"2.0","id":0,"result":"Hello, John Doe"}
Unit testing of the example makes no sense since it is ver simple. But integration tests are very interesting to demonstrate how JSON-RPC works. Some are implemented in the SampleRPRCServiceIntegrationTest
class.
The tests uses RestAssured as a HTTP test client, like in this example:
@Test
public void testSayHello() {
// Build the data to send to the service.
Map<String,Object> payload = new HashMap<>();
payload.put("id", "0"); // id is mandatory.
payload.put("method", "sayHello");
payload.put("params", new ArrayList<>(Arrays.asList(userName)));
// Do the request.
given()
.filter(new RequestLoggingFilter()) // Log request details.
.filter(new ResponseLoggingFilter()) // Same for response.
.contentType("application/json")
.body(payload) // Auto converted to JSON.
.when()
.post("http://localhost:" + serverPort + "/rpc/sample")
.then()
.statusCode(200)
.body(containsString(userName));
}