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:
- A microservice listens to incoming requests on its own dedicated and singular queue (RabbitMQ).
- Incoming requests are in the form of a 64-bit unsigned integer (
u64). - Microservices can run optional startup work via a
startupfunction, which takes three arguments: aread_filefunction, awrite_filefunction, and a database ORMconnection. - Microservices process requests via a
processfunction, which takes four arguments: the incoming request (u64), aread_filefunction, awrite_filefunction, and a database ORMconnection. - 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.
- Rust microservices use
- The
processfunction returns a set of IDs (alsou64) 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. - 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
passreferences 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 startup(
read_file: ReadFileFn,
write_file: WriteFileFn,
connection: Connection,
) -> Generator[tuple[int, bool | int | str], None, None]:
if False:
yield (0, True)
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",
startup,
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 |
StartupFn |
The generator signature expected by Microservice startup hook with (read_file, write_file, connection) |
ProcessFn |
The generator signature expected by Microservice process callback 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 startup(
read_file: &ReadFileFn,
write_file: &WriteFileFn,
connection: &mut PgConnection,
) -> Result<Vec<(u64, String)>, AnyError> {
Ok(Vec::new())
}
fn main() {
// Create a new microservice instance with startup and process functions
let microservice = Microservice::new(
"simple-microservice",
"sys-map.example.com",
startup,
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. Before consuming inbound requests, the user-programmed startup
function runs once with (read_file, write_file, connection) and may return
zero or more (result_id, case_variable) tuples for normal outbound routing.
Inbound requests are then 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:
read_file(key, id)treatskeyas a bucket reference such asin, not as the canonical bucket name. On first use, the runtime fetcheshttps://{HOSTNAME}/{MICROSERVICE_NAME}/{key}to resolve the real bucket name, caches that mapping, and then returns a synchronous reader for objectidin that bucket using the AWS SDK.write_file(key, id)resolveskeythrough the same cached lookup and returns an opened local file handle for writing, staging the output fors3://{resolved_bucket}/{id}.connectionis an ORM-backed PostgreSQL connection passed intoprocess(diesel::PgConnectionin Rust,sqlalchemy.engine.base.Connectionin Python).- After
processreturns, opened files are closed. - Then staged write files are uploaded to S3 with the AWS SDK, local staged files are deleted, and local temporary directories are removed.
- 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 startup(read_file, write_file, connection):
for each outbound_queue in config.out[case_variable]:
send result_id to outbound_queue
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slingshot_microservice-0.1.7.tar.gz.
File metadata
- Download URL: slingshot_microservice-0.1.7.tar.gz
- Upload date:
- Size: 382.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e7e83aa5b305caf9f4970550bbac501b173c6bdc29b8c96bfc2d10a6ddf69ec
|
|
| MD5 |
aa300fa0cf551b5b0e9df0a745b76e0e
|
|
| BLAKE2b-256 |
4edb735d5c74d55937ac52f726d805fbeae8763b1af2252d3c25c93133f22c38
|
File details
Details for the file slingshot_microservice-0.1.7-cp38-abi3-manylinux_2_39_x86_64.whl.
File metadata
- Download URL: slingshot_microservice-0.1.7-cp38-abi3-manylinux_2_39_x86_64.whl
- Upload date:
- Size: 9.1 MB
- Tags: CPython 3.8+, manylinux: glibc 2.39+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da91ec507224e81f23671deaad522243f64305d55f1f8213184a42df20977283
|
|
| MD5 |
a6af8d7681da19ad25aa2792db7339c6
|
|
| BLAKE2b-256 |
4a9f228dbc0048f6ddab76963434e585ac6addaaabed2125b0002bbbc2314e89
|