Skip to content

Commit ec336fb

Browse files
committed
docs: update README to clarify module configuration and initialization process
1 parent 64a89ed commit ec336fb

File tree

1 file changed

+54
-40
lines changed

1 file changed

+54
-40
lines changed

sdk/README.md

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Caryatid module SDK
22

3-
This is an SDK for building modules in Caryatid. It provides a `#[module]` macro which does
3+
This is an SDK for building modules in Caryatid. It provides a `#[module]` macro which does
44
most of the hard work - all you need to do is implement an `init` function, which gets passed
55
a `context` which contains the message bus and global configuration, and a `config` which
66
is the subset of configuration for your particular module.
77

8-
You need to choose a message type that this module will handle. This can be a generic format
9-
like JSON, or a specific Message enum for all the messages in your application. The message type
8+
You need to choose a message type that this module will handle. This can be a generic format
9+
like JSON, or a specific Message enum for all the messages in your application. The message type
1010
has to be common across all modules built into a single process.
1111

1212
### Template module
@@ -34,17 +34,17 @@ impl MyModule {
3434
// Implement the single initialisation function, with application
3535
// Context and this module's Config
3636
fn init(&self, context: Arc<Context<MType>>, config: Arc<Config>) -> Result<()> {
37-
// ... Read configuration from config
38-
// ... Register message subscribers and/or request handlers on context.message_bus
39-
// ... Start other async processes
37+
// ... Read configuration from config
38+
// ... Register message subscribers and/or request handlers on context.message_bus
39+
// ... Start other async processes
4040
}
4141
}
4242
```
4343

4444
## Message bus
4545

4646
The SDK provides a `MessageBus` trait which is implemented by a collection of units in
47-
[`process`](../process). As far as a module is concerned it doesn't need to know what kind
47+
[`process`](../process). As far as a module is concerned it doesn't need to know what kind
4848
of message bus it is talking to, whether messages are routed internal or externally, or both.
4949
It just publishes and subscribes on the bus given in `context.message_bus`.
5050

@@ -59,27 +59,27 @@ Assuming the module is created with message_type `serde_json::Value` as above:
5959

6060
```rust
6161
let topic = "test.simple";
62-
let test_message = Arc::new(json!({
62+
let test_message = Arc::new(json!({
6363
"message": "Hello, world!",
6464
}));
6565

66-
context.message_bus.publish(topic, test_message)
67-
.await
68-
.expect("Failed to publish message");
66+
context.message_bus.publish(topic, test_message)
67+
.await
68+
.expect("Failed to publish message");
6969
```
7070

71-
Note that the message must be wrapped in an `Arc` to be passed around. You can see that publish
71+
Note that the message must be wrapped in an `Arc` to be passed around. You can see that publish
7272
is async, so this needs to be called in an async context.
7373

7474
#### Subscribing
7575

7676
```rust
7777
let topic = "test.simple";
7878

79-
context.message_bus.subscribe(&topic,
80-
|message: Arc<serde_json::Value>| {
81-
info!("Received: {:?}", message);
82-
})?;
79+
context.message_bus.subscribe( & topic,
80+
| message: Arc<serde_json::Value>| {
81+
info ! ("Received: {:?}", message);
82+
}) ?;
8383
```
8484

8585
The subscriber is a closure (lambda) which takes an Arc containing the message type.
@@ -89,25 +89,25 @@ A full version of this can be found in [examples/simple](../examples/simple).
8989
### Request-response
9090

9191
The SDK also offers a request-response model, layered on top of the basic pub-sub in an
92-
implementation-independent way. In this case you call `context.message_bus.request()` and
92+
implementation-independent way. In this case you call `context.message_bus.request()` and
9393
`context.message_bus.handle()`:
9494

9595
#### Requesting
9696

9797
```rust
9898
let topic = "test.simple";
99-
let test_message = Arc::new(json!({
99+
let test_message = Arc::new(json!({
100100
"message": "Hello, world!",
101101
}));
102102

103-
match context.message_bus.request(topic, test_message).await {
104-
Ok(response) => { info!("Got response: {:?}", response); },
105-
Err(e) => { error!("Got error: {e}"); }
106-
}
103+
match context.message_bus.request(topic, test_message).await {
104+
Ok(response) => { info ! ("Got response: {:?}", response); },
105+
Err(e) => { error ! ("Got error: {e}"); }
106+
}
107107
```
108108

109109
`request()` returns a Result, which either contains an `Arc` of the response message, or an error
110-
if nothing responded (after a timeout). Note that application-level errors need to be encoded into
110+
if nothing responded (after a timeout). Note that application-level errors need to be encoded into
111111
the message type - in this case, in the JSON object.
112112

113113
#### Responding
@@ -117,15 +117,15 @@ an `Arc` with the message type.
117117

118118
```rust
119119
async fn handler(message: Arc<Value>) -> Arc<Value> {
120-
let response = json!({
120+
let response = json!({
121121
"message": "Pleased to meet you!",
122122
});
123123

124-
Arc::new(response)
125-
}
124+
Arc::new(response)
125+
}
126126

127-
let topic = "test.simple";
128-
context.message_bus.handle(topic, handler)?;
127+
let topic = "test.simple";
128+
context.message_bus.handle(topic, handler) ?;
129129
```
130130

131131
Note that as above the handler must return a valid message - it cannot return an Error.
@@ -135,10 +135,10 @@ A full version of request/response can be found in [examples/request](../example
135135
## Typed Messages
136136

137137
It is perfectly possible to communicate with JSON objects, or even strings, and some
138-
applications may wish to do so. However, there is a cost both in execution time and
138+
applications may wish to do so. However, there is a cost both in execution time and
139139
code complexity in packing and unpacking JSON for every message.
140140

141-
The Caryatid SDK therefore provides a way to specify messages as native Rust types. Since in any
141+
The Caryatid SDK therefore provides a way to specify messages as native Rust types. Since in any
142142
real-world system there will be more than one type, this is done by defining a system-wide
143143
`enum` of all the messages - e.g.
144144

@@ -184,9 +184,7 @@ use crate::message::Message;
184184
pub struct TypedSubscriber;
185185

186186
impl TypedSubscriber {
187-
188187
fn init(&self, context: Arc<Context<Message>>, config: Arc<Config>) -> Result<()> {
189-
190188
let topic = "test.simple";
191189
context.message_bus.subscribe(topic, |message: Arc<Message>| {
192190
match message.as_ref()
@@ -219,7 +217,6 @@ use crate::message::{Test, Message};
219217
pub struct TypedPublisher;
220218

221219
impl TypedPublisher {
222-
223220
// Context and this module's Config
224221
fn init(&self, context: Arc<Context<Message>>, config: Arc<Config>) -> Result<()> {
225222
let message_bus = context.message_bus.clone();
@@ -241,25 +238,42 @@ impl TypedPublisher {
241238
The full versions of `TypedSubscriber` and `TypedPublisher` can be found in
242239
[examples/typed](../examples/typed).
243240

241+
---
244242
## Module configuration
245243

246-
The module's `init` function is also passed a [`config::Config`](https://docs.rs/config/)
247-
extracted from the global configuration file specifically for this module. You can use
248-
this like any other config to get configuration values.
244+
The module's `init` function is passed a [`config::Config`](https://docs.rs/config/) that merges the `[global]` section
245+
with the `[module.x]` module-specific configuration. This allows modules to access shared configuration values defined in
246+
`[global.*]` while still having their own overrides. Module-specific values take precedence over global values when keys
247+
collide.
249248

250249
For example, if your module was called `my-module` and the global configuration file contained:
251250

252251
```toml
252+
[global.startup]
253+
method = "default"
254+
topic = "app.startup"
255+
253256
[module.my-module]
254257
topic = "test.simple"
255258
count = 10
256259
```
257260

258-
You could get these values with
261+
Your module config would contain:
262+
263+
- `startup.method``"default"` (from global)
264+
- `startup.topic``"app.startup"` (from global)
265+
- `topic``"test.simple"` (module-specific)
266+
- `count``10` (module-specific)
267+
268+
You can access these values like any other config:
259269

260270
```rust
261-
let topic = config.get_string("topic")?;
262-
let count = config.get::<u32>("count")?;
271+
// ... other imports
272+
use config::Config;
273+
274+
let method = config.get_string("startup.method")?;
275+
let topic = config.get_string("topic")?;
276+
let count = config.get::<u32>("count")?;
263277
```
264278

265-
(better error handling or defaulting is left as an exercise for the reader!)
279+
(Better error handling or defaulting is left as an exercise for the reader!)

0 commit comments

Comments
 (0)