= RFC-0005: JSON over HTTP for data serialization and transport :toc: preamble :toclevels: 3 ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: :important-caption: :heavy_exclamation_mark: :caution-caption: :fire: :warning-caption: :warning: endif::[] .**RFC Template** .Metadata [cols="1h,1"] |=== | RFC | 0005 | Title | JSON over HTTP for data serialization and transport | Sponsor | link:https://github.com/rtyler/[R Tyler Croy] | Status | Draft :speech_balloon: | Type | Standards | Created | 2020-01-07 |=== == Abstract Otto is a distributed system in which multiple services need to communicate with one another in standardized formats with a common transport layer. Practically every component in the system acts as both a producer and a consumer of data. Consolidating all service-to-service interactions onto a single format and transport layer offers numerous benefits in tooling and shared code. It is also very important to make the Otto ecosystem easily interoperable, such that users can bring their own services into the Otto ecosystem without requiring any implementation changes from the existing services. == Specification This specification describes the standard methoa and format for all service-to-service interaction in the Otto distributed system. The interactions in the system are expected to take two forms: . *Synchronous*: a traditional request/response model with two services, or a client and service, directly interacting with one another. This flow is expected to follow the <> described below. . *Asynchronous*: oriented around the Otto eventbus, whereby services can send a message to a channel and expect no response other than a confirmation that the message was received. The details for event transmission and interaction with the Eventbus is described in the <> section below. [[rest-api]] === REST API Generally speaking, REST APIs are intended for administrative for user-interface related data requirements. For the most part, services are expected to communicate with one another using <> rather than sending synchronous requests back and forth. All REST APIs must be: * *Versioned*: The URLs should be versioned using the major version number from the service's link:http://semver.org/[Semantic Versioning] scheme. For example, `GET /v1/channels`. * *Documented with OpenAPI*: * *Accept JSON*: All requests to REST APIs must be documented JSON payloads, except in cases of binary object uploads * *Respond with JSON*: All REST APIs are expected to _only_ respond with documented JSON payloads. Any non-JSON response, except for expected binary blobs, are to be considered by the client as a server-side error. ==== Error Handling Errors should be communicated by the server with JSON encoded error messages and status codes: * *4xx*: Malformed request or other client errors. Clients should not attempt to retry these errors, but instead log them as local errors and try to recover. * *5xx*: Server-side failures. Clients _should* attempt to retry the requests after an exponential back-off, with some permanent failure mode after a period of 30+ minutes. If the server does *not* respond with a JSON payload, the client should treat the response as if it is a recoverable error and perform an exponential back-off retry. [[events]] === Events Services are all expected to emit and receive events over a link:https://en.wikipedia.org/wiki/WebSocket[WebSocket] connection to the Eventbus service, e.g. `http://eventbus.lan/ws/`. The Eventbus acts as a quasi-central broker of all events occurring in the Otto distributed system. Its design is discussed in another RFC< but the message payloads sent to "channels" on the eventbus *must* be JSON data structures matching JSON Schemas defined for each channel. ==== Error Handling For cases where the WebSocket connection has errored, reset, or is otherwise unavailable, services must attempt to retry into infinity. Local error logs should be generated during these failure schenarios and the service's health check URLs should also convey that the Eventbus connection is down. The only errors which should be expected by services writing events to the Eventbus should be schema validation errors, which are considered to be similar to REST API client-side errors Therefor local errors should be logged, but the message should not be retransmitted. == Reasoning Interprocess communication occurring via JSON over HTTP helps meet a number of the key design goals for Otto: * *Avoiding a singular central hub* * *Extensibility must not come at the expense of system integrity* * *User-level extension of the system must be viable* The key aspect of JSON over HTTP which helps address all of these goals is that JSON and HTTP are interoperable with practically _everything_ in the modern technology environment, from backend services, to cloud storage providers, and also user's browsers. By providing documented synchronous and asynchronous APIs, Otto should be able to accomodate new user-provided services into its ecosystem that add additional functionality without any substantive change to the core components shipped with Otto. One example which comes to mind is an analytics system around Otto. In its current design there is not analytics system tracking tasks executed, resources utilized, etc. If a user or provider wished to offer budget forecasting, as an example, this JSON over HTTP model with REST APIs and events via WebSockets would allow for the deployment of services which could tabulate interesting events off of the Eventbus, and easily provide the end-user with a forecast. Implementing such a system, assuming the necessary events are present in the Eventbus and the necessary data is available from existing services' REST APIs, would require *zero* changes to the "base" components of Otto. Since browsers can also natively "speak" JSON over HTTP, this also allows for new and interesting user interfaces atop these services and events to be developed. === Alternatives Considered ==== Protocol Buffers / gRPC The only serious alternative considered was the use of link:https://developers.google.com/protocol-buffers/[Protocol Buffers] for the data format/serialization and gRPC for the transport layer between services. This approach would be functionally very similar, and may even offer some schema and data validation benefits when compared to JSON over HTTP. The gRPC approach does add implementation overhead however for new clients, which must have shared binary stubs with other services generated from the Protocol Buffer IDL footnote:[Interactive Data Language, in Protocol Buffers the language for describing the binary data structure]. This increases the coupling between services, which must all be booted with shared definitions of data, but also prevents easy interoperability with "outside" services and web browsers. Ultimately, the portability story for Protocol Buffers and gRPC pales in comparison to JSON over HTTP. For Otto, the extensibility and portability of data in the system is a key design goal. == Security The authentication and authorization of connections over HTTP is not subject to this specification. Additional concerns around data verification through signatures or other means are also not intended to be discussed in this specification. The securing of the transport layer is a concern which will be addressed in future extensions to this design. == Testing There are no testing issues specifically related to this proposal. However clients and services will need to express API documentation which can be automatically tested in the future. Tooling to help this already exists, for example link:https://github.com/apiaryio/dredd[Dredd] which works with OpenAPI/Swagger specifications. == References * link:https://github.com/actix/actix-web[actix-web]: a Rust framework for building high-performance web services. * link:https://docs.serde.rs/serde_json/[serde_json]: a Rust library for serializing and deserializing JSON objects * link:https://json-schema.org/learn/getting-started-step-by-step.html[JSON Schema]: tool for specifying JSON data structure and requirements. * link:https://swagger.io/specification/[OpenAPI/Swagger specification]