-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(*)!: create bank aggregate and projector
Signed-off-by: Brooks Townsend <[email protected]> ensure providers run on cosmonic Signed-off-by: Brooks Townsend <[email protected]>
- Loading branch information
1 parent
386e4ec
commit c7f45bc
Showing
32 changed files
with
611 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[build] | ||
target = "wasm32-unknown-unknown" | ||
|
||
[net] | ||
git-fetch-with-cli = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
SMANGOYSDZ4P3SG4GDUXECMO2J2GFOIV6NQJWGFOVA3GZBSXMGZT5GWFJ4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"} | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
use crate::*; | ||
|
||
pub(crate) fn handle_create_account(input: CreateAccount) -> Result<EventList> { | ||
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, | ||
}, | ||
)]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use crate::*; | ||
|
||
impl From<AccountCreated> 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<StateAck> { | ||
Ok(StateAck::ok(Some(BankAccountAggregateState::from(input)))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<BankAccountAggregateState>, | ||
) -> anyhow::Result<EventList> { | ||
commands::handle_create_account(input) | ||
} | ||
|
||
// -- Events -- | ||
fn apply_account_created( | ||
&self, | ||
input: AccountCreated, | ||
_state: Option<BankAccountAggregateState>, | ||
) -> anyhow::Result<StateAck> { | ||
events::apply_account_created(input) | ||
} | ||
} | ||
|
||
const STREAM: &str = "bankaccount"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String, u32>, // 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::<u32>()) | ||
.unwrap_or(0) | ||
} | ||
|
||
/// Returns the total amount of funds on hold | ||
pub fn total_reserved(&self) -> u32 { | ||
self.reserved_funds.values().sum::<u32>() | ||
} | ||
|
||
/// 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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name = "BankAccountAggregate" | ||
language = "rust" | ||
type = "actor" | ||
|
||
[actor] | ||
key_directory = "./.keys" | ||
claims = ["cosmonic:eventsourcing", "wasmcloud:builtin:logging"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,7 @@ npm run build | |
cd actor | ||
wash build | ||
``` | ||
|
||
## All Events | ||
|
||
![All Bank Account Events](./all_events.png) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
<Mermaid /> | ||
|
||
## Schema | ||
<SchemaViewer /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
<Mermaid /> | ||
|
||
## Schema | ||
<SchemaViewer /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.