Introduction

Remote Procedure Call (RPC) interfaces define the interface a given micro-service exposes via the network. The interface describes how each call should be made on a concrete RPC backend. Each micro-service define the interfaces in the api package. You can read more about the overall structure of a micro-service here.

The interfaces themselves are defined using a Kotlin DSL. If you are unfamiliar with the syntax it might help to read this article.

Example: Defining a remote procedure call (RPC) interface

// Stored in /backend/avatar-service/api/src/main/kotlin/Avatars.kt

data class SerializedAvatar(/* left out for brevity */)

typealias UpdateRequest = SerializedAvatar
typealias UpdateResponse = Unit

typealias FindRequest = Unit
typealias FindResponse = SerializedAvatar

object Avatars : CallDescriptionContainer("avatar") {
    val baseContext = "/api/avatar"

    val update = call<UpdateRequest, UpdateResponse, CommonErrorMessage>("update") {
        auth {
            access = AccessRight.READ_WRITE
        }

        http {
            method = HttpMethod.Post

            path {
                using(baseContext)
                +"update"
            }

            body { bindEntireRequestFromBody() }
        }
    }

    val findAvatar = call<FindRequest, FindResponse, CommonErrorMessage>("findAvatar") {
        auth {
            access = AccessRight.READ
        }

        http {
            method = HttpMethod.Get

            path {
                using(baseContext)
                +"find"
            }
        }
    }
}

Example: Calling a remote procedure call

val avatar: FindResponse = Avatars.findAvatar.call(FindRequest, serviceClient).orThrow()

Example: Adding a dependency on another service api package

// build.gradle.kts of the service

dependencies {
    implementation(project(":avatar-service:api"))
}

Anatomy of a remote procedure call

A remote procedure calls consists of two parts:

  1. The header

  2. The body

In the header we define the calls types. The system uses the types in serialization of the data as well as type-safety. The types themselves must also be stored in the api component.

Figure: The remote procedure call header contains a name and three types associated with it (request, success, error).

In the body of the remote procedure call definition we place one or more blocks. Each block provides instructions to both the RPC client and the server. For example, the server uses this information to configure the underlying server implementation (e.g. Ktor) to listen on the correct endpoint. Similarly, the client uses this information to make the correct call on the network.

Figure: The body of an RPC contains several ‘blocks’. Each block helps define how the client and server should treat these calls.

Reference

Block Mandatory Description
auth ✅ Yes Provides configuration for authentication and authorization
audit ❌ No The audit block provides a way to change the information which will be written to the audit log
http ❌ No http enables communication of this call via the HTTP backend
websocket ❌ No websocket enables communication of this call via the WebSocket backend

Note: Even though both http and websocket is optional you must select at least one. We recommend that you use http for most calls.