Skip to main content

Opinionated Rust framework for queue-driven microservices

Project description

slingshot-microservice: A Rust framework for standard microservice design

slingshot-microservice is a Rust package that provides a simple, opinionated framework for building microservices. The framework makes the following assumptions about a microservice:

  1. A microservice listens to incoming requests on its own dedicated and singular queue (RabbitMQ).
  2. Incoming requests are in the form of a 64-bit unsigned integer (u64).
  3. Microservices process requests via a process function, which takes four arguments: the incoming request (u64), a read_file function, a write_file function, and a database ORM connection.
  4. All microservices must communicate with the shared PostgreSQL database via an ORM connection passed into process.
    • Rust microservices use diesel::PgConnection.
    • Python microservices use sqlalchemy.engine.base.Connection.
  5. The process function returns a set of IDs (also u64) that are the result of processing the incoming request. Each of these IDs is also associated with a "case variable" that is used for routing the result to the appropriate outbound queues. Case variables for routing must be one of: boolean, integer, or string.
  6. Rather than hard-coding the inbound and outbound queues, the microservice communicates with a self-contained configuration service shared across all microservices.
    • This service provides inbound queue name, as well as any outbound queues and their corresponding case variables.
    • It is also responsible for providing the RabbitMQ connection details (host, port, username, password), and the object-storage host plus GNU pass references for the S3 access key and secret key.

The slingshot-microservice framework handles setting up the RabbitMQ connection, listening to the inbound queue and routing results based on case variables.

Adding The Framework To Your Project

Add slingshot-microservice to your Cargo.toml dependencies directly from Codeberg:

[dependencies]
slingshot-microservice = { git = "https://codeberg.org/seanhly/slingshot-microservice" }

Then fetch and build dependencies:

cargo build

Python Usage

slingshot-microservice ships Python bindings built with PyO3 and maturin. Pre-built ABI3 wheels work on Python ≥ 3.8 without requiring Rust locally.

Installing

From PyPI (once published):

pip install slingshot-microservice

From git (Rust toolchain required):

pip install git+https://codeberg.org/seanhly/slingshot-microservice

From a local clone (for development):

pip install maturin
pip install -e .

Usage

from typing import Generator
from sqlalchemy.engine.base import Connection

from slingshot_microservice.typing import ReadFileFn, WriteFileFn
from slingshot_microservice import Microservice


def process(
    request: int,
    read_file: ReadFileFn,
    write_file: WriteFileFn,
    connection: Connection,
) -> Generator[tuple[int, bool | int | str], None, None]:
    reader = read_file("in", request)
    input_data = reader.read().decode()

    writer = write_file("out", request)
    writer.write(f"Hello {input_data}".encode())

    yield (request, True)


microservice = Microservice("simple-py-microservice", "sys-map.slingshot.cv", process)
microservice.start()

Type Annotations

slingshot_microservice.typing exports Protocol-based types for use in editors and type-checkers:

Symbol Description
ReadFileFn Callable returned by read_file(key, id) – behaves like BinaryIO
WriteFileFn Callable returned by write_file(key, id) – behaves like BinaryIO
ProcessFn The generator signature expected by Microservice with (request, read_file, write_file, connection)
CaseVariable bool | int | str – valid case variable types

Publishing Wheels

Build and upload to PyPI using maturin:

pip install maturin
maturin publish

For CI/cross-compilation (Linux, macOS, Windows), use maturin-action in GitHub/Codeberg Actions. Because the extension is compiled with ABI3 (abi3-py38), a single Linux wheel covers all CPython versions ≥ 3.8.

Example Usage

use slingshot_microservice::Microservice;
use diesel::PgConnection;
use slingshot_microservice::{AnyError, ReadFileFn, WriteFileFn};
use std::io::{Read, Write};

fn process(
    request: u64,
    read_file: &ReadFileFn,
    write_file: &WriteFileFn,
    connection: &mut PgConnection,
) -> Result<Vec<(u64, String)>, AnyError> {
    let mut input = String::new();
    let mut reader = read_file("in", request)?;
    reader.read_to_string(&mut input)?;

    let mut writer = write_file("out", request)?;
    writer.write_all(input.as_bytes())?;

    Ok(vec![(request, "case_a".to_string())])
}

fn main() {
    // Create a new microservice instance with the processing function
    let microservice = Microservice::new(
        "simple-microservice",
        "sys-map.example.com",
        process
    );

    // Start the microservice (this will block and listen for incoming requests)
    microservice.start();
}

How it works:

The configuration service responds to requests of the form: https://{HOSTNAME}/{MICROSERVICE_NAME}. All configuration is done over HTTP GET. The response contains a JSON object with two fields: an inbound queue name and a mapping of case variables to outbound queue names. For example:

{
    "in": "simple-microservice-inbound",
    "out": [
        {
            "case": "case_a",
            "queues": ["case_a_outbound_1", "case_a_outbound_2"]
        },
        {
            "case": "case_b",
            "queues": ["case_b_outbound"]
        }
    ]
}

The case variables used for routing can be one of: string, integer, or boolean. E.g. a binary classification microservice might decide on which outbound queue to send results to based on a case variable that is either false or true:

{
    "in": "binary-classification-inbound",
    "out": [
        {
            "case": false,
            "queues": ["binary-classification-false-outbound"]
        },
        {
            "case": true,
            "queues": ["binary-classification-true-outbound"]
        }
    ]
}

The configuration service also provides the RabbitMQ connection details (host, port, etc.):

Object storage credentials are fetched separately from https://sys-map.slingshot.cv/object-storage. The access-key and secret-key values returned there are GNU pass entry names, so the runtime resolves the actual secrets with pass show <key> before constructing the S3 client.

When the microservice first starts up, it makes a request to the configuration service to get the queue metadata. Then it starts to listen to the inbound queue. Inbound requests are processed by the user-programmed process function, which is called with (request, read_file, write_file, connection) and returns a set of tuples of the form (result_id, case_variable).

Within each process pass:

  1. read_file(key, id) treats key as a bucket reference such as in, not as the canonical bucket name. On first use, the runtime fetches https://{HOSTNAME}/{MICROSERVICE_NAME}/{key} to resolve the real bucket name, caches that mapping, and then returns a synchronous reader for object id in that bucket using the AWS SDK.
  2. write_file(key, id) resolves key through the same cached lookup and returns an opened local file handle for writing, staging the output for s3://{resolved_bucket}/{id}.
  3. connection is an ORM-backed PostgreSQL connection passed into process (diesel::PgConnection in Rust, sqlalchemy.engine.base.Connection in Python).
  4. After process returns, opened files are closed.
  5. Then staged write files are uploaded to S3 with the AWS SDK, local staged files are deleted, and local temporary directories are removed.
  6. Only after file finalization is complete are output IDs published to outbound queues.

The output queue routing step looks like this:

Peudocode:

for each (result_id, case_variable) in process(request, read_file, write_file, connection):
    for each outbound_queue in config.out[case_variable]:
        send result_id to outbound_queue

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

slingshot_microservice-0.1.4.tar.gz (380.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

slingshot_microservice-0.1.4-cp38-abi3-manylinux_2_39_x86_64.whl (9.1 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.39+ x86-64

File details

Details for the file slingshot_microservice-0.1.4.tar.gz.

File metadata

File hashes

Hashes for slingshot_microservice-0.1.4.tar.gz
Algorithm Hash digest
SHA256 ef539db1e1461641db57a071c7ba6b9d4325f49d7768ad9af150119e78cfa81d
MD5 39aa284e5c460d4a37145b7d006a0c82
BLAKE2b-256 47d8bc86acc0ba0ae362823122ef176218387a40058ec5d3018503189b4de986

See more details on using hashes here.

File details

Details for the file slingshot_microservice-0.1.4-cp38-abi3-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for slingshot_microservice-0.1.4-cp38-abi3-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 f164e8ca2f97c0c46e975131b1065cbccf0c647f1c22504f2ba431b09aa32d73
MD5 90041ed1e6516c58a99bf1258fa640c7
BLAKE2b-256 bec5ed8b1c3ff2871473c6958ead2df6dfec5b33cdd6e2cf273cc3603d3d49e1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page