SlideShare a Scribd company logo
Introducing
Automorph
Simple and powerful RPC for Scala
Martin Ockajak
Software Engineer, Zürich
Outline
●
Overview
Goals, functionality, configuration, plugins, artifacts
●
Features
●
Customization, abstractions, escape hatches
●
Integration
Effect types, message formats, transport layers
●
Web-RPC
REST without boilerplate
Overview
Functionality
●
RPC library
Client, server, endpoint
API discovery
●
Platforms
Scala 2.13 & 3.3+
ScalaJS (in development)
●
Compile-time bindings generation
From public methods of an API trait or instance
Design Goals
●
Flexibility, type-safety, performance
●
No boilerplate
●
Minimal dependencies - SLF4J API
●
Dynamic message payload
●
Transport protocol metadata
●
Customization with sensible defaults
●
Easy integration with existing technology
Plugin Types
●
RPC protocol
JSON-RPC, Web-RPC
●
Effect system
Asynchronous, synchronous, monadic
●
Message codec
JSON, MessagePack
●
Transport layer
HTTP, WebSocket, AMQP
Server Usage
// Define a remote API
trait Api:
def hello(n: Int): Future[String]
// Create server implementation of the remote API
val service = new Api:
def hello(n: Int): Future[String] =
Future(s"Hello world $n")
// Expose the server API implementation to be called remotely
val apiServer = server.bind(service)
Server Initialization
import automorph.Default
// Configure JSON-RPC HTTP & WebSocket server
val server = Default.rpcServer(9000, "/api")
// Expose the server API implementation to be called remotely
val apiServer = server.bind(service).discovery(true)
// Start the JSON-RPC server
Await.result(activeServer <- apiServer.init(), Duration.Inf)
// Stop the JSON-RPC server
activeServer.close()
Client Usage
1
// Define a remote API
trait Api:
def hello(n: Int): Future[String]
// Create a type-safe local proxy for the remote API from an API trait
val remoteApi = client.bind[Api]
// Call the remote API function via the local proxy
remoteApi.hello(1)
// Call the remote API function dynamically without using the API trait
client.call[String]("hello")("n" -> 1)
Client Initialization
1
import automorph.Default
// Configure JSON-RPC HTTP client
val client = Default.rpcClient(URI("http://localhost:9000/api"))
// Create a type-safe local proxy for the remote API from the API trait
val remoteApi = client.bind[Api]
// Initialize the JSON-RPC client
Await.result(activeClient <- client.init(), Duration.Inf)
// Close the JSON-RPC client
activeClient.close()
Features
1
1
Dynamic payload
1
import io.circe.Json
// Define client view of a remote API
trait Api:
def hello(who: String, n: Json): Future[Json]
// Call the remote API function a proxy instance
remoteApi.hello("test", Json.fromInt(1))
// Call the remote API function dynamically without an API trait
client.call[Seq[Int]]("hello")("who" -> Json.fromInt(0), "n" -> 1)
Transport Layer Metadata
●
Request context
Modeled via optional implicit parameter
●
Response context
Modeled via optional wrapper data type
●
Completely general
Context type is determined by the transport layer plugin
1
Server Request Metadata
1
import automorph.Default.ServerContext
// Create server implementation of the remote API
class Service:
// Accept HTTP request context from from server transport plugin
def hello(message: String)(using request: ServerContext): String =
Seq(
Some(message),
request.path,
request.header("X-Test")
).flatten.mkString(",")
Server Call Filter
1
import automorph.Default.ServerContext
import automorph.RpcCall
// Customize RPC request filtering to reject unauthenticated calls
val filterCall = (call: RpcCall[ServerContext]) =>
call.context.authorization("Bearer") match
case Some("valid") => None
case _ if call.function == "public" => None
case _ => Some(IllegalAccessException("Authentication failed"))
// Initialize server with API with custom request filter
val apiServer = server.bind(service).callFilter(filterCall)
Client Request Metadata
1
import automorph.Default.ClientContext
// Create HTTP request context for client transport plugin
given request: ClientContext = client.context
.parameters("test" -> "value")
.header("X-Test" -> "value")
.cookies("Example" -> "value")
.authorization("Bearer", "value")
// Call the remote API function using given HTTP request metadata
val remoteApi = client.bind[Api]
remoteApi.hello("test")
Server Response Metadata
1
import automorph.Default.ServerContext
import automorph.RpcResult
import automorph.transport.http.HttpContext
// Create server implementation of the remote API
class Service:
// Return HTTP response context for the server transport plugin
def hello(message: String): RpcResult[String, ServerContext] =
RpcResult(
message,
HttpContext().header("X-Test" -> "value").statusCode(200)
)
Server Function Names
1
// Customize served API to RPC function name mapping
val mapName = (name: String) => name match
// 'test' is exposed both as 'hello' and 'hi'
case "test" => Seq("hello", "hi")
// 'hidden' is not exposed
case "hidden" => Seq.empty
// 'sum' is exposed as 'test.sum'
case other => Seq(s"test.$other")
// Initialize JSON-RPC HTTP & WebSocket server
val server = Default.rpcServer(9000, "/api").bind(service, mapName)
Client Function Names
2
// Customize local proxy API to RPC function name mapping
val mapName = (name: String) => name match
// Calling 'hi' translates to calling 'hello'
case "hi" => "hello"
// Other calls remain unchanged
case other => other
// Call the remote API function via a local proxy
val remoteApi =
Default.rpcClient(new URI("http://localhost:9000/api")).init()
.bind[Api](mapName)
remoteApi.hello(1)
remoteApi.hi(1)
HTTP Status Code
2
// Create server implementation of the remote API
val service = new Api:
def hello(n: Int): Future[String] =
Future.failed(new SQLException("Bad request"))
// Customize remote API server exception to HTTP status code mapping
val mapException = (error: Throwable) => error match
case _: SQLException => 400
case e => HttpContext.defaultExceptionToStatusCode(e)
// Initialize JSON-RPC HTTP & WebSocket server
val server = Default.rpcServer(
9000, "/api", mapException = mapException
).bind(service)
Integration
2
2
Default plugins
●
RPC protocol
JSON-RPC, Web-RPC
●
Message codec
Circe
●
HTTP & WebSocket client
JRE HTTP client
●
HTTP & WebSocket server
Undertow
2
Effect Systems
●
Future
●
Identity
●
Try
●
ZIO
●
Monix
●
Cats Effect
2
ZIO Effect System
2
import automorph.transport.http.HttpContext
import automorph.system.ZioSystem
// Create server implementation of the remote API
val service = new Api:
def hello(n: Int): Task[String] =
ZIO.succeed(s"Hello world $n")
// Create ZIO effect system plugin
val effectSystem = ZioSystem.default
// Initialize JSON-RPC HTTP & WebSocket server
val server = Default.rpcServerCustom(effectSystem, 9000, "/api")
server.bind(service).init()
Message Codecs
●
Circe
●
Jackson
●
uPickle
●
weePickle (in development)
●
Play JSON (in development)
●
Json4s (in development)
2
uPickle Message Codec
2
import automorph.codec.messagepack.UpickleMessagePackCodec
import automorph.codec.messagepack.UpickleMessagePackCustom
// Create uPickle message codec for JSON format
val messageCodec = UpickleMessagePackCodec[UpickleMessagePackCustom]()
// Create a server RPC protocol plugin
val rpcProtocol = Default.rpcProtocol(messageCodec)
// Create HTTP & WebSocket server transport
val serverTransport = Default.serverTransport(9000, "/api")
// Initialize custom JSON-RPC HTTP & WebSocket server
val server =
RpcServer.transport(serverTransport).rpcProtocol(rpcProtocol)
Server Transports
●
Undertow
●
Vert.x
●
Jetty
●
Tapir
●
Akka
●
Finagle
●
RabbitMQ
2
Server Transports
●
NanoHTTPD
●
ZIO HTTP (in development)
●
Pekko (in development)
●
Play (in development)
●
Helidon (in development)
●
Micronaut (in development)
●
Custom
2
Vert.x Server Transport
3
import automorph.RpcServer
import automorph.transport.http.server.VertxServer
// Create Vert.x HTTP & WebSocket server transport plugin
val serverTransport = VertxServer(Default.effectSystem, 9000, "/api")
// Initialize custom JSON-RPC HTTP & WebSocket server
val server =
RpcServer.transport(serverTransport).rpcProtocol(Default.rpcProtocol)
Undertow Endpoint Transport
3
import io.undertow.{Handlers, Undertow}
// Setup JSON-RPC HTTP endpoint with Undertow adapter
val endpoint = Default.rpcEndpoint().bind(service)
// Configure Undertow HTTP server listening on port 9000
val serverBuilder = Undertow.builder().addHttpListener(9000, "0.0.0.0")
// Use the JSON-RPC HTTP endpoint adapter as an Undertow handler
val pathHandler =
Handlers.path().addPrefixPath("/api", endpoint.adapter)
// Create and start the Underdow server
val server = serverBuilder.setHandler(pathHandler).build()
server.start()
Arbitrary Server Transport
3
import automorph.transport.generic.endpoint.GenericEndpoint
// Create generic endpoint transport plugin with Unit request context
val endpointTransport =
GenericEndpoint.context[Unit].effectSystem(Default.effectSystem)
// Setup JSON-RPC endpoint and bind the API implementation to it
val endpoint = RpcEndpoint.transport(endpointTransport)
.rpcProtocol(Default.rpcProtocol).bind(service)
// Call the endpoint from request handling logic of an arbitrary server
val requestContext = ()
val requestId = Random.nextInt(Int.MaxValue).toString
endpoint.handler.processRequest(requestBody, requestContext, requestId)
Client Transports
●
JRE HTTP client
●
UrlClient
●
Jetty
●
STTP
●
RabbitMQ
●
Local
3
STTP Client Transport
3
import automorph.RpcClient
import automorph.transport.http.HttpMethod
import automorph.transport.http.client.SttpClient
import sttp.client3.asynchttpclient.future.AsyncHttpClientFutureBackend
// Create STTP HTTP client transport
val clientTransport = SttpClient(
Default.effectSystem, AsyncHttpClientFutureBackend(),
URI("http://localhost:9000/api"), HttpMethod.Post
)
// Initialize custom JSON-RPC HTTP client
val client =
RpcClient.transport(clientTransport).rpcProtocol(Default.rpcProtocol)
Web-RPC
3
3
Motivation
●
REST is a loosely defined yet limiting concept
Typically CRUD operations using JSON over HTTP
RPC in disguise but without a standard
●
Everyone has to design their own REST protocol
Repeatedly solving the same problem
●
Preferably this would not be necessary
Web-RPC defines standardized protocol for REST APIs
3
Specification
●
HTTP as transport layer
●
Messages in JSON format
●
API function name as a last URL path element
●
HTTP method selected by the client
POST – arguments in request body
GET – arguments as URL query parameters only
●
Additional metadata in HTTP headers
3
POST Request
3
{
"who": "world",
"n": 1
}
POST http://example.org/api/hello
Content-Type: application/json
Request headers
Request body
Remote call
hello(who = "world", n = 1)
GET Request
3
Request headers
Request body
Empty
Remote call
hello(who = "world", n = 1)
GET http://example.org/api/hello?who=world&n=1
Success Response
4
{
"result": "test"
}
Content-Type: application/json
Response headers
Response body
Remote call
hello(who = "world", n = 1)
Error Response
4
{
"error": {
"message": "Some error",
"code": 1,
"details": {
...
}
}
}
Content-Type: application/json
Response headers
Response body
Thank you :)
https://automorph.org

More Related Content

PDF
Finagle - an intro to rpc & a sync programming in jvm
PDF
MessagePack Rakuten Technology Conference 2010
PDF
Fast and Reliable Swift APIs with gRPC
ODP
OpenStack Oslo Messaging RPC API Tutorial Demo Call, Cast and Fanout
PPTX
Intro to Node
PDF
Running gRPC Services for Serving Legacy API on Kubernetes
PDF
XML-RPC and SOAP (April 2003)
PDF
Networked APIs with swift
Finagle - an intro to rpc & a sync programming in jvm
MessagePack Rakuten Technology Conference 2010
Fast and Reliable Swift APIs with gRPC
OpenStack Oslo Messaging RPC API Tutorial Demo Call, Cast and Fanout
Intro to Node
Running gRPC Services for Serving Legacy API on Kubernetes
XML-RPC and SOAP (April 2003)
Networked APIs with swift

Similar to Introducing Automorph - RPC library for Scala (20)

PDF
Ruby HTTP clients comparison
PDF
Better Open Source Enterprise C++ Web Services
PPTX
Sharding and Load Balancing in Scala - Twitter's Finagle
PPT
AK 3 web services using apache axis
KEY
Intro to PSGI and Plack
PPT
Distributes objects and Rmi
PPTX
What I learned about APIs in my first year at Google
KEY
How and why i roll my own node.js framework
PPT
JS everywhere 2011
PPTX
Introduction to Vert.x
PPTX
Node.js System: The Approach
PDF
How to Leverage Go for Your Networking Needs
PPTX
Scaling application with RabbitMQ
PPT
Server side JavaScript: going all the way
PDF
Python Streaming Pipelines on Flink - Beam Meetup at Lyft 2019
PDF
Clojure and the Web
PDF
Deep Dive into SpaceONE
KEY
Plack - LPW 2009
PDF
CODEONTHEBEACH_Streaming Applications with Apache Pulsar
PDF
服务框架: Thrift & PasteScript
Ruby HTTP clients comparison
Better Open Source Enterprise C++ Web Services
Sharding and Load Balancing in Scala - Twitter's Finagle
AK 3 web services using apache axis
Intro to PSGI and Plack
Distributes objects and Rmi
What I learned about APIs in my first year at Google
How and why i roll my own node.js framework
JS everywhere 2011
Introduction to Vert.x
Node.js System: The Approach
How to Leverage Go for Your Networking Needs
Scaling application with RabbitMQ
Server side JavaScript: going all the way
Python Streaming Pipelines on Flink - Beam Meetup at Lyft 2019
Clojure and the Web
Deep Dive into SpaceONE
Plack - LPW 2009
CODEONTHEBEACH_Streaming Applications with Apache Pulsar
服务框架: Thrift & PasteScript
Ad

Recently uploaded (20)

PDF
System and Network Administration Chapter 2
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
Digital Systems & Binary Numbers (comprehensive )
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PPTX
Transform Your Business with a Software ERP System
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PPT
Introduction Database Management System for Course Database
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
ai tools demonstartion for schools and inter college
PPTX
L1 - Introduction to python Backend.pptx
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
Digital Strategies for Manufacturing Companies
System and Network Administration Chapter 2
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Digital Systems & Binary Numbers (comprehensive )
PTS Company Brochure 2025 (1).pdf.......
Odoo Companies in India – Driving Business Transformation.pdf
Wondershare Filmora 15 Crack With Activation Key [2025
Transform Your Business with a Software ERP System
CHAPTER 2 - PM Management and IT Context
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Introduction Database Management System for Course Database
2025 Textile ERP Trends: SAP, Odoo & Oracle
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
ai tools demonstartion for schools and inter college
L1 - Introduction to python Backend.pptx
wealthsignaloriginal-com-DS-text-... (1).pdf
Reimagine Home Health with the Power of Agentic AI​
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
How to Migrate SBCGlobal Email to Yahoo Easily
Digital Strategies for Manufacturing Companies
Ad

Introducing Automorph - RPC library for Scala

  • 3. Outline ● Overview Goals, functionality, configuration, plugins, artifacts ● Features ● Customization, abstractions, escape hatches ● Integration Effect types, message formats, transport layers ● Web-RPC REST without boilerplate
  • 5. Functionality ● RPC library Client, server, endpoint API discovery ● Platforms Scala 2.13 & 3.3+ ScalaJS (in development) ● Compile-time bindings generation From public methods of an API trait or instance
  • 6. Design Goals ● Flexibility, type-safety, performance ● No boilerplate ● Minimal dependencies - SLF4J API ● Dynamic message payload ● Transport protocol metadata ● Customization with sensible defaults ● Easy integration with existing technology
  • 7. Plugin Types ● RPC protocol JSON-RPC, Web-RPC ● Effect system Asynchronous, synchronous, monadic ● Message codec JSON, MessagePack ● Transport layer HTTP, WebSocket, AMQP
  • 8. Server Usage // Define a remote API trait Api: def hello(n: Int): Future[String] // Create server implementation of the remote API val service = new Api: def hello(n: Int): Future[String] = Future(s"Hello world $n") // Expose the server API implementation to be called remotely val apiServer = server.bind(service)
  • 9. Server Initialization import automorph.Default // Configure JSON-RPC HTTP & WebSocket server val server = Default.rpcServer(9000, "/api") // Expose the server API implementation to be called remotely val apiServer = server.bind(service).discovery(true) // Start the JSON-RPC server Await.result(activeServer <- apiServer.init(), Duration.Inf) // Stop the JSON-RPC server activeServer.close()
  • 10. Client Usage 1 // Define a remote API trait Api: def hello(n: Int): Future[String] // Create a type-safe local proxy for the remote API from an API trait val remoteApi = client.bind[Api] // Call the remote API function via the local proxy remoteApi.hello(1) // Call the remote API function dynamically without using the API trait client.call[String]("hello")("n" -> 1)
  • 11. Client Initialization 1 import automorph.Default // Configure JSON-RPC HTTP client val client = Default.rpcClient(URI("http://localhost:9000/api")) // Create a type-safe local proxy for the remote API from the API trait val remoteApi = client.bind[Api] // Initialize the JSON-RPC client Await.result(activeClient <- client.init(), Duration.Inf) // Close the JSON-RPC client activeClient.close()
  • 13. Dynamic payload 1 import io.circe.Json // Define client view of a remote API trait Api: def hello(who: String, n: Json): Future[Json] // Call the remote API function a proxy instance remoteApi.hello("test", Json.fromInt(1)) // Call the remote API function dynamically without an API trait client.call[Seq[Int]]("hello")("who" -> Json.fromInt(0), "n" -> 1)
  • 14. Transport Layer Metadata ● Request context Modeled via optional implicit parameter ● Response context Modeled via optional wrapper data type ● Completely general Context type is determined by the transport layer plugin 1
  • 15. Server Request Metadata 1 import automorph.Default.ServerContext // Create server implementation of the remote API class Service: // Accept HTTP request context from from server transport plugin def hello(message: String)(using request: ServerContext): String = Seq( Some(message), request.path, request.header("X-Test") ).flatten.mkString(",")
  • 16. Server Call Filter 1 import automorph.Default.ServerContext import automorph.RpcCall // Customize RPC request filtering to reject unauthenticated calls val filterCall = (call: RpcCall[ServerContext]) => call.context.authorization("Bearer") match case Some("valid") => None case _ if call.function == "public" => None case _ => Some(IllegalAccessException("Authentication failed")) // Initialize server with API with custom request filter val apiServer = server.bind(service).callFilter(filterCall)
  • 17. Client Request Metadata 1 import automorph.Default.ClientContext // Create HTTP request context for client transport plugin given request: ClientContext = client.context .parameters("test" -> "value") .header("X-Test" -> "value") .cookies("Example" -> "value") .authorization("Bearer", "value") // Call the remote API function using given HTTP request metadata val remoteApi = client.bind[Api] remoteApi.hello("test")
  • 18. Server Response Metadata 1 import automorph.Default.ServerContext import automorph.RpcResult import automorph.transport.http.HttpContext // Create server implementation of the remote API class Service: // Return HTTP response context for the server transport plugin def hello(message: String): RpcResult[String, ServerContext] = RpcResult( message, HttpContext().header("X-Test" -> "value").statusCode(200) )
  • 19. Server Function Names 1 // Customize served API to RPC function name mapping val mapName = (name: String) => name match // 'test' is exposed both as 'hello' and 'hi' case "test" => Seq("hello", "hi") // 'hidden' is not exposed case "hidden" => Seq.empty // 'sum' is exposed as 'test.sum' case other => Seq(s"test.$other") // Initialize JSON-RPC HTTP & WebSocket server val server = Default.rpcServer(9000, "/api").bind(service, mapName)
  • 20. Client Function Names 2 // Customize local proxy API to RPC function name mapping val mapName = (name: String) => name match // Calling 'hi' translates to calling 'hello' case "hi" => "hello" // Other calls remain unchanged case other => other // Call the remote API function via a local proxy val remoteApi = Default.rpcClient(new URI("http://localhost:9000/api")).init() .bind[Api](mapName) remoteApi.hello(1) remoteApi.hi(1)
  • 21. HTTP Status Code 2 // Create server implementation of the remote API val service = new Api: def hello(n: Int): Future[String] = Future.failed(new SQLException("Bad request")) // Customize remote API server exception to HTTP status code mapping val mapException = (error: Throwable) => error match case _: SQLException => 400 case e => HttpContext.defaultExceptionToStatusCode(e) // Initialize JSON-RPC HTTP & WebSocket server val server = Default.rpcServer( 9000, "/api", mapException = mapException ).bind(service)
  • 23. Default plugins ● RPC protocol JSON-RPC, Web-RPC ● Message codec Circe ● HTTP & WebSocket client JRE HTTP client ● HTTP & WebSocket server Undertow 2
  • 25. ZIO Effect System 2 import automorph.transport.http.HttpContext import automorph.system.ZioSystem // Create server implementation of the remote API val service = new Api: def hello(n: Int): Task[String] = ZIO.succeed(s"Hello world $n") // Create ZIO effect system plugin val effectSystem = ZioSystem.default // Initialize JSON-RPC HTTP & WebSocket server val server = Default.rpcServerCustom(effectSystem, 9000, "/api") server.bind(service).init()
  • 26. Message Codecs ● Circe ● Jackson ● uPickle ● weePickle (in development) ● Play JSON (in development) ● Json4s (in development) 2
  • 27. uPickle Message Codec 2 import automorph.codec.messagepack.UpickleMessagePackCodec import automorph.codec.messagepack.UpickleMessagePackCustom // Create uPickle message codec for JSON format val messageCodec = UpickleMessagePackCodec[UpickleMessagePackCustom]() // Create a server RPC protocol plugin val rpcProtocol = Default.rpcProtocol(messageCodec) // Create HTTP & WebSocket server transport val serverTransport = Default.serverTransport(9000, "/api") // Initialize custom JSON-RPC HTTP & WebSocket server val server = RpcServer.transport(serverTransport).rpcProtocol(rpcProtocol)
  • 29. Server Transports ● NanoHTTPD ● ZIO HTTP (in development) ● Pekko (in development) ● Play (in development) ● Helidon (in development) ● Micronaut (in development) ● Custom 2
  • 30. Vert.x Server Transport 3 import automorph.RpcServer import automorph.transport.http.server.VertxServer // Create Vert.x HTTP & WebSocket server transport plugin val serverTransport = VertxServer(Default.effectSystem, 9000, "/api") // Initialize custom JSON-RPC HTTP & WebSocket server val server = RpcServer.transport(serverTransport).rpcProtocol(Default.rpcProtocol)
  • 31. Undertow Endpoint Transport 3 import io.undertow.{Handlers, Undertow} // Setup JSON-RPC HTTP endpoint with Undertow adapter val endpoint = Default.rpcEndpoint().bind(service) // Configure Undertow HTTP server listening on port 9000 val serverBuilder = Undertow.builder().addHttpListener(9000, "0.0.0.0") // Use the JSON-RPC HTTP endpoint adapter as an Undertow handler val pathHandler = Handlers.path().addPrefixPath("/api", endpoint.adapter) // Create and start the Underdow server val server = serverBuilder.setHandler(pathHandler).build() server.start()
  • 32. Arbitrary Server Transport 3 import automorph.transport.generic.endpoint.GenericEndpoint // Create generic endpoint transport plugin with Unit request context val endpointTransport = GenericEndpoint.context[Unit].effectSystem(Default.effectSystem) // Setup JSON-RPC endpoint and bind the API implementation to it val endpoint = RpcEndpoint.transport(endpointTransport) .rpcProtocol(Default.rpcProtocol).bind(service) // Call the endpoint from request handling logic of an arbitrary server val requestContext = () val requestId = Random.nextInt(Int.MaxValue).toString endpoint.handler.processRequest(requestBody, requestContext, requestId)
  • 33. Client Transports ● JRE HTTP client ● UrlClient ● Jetty ● STTP ● RabbitMQ ● Local 3
  • 34. STTP Client Transport 3 import automorph.RpcClient import automorph.transport.http.HttpMethod import automorph.transport.http.client.SttpClient import sttp.client3.asynchttpclient.future.AsyncHttpClientFutureBackend // Create STTP HTTP client transport val clientTransport = SttpClient( Default.effectSystem, AsyncHttpClientFutureBackend(), URI("http://localhost:9000/api"), HttpMethod.Post ) // Initialize custom JSON-RPC HTTP client val client = RpcClient.transport(clientTransport).rpcProtocol(Default.rpcProtocol)
  • 36. Motivation ● REST is a loosely defined yet limiting concept Typically CRUD operations using JSON over HTTP RPC in disguise but without a standard ● Everyone has to design their own REST protocol Repeatedly solving the same problem ● Preferably this would not be necessary Web-RPC defines standardized protocol for REST APIs 3
  • 37. Specification ● HTTP as transport layer ● Messages in JSON format ● API function name as a last URL path element ● HTTP method selected by the client POST – arguments in request body GET – arguments as URL query parameters only ● Additional metadata in HTTP headers 3
  • 38. POST Request 3 { "who": "world", "n": 1 } POST http://example.org/api/hello Content-Type: application/json Request headers Request body Remote call hello(who = "world", n = 1)
  • 39. GET Request 3 Request headers Request body Empty Remote call hello(who = "world", n = 1) GET http://example.org/api/hello?who=world&n=1
  • 40. Success Response 4 { "result": "test" } Content-Type: application/json Response headers Response body Remote call hello(who = "world", n = 1)
  • 41. Error Response 4 { "error": { "message": "Some error", "code": 1, "details": { ... } } } Content-Type: application/json Response headers Response body