fabric

Layered Architecture

Onions have layers, ogres have layers, and so does a good software architecture.

Microbus solutions are constructed in 5 layers:

Applications

Microservices in Microbus are not by themselves runnable, rather they are bundled in applications that manage their lifecycle.

Typically, all microservices are bundled into a single application for local development.

Similarly, integration tests are executed against an application that contains the microservice under test and its downstream dependencies.

Applications can contain any number of microservices, making them a flexible vehicle in the construction of the production topology.

Solution Microservices

These are the microservices that implement the business logic of the solution.

Core Microservices

Microbus comes bundled with a few core microservices that implement common functionality required by most if not all Microbus applications.

The HTTP ingress proxy bridges the gap between HTTP-based clients and microservices running on Microbus.

The HTTP egress proxy relays HTTP requests to non-Microbus URLs.

The SMTP ingress microservice captures incoming emails and transforms them to actionable events.

The configurator is responsible for delivering configuration values to microservices that define configuration properties. It is a must-have in practically all applications.

The metrics microservice aggregates metrics from all microservices in response to a request from Prometheus.

The OpenAPI portal microservice renders a catalog of the OpenAPI documents of each and every microservices.

Code Generator

Code generation facilitates rapid application development (RAD) by generating boilerplate and skeleton code from declarations in a service.yaml file. The developer needs only fill in the gaps and implement the business logic.

Skeletons are created for each of the microservice’s endpoints with TODO markers for the developer to fill in the gaps. For functional endpoints (RPCs), a wrapper takes care of unmarshaling the request’s JSON payload into type-safe arguments.

Client stubs are created for the microservice’s public endpoints. These stubs are used by upstream clients to call the microservice in a type-safe fashion. For functional endpoints (RPCs), the stubs take care of marshaling the request arguments into a JSON payload.

Events are a type-safe abstraction of publish/subscribe.

The integration test harness spins up the microservice under test along with the actual downstream microservices it depends on into a single testable application, allowing full-blown integration tests to run inside go test.

An OpenAPI document is automatically created with descriptors for each of the microservice’s endpoints.

A uniform code structure is a byproduct of using code generation. A familiar code structure helps engineers get oriented quickly also when they are not the original authors of the code.

Connector Construct

The Connector is the base class of all microservices and provides most of their core capabilities.

General

Configuration properties are common means to initialize and customize microservices without the need for code changes. In Microbus, microservices define their configuration properties but obtain the runtime values of those properties from the configurator.

The distributed cache is an in-memory cache that is shared among the replica peers of microservice.

Tickers are jobs that run on a recurring basis.

Microservices are shutdown gracefully. All pending requests, goroutines and jobs are drained before the microservice quits.

Images, scripts, templates and any other static resources are made available to each microservice by association of a file system (FS).

A specially-named resource strings.yaml enables internationalization (i18n) of user-facing display strings.

Observability

Structured logs are sent to stderr.

Distributed tracing enables the visualization of the flow of function calls across microservices and processes. Tracing spans are automatically captured for each endpoint call.

Metrics such as latency, duration, byte size and count are collected automatically for all microservice endpoint calls. Custom metrics may be defined by the developer. Metrics are stored in Prometheus and visualized with Grafana dashboards.

Errors are unavoidable. When they occur, they are captured, augmented with a full stack trace, logged, metered (Grafana), traced (Jaeger) and propagated up the stack to the upstream microservice.

Transport

Microbus uses a messaging bus for the transport layer of service-to-service communications.

Unicast request/response is an emulation of the familiar synchronous 1:1 request/response pattern of HTTP over the asynchronous messaging pattern of the underlying transport bus.

Multicast publish/subscribe enhances the publish/subscribe pattern of the bus by introducing a familiar HTTP interface and a bi-directional 1:N request/response pattern.

Microservice are connected to the messaging bus with a persistent multiplexed connection that enables holding multiple concurrent conversations on a single connection. Multiplexing results in lower resource requirements and a simplified network topology that is less prone to error.

Time budget is a depleting timeout that is passed downstream along the call stack. It is the proper way to handle client-to-server timeouts.

Ack or fail fast is a pattern by which the server responds with an ack to the client before processing the request. The client knows to wait for the response only if an ack is received, and fail quickly if it’s not.

A microservices transparently makes itself discoverable by subscribing to the messaging bus. Once subscribed to a subject it immediately starts receiving message from the corresponding queue. An external service discovery system is not required.

Load balancing is handled transparently by the messaging bus. Multiple microservices that subscribe to the same queue are delivered messages randomly. An external load balancer is not required.

With locality-aware routing unicast requests are routed to the replica of the downstream microservice whose locality is nearest to the upstream’s locality.

A microservice is alive when it is connected to the messaging bus and can send and receive messages. The bus validates the connection using regular pings. Explicit liveness checks are unnecessary.

OSS

NATS sits at the core of Microbus and makes much of its magic possible. NATS is a full-mesh, highly-available, lighting-fast, real-time, at-most-once, messaging bus that supports dynamic subscriptions. It enables request/response, publish/subscribe, load-balancing and dynamic discovery.

OpenTelemetry is a standard for the collection of metrics, distributed tracing and logs.

Jaeger is a distributed tracing observability platform that maps the flow of requests as they traverse a distributed system such as Microbus. It is an implementation of the OpenTelemetry standards.

Prometheus is a database for storing highly-dimensional time-series data, specifically system and solution-specific metrics.

Grafana is a dashboard that provides visibility into the metrics collected by Prometheus.

OpenAPI is a widely used API description standard. The endpoints of all microservices on Microbus are publicly described with OpenAPI.

Cascadia implements CSS selectors for use with parsed HTML trees produced by Go’s html package. Used in unit and integration tests, it facilitates assertions against an HTML document.