diff --git a/README.md b/README.md new file mode 100644 index 0000000..38446cc --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# `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. Microservices listens to incoming requests on a RabbitMQ queue. +2. Incoming requests are in the form of a 64-bit unsigned integer (enough + granularity to work as a resource identifier or ID). +2. Microservices process incoming requests via a `process` function, which + takes one argument: the incoming request (`u64`). +3. 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. +4. Rather than hard-coding the inbound and outbound RabbitMQ queues, the + microservice communicates with a configuration service which provides the + inbound queue name, as well as any outbound queues and their corresponding case variables. + +The `slingshot-microservice` framework handles setting up the RabbitMQ +connection, listening to the inbound queue and routing results based on case variables. + +## Example Usage + +```rust +use slingshot_microservice::Microservice; + +fn process(request: u64) -> Vec<(u64, String)> { + // Example processing logic: return the request ID and a case variable + 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:s +`https://{HOSTNAME}/{MICROSERVICE_NAME}` with a JSON object that contains the +inbound queue name and a mapping of case variables to outbound queue names. +For example: + +```json +{ + "in": "simple-microservice-inbound", + "out": [ + { + "case": "case_a", + "queues": ["case_a_outbound_1", "case_a_outbound_2"] + }, + { + "case": "case_b", + "queue": ["case_b_outbound"] + } + ] +} +``` + +The case variables can be any primitive type (e.g. string, integer, 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`: + +```json +{ + "in": "binary-classification-inbound", + "out": [ + { + "case": false, + "queues": ["binary-classification-false-outbound"] + }, + { + "case": true, + "queue": ["binary-classification-true-outbound"] + } + ] +} +``` + +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 returns a set of tuples of the form `(result_id, case_variable)`. +The microservice then routes each `result_id` to the appropriate outbound +queue(s) based on the `case_variable`, using a process that looks like this: + +Pseudocode: +``` +for each (result_id, case_variable) in process(request): + for each outbound_queue in config.out[case_variable]: + send result_id to outbound_queue +```