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
44most of the hard work - all you need to do is implement an ` init ` function, which gets passed
55a ` context ` which contains the message bus and global configuration, and a ` config ` which
66is 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
1010has 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
4646The 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
4848of message bus it is talking to, whether messages are routed internal or externally, or both.
4949It 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
7272is 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
8585The 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
9191The 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
111111the 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
131131Note 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
137137It 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
139139code 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
142142real-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;
184184pub struct TypedSubscriber ;
185185
186186impl 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};
219217pub struct TypedPublisher ;
220218
221219impl 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 {
241238The 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
250249For 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 ]
254257topic = " test.simple"
255258count = 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