Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cargo-prosa/tests/cargo-prosa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,15 @@ If you have an external git dependency, specify your ssh agent with:
cmd.args(["completion", "bash"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("cargo__prosa"));
.stdout(predicate::str::contains("cargo__subcmd__prosa"));

// Get Zsh command completion
let mut cmd = cargo_prosa_command();
cmd.current_dir(&prosa_path);
cmd.args(["completion", "zsh"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("cargo__prosa"));
.stdout(predicate::str::contains("cargo__subcmd__prosa"));

Ok(())
}
3 changes: 3 additions & 0 deletions prosa_book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[ProSA - Protocol Service Adaptor](ch00-00-prosa.md)
[Foreword](ch00-01-foreword.md)
[Introduction](ch00-02-introduction.md)
[Architecture](ch00-03-architecture.md)

- [OPS](ch01-00-ops.md)
- [Cargo-ProSA](ch01-01-cargo-prosa.md)
Expand All @@ -16,6 +17,7 @@
- [Cloud](ch01-05-cloud.md)
- [GCP - Cloud Run](ch01-05-01-gcp-cloud_run.md)
- [Clever Cloud](ch01-05-02-clever_cloud.md)
- [Putting It All Together](ch01-06-sumup.md)

- [Adaptor](ch02-00-adaptor.md)
- [TVF](ch02-01-tvf.md)
Expand All @@ -32,3 +34,4 @@
- [Events](ch03-06-events.md)
- [I/O](ch03-07-io.md)
- [Threads](ch03-08-threads.md)
- [Built-in Processors](ch03-09-builtin.md)
142 changes: 142 additions & 0 deletions prosa_book/src/ch00-03-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Architecture

This chapter provides a high-level overview of the ProSA architecture, explaining how all the components interact to form a working microservice platform.

## Overview

ProSA follows a **service bus** pattern. At its core, a central **Main** processor routes messages between autonomous **Processors**, each of which communicates with external systems through an **Adaptor**. Messages flow through the system using an internal format called **TVF** (Tag-Value Field).

```mermaid
graph TB
ext_a(External System A)
ext_b(External System B)

subgraph prosa[ProSA Instance]
subgraph main[Main - Service Bus]
main_task[Main task]
table>Service table]
table <--> main_task
end

subgraph proc_a[Processor A]
task_a[Task]
adaptor_a[Adaptor A]
task_a <--> adaptor_a
end

subgraph proc_b[Processor B]
task_b[Task]
adaptor_b[Adaptor B]
task_b <--> adaptor_b
end

table --> task_a
table --> task_b
end

ext_a <--> adaptor_a
ext_b <--> adaptor_b
```

## Components

### Main (Service Bus)

The **Main** is the central hub of a ProSA instance. It is responsible for:

- **Message routing**: forwarding requests and responses between processors
- **Service table management**: maintaining a registry of which processors provide which services
- **Processor lifecycle**: tracking processor registration, removal, crashes, and restarts
- **Observability**: collecting system-wide metrics (RAM usage, service counts, processor states)
- **Shutdown coordination**: propagating shutdown signals to all processors

There is exactly **one Main** per ProSA instance. It runs on the Tokio async runtime and processes messages from its internal queue.

### Processors

A **Processor** is an autonomous unit that runs in its own thread(s). Processors:

- **Register** with the Main to join the service bus
- **Declare services** they can provide (e.g., `"PAYMENT"`, `"AUTH"`)
- **Send and receive messages** through the Main's routing system
- **Run independently**, each in their own Tokio runtime (configurable: single-thread, multi-thread, or shared with Main)

Processors communicate exclusively through the Main bus using **internal messages** (`InternalMsg`):

| Message Type | Purpose |
|-------------|---------|
| `Request` | Send a transaction to a service for processing |
| `Response` | Return the result of a processed request |
| `Error` | Return an error for a request (timeout, unreachable, protocol error) |
| `Service` | Update the processor's copy of the service table |
| `Shutdown` | Ask the processor to stop gracefully |

### Adaptors

An **Adaptor** is the bridge between a processor and the outside world. It handles:

- **Protocol translation**: converting external protocol messages (HTTP, TCP, custom binary, etc.) into internal TVF messages
- **Initialization**: setting up connections and resources when the processor starts
- **Termination**: cleaning up when the processor shuts down

Each processor handle their adaptor as they wish. The adaptor is defined as a trait, so different protocol implementations can be swapped in without changing the processor logic.

Adaptors can return values either synchronously or asynchronously using the [`MaybeAsync`](https://docs.rs/prosa/latest/prosa/core/adaptor/enum.MaybeAsync.html) enum.

### TVF (Tag-Value Field)

**TVF** is the internal message format. It is a trait, not a concrete type, which means different implementations can be used depending on performance or protocol requirements.

The TVF trait provides a key-value interface where fields are identified by numeric IDs and can hold various types: strings, integers, floats, bytes, dates, and nested TVF buffers.

`SimpleStringTvf` is the built-in reference implementation. See the [TVF chapter](ch02-01-tvf.md) for details.

### Service Table

The **Service Table** is maintained by Main and distributed to all processors. It maps service names (strings) to processor queues. When multiple processors provide the same service, requests are distributed using **round-robin** load balancing.

When the service table changes, Main notifies all processors with an updated copy.

## Message Flow

Here is the typical flow of a request through ProSA:

```mermaid
sequenceDiagram
participant ExtA as External System A
participant ProcA as Processor A (Consume service)
participant Main as Main (Service Bus)
participant ProcB as Processor B (Expose service)
participant ExtB as External System B

ProcB-->>Main: Register service
Note over Main: Service table update
ExtA->>ProcA: Protocol message
Note over ProcA: Adaptor converts to TVF
ProcA->>Main: Lookup service
ProcA->>ProcB: RequestMsg (via service queue)
Note over ProcB: Adaptor processes request
ProcB->>ExtB: Protocol message (optional)
ExtB->>ProcB: Protocol response (optional)
ProcB->>ProcA: ResponseMsg
Note over ProcA: Adaptor converts from TVF
ProcA->>ExtA: Protocol response
```
Comment thread
reneca marked this conversation as resolved.

Note that processors send requests **directly** to the target processor's queue (looked up from the service table). They do not go through Main for every transaction — Main is only involved in service registration and table distribution.

## Processor Lifecycle

1. **Creation**: `ProcConfig::create()` — allocates the processor with its settings and Main bus reference
2. **Run**: `Proc::run()` — spawns the processor in its own thread/runtime
3. **Registration**: `proc.add_proc()` — notifies Main that the processor is active
4. **Service declaration**: `proc.add_service_proc(names)` — registers the services this processor provides
5. **Processing loop**: `internal_run()` — the main event loop, receiving and processing messages
6. **Shutdown**: on `InternalMsg::Shutdown`, the processor calls `adaptor.terminate()` and `proc.remove_proc()`

### Error Recovery

If a processor's `internal_run()` returns an error:
- **Recoverable errors**: the processor restarts after a delay (exponential backoff up to a configured maximum)
- **Fatal errors**: the processor is marked as crashed and does not restart
- If ProSA is shutting down, no restart is attempted
2 changes: 1 addition & 1 deletion prosa_book/src/ch01-02-01-observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ To set it up, you have to:
- Use a direct connection with a token
- Decode the base64-encoded basic authorization token from the `Create an Instrumentation Instance`. You'll get an `id:password` to set OTLP credentials with.

> For the first configuration you may have to set the service name to `my-app` to let Grafana Cloud detect that it's working.
> If your token contains a trailing `=`, you need to replace it with `%3D` to ensure a correct URL format.

With this information, set up your observability stack (look before if you want to set up traces):
```yaml
Expand Down
1 change: 0 additions & 1 deletion prosa_book/src/ch01-02-02-run.md

This file was deleted.

79 changes: 73 additions & 6 deletions prosa_book/src/ch01-02-04-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Once you have created a binary using `cargo-prosa`, the next step is to run this
If you've installed a package or a container, you don't need to worry about the inner workings.
However, if you want to execute the binary manually, this section explains the available parameters.

## Command-Line Options

When you run `prosa_binary -h`, you'll see output like the following:
```
Usage: prosa_binary [OPTIONS]
Expand All @@ -18,16 +20,56 @@ Options:
-V, --version Print version
```

Based on this, you have several options:
## Dry Run

Use `--dry_run` to validate your configuration without actually starting ProSA:

```bash
prosa_binary -c config.yaml --dry_run
```

This will:
1. Parse and validate your configuration file
2. If the configuration file **does not exist**, write a default one with all your processors' default settings
3. Display how ProSA would run (which processors, which adaptors)
4. Exit without starting any processor

This is particularly useful for:
- Generating a starter configuration file for a new ProSA instance
- Validating configuration changes before applying them in production

## Configuration Path

The `-c` / `--config` option accepts either a **single file** or a **directory**:

```bash
# Single configuration file
prosa_binary -c /etc/prosa.yml

# Directory containing multiple configuration files
prosa_binary -c /etc/myprosa/
```

When pointing to a directory, both YAML/TOML files in that directory are merged together. This lets you split configuration across multiple files (e.g., one for observability, one per processor). See the [Configuration](ch01-02-config.md) chapter for details.

## Environment Variables

Configuration values can also be set through environment variables. For example:

```bash
PROSA_NAME="my-prosa" prosa_binary -c config.yaml
```

This is useful in containerized environments where configuration is injected via environment variables.

## Version Information

The `-V` flag provides a short version, while `--version` shows detailed component information:

- dry_run: Use this option to test your configuration file or create it if it doesn't exist.
- config: Specify the path to your configuration folder/file.
- name: Override the name in your prosa settings. This sets the name of your ProSA instance.
- worker_threads: Specify the number of threads allocated for ProSA. Each processor can launch threads individually; thus, this option may have varying effects depending on your processor's capabilities.
- version: Provides information about the binary crate version as well as the versions of all components used. For example:
```bash
$ prosa_binary -V
prosa 0.1.0

$ prosa_binary --version
prosa 0.1.0 - core::main::MainProc = { crate = prosa, version = 0.2.0 }
inj
Expand All @@ -37,3 +79,28 @@ prosa 0.1.0 - core::main::MainProc = { crate = prosa, version = 0.2.0 }
Processor: stub::proc::StubProc = { crate = prosa, version = 0.2.0 }
Adaptor : stub::adaptor::StubParotAdaptor = { crate = prosa, version = 0.2.0 }
```

The verbose output shows:
- The **Main** processor type and its crate/version
- Each **Processor** instance with its name, type, crate, and version
- Each **Adaptor** associated with its processor

This is valuable for debugging version mismatches or verifying that the correct components are compiled in.

## Worker Threads

The `-t` / `--worker_threads` option controls the number of threads for the **Main** task's Tokio runtime (default: 1).

Note that each processor runs in its own thread(s) independently — see [Threads](ch03-08-threads.md) for details on per-processor thread configuration.

## Graceful Shutdown

ProSA handles `Ctrl+C` (SIGINT) for graceful shutdown:

1. The Main processor receives the signal
2. A `Shutdown` message is sent to every registered processor
3. Each processor calls `adaptor.terminate()` to clean up resources
4. Each processor deregisters from the Main bus
5. The Main processor exits once all processors have stopped

This ensures that in-flight transactions are not abruptly terminated and that resources (connections, files, etc.) are properly released.
99 changes: 99 additions & 0 deletions prosa_book/src/ch01-06-sumup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Putting It All Together

Now that you're familiar with [Cargo-ProSA](ch01-01-cargo-prosa.md), [configuration](ch01-02-config.md), [observability](ch01-02-01-observability.md), and [running ProSA](ch01-02-04-run.md), let's walk through a complete example from scratch using the built-in processors.

## Prerequisites

You need [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) installed, along with `cargo-prosa`:

```bash
cargo install cargo-prosa
```

## Create and scaffold a project

```bash
cargo prosa new my-first-prosa
cd my-first-prosa
```

This generates a Rust project with a `ProSA.toml` descriptor, a `build.rs`, and a `main.rs` that will be auto-generated from your ProSA configuration.

You can inspect the available components with:

```bash
cargo prosa list
```

This shows all discovered components: Main, TVF, Processors, and Adaptors.

## Add processors

Add a **stub** processor that will respond to requests, and an **injector** that will send requests:

```bash
cargo prosa add -n stub-1 -a StubParotAdaptor stub
cargo prosa add -n inj-1 -a InjDummyAdaptor inj
```

- `-n` sets the processor instance name (used in configuration)
- `-a` selects which adaptor to use

Your `ProSA.toml` file now contains the processor configuration. You can also edit this file manually.

## Generate and edit the configuration

Build the project and retrieve the generated configuration from _target/config.yml_ (or _target/config.toml_).

You can also generate a default configuration using `--dry_run` (see [Run ProSA](ch01-02-04-run.md) for details on this flag):

```bash
cargo run -- -c config.yaml --dry_run
```

Then edit `config.yaml` to wire the injector to the stub. The stub needs to declare a service name, and the injector needs to target that same service:

```yaml
name: "my-first-prosa"
observability:
level: debug
metrics:
stdout:
level: info
traces:
stdout:
level: debug

stub_1:
service_names:
- "TEST_SERVICE"

inj_1:
service_name: "TEST_SERVICE"
max_speed: 1.0
```

This configures:
- The stub to respond to requests on `"TEST_SERVICE"`
- The injector to send 2 transactions per second to `"TEST_SERVICE"`
- [Observability](ch01-02-01-observability.md) output to stdout so you can see what's happening

## Run it

```bash
cargo run -- -n "MyFirstProSA" -c config.yaml
```

You should see log output showing:
1. ProSA starting with the configured name
2. The stub processor registering `TEST_SERVICE`
3. The injector discovering the service and starting to send transactions
4. Responses flowing back from the stub to the injector

Press `Ctrl+C` to stop (see [Graceful Shutdown](ch01-02-04-run.md#graceful-shutdown)).

## What's next?

You now know how to build and operate a ProSA instance. The next chapters cover how to develop your own components:
- **[Adaptor](ch02-00-adaptor.md)** — write custom protocol adaptors
- **[Processor](ch03-00-proc.md)** — write custom processors with their own business logic
Loading
Loading