diff --git a/Justfile b/Justfile index e6023c6..56adbc6 100644 --- a/Justfile +++ b/Justfile @@ -10,7 +10,9 @@ build: (cd $dir && wash build); \ done -version := "0.0.0" +version := "0.1.0" push: # Push to GHCR + wash push ghcr.io/cosmonic/cosmonic-gitops/bankaccount_projector:{{version}} projector/build/bankaccount_projector_s.wasm + wash push ghcr.io/cosmonic/cosmonic-gitops/bankaccount_aggregate:{{version}} aggregate/build/bankaccount_aggregate_s.wasm wash push ghcr.io/cosmonic/cosmonic-gitops/bankaccount_catalog:{{version}} eventcatalog/actor/build/bankaccountcatalog_s.wasm \ No newline at end of file diff --git a/aggregate/.cargo/config.toml b/aggregate/.cargo/config.toml new file mode 100644 index 0000000..4905f77 --- /dev/null +++ b/aggregate/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "wasm32-unknown-unknown" + +[net] +git-fetch-with-cli = true \ No newline at end of file diff --git a/aggregate/.gitignore b/aggregate/.gitignore new file mode 100644 index 0000000..262ca9a --- /dev/null +++ b/aggregate/.gitignore @@ -0,0 +1,16 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +build/ diff --git a/aggregate/.keys/bankaccount_aggregate_module.nk b/aggregate/.keys/bankaccount_aggregate_module.nk new file mode 100644 index 0000000..54994b7 --- /dev/null +++ b/aggregate/.keys/bankaccount_aggregate_module.nk @@ -0,0 +1 @@ +SMANGOYSDZ4P3SG4GDUXECMO2J2GFOIV6NQJWGFOVA3GZBSXMGZT5GWFJ4 \ No newline at end of file diff --git a/aggregate/Cargo.toml b/aggregate/Cargo.toml new file mode 100644 index 0000000..2239cf3 --- /dev/null +++ b/aggregate/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "bankaccount-aggregate" +version = "0.2.0" +authors = ["Cosmonic Team"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "bankaccount_aggregate" + +[dependencies] +anyhow = "1.0.40" +async-trait = "0.1" +futures = { version = "0.3", features = ["executor"] } +serde_bytes = "0.11" +serde_json = "1.0.94" +serde = { version = "1.0", features = ["derive"] } +wasmbus-rpc = "0.14.0" +concordance-gen = { git = "https://github.com/cosmonic/concordance"} +wasmcloud-interface-logging = {version = "0.10.0", features = ["sync_macro"]} +regress = "0.7.1" + +[profile.release] +# Optimize for small code size +lto = true +opt-level = "s" +strip = true \ No newline at end of file diff --git a/aggregate/README.md b/aggregate/README.md new file mode 100644 index 0000000..4c02bb9 --- /dev/null +++ b/aggregate/README.md @@ -0,0 +1,33 @@ +# Bank Account Aggregate +This aggregate represents the sum of events on the `bankaccount` stream, which is keyed by the account number on the commands and events in this logical stream. + +# Configuration +The following configuration values should be set for this aggregate to work properly. +* `ROLE` - `aggregate` +* `INTEREST` - `bankaccount` +* `NAME` - `bankaccount` +* `KEY` - `account_number` + +# Manual Testing +You can send the following commands manually to watch the aggregate perform its tasks: + +## Creating an Account +You can use the following `nats req` command (edit the data as you see fit) to create a new account by submitting a new `create_account` command: +``` +nats req cc.commands.bankaccount '{"command_type": "create_account", "key": "ABC123", "data": {"account_number": "ABC123", "initial_balance": 4000, "min_balance": 100, "customer_id": "CUSTBOB"}}' +``` +You should receive a reply that looks something like this: +``` +11:25:05 Sending request on "cc.commands.bankaccount" +11:25:05 Received with rtt 281.083µs +{"stream":"CC_COMMANDS", "seq":2} +``` + +And now you can verify that you have indeed created the `ABC123` account (note the key is account number and not customer ID). +``` +nats kv get CC_STATE agg.bankaccount.ABC123 +CC_STATE > agg.bankaccount.ABC123 created @ 20 Mar 23 15:25 UTC + +{"balance":4000,"min_balance":100,"account_number":"ABC123"} +``` + diff --git a/aggregate/src/commands.rs b/aggregate/src/commands.rs new file mode 100644 index 0000000..e79e664 --- /dev/null +++ b/aggregate/src/commands.rs @@ -0,0 +1,14 @@ +use crate::*; + +pub(crate) fn handle_create_account(input: CreateAccount) -> Result { + Ok(vec![Event::new( + AccountCreated::TYPE, + STREAM, + &AccountCreated { + initial_balance: input.initial_balance, + account_number: input.account_number.to_string(), + min_balance: input.min_balance, + customer_id: input.customer_id, + }, + )]) +} diff --git a/aggregate/src/events.rs b/aggregate/src/events.rs new file mode 100644 index 0000000..8964974 --- /dev/null +++ b/aggregate/src/events.rs @@ -0,0 +1,17 @@ +use crate::*; + +impl From for BankAccountAggregateState { + fn from(input: AccountCreated) -> BankAccountAggregateState { + BankAccountAggregateState { + balance: input.initial_balance.unwrap_or(0) as _, + min_balance: input.min_balance.unwrap_or(0) as _, + account_number: input.account_number, + customer_id: input.customer_id, + reserved_funds: HashMap::new(), + } + } +} + +pub(crate) fn apply_account_created(input: AccountCreated) -> Result { + Ok(StateAck::ok(Some(BankAccountAggregateState::from(input)))) +} diff --git a/aggregate/src/lib.rs b/aggregate/src/lib.rs new file mode 100644 index 0000000..baa5b2b --- /dev/null +++ b/aggregate/src/lib.rs @@ -0,0 +1,38 @@ +use anyhow::Result; +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +mod commands; +mod events; +mod state; + +use state::BankAccountAggregateState; + +concordance_gen::generate!({ + path: "../eventcatalog", + role: "aggregate", + entity: "bank account" +}); + +impl BankAccountAggregate for BankAccountAggregateImpl { + // -- Commands -- + fn handle_create_account( + &self, + input: CreateAccount, + _state: Option, + ) -> anyhow::Result { + commands::handle_create_account(input) + } + + // -- Events -- + fn apply_account_created( + &self, + input: AccountCreated, + _state: Option, + ) -> anyhow::Result { + events::apply_account_created(input) + } +} + +const STREAM: &str = "bankaccount"; diff --git a/aggregate/src/state.rs b/aggregate/src/state.rs new file mode 100644 index 0000000..b3aaa9c --- /dev/null +++ b/aggregate/src/state.rs @@ -0,0 +1,69 @@ +use crate::*; + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct BankAccountAggregateState { + pub balance: u32, // CENTS + pub min_balance: u32, + pub account_number: String, + pub customer_id: String, + pub reserved_funds: HashMap, // wire_transfer_id -> amount +} + +impl BankAccountAggregateState { + /// Returns the regular balance minus the sum of transfer holds + pub fn available_balance(&self) -> u32 { + self.balance + .checked_sub(self.reserved_funds.values().sum::()) + .unwrap_or(0) + } + + /// Returns the total amount of funds on hold + pub fn total_reserved(&self) -> u32 { + self.reserved_funds.values().sum::() + } + + /// Releases the funds associated with a wire transfer hold. Has no affect on actual balance, only available + pub fn release_funds(self, reservation_id: &str) -> Self { + let mut new_state = self.clone(); + new_state.reserved_funds.remove(reservation_id); + + new_state + } + + /// Adds a reservation hold for a given wire transfer. Has no affect on actual balance, only available + pub fn reserve_funds(self, reservation_id: &str, amount: u32) -> Self { + let mut new_state = self.clone(); + new_state + .reserved_funds + .insert(reservation_id.to_string(), amount); + new_state + } + + /// Commits held funds. Subtracts held funds from balance. Note: A more realistic banking + /// app might emit an overdrawn/overdraft event if the new balance is less than 0. Here we + /// just floor the balance at 0. Also note that overcommits shouldn't happen because we reject + /// attempts to hold beyond available funds + pub fn commit_funds(self, reservation_id: &str) -> Self { + let mut new_state = self.clone(); + let amount = new_state.reserved_funds.remove(reservation_id).unwrap_or(0); + new_state.balance = new_state.balance.checked_sub(amount).unwrap_or(0); + new_state + } + + /// Withdraws a given amount of funds + pub fn withdraw(self, amount: u32) -> Self { + let mut new_state = self.clone(); + new_state.balance = new_state.balance.checked_sub(amount).unwrap_or(0); + new_state + } + + /// Deposits a given amount of funds. Ceilings at u32::MAX + pub fn deposit(self, amount: u32) -> Self { + let mut new_state = self.clone(); + new_state.balance = new_state + .balance + .checked_add(amount) + .unwrap_or(new_state.balance); + new_state + } +} diff --git a/aggregate/wasmcloud.toml b/aggregate/wasmcloud.toml new file mode 100644 index 0000000..ecd3909 --- /dev/null +++ b/aggregate/wasmcloud.toml @@ -0,0 +1,7 @@ +name = "BankAccountAggregate" +language = "rust" +type = "actor" + +[actor] +key_directory = "./.keys" +claims = ["cosmonic:eventsourcing", "wasmcloud:builtin:logging"] diff --git a/eventcatalog/README.md b/eventcatalog/README.md index 086c479..c0caa4f 100644 --- a/eventcatalog/README.md +++ b/eventcatalog/README.md @@ -17,3 +17,7 @@ npm run build cd actor wash build ``` + +## All Events + +![All Bank Account Events](./all_events.png) diff --git a/eventcatalog/actor/Cargo.lock b/eventcatalog/actor/Cargo.lock index 59b59d6..5962c99 100644 --- a/eventcatalog/actor/Cargo.lock +++ b/eventcatalog/actor/Cargo.lock @@ -250,7 +250,7 @@ dependencies = [ [[package]] name = "bankaccountcatalog" -version = "0.3.0" +version = "0.2.0" dependencies = [ "async-trait", "futures", diff --git a/eventcatalog/actor/Cargo.toml b/eventcatalog/actor/Cargo.toml index 9a307db..1d04bb0 100644 --- a/eventcatalog/actor/Cargo.toml +++ b/eventcatalog/actor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bankaccountcatalog" -version = "0.3.0" +version = "0.2.0" authors = ["Cosmonic Team"] edition = "2021" diff --git a/eventcatalog/all_events.png b/eventcatalog/all_events.png new file mode 100644 index 0000000..69baebd Binary files /dev/null and b/eventcatalog/all_events.png differ diff --git a/eventcatalog/events/AccountCreated/index.md b/eventcatalog/events/AccountCreated/index.md new file mode 100644 index 0000000..454140b --- /dev/null +++ b/eventcatalog/events/AccountCreated/index.md @@ -0,0 +1,20 @@ +--- +name: AccountCreated +summary: "Indicates the creation of a new bank account" +version: 0.0.1 +consumers: + - 'Bank Account Aggregate' + - 'Bank Account Projector' +producers: + - 'Bank Account Aggregate' +tags: + - label: 'event' +externalLinks: [] +badges: [] +--- +Indicates that a bank account has been created. As with all events, this is immutable truth. + + + +## Schema + \ No newline at end of file diff --git a/eventcatalog/events/AccountCreated/schema.json b/eventcatalog/events/AccountCreated/schema.json new file mode 100644 index 0000000..32d1bd2 --- /dev/null +++ b/eventcatalog/events/AccountCreated/schema.json @@ -0,0 +1,26 @@ +{ + "$id": "https://cosmonic.com/concordance/bankaccount/AccountCreated.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "AccountCreated", + "type": "object", + "properties": { + "accountNumber": { + "type": "string", + "description": "The account number of the new account" + }, + "minBalance": { + "type": "integer", + "description": "The minimum required maintenance balance for the account" + }, + "initialBalance": { + "type": "integer", + "description": "Initial deposit amount for the account" + }, + "customerId": { + "type": "string", + "description": "The ID of the customer" + } + }, + "required": ["accountNumber", "customerId"] + } + \ No newline at end of file diff --git a/eventcatalog/events/CreateAccount/index.md b/eventcatalog/events/CreateAccount/index.md new file mode 100644 index 0000000..ba54d68 --- /dev/null +++ b/eventcatalog/events/CreateAccount/index.md @@ -0,0 +1,17 @@ +--- +name: CreateAccount +summary: "Requests the creation of a new bank account" +version: 0.0.1 +consumers: + - 'Bank Account Aggregate' +tags: + - label: 'command' +externalLinks: [] +badges: [] +--- +Requests the creation of a new bank account. This command can fail to process if the parameters are invalid or if the account already exists. + + + +## Schema + \ No newline at end of file diff --git a/eventcatalog/events/CreateAccount/schema.json b/eventcatalog/events/CreateAccount/schema.json new file mode 100644 index 0000000..8dabe27 --- /dev/null +++ b/eventcatalog/events/CreateAccount/schema.json @@ -0,0 +1,25 @@ +{ + "$id": "https://cosmonic.com/concordance/bankaccount/CreateAccount.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "CreateAccount", + "type": "object", + "properties": { + "accountNumber": { + "type": "string", + "description": "The account number to be created" + }, + "minBalance": { + "type": "integer", + "description": "The minimum required maintenance balance for the account" + }, + "initialBalance": { + "type": "integer", + "description": "Initial deposit amount for the account" + }, + "customerId": { + "type": "string", + "description": "The ID of the customer" + } + }, + "required": ["accountNumber", "customerId"] +} diff --git a/eventcatalog/package-lock.json b/eventcatalog/package-lock.json index 12eeef8..1529e99 100644 --- a/eventcatalog/package-lock.json +++ b/eventcatalog/package-lock.json @@ -1,12 +1,12 @@ { "name": "eventcatalog", - "version": "0.0.0", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "eventcatalog", - "version": "0.0.0", + "version": "0.0.1", "dependencies": { "@eventcatalog/core": "1.0.1" }, diff --git a/eventcatalog/package.json b/eventcatalog/package.json index bc46ce7..f767f0b 100644 --- a/eventcatalog/package.json +++ b/eventcatalog/package.json @@ -1,6 +1,6 @@ { "name": "eventcatalog", - "version": "0.0.0", + "version": "0.1.0", "private": true, "scripts": { "start": "eventcatalog start", diff --git a/eventcatalog/services/Bank Account Aggregate/index.md b/eventcatalog/services/Bank Account Aggregate/index.md new file mode 100644 index 0000000..d032b60 --- /dev/null +++ b/eventcatalog/services/Bank Account Aggregate/index.md @@ -0,0 +1,11 @@ +--- +name: Bank Account Aggregate +summary: | + The aggregate for managing individual bank accounts +tags: + - label: 'aggregate' +--- + +The bank account aggregate is responsible for validating incoming commands and emitting the appropriate events. + + \ No newline at end of file diff --git a/eventcatalog/services/Bank Account Projector/index.md b/eventcatalog/services/Bank Account Projector/index.md new file mode 100644 index 0000000..96ce287 --- /dev/null +++ b/eventcatalog/services/Bank Account Projector/index.md @@ -0,0 +1,11 @@ +--- +name: Bank Account Projector +summary: | + The projector responsible for creating bank account read model +tags: + - label: 'projector' +--- + +This projector monitors bank account events and projects the corresponding read model. + + \ No newline at end of file diff --git a/projector/.cargo/config.toml b/projector/.cargo/config.toml new file mode 100644 index 0000000..4905f77 --- /dev/null +++ b/projector/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "wasm32-unknown-unknown" + +[net] +git-fetch-with-cli = true \ No newline at end of file diff --git a/projector/.gitignore b/projector/.gitignore new file mode 100644 index 0000000..262ca9a --- /dev/null +++ b/projector/.gitignore @@ -0,0 +1,16 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +build/ diff --git a/projector/.keys/bankaccount_projector_module.nk b/projector/.keys/bankaccount_projector_module.nk new file mode 100644 index 0000000..24f7801 --- /dev/null +++ b/projector/.keys/bankaccount_projector_module.nk @@ -0,0 +1 @@ +SMADQF4DVD4AUK2WAR5RKDW4G2S4TRNS4MS5ADERYQ6STLK7MANGYNQCPA \ No newline at end of file diff --git a/projector/Cargo.toml b/projector/Cargo.toml new file mode 100644 index 0000000..3f42a65 --- /dev/null +++ b/projector/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "bankaccount-projector" +version = "0.2.0" +authors = ["Cosmonic Team"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "bankaccount_projector" + +[dependencies] +anyhow = "1.0.40" +async-trait = "0.1" +futures = { version = "0.3", features = ["executor"] } +serde_bytes = "0.11" +serde_json = "1.0.94" +serde = { version = "1.0", features = ["derive"] } +wasmbus-rpc = "0.14.0" +concordance-gen = { git = "https://github.com/cosmonic/concordance"} +wasmcloud-interface-logging = {version = "0.10.0", features = ["sync_macro"]} +wasmcloud-interface-keyvalue = "0.11.0" +regress = "0.7.1" + +[profile.release] +# Optimize for small code size +lto = true +opt-level = "s" +strip = true \ No newline at end of file diff --git a/projector/README.md b/projector/README.md new file mode 100644 index 0000000..07e2314 --- /dev/null +++ b/projector/README.md @@ -0,0 +1,22 @@ +# Bank Account Projector +This projector is responsible for storing read-optimized view data for bank accounts as a function application over the stream of inbound bank account events. + +This projector maintains the following projections: +* **balances** - The current balance of any account can be looked up immediately by querying the key `balance.{account_number}` +* **ledger** - The ledger (chronological transaction history) of any account can be received as a JSON string via the key `ledger.{account_number}` + +⚠️ NOTE: for testing purposes please don't use non-alphanumeric characters for the fake account numbers as it could potentially mess up key value storage depending on the chosen provider's support for complex keys. + +# Configuration +This actor needs to be linked (bound) to two capability providers. One must support the `cosmonic:eventsourcing` contract. The Concordance provider for this contract requires the following configuration: + +* `ROLE` - `projector` +* `INTEREST` - `account_created,funds_deposited,funds_withdrawn,wire_funds_reserved,wire_funds_released` +* `NAME` - `bankaccount_projector` + +Note that stateless event handlers (whether you're using them as projectors, notifiers, gateways, etc) must declare their interest in events _explicitly_ in a comma-delimited list. Because of the use of commas in this data, it's probably easier and more reliable to use `wash ctl put link` rather than using the graphical wasmCloud dashboard. + +This actor will also need to be linked to a `wasmcloud:keyvalue` capability provider, the implementation of which is entirely up to the developer and the configuration is likely specific to the implementation chosen (e.g. Redis vs NATS, etc). + +# Manual Testing +You can start a wasmCloud host, start all of the bank account actors, and then start both the Concordance provider and your key-value provider of choice. Set the link definitions accordingly and then run the `scenario_1.sh` script in the [scripts](../scripts/) directory. You should then see the aggregate state stored in the `CC_STATE` bucket, the resulting events in the `CC_EVENTS` stream, and, assuming you used Redis, you'll see a balance projection in `balance.ABC123` and the ledger JSON structure in `ledger.ABC123`. diff --git a/projector/src/lib.rs b/projector/src/lib.rs new file mode 100644 index 0000000..b73d3ca --- /dev/null +++ b/projector/src/lib.rs @@ -0,0 +1,17 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +concordance_gen::generate!({ + path: "../eventcatalog", + role: "projector", + entity: "bank account" +}); + +mod store; + +#[async_trait] +impl BankAccountProjector for BankAccountProjectorImpl { + async fn handle_account_created(&self, input: AccountCreated) -> Result<()> { + store::initialize_account(input).await + } +} diff --git a/projector/src/store.rs b/projector/src/store.rs new file mode 100644 index 0000000..a43700b --- /dev/null +++ b/projector/src/store.rs @@ -0,0 +1,90 @@ +use std::collections::HashMap; + +use crate::*; + +use serde::{Deserialize, Serialize}; +use wasmbus_rpc::actor::prelude::*; +use wasmcloud_interface_keyvalue::{KeyValue, KeyValueSender, SetRequest}; +use wasmcloud_interface_logging::{debug, error}; + +// Note an invariant: the last() element in a ledger's effective_balance field is +// always the same as the balance stored in the balance.{account} key. + +/// Creates a new AccountLedger instance with an initial transaction as a deposit, +/// sets the current balance to the initial amount +pub async fn initialize_account(event: AccountCreated) -> Result<()> { + debug!("Initializing account {}", event.account_number); + let kv = KeyValueSender::new(); + + let account_number = event.account_number.to_string(); + let ctx = Context::default(); + + let initial_balance = event.initial_balance.unwrap_or_default() as u32; + + // Set up the initial ledger + let ledger_key = format!("ledger.{account_number}"); + let ledger = AccountLedger::new(event.account_number, initial_balance); + let ledger_json = serde_json::to_string(&ledger).unwrap(); // we know this won't fail + + // set the current balance + let balance_key = format!("balance.{account_number}"); + + set(&ctx, &kv, ledger_key, ledger_json).await; + set(&ctx, &kv, balance_key, initial_balance.to_string()).await; + + Ok(()) +} + +async fn set(ctx: &Context, kv: &KeyValueSender, key: String, value: String) { + if let Err(e) = kv + .set( + ctx, + &SetRequest { + key: key.clone(), + value, + expires: 0, + }, + ) + .await + { + error!("Failed to set {key} in store: {e}"); + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct AccountLedger { + pub account_number: String, + pub ledger_lines: Vec, + pub holds: HashMap, +} + +impl AccountLedger { + fn new(account_number: String, initial_balance: u32) -> AccountLedger { + AccountLedger { + account_number, + holds: HashMap::new(), + ledger_lines: vec![LedgerLine { + amount: initial_balance, + tx_type: TransactionType::Deposit, + effective_balance: initial_balance, + }], + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct LedgerLine { + pub amount: u32, + pub tx_type: TransactionType, + pub effective_balance: u32, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +enum TransactionType { + Withdrawal, + Deposit, + Transfer, + FundsReserve, + FundsRelease, + Unknown, +} diff --git a/projector/wasmcloud.toml b/projector/wasmcloud.toml new file mode 100644 index 0000000..48b8c46 --- /dev/null +++ b/projector/wasmcloud.toml @@ -0,0 +1,7 @@ +name = "BankAccountProjector" +language = "rust" +type = "actor" + +[actor] +key_directory = "./.keys" +claims = ["cosmonic:eventsourcing", "wasmcloud:keyvalue", "wasmcloud:builtin:logging"] diff --git a/wadm.yaml b/wadm.yaml index 3897375..0c19113 100644 --- a/wadm.yaml +++ b/wadm.yaml @@ -3,23 +3,96 @@ kind: Application metadata: name: bank-account annotations: - version: v0.0.0 + version: v0.1.0 description: "The concordance bank account example" spec: components: - name: catalog type: actor properties: - image: ghcr.io/cosmonic/cosmonic-gitops/bankaccount_catalog:0.0.0 + image: ghcr.io/cosmonic/cosmonic-gitops/bankaccount_catalog:0.1.0 traits: - - type: spreadscaler + - type: daemonscaler properties: replicas: 3 - type: linkdef properties: target: httpserver + - name: projector + type: actor + properties: + image: ghcr.io/cosmonic/cosmonic-gitops/bankaccount_projector:0.1.0 + traits: + - type: daemonscaler + properties: + replicas: 3 + - type: linkdef + properties: + target: concordance + values: + NAME: bankaccount_projector + ROLE: projector + INTEREST: account_created + - type: linkdef + properties: + target: keyvalue + + - name: aggregate + type: actor + properties: + image: ghcr.io/cosmonic/cosmonic-gitops/bankaccount_aggregate:0.1.0 + traits: + - type: daemonscaler + properties: + replicas: 3 + - type: linkdef + properties: + target: concordance + values: + ROLE: aggregate + INTEREST: bankaccount + NAME: bankaccount + KEY: accountNumber + + - name: concordance + type: capability + properties: + config: + js_domain: cosmonic + nats_url: 0.0.0.0:4222 + contract: cosmonic:eventsourcing + image: registry.hub.docker.com/cosmonic/concordance:0.1.0 + traits: + - properties: + replicas: 1 + spread: + - name: oncosmonic + requirements: + cosmonic_managed: "true" + type: spreadscaler + - name: keyvalue + type: capability + properties: + image: cosmonic.azurecr.io/builtin_keyvalue:0.2.5 + contract: wasmcloud:keyvalue + traits: + - properties: + replicas: 1 + spread: + - name: oncosmonic + requirements: + cosmonic_managed: "true" + type: spreadscaler - name: httpserver type: capability properties: image: cosmonic.azurecr.io/httpserver_wormhole:0.6.2 contract: wasmcloud:httpserver + traits: + - properties: + replicas: 1 + spread: + - name: oncosmonic + requirements: + cosmonic_managed: "true" + type: spreadscaler