girolle
Description
A nameko-rpc like lib in rust. Check the To-Do section to see limitation.
Do not use in production!
Girolle use Nameko architecture to send request and get response.
Documentation
User documentation and Rust documentation
Installation
cargo add girolle
Configuration
There is two way to create a configuration. The first one is to use the Config::with_yaml_defaults function that will read a configuration from
a YAML file, see example. The second one is to create a configuration by hand.
Create a configuration from a yaml file
The configuration is done by a yaml file. It should be compliant with a Nameko one. The file should look like this:
AMQP_URI: 'amqp://toto:super@$172.16.1.1:5672//'
rpc_exchange: 'nameko-rpc'
max_workers: 10
parent_calls_tracked: 10
In this example:
- The
AMQP_URIis the connection string to the RabbitMQ server. - The
rpc_exchangeis the exchange name for the rpc calls. - The
max_workersis the max number of workers that will be created to handle the rpc calls. - The
parent_calls_trackedis the number of parent calls that will be tracked by the service.
Create a configuration by hand
let conf = default;
conf.with_amqp_uri
.with_rpc_exchange
.with_max_workers
.with_parent_calls_tracked;
Environment variables
The configuration supports the expansion of the environment variables with the
following syntax ${VAR_NAME}. Like in this example:
AMQP_URI: 'amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@${RABBITMQ_HOST}:${RABBITMQ_PORT}/%2f'
rpc_exchange: 'nameko-rpc'
max_workers: 10
parent_calls_tracked: 10
How to use it
The core concept is to remove the pain of queue creation and replies by
mirroring the Nameko architecture with a RpcService or RpcClient, and
to use an abstract type serde_json::Value to carry serializable data.
Service handlers run as async futures and receive an [RpcContext] giving
access to inbound headers and two capability handles:
ctx.rpc.call(service, method, payload).await— call any other service.ctx.events.dispatch(source, event_type, &payload).await— emit a Nameko-compatible event on<source>.events.
The #[girolle] attribute generates a handler from a sync or async function.
An optional first ctx: RpcContext argument is detected automatically.
If you'd rather skip the macro you can build an [RpcTask] by hand from an
[RpcHandler] closure:
use *;
use Arc;
Examples
The examples/ crate contains runnable services and senders:
| example | what it shows |
|---|---|
simple_macro |
basic #[girolle] service with sync handlers |
simple_service |
hand-rolled RpcTask::new with an async closure |
proxy_service |
ctx.rpc.call from inside a handler |
event_emitter |
ctx.events.dispatch from inside a handler |
event_observer |
RpcService::subscribe to consume events |
cli_sender |
generic CLI sender — <service> <method> [arg…] |
simple_sender |
RpcClient round-trip with sync + async calls |
Create a simple service
use *;
use ;
Call another service from a handler
use *;
async
Emit an event from a handler
use *;
use Serialize;
async
Subscribe to events
use *;
use Arc;
A service can mix RPC methods (register(...)) and event subscriptions
(subscribe(...)) freely; either one alone is also valid.
Create multiple calls to service of methods, sync and async
use Payload;
use ;
use Instant;
use ;
async
Stack
Girolle use lapin as an AMQP client/server library.
Supported features
- standalone client (sync and async)
- simple service with
#[girolle]- error handling
- tests
- macro
- basic
- handle
return - handle recursive functions
- support
async fnand an optionalctx: RpcContextfirst argument
- in-service RPC client —
ctx.rpc.call(...)from inside a handler - event publishing —
ctx.events.dispatch(...)(Nameko{source}.events) - event subscriptions —
RpcService::subscribe(source, event_type, handler) -
#[girolle_event]macro for ergonomic subscription handlers - HTTP / web service entrypoint
nameko-client
The Girolle client provides sync and async sends. There's also a
cli_sender example for poking at any service
from a terminal without hand-editing a sender file.
nameko-rpc
RpcService plus the #[girolle] macro is the core. Handlers are async,
receive an RpcContext, and can call other services from inside the handler
via ctx.rpc.call(...) — the in-service RPC core handles the reply queue,
correlation map, and nameko.call_id_stack propagation transparently.
nameko-pubsub
Both sides are supported: handlers can publish events with
ctx.events.dispatch(source, event_type, &payload) and services can
subscribe to events with RpcService::subscribe(source, event_type, handler).
Exchanges follow the Nameko {source}.events topic convention, so a Girolle
service can publish events that a Python Nameko service subscribes to (and
vice versa).
nameko-web
The web service is not implemented. I'm not sure if i will implement it. I need to rework the client to be make it 100% thread safe. It should be a commun subject with the proxy.
Limitation
The current code as been tested with the nameko and girolle examples in this repository.
| nameko_test.py | simple_sender.rs | |
|---|---|---|
| nameko_service.py | x | x |
| simple_macro | x | x |
Benchmark
Simple message benchmark
| nameko_test.py | simple_sender.rs | |
|---|---|---|
| nameko_service.py | 15.587 s | 11.532 s |
| simple_macro.rs | 15.654 s | 8.078 s |
Client benchmark
Using hyperfine to test the client benchmark.
Girolle client ( with Girolle service )
)
)
Nameko client ( with Girolle service )
)
)
Service benchmark
Girolle service ( with Girolle client )
)
)
Nameko service running python 3.9.15 ( with Girolle client )
)
)
Nameko service running python 3.9.15 ( with Nameko client )
)
)
Fibonacci benchmark
The benchmark use a static set of random int to compute fibonacci.
| nameko_fib_payload.py | |
|---|---|
| nameko_service.py | 03 min 58.11 s |
| simple_macro.rs | 6.99 s |
Macro-overhead benchmark
The benchmark is done to test the overhead of the macro.