JSON-RPC + IDL = Barrister RPC

Define your interface in a human readable IDL

Run barrister to convert IDL to JSON and produce docco style HTML docs for your interface

to convert IDL to JSON and produce docco style HTML docs for your interface Write your server implementation

Consume it

Overview

Barrister is a RPC system that uses an external interface definition (IDL) file to describe the interfaces and data structures that a component implements. It is similar to tools like Protocol Buffers, Thrift, Avro, and SOAP.

Barrister emphasizes:

Ease of use. You should be able to write a client and server in minutes.

You should be able to write a client and server in minutes. Interface as documentation. The interface definition should be human readable. The collection of interfaces of a system's components can be an excellent way to understand the system.

The interface definition should be human readable. The collection of interfaces of a system's components can be an excellent way to understand the system. Being idiomatic. Provide code generation for static languages which can enforce type safety at compile time. Don't use code generation for dynamic languages. Language bindings should feel natural for developers of each language.

Provide code generation for static languages which can enforce type safety at compile time. Don't use code generation for dynamic languages. Language bindings should feel natural for developers of each language. Interoperability. You shouldn't have to worry about whether your Python client will work with a Node.js server. Barrister has a conformance suite that validates all supported language bindings enforce the IDL rules uniformly.

RPC calls are encoded as JSON-RPC 2.0 requests/responses. Consequently, any JSON-RPC 2.0 client should be able to consume a Barrister RPC service, but Barrister clients are preferred as they provide client side type validation and IDL discovery.

Example

IDL

Servers java node php python ruby

Clients java node php python ruby

Output // // The Calculator service is easy to use. // // Examples // -------- // // x = calc.add(10, 30) // # x == 40 // // y = calc.subtract(44, 10) // # y == 34 interface Calculator { // Adds two numbers together and returns the result add ( a float , b float ) float // Subtracts b from a and returns the result subtract ( a float , b float ) float } package example ; public class Server implements Calculator { public Double add ( Double a , Double b ) { return a + b ; } public Double subtract ( Double a , Double b ) { return a - b ; } } package example ; import com.bitmechanic.barrister.HttpTransport ; public class Client { public static void main ( String argv []) throws Exception { HttpTransport trans = new HttpTransport ( "http://127.0.0.1:8080/example/" ); CalculatorClient calc = new CalculatorClient ( trans ); System . out . println ( String . format ( "1+5.1=%.1f" , calc . add ( 1.0 , 5.1 ))); System . out . println ( String . format ( "8-1.1=%.1f" , calc . subtract ( 8.0 , 1.1 ))); System . out . println ( "

IDL metadata:" ); // BarristerMeta is a Idl2Java generated class in the same package // as the other generated files for this IDL System . out . println ( "barrister_version=" + BarristerMeta . BARRISTER_VERSION ); System . out . println ( "checksum=" + BarristerMeta . CHECKSUM ); } } var barrister = require ( 'barrister' ); var express = require ( 'express' ); var fs = require ( 'fs' ); function Calculator () { } Calculator . prototype . add = function ( a , b , callback ) { // first param is for errors callback ( null , a + b ); }; Calculator . prototype . subtract = function ( a , b , callback ) { callback ( null , a - b ); }; var idl = JSON . parse ( fs . readFileSync ( "../calc.json" ). toString ()); var server = new barrister . Server ( idl ); server . addHandler ( "Calculator" , new Calculator ()); var app = express . createServer (); app . use ( express . bodyParser ()); app . post ( '/calc' , function ( req , res ) { server . handle ({}, req . body , function ( respJson ) { res . contentType ( 'application/json' ); res . send ( respJson ); }); }); app . listen ( 7667 ); var barrister = require ( 'barrister' ); function checkErr ( err ) { if ( err ) { console . log ( "ERR: " + JSON . stringify ( err )); process . exit ( 1 ); } } var client = barrister . httpClient ( "http://localhost:7667/calc" ); client . loadContract ( function ( err ) { checkErr ( err ); var calc = client . proxy ( "Calculator" ); calc . add ( 1 , 5.1 , function ( err , result ) { var i ; checkErr ( err ); console . log ( "1+5.1=" + result ); calc . subtract ( 8 , 1.1 , function ( err , result ) { checkErr ( err ); console . log ( "8-1.1=" + result ); console . log ( "

IDL metadata:" ); meta = client . getMeta (); keys = [ "barrister_version" , "checksum" ]; for ( i = 0 ; i < keys . length ; i ++ ) { console . log ( keys [ i ] + "=" + meta [ keys [ i ]]); } }); }); }); <?php $path = $_ENV [ "BARRISTER_PHP" ]; include_once ( " $path /barrister.php" ); class Calculator { function add ( $a , $b ) { return $a + $b ; } function subtract ( $a , $b ) { return $a - $b ; } } $server = new BarristerServer ( "../calc.json" ); $server -> addHandler ( "Calculator" , new Calculator ()); $server -> handleHTTP (); ?> <?php $path = $_ENV [ "BARRISTER_PHP" ]; include_once ( " $path /barrister.php" ); $barrister = new Barrister (); $client = $barrister -> httpClient ( "http://localhost:8080/cgi-bin/server.php" ); $calc = $client -> proxy ( "Calculator" ); echo sprintf ( "1+5.1=%.1f

" , $calc -> add ( 1 , 5.1 )); echo sprintf ( "8-1.1=%.1f

" , $calc -> subtract ( 8 , 1.1 )); echo "

IDL metadata:

" ; $meta = $client -> getMeta (); $keys = array ( "barrister_version" , "checksum" ); foreach ( $keys as $i => $key ) { echo " $key = $meta[$key]

" ; } ?> from flask import Flask , request , make_response import barrister # Our implementation of the 'Calculator' interface in the IDL class Calculator ( object ): # Parameters match the params in the functions in the IDL def add ( self , a , b ): return a + b def subtract ( self , a , b ): return a - b contract = barrister . contract_from_file ( "../calc.json" ) server = barrister . Server ( contract ) server . add_handler ( "Calculator" , Calculator ()) app = Flask ( __name__ ) @app.route ( "/calc" , methods = [ "POST" ]) def calc (): resp_data = server . call_json ( request . data ) resp = make_response ( resp_data ) resp . headers [ 'Content-Type' ] = 'application/json' return resp app . run ( host = "127.0.0.1" , port = 7667 ) import barrister trans = barrister . HttpTransport ( "http://localhost:7667/calc" ) # automatically connects to endpoint and loads IDL JSON contract client = barrister . Client ( trans ) print "1+5.1= %.1f " % client . Calculator . add ( 1 , 5.1 ) print "8-1.1= %.1f " % client . Calculator . subtract ( 8 , 1.1 ) print print "IDL metadata:" meta = client . get_meta () for key in [ "barrister_version" , "checksum" ]: print " %s = %s " % ( key , meta [ key ]) # not printing this one because it changes per run, which breaks our # very literal 'examples' test harness, but let's verify it exists at least.. assert meta . has_key ( "date_generated" ) require 'sinatra' require 'barrister' class Calculator def add ( a , b ) return a + b end def subtract ( a , b ) return a - b end end contract = Barrister :: contract_from_file ( "../calc.json" ) server = Barrister :: Server . new ( contract ) server . add_handler ( "Calculator" , Calculator . new ) post '/calc' do request . body . rewind resp = server . handle_json ( request . body . read ) status 200 headers "Content-Type" => "application/json" resp end require 'barrister' trans = Barrister :: HttpTransport . new ( "http://localhost:7667/calc" ) # automatically connects to endpoint and loads IDL JSON contract client = Barrister :: Client . new ( trans ) puts "1+5.1=%.1f" % client . Calculator . add ( 1 , 5 . 1 ) puts "8-1.1=%.1f" % client . Calculator . subtract ( 8 , 1 . 1 ) puts puts "IDL metadata:" meta = client . get_meta [ "barrister_version" , "checksum" ]. each do | key | puts " #{ key } = #{ meta [ key ] } " end 1+5.1=6.1 8-1.1=6.9 IDL metadata: barrister_version=0.1.3 checksum=51a911b5eb0b61fbb9300221d8c37134

To convert calc.idl to JSON and HTML forms, run:

barrister -t "Calculator Interface" -d calc.html -j calc.json calc.idl

Or use the hosted translator (this will only output the JSON file, not the docs):

curl --data-urlencode idl@calc.idl http://barrister.bitmechanic.com/run > calc.json

Output files:

Nifty, now what?