therapi

JSON-RPC in a Nutshell

JSON-RPC 2.0 is a standard protocol for making remote procedure calls. The client makes a request to the server by passing a JSON object specifying the method name and arguments. The server returns a JSON object containing the method’s return value or a message describing an error condition. It’s simple, and it works! Take a moment to peruse the brief specification.

Therapi is JSON-RPC for Java

Therapi is a Java library that uses Jackson data binding to seamlessly expose your Java services over JSON-RPC. Annotated interfaces define the methods you want to expose. Method arguments and return values can be any object serializable by Jackson (including complex data types). Registering a service POJO with Therapi adds its methods to your JSON-RPC API. If you’re using Spring, Therapi can scan your application context and automatically register any service beans it discovers.

Bells and Whistles

  • Because Therapi knows the method signatures of your API methods, it can automatically generate API documentation that incorporates Javadoc from your service methods.

  • Because your API is defined by Java interfaces, Therapi can use dynamic proxies to implement a Java client library for your API.

  • AOP Alliance method interceptors may be selectively applied to methods, making it easy to enforce authentication requirements, monitor response times, and apply Bean Validation.

A Quick Example

Let’s make a web service for multiplying a list of fractions together. It will operate on a model called Fraction. Here’s what the service interface looks like:

package com.github.therapi.example;

import com.github.therapi.core.annotation.ExampleModel;
import com.github.therapi.core.annotation.Remotable;

import java.util.List;

@Remotable("calculator")
public interface CalculatorService {
    /**
     * Multiplies a list of fractions together and returns the result.
     * If the list is empty, returns 1/1.
     *
     * @param multiplicands The fractions to multiply together
     * @return The result of multiplying the given fractions together
     */
    Fraction multiplyFractions(List<Fraction> multiplicands);

    /**
     * Fractional representation of a rational number.
     */
    class Fraction {
        private final int numerator;
        private final int denominator;

        public Fraction(int numerator, int denominator) {
            this.numerator = numerator;
            this.denominator = denominator;
        }

        public int getNumerator() {
            return numerator;
        }

        public int getDenominator() {
            return denominator;
        }

        public Fraction multiplyBy(Fraction other) {
            return new Fraction(numerator * other.numerator,
                    denominator * other.denominator);
        }
    }

    /**
     * A decent approximation of π.
     */
    @ExampleModel
    static Fraction exampleFraction() {
        return new Fraction(22, 7);
    }
}

Notice that the interface is annotated as @Remotable. This is what tells Therapi that the interface methods should be included in your JSON-RPC API. It also assigns a namespace prefix to the methods, in this case calculator. Consequently, the method will be exported as calculator.multiplyFractions.

The @ExampleModel annotation tells Therapi that when it generates the documentation for the Fraction model it should include the serialized JSON form of the object returned by this method.

Now let’s implement the CalculatorService interface:

package com.github.therapi.example;

import java.util.List;

public class CalculatorServiceImpl implements CalculatorService {
    private static final Fraction ONE = new Fraction(1, 1);

    @Override
    public Fraction multiplyFractions(List<Fraction> multiplicands) {
        return multiplicands.stream().reduce(ONE, Fraction::multiplyBy);
    }
}

No surprises there — just standard Java.

Invoking the method via JSON-RPC

Here’s the JSON-RPC2 request the client sends to invoke the method:

{
  "jsonrpc": "2.0",
  "method": "calculator.multiplyFractions",
  "params": {
    "multiplicands": [
      {"numerator": 2, "denominator": 5},
      {"numerator": 3, "denominator": 7}
    ]
  },
  "id": 1
}

And the server response:

{
  "jsonrpc": "2.0",
  "result": {"numerator": 6, "denominator": 35},
  "id": 1
}