Source: Aiden.ai Blog

Aiden.ai Blog Open sourcing rpc_ts, an RPC framework for TypeScript

Written by Hadrien Chauvin — Software engineer at aiden.ai.You can find the source for rpc_ts on GitHub, and an example project using rpc_ts (a real-time chat room) here.rpc_ts is a framework for type-safe Remote Procedure Calls (RPC) in TypeScript. At aiden.ai, we use it in production as a “first line of communication” between the frontend and the backend of our web app.In a nutshell, rpc_ts provides an RPC protocol with the following features:It is tailored to isomorphic web applications written in TypeScript with a Minimal Viable Product mentality — more below.It doesn’t rely on a Domain-Specific Language: all the definitions are written in TypeScript, code generation is avoided, and you use the same code completion, same plugins, and same IDE capabilities you are accustomed to when working with TypeScript.It implements the grpc-web+json protocol (an adaptation of the popular gRPC protocol for the web), so we are not reinventing anything here.Type safety/data validation comes from TypeScript.It provides both unary method calls and half-duplex communication for push notifications and real-time feeds.We discovered that this approach shortens the development cycle of web applications written in isomorphic TypeScript. We believe rpc_ts fills an important gap in the RPC ecosystem.The four layers (validation, serialization, routing, and transport) of an RPC system, and the choices we made for rpc_ts.In this post, I’m going to compare our design to other solutions out there and go through the rationale for coming up with rpc_ts.Agilityrpc_ts was made for the startup engineer who prioritizes time to market, readability and correctness.rpc_ts was developed with agility first and foremost in mind. It does not explicitly address scalability or performance (although this should not be a problem, really), but helps writing Minimal Viable web apps, decreasing time to market, and improving the developer experience without compromising correctness. rpc_ts was made for the startup engineer who recognizes that using one language is always preferable to using many, that proven technologies should be favoured over new, shiny ones, and that messing with the toolchain to enable code generation with custom DSLs (Domain-Specific Languages) is a time pit. All the design decisions detailed below are informed by these constant, unrelenting concerns.What goes in an RPC protocol and where rpc_ts fitsTo further detail the choices that went into rpc_ts, let’s try to locate it in the space of RPC protocols. Briefly, their design mainly revolves around four concerns:Serialization/codec choice — How to serialize the data?Data validation — How to validate the data?Routing — How to know which procedure to call, on which server? How are errors reported?Transport — Which “transport protocol” is used under the hood?In the case of rpc_ts, we provide defaults that we think make sense in the case of an isomorphic web app written in TypeScript. (However, our architecture is modular enough to accommodate for other choices.)Data serializationData serialization formats come in many flavours, but JSON is arguably the most popular one today.Data serialization formats come in many flavours, as serialization has many objectives incompatible with each other:Language-specific integration vs. broader language support — Some formats closely map the type systems of target languages (e.g., Pickle for Python, RData for the R Statistical Language, native serialization for Java), others are made with language interoperability in mind (Protocol Buffers, the Thrift binary protocol). In the case of language-specific integration, data serialization can be dealt with runtime type reflection or walking through the Abstract Syntax Tree. On the other hand, as language interoperability usually entails defining the data schema with an Interface Definition Language, integration in this latter case usually proceeds from code generation.Human-readability vs. speed/payload size — The payload for some protocols is human-readable (e.g., CSV, JSON, XML) and binary for others. Binary encoding speeds up serialization/deserialization and lowers payload size. In this category, we find CBOR, BSON, Apache Avro, Protocol Buffers, the Thrift binary protocol, etc.Relying on a broad ecosystem vs. advocating an improved format — JSON and XML serializers are broadly available in all mainstream languages, as are HTTP rest clients and servers. In contrast, binary protocols are less broadly supported. As a consequence, even though binary protocols might offer some advantages, it is sometimes more judicious to stick to more common, less efficient protocols and to benefit from a larger ecosystem.In the case of rpc_ts, we made the choice to use JSON by default:Language-specific integration — JSON stands for JavaScript Object Notation. It is aptly named as everything in JavaScript that is not a function, part of a class prototype or recursive can be JSON-serialized. Therefore, JSON is ideal for JavaScript.Human readability — It is possible to read JSON in the network tab of a browser’s devtools, to pretty-print JSON in the browser console, in the server logs, …Codec speed — It is difficult to be more efficient than JSON serialization in the browser, as JSON.parse and JSON.stringify benefit from native implementations. Moreover, network latency dominates by a large margin in the context of browser-server interactions, and the duration of the serialization itself stays inconsequential.Payload size — Payload size is reduced by compression. With HTTP2, over a unique TCP connection, we are even using a single compression context, mutualizing the same schema between repeated payloads.Broad ecosystem — JSON is arguably the most popular serialization format today.Data validationWe believe making data validation the responsibility of the RPC protocol improves readability and shortens the development cycle.Type systems have two responsibilities:data representation — How is the data represented in memory?data validation — What is the set of acceptable values?Data validation can go beyond ensuring the validity of the data representation (e.g., a string cannot be stored where an integer is expected). For instance, while UUIDs, RFC3339 date-times and URLs can be stored as strings (data representation), the sets of acceptable values differ (data validation). Likewise, an array and a non-empty array can be validated differently, although the underlying representation could in both cases be a memory slice.In a type system viewed as a means of data validation, the type of a variable is a concise specification of the set of values this variable can take. One popular way to implement this in practice is through (structural) algebraic data typesand newtypes/brands/nominal typing. With such an approach, all the validation for some data can be incorporated in their type, meaning: (1) exceptions should (in theory) always be I/O related, (2) a function’s requirements now appear in its signature, (3) you can perform hypothesis testing a.k.a. quick checks solely based on a function’s signature instead of having to maintain separate filters.We believe making data validation the responsibility of the RPC protocol improves readability and shortens the development cycle. However, approaches such as protoc-gen-validate and JSON-schema feel ad hoc as their treatment of types is not systematic. In the case of rpc_ts, we decided to piggy-back on the TypeScript type system: it provides algebraic data types out of the box and the newtype pattern can be “emulated,” thus meeting our requirements for systematic data validation at the RPC protocol level. Furthermore, “compile-time” validation, before full type erasure, is augmented with runtime validation through type reflection.https://medium.com/media/db90a805b22cb6b6680df13ad49566df/hrefRoutingrpc_ts implements the grpc-web protocol (let’s not reinvent the wheel).In routing, I include the following decisions:Endpoint naming — In gRPC and Thrift, an “endpoint” is designated by a service name and a method name. In REST, the endpoint pairs an HTTP path (e.g., /user/1239df29) with a verb/method (GET, POST, PATCH, DELETE, …).Error reporting — In REST, HTTP status codes would be used, in gRPC a separate set of error codes. In both cases, error reporting can be refined with custom error messages. Thrift adopts a similar approach, although its error handling is based on a more general exception mechanism.Between-server routing — Proxies such as Linkerd and Envoy can perform complex gRPC load balancing based on the service and method names, and Thrift has Finagle for service discovery.Following the grpc-web specification, rpc_ts endpoints are HTTP POST methods with paths of the form /servicePrefix/methodName (by convention, servicePrefix doubles as the service name), and we use gRPC error codes with optional custom messages for error reporting.Between-server routing is not really needed when a web app frontend talks to a backend, and rpc_ts does not provide such proxying.TransportIn the RPC world, transport is almost always based on the TCP protocol (but Unix sockets can be seen). A layer can be added for multiplexing (e.g., HTTP2).As we follow the grpc-web protocol, rpc_ts uses HTTP2 framing as partially exposed by WHATWG-Fetch.rpc_ts does not use any Domain Specific LanguageStatic typing with DSLs requires code generation, and code generation is hard and should only be used as a last resort:Maintainability — You need to write and maintain the code generator.Documentation of the generated code — You will probably always need to look at the generated code to understand what’s going on.Documentation of the Domain Specific Language — For instance, in some cases, is using an annotation-based DSL in Java really better than writing statements?Toolchain — The only sane way I know to generate code is to use a build system such as Bazel. Otherwise, you have to cope with a ballet of checked-in generated

Read full article »
Est. Annual Revenue
$100K-5.0M
Est. Employees
1-25
Marie Outtier's photo - Co-Founder & CEO of Aiden.ai

Co-Founder & CEO

Marie Outtier

CEO Approval Rating

84/100

Read more