diff --git a/.github/init.sh b/.github/init.sh new file mode 100644 index 0000000..afd979f --- /dev/null +++ b/.github/init.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# Remove the init workflow so it won't get run more than once +git rm .github/workflows/init.yml + +# Update links in the README +UPSTREAM_REPOSITORY="github.com/${TEMPLATE_REPOSITORY}/compare" +CURRENT_REPOSITORY="github.com/${GITHUB_REPOSITORY}/compare" +sed -i "s|${UPSTREAM_REPOSITORY}|${CURRENT_REPOSITORY}|g" README.md + +# Update the Argo CD Application definition to point to this repository +sed -i "s|${TEMPLATE_REPOSITORY}|${GITHUB_REPOSITORY}|g" setup/argocd-application.yaml \ No newline at end of file diff --git a/.github/workflows/init.yml b/.github/workflows/init.yml new file mode 100644 index 0000000..2849ae8 --- /dev/null +++ b/.github/workflows/init.yml @@ -0,0 +1,74 @@ +name: Initialize the repository + +on: + push: + branches: + - main + +env: + TEMPLATE_REPOSITORY: cosmonic/concordance-gitops + +jobs: + init: + # Don't run this in the template repository. + if: ${{ github.repository != 'cosmonic/concordance-gitops' }} + runs-on: ubuntu-latest + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push the + # added or changed files to the repository. + contents: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: "main" + + - name: Setup GitHub Actions as the commiter + run: | + git config --local user.name 'github-actions[bot]' + git config --local user.email 'github-actions[bot]@users.noreply.github.com' + + # Remove this workflow so it won't run again. + - name: Clean up main + run: | + ./.github/init.sh + git add -A + git commit -m "Initialize the repository" + + - name: Ensure release branches exist + run: | + release_branches=$(git branch -r | awk '/release/ { gsub(/origin\//, "", $1); print $1 }') + if [ -z "${release_branches}" ]; then + git remote add upstream "https://github.com/${TEMPLATE_REPOSITORY}" + git fetch upstream + upstream_branches=$(git branch -r | awk '/release/ { gsub(/upstream\//, "", $1); print $1 }') + for branch_name in $upstream_branches + do + git checkout --orphan "${branch_name}" "upstream/${branch_name}" + git init . + git commit --message "Initialize ${upstream_branch}" + done + git push --branches + git remote remove upstream + fi + + - name: Setup each release branch for pull requests + run: | + release_branches=$(git branch -r | awk '/release/ { gsub(/origin\//, "", $1); print $1 }') + if [ -z "${release_branches}" ]; then + echo "No release branches." + exit 1 + fi + rebase_from='main' + for branch_name in $release_branches + do + git checkout "${branch_name}" + git rebase -X theirs "${rebase_from}" + ./.github/init.sh + git add -A + git commit --amend --no-edit + rebase_from=$branch_name + done + git checkout main + git push --all -f diff --git a/.gitignore b/.gitignore index d142cc5..f68e555 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ host_config.json # rebar lock file rebar.lock + +# wasm files +*.wasm \ No newline at end of file diff --git a/bank_issuer.nk b/.keys/bank_issuer.nk similarity index 100% rename from bank_issuer.nk rename to .keys/bank_issuer.nk diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..e6023c6 --- /dev/null +++ b/Justfile @@ -0,0 +1,16 @@ +set shell := ["bash", "-cu"] + +build: + # Ensure events are up-to-date + cd eventcatalog && npm run build + # Build all the actors using `wash build` + for dir in $(find . -type f -name 'Cargo.toml' | xargs -n1 dirname | rg -v node_modules); do \ + echo "Building $dir"; \ + export WASH_ISSUER_KEY=$(pwd)/.keys/bank_issuer.nk; \ + (cd $dir && wash build); \ + done + +version := "0.0.0" +push: + # Push to GHCR + 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/README.md b/README.md index 54ae07d..2350f28 100644 --- a/README.md +++ b/README.md @@ -1,296 +1,212 @@ # Concordance Banking Example -This example illustrates all of the core event sourcing building blocks applied to a simplified banking model. In this sample, we have modeled a bank account aggregate, projector, process manager, and an artificial gateway that can be used for testing simulated inter-bank transfers (processes/sagas). - -## Step by step guide - -To run these examples, use the following steps. - -
- - - -### 0. ๐Ÿ“ฆ Download and set up `wash` and other dependencies - - - -To run [wasmCloud][wasmcloud] locally, you'll need to download and install `wash`: +[โžก๏ธClick me to clone this repo][new-repository-link] -If you have the [rust toolchain][rust-toolchain] installed, you can use `cargo` to install wash: +> [!IMPORTANT] +> `cosmonic/concordance-gitops` repository is intended to be used as a [template for you to generate your own version](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template) of the demo project for your own testing. +> +> When you create your own repository from the template, it is important to select the option for `Include all branches`, as that is used as part of the initial re-configuration to setup steps for the demo. +> +> Once you have created the repository, [an automated GitHub Action gets kicked off to do a set of customization](./actions) to replace values in the template README with values that match your newly created repository. +> +> Before you get started with rest of the README, please check that the [initial GitHub Action](./actions) has finished and then refresh this README to see it with the updated values. -```console -cargo install wash-cli -``` +This example illustrates all of the core event sourcing building blocks applied to a simplified banking model. In this sample, we have modeled a bank account aggregate, projector, process manager, and an artificial gateway that can be used for testing simulated inter-bank transfers (processes/sagas). -On a Ubuntu based system this you can use `apt` +This particular repository is a template, meant to demonstrate a GitOps flow for deploying a [wasmCloud application][wasmcloud-application] to Cosmonic. The application is a simple banking example, which is a fork of the [Concordance Banking Example][concordance-banking-example]. -```console -sudo apt install wash -``` +## Prerequisites -For other options on how to install `wash`, see [the `wasmcloud/wash`][wash-docs-install]. +In order to try this repository out, you will need: -Along with `wash`, ensure you have access to the following: +1. A Kubernetes cluster with access to deploy to. For local testing, we recommend [kind][`kind`]. +2. [Kubectl][`kubectl`] with kubeconfig pointing to your cluster from the previous step. Use `kubectl config view` to validate. +3. Argo CD deployed into your cluster. For local testing, we recommend following [Argo's Getting Started guide][argo-getting-started] for quick setup. +4. [`cosmo` cli][cosmo-cli-download] `v0.23.2` or later installed. Use `cosmo --version` to validate. +5. Account on [Cosmonic][cosmonic]. It's free to sign up! -- [`nats` CLI](https://docs.nats.io/using-nats/nats-tools/nats_cli) for communicating with NATS -- [`jq`](https://stedolan.github.io/jq/) for displaying and parsing JSON -- [`redis-server`][redis] to use with your [`kvredis` capability provider][wasmcloud-kvredis] +
-[wasmcloud]: https://wasmcloud.com -[wash-docs-install]: https://github.com/wasmCloud/wash#installing-wash -[rust-toolchain]: https://www.rust-lang.org/tools/install -[redis]: https://redis.io -[wasmcloud-kvredis]: https://github.com/wasmCloud/capability-providers/tree/main/kvredis +## Demo -
+In this demo, we will walk through a set of steps with each step building on the previous one. With that being said, the steps we will walk through are as follows: -
+1. [Logging into Cosmonic](#login-to-cosmonic) +1. [Connect your Kubernetes cluster to Cosmonic](#connect-cluster-to-cosmonic) +1. [Deploy the initial state (v0.0.0) of the demo application](#deploy-version-0_0_0) +1. [Deploy v0.1.0 release](#deploy-version-0_1_0) +1. [Deploy v0.2.0 release](#deploy-version-0_2_0) +1. [Deploy v0.3.0 release](#deploy-version-0_3_0) - +
-### 1. ๐ŸŒฉ๏ธ Start wasmCloud +### Logging into Cosmonic -
+Before you proceed with the rest of this demo, you will need to make sure that you are logged in on the command-line to Cosmonic. -Run an instance of wasmCloud locally by running `wash up`: +To check whether you are logged in or not, you can run: -```console -wash up +```shell +cosmo whoami ``` -You can visit the wasmCloud dashboard (also known as the "washboard") at [https://localhost:4000](https://localhost:4000) (by default). - -
- -
+If you have not yet logged in, you will see the following message, prompting you to do so: - - -### 2. ๐Ÿ› ๏ธ Build all the components - - - -You will need to run `make par full` in the `/capability-provider` directory in this repository to generate your own copy of the provider. Once Concordance has had a published release, you'll be able to use its OCI reference. - -Next, build all actors in this repository by executing the default target of the Makefile in this folder: - -```console -make +``` +$ cosmo whoami +Failed to open user credentials. Have you run `cosmo login`? ``` -That command will build the following [identity-verified actors][wasmcloud-signing]: - -| Name | File Path after build | Description | -|-------------------|-------------------------------------------------------------|--------------------------------------------------| -| `process_manager` | `./process_manager/build/bankaccount_processmanager_s.wasm` | The inter-bank transfer process manager | -| `projector` | `./projector/build/bankaccount_projector_s.wasm` | Projector for account balance and ledger | -| `aggregate` | `./aggregate/build/bankaccount_aggregate_s.wasm` | Aggregate that validates commands and emits events | - -[wasmcloud-signing]: https://wasmcloud.com/docs/reference/host-runtime/security#actor-identity - -
- -
- - - -### 3. ๐Ÿ Start relevant providers - - - -#### 3.1 Start the `concordance` provider - -To facilitate the [Event Sourcing][wiki-es] paradigm, we'll need to use Cosmonic's event sourcing capability provider - `concordance`. - -To start the provider, use the washboard to upload `concordance.par.gz`: - -![Upload concordance provider to washboard](./docs/videos/install-concordance-par.gif) - -When finished, the washboard should display the Concordance provider: - -![Washboard with concordance provider loaded](./docs/images/washboard-with-concordance.png) - -[wiki-es]: https://en.wikipedia.org/wiki/Domain-driven_design#Event_sourcing - -#### 3.2 Start the `keyvalue` provider - -To store state from actors like the `projector`, the [`keyvalue` redis provider][wasmcloud-kv-provider] can be used. - -You can start the `keyvalue` provider by its container image (`wasmcloud.azurecr.io/kvredis:0.19.0`): - -![Start redis provider](./docs/videos/start-keyvalue-provider.gif) - -[wasmcloud-kv-provider]: https://github.com/wasmCloud/capability-providers/tree/main/kvredis - -
- -
- - - -### 4. ๐ŸŽ๏ธ Start the relevant actors - - - -Start the `projector`, `process_manager` and `aggregate` actors via the washboard. +Once you are logged in, you will be greeted with a message that should look something like the following with the actual values replaced with your account specific values: -For example, to start the `projector` actor: +```shell +$ cosmo whoami +Current user information: -![Start projector for concordance demo in washboard](./docs/videos/start-projector-for-concordance-demo.gif) +Constellation IDs: dd722aff-7849-4f72-8f63-ca843999202c +NATS public key: AAYOBUJ5QG5YF3OYYSJERTW23SPWBTUF2AJRW3C4HPPVV5YOOM6LZWSV +``` -Follow the same process for `process_manager` and `aggregate` actors. +With your `cosmo` cli now logged in, you are ready to proceed with the rest of the demo! -
+
-
+### Connecting your Kubernetes cluster to Cosmonic - +In order to begin deploying WebAssembly-based applications to your Kubernetes cluster, you will need to connect your cluster with Cosmonic. -### 5. ๐Ÿ”— Link the actors to the providers +To connect your Kubernetes cluster, we will deploy `cosmo-controller` in to your cluster by running the following: - +``` +cosmo connect k8s +``` -To enable communication between the actors and providers, we need to [link them][wasmcloud-docs-linkdefs]. While you can usually use the dashboard to link actors and providers, because one of the providers requires JSON data, we currently have to use the `wash` CLI. +By default without any options provided, this will: + +1. Connect to your Cosmonic account, prompting you to login if you have not already. +2. Attempt to establish a connection to locally configured Kubernetes credentials by checking for existence of `KUBECONFIG` environment variable and then `~/.kube/config` file. +3. Configure and deploy [`cosmo-controller`][cosmo-controller] into the Kubernetes cluster using the locally configured credentials. +4. Deploy a set of wasmCloud hosts using the bundled `CosmonicHostConfig` CRD to the specified namespace (or `default` namespace if no namespace is provided). +5. Deploy [Kubernetes Applier Capability Provider][kubernetes-applier-provider] and [Kubernetes Applier Actor][kubernetes-applier-actor] as [wadm-managed applications][wadm] into your constellation, which will help expose any HTTP-based actors as services inside of your Kubernetes cluster + +Once `cosmo connect k8s` has run successfully, you should see the following output with the actual values matching your account: + +```shell +$ cosmo connect k8s +Successfully connected to constellation [dd722aff-7849-4f72-8f63-ca843999202c] +โœ… Successfully connected to k8s cluster +โœ… Successfully configured controller +โ„น๏ธ Applying additional host labels: +โœ… Successfully started wasmcloud hosts +โœ… Wadm manifest deployed. You can check deployment status at https://app.cosmonic.com/constellation/applications/detail/cosmo-connect-127-0-0-1-default?view=manifest +๐Ÿ”— Kubernetes cluster successfully connected! +๐Ÿš€ Open Cosmonic (https://app.cosmonic.com) to interact with your Kubernetes hosts. +``` -Run the script below to create the links: +With your Kubernetes cluster successfully connected to Cosmonic, you are ready to proceed to the next step of the demo! -```console -export CONCORDANCE_PROVIDER_ID=VAW26CNCVKOTLIJVX2H4WD5T36NKBGWS2GVOIOKAAOOFIJDOJBRFMQZX -export KVREDIS_PROVIDER_ID=VAZVC4RX54J2NVCMCW7BPCAHGGG5XZXDBXFUMDUXGESTMQEJLC3YVZWB +
-export PROJECTOR_ACTOR_ID=MC5D3GHCW3FN6UWHJDH63VQI36L66YN73OIBFVSM3EXPIC6ZG3AEVTE3 -export PROCESS_MANAGER_ACTOR_ID=MC5EQZ6NZY2T5US5JJTCJVAWHETCIIZLLVFUPERSTO2T3AR2NF62JWKI -export AGGREGATE_ACTOR_ID=MCZ2V2VTF4S4QAYKHJTGARIGWFMQXS2FDHKNNI3H7ZHHYAWE6IVCTD7M +### Deploy the initial state (`v0.0.0`) of the demo application -# Link projector <-> concordance -wash ctl link put $PROJECTOR_ACTOR_ID $CONCORDANCE_PROVIDER_ID \ - cosmonic:eventsourcing \ - ROLE=projector INTEREST=account_created,funds_deposited,funds_withdrawn,wire_funds_reserved,wire_funds_released NAME=bankaccount_projector +For the next part, you will need to clone down your version of this repository that you have created based on the `cosmonic/concordance-gitops` template. -# Link projector <-> keyvalue -wash ctl link put $PROJECTOR_ACTOR_ID $KVREDIS_PROVIDER_ID wasmcloud:keyvalue URL='redis://0.0.0.0:6379/' +Once you have cloned the repository to your machine, you will find `setup/argocd-application.yaml` at the root of this repository, which defines a simple [Argo CD directory-type Application][argo-directory-application]. -# Link process manager <-> concordance -wash ctl link put $PROCESS_MANAGER_ACTOR_ID $CONCORDANCE_PROVIDER_ID \ - cosmonic:eventsourcing \ - ROLE=process_manager KEY=wire_transfer_id NAME=interbankxfer INTEREST='{"start":"wire_transfer_requested","advance":["wire_funds_reserved","interbank_transfer_initiated"],"stop":["interbank_transfer_completed","interbank_transfer_failed"]}' +To deploy this application in to your cluster, at the root of this repository run the following command: -# Link aggregate <-> concodrance -wash ctl link put $AGGREGATE_ACTOR_ID $CONCORDANCE_PROVIDER_ID \ - cosmonic:eventsourcing \ - ROLE=aggregate KEY=account_number INTEREST=bankaccount NAME=bankaccount +```shell +kubectl apply -f setup/argocd-application.yaml -n argocd ``` -โš ๏ธ **WARNING** - The value of `CONCORDANCE_PROVIDER_ID` will be different on your machine when you're building locally. To determine what your provider's public key is, run `wash claims inspect` on the `/capability-provider/build/concordance.par.gz` file in this repository after you've built it. You can also copy this public key from the washboard on port 4000 if you find that easier. +When you apply the Argo CD Application definition to your cluster, Argo CD will check out the source code from your repository and deploy the [wadm application][wadm] defined at the root of this repository (in the `wadm.yaml` file) to the `default` namespace in your Kubernetes cluster. -After the script completes, the links should look like the following: +To check the on the state of deployed application, you can check on it by running: -| actor | provider | link name | contract ID | values | -|-------------------|---------------|-----------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `projector` | `concordance` | `default` | `wasmcloud:eventsourcing` | `ROLE=projector,INTEREST=account_created,funds_deposited,funds_withdrawn,wire_funds_reserved,wire_funds_released,NAME=bankaccount_projector` | -| `projector` | `keyvalue` | `default` | `wasmcloud:keyvalue` | `URL='redis://0.0.0.0:6379/'` | -| `process_manager` | `concordance` | `default` | `wasmcloud:eventsourcing` | `ROLE=process_manager,KEY=wire_transfer_id,NAME=interbankxfer,INTEREST='{"start":"wire_transfer_requested","advance":["wire_funds_reserved","interbank_transfer_initiated"],"stop":["interbank_transfer_completed","interbank_transfer_failed"]}'` | -| `aggregate` | `concordance` | `default` | `wasmcloud:eventsourcing` | `ROLE=aggregate,KEY=account_number,INTEREST=bankaccount,NAME=bankaccount` | +```shell +kubectl get app/bank-account -n default +``` -Once the script has been run, your dashboard should look like the following: +Or you can check via Argo CD by running: -![Link actors to concordance provider](./docs/images/all-links-established.png) +```shell +kubectl get applications.argoproj.io/bank-account --namespace argocd +``` -Follow the same process for all other links. +Once the application has successfully been deployed, you can navigate to https://app.cosmonic.com/constellation/applications/detail/bank-account?view=manifest to see your application. -[wasmcloud-docs-linkdefs]: https://wasmcloud.com/docs/reference/host-runtime/links/ +Under the **"Wormholes"** section you will find a link that you can click to view the application running live: -
+![bank-account wormhole section][wormhole] -
+With the application now deployed to your Kubernetes cluster, you are ready to iterate! - +
-### 6. ๐Ÿš€ Run the Event Sourcing scenario +### Deploy `v0.1.0` release -
+Now that you have the initial version of the application, it's time to make some changes. -With all the pieces of the concordance demo running, you can check the state of the system with `nats stream list`: +As part of the initialization we've set up for this repository, we've created 3 release branches (named `release/v0.1.0` .. `release/v0.3.0`) that each have a set of changes which build on the previous release (or the initial state in the case of `v0.1.0`) as a way to demonstrate how you can use GitOps and wadm together to roll out different changes throughout your application's lifecycle. -```console -$ nats stream list -โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -โ”‚ Streams โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Name โ”‚ Description โ”‚ Created โ”‚ Messages โ”‚ Size โ”‚ Last Message โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ CC_COMMANDS โ”‚ Concordance command stream for event sourcing capability provider โ”‚ 2023-04-11 01:51:24 โ”‚ 0 โ”‚ 0 B โ”‚ never โ”‚ -โ”‚ CC_EVENTS โ”‚ Concordance event stream for event sourcing capability provider โ”‚ 2023-04-11 01:51:24 โ”‚ 0 โ”‚ 0 B โ”‚ never โ”‚ -โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ -``` +So to get going with the first set of changes, what you will want to do is set up a Pull Request for the `release/v0.1.0` branch against `main`. Here's a link for you to do exactly that: -Here we can observe that the NATS streams that will carry our event sourcing traffic have been created successfully. +[https://github.com/cosmonic/concordance-gitops/compare/release/v0.1.0?expand=1](https://github.com/cosmonic/concordance-gitops/compare/release/v0.1.0?expand=1) -> *NOTE* At this point if you wanted to *reset* their contents, you could do so with the following commands: -> -> ```console -> nats stream purge CC_EVENTS -f -> nats stream purge CC_COMMANDS -f -> ``` +Once you merge this Pull Request, Argo will detect that there are changes and push up the changes in the `wadm.yaml` file. Afterwards, you will be able to reload the [Wormhole from before](#wormhole-section) to see the changes. Youโ€™ll see two new events, `CreateAccount` and `AccountCreated`, corresponding to the request to create an account and the event indicating that the account creation was successful. -To set the system in motion, from the `examples/bankaccount` directory (where this README is), we can execute a simple scenario: +
-**First, we create an account `ABC123` with an intiial balance of 4000 cents (used to avoid floating point friction), the event sourcing way (by creating a command):** +### Deploy `v0.2.0` release -```console -nats req cc.commands.bankaccount "`cat ./scripts/create_account_cmd.json | jq -c`" -``` +To demonstrate further iterations, we also have a `release/v0.2.0` branch that adds the ability to deposit and withdraw funds into / from an account. This adds additional logic into the aggregate and projector microservices which were previously just handling the creation of accounts. -> **NOTE** If this command is successful, you should see output like: -> -> ``` -> 03:31:42 Sending request on "cc.commands.bankaccount" -> 03:31:42 Received with rtt 312.417ยตs -> {"stream":"CC_COMMANDS", "domain":"core", "seq":1} -> ``` +In order to deploy the changes from `release/v0.2.0`, you can create a Pull Request from the following link: -**Then we create a deposit in account `ABC123` for 3000 (30 dollars)**: +[https://github.com/cosmonic/concordance-gitops/compare/release/v0.2.0?expand=1](https://github.com/cosmonic/concordance-gitops/compare/release/v0.2.0?expand=1) -```console -nats req cc.commands.bankaccount "`cat ./scripts/deposit_cmd_1.json | jq -c`" -``` +Once merged, Argo will once again sync the changes in `wadm.yaml` by applying them against the cluster, which in turn will prompt wadm to deploy the changes into the cluster. -**We can then create a second deposit for 1000 cents**: +As before, you will be able to view these changes by refreshing the [Wormhole from before](#wormhole-section). -```console -nats req cc.commands.bankaccount "`cat ./scripts/deposit_cmd_2.json | jq -c`" -``` +
-**Finally, we make a withdrawal of 2000 cents**: +### Deploy `v0.3.0` release -```console -nats req cc.commands.bankaccount "`cat ./scripts/withdraw_cmd_1.json | jq -c`" -``` +Finally, we have one set of changes queued up for you to complete the exercise. These changes add the ability to wire funds between bank accounts using a wire transfer process manager. -If you're good at quick math, you already know the amount that should be in the state at the end of our scenario: +Release v0.3.0 introduces many new events so that we can wire money with a robust log of events, including exactly when money is committed to be wired and when it finishes transferring with checks along the way to ensure no account goes below the specified minimum balance. -4000 + 3000 + 1000 - 2000 = 6000 +In order to get these changes deployed, create a Pull Request for the `release/v0.3.0` branch with the following link: -Let's check if we have 6000 cents: +[https://github.com/cosmonic/concordance-gitops/compare/release/v0.3.0?expand=1](https://github.com/cosmonic/concordance-gitops/compare/release/v0.3.0?expand=1) -``` -$ nats kv get CC_STATE agg.bankaccount.ABC123 -CC_STATE > agg.bankaccount.ABC123 created @ 10 Apr 23 18:32 UTC +Once merged, Argo will take care of applying the changes to the cluster and wadm will roll out a new version of the application in response. -{"balance":6000,"min_balance":100,"reserved_amount":0,"account_number":"ABC123","customer_id":"CUSTBOB"} -``` +As before, you can refresh the [Wormhole from before](#wormhole-section) to see the final changes live! -You can also confirm that state was persisted in redis if you have [`redis-cli`][redis-cli] installed: +
-``` -$ redis-cli get balance.ABC123 -"6000" -``` +## End of demo -You can also see a JSON representation of the account ledger by using the Redis CLI to query the `ledger.ABC123` key. +That concludes the demo. We hope that this demonstration has helped illustrate how you can combine the familiar practice of GitOps with the new and emerging technology of WebAssembly! -๐ŸŽ‰ Congratulations, you've completed the demo and run a fully scalable and resilient event sourced application with the safety and performance of WebAssembly! ๐ŸŽ‰ +If you would like to learn more about how wasmCloud and Cosmonic can help you realize the benefits of WebAssembly in development and production, you can connect with us at: -[redis-cli]: https://redis.io/docs/ui/cli/ +- [Cosmonic Discord](https://discord.com/invite/VcvdfAdE6R) +- [wasmCloud Slack](https://slack.wasmcloud.com/) -
+[new-repository-link]: https://github.com/new?template_name=concordance-gitops&template_owner=cosmonic&owner=@me&name=cosmonic-bank-account&description=Cosmonic%20bank%20account%20GitOps%20demo&visibility=public +[argo-directory-application]: https://argo-cd.readthedocs.io/en/stable/user-guide/directory/ +[argo-getting-started]: https://argo-cd.readthedocs.io/en/stable/getting_started/ +[concordance-banking-example]: https://github.com/cosmonic/concordance/tree/main/examples/bankaccount +[cosmo-cli-download]: https://cosmonic.com/docs/getting-started/get-the-cli +[cosmo-controller]: https://cosmonic.com/docs/kubernetes/cosmo-controller +[cosmonic]: https://app.cosmonic.com/ +[`kind`]: https://kind.sigs.k8s.io/docs/user/quick-start/#installation +[`kubectl`]: https://kubernetes.io/docs/tasks/tools/#kubectl +[kubernetes-applier-actor]: https://github.com/cosmonic/kubernetes-applier/tree/main/service-applier +[kubernetes-applier-provider]: https://github.com/cosmonic/kubernetes-applier/tree/main/applier +[wadm]: https://wasmcloud.com/docs/ecosystem/wadm/model +[wasmcloud-application]: https://wasmcloud.com/docs/concepts/applications +[wormhole]: /assets/bank-account-wormhole.png diff --git a/aggregate/.cargo/config.toml b/aggregate/.cargo/config.toml deleted file mode 100644 index 4905f77..0000000 --- a/aggregate/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[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 deleted file mode 100644 index 262ca9a..0000000 --- a/aggregate/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# 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 deleted file mode 100644 index 54994b7..0000000 --- a/aggregate/.keys/bankaccount_aggregate_module.nk +++ /dev/null @@ -1 +0,0 @@ -SMANGOYSDZ4P3SG4GDUXECMO2J2GFOIV6NQJWGFOVA3GZBSXMGZT5GWFJ4 \ No newline at end of file diff --git a/aggregate/Cargo.toml b/aggregate/Cargo.toml deleted file mode 100644 index c82fa73..0000000 --- a/aggregate/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "bankaccount-aggregate" -version = "0.1.0" -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 = { path = "../../../crates/concordance-gen"} -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 deleted file mode 100644 index 4c02bb9..0000000 --- a/aggregate/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# 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 deleted file mode 100644 index a58cba9..0000000 --- a/aggregate/src/commands.rs +++ /dev/null @@ -1,199 +0,0 @@ -use crate::*; - -pub(crate) fn handle_reserve_funds( - input: ReserveFunds, - state: Option, -) -> Result { - let Some(old_state) = state else { - return Err(anyhow::anyhow!( - "Rejected command to reserve funds. Account {} does not exist.", - input.account_number - )); - }; - let avail_balance = old_state.available_balance(); - if input.amount as u32 > avail_balance { - error!( - "Rejecting command to reserve funds, account {} does not have sufficient funds. Available {}", - &input.account_number, avail_balance - ); - Ok(vec![]) - } else { - Ok(vec![Event::new( - FundsReserved::TYPE, - STREAM, - &FundsReserved { - account_number: input.account_number.to_string(), - wire_transfer_id: input.wire_transfer_id, - customer_id: old_state.customer_id.to_string(), - amount: input.amount, - }, - )]) - } -} - -pub(crate) fn handle_release_funds( - input: ReleaseFunds, - state: Option, -) -> Result { - let Some(old_state) = state else { - return Err(anyhow::anyhow!( - "Rejected command to release funds. Account {} does not exist.", - input.account_number - )); - }; - - if old_state - .reserved_funds - .contains_key(&input.wire_transfer_id) - { - Ok(vec![Event::new( - FundsReleased::TYPE, - STREAM, - &FundsReleased { - customer_id: input.customer_id, - account_number: input.account_number.to_string(), - wire_transfer_id: input.wire_transfer_id.to_string(), - }, - )]) - } else { - error!( - "Rejecting command to release funds, account {} does not have a wire transfer hold for {}", - &input.account_number, input.wire_transfer_id - ); - Ok(vec![]) - } -} - -pub(crate) fn handle_commit_funds( - input: CommitFunds, - state: Option, -) -> Result { - let Some(old_state) = state else { - return Err(anyhow::anyhow!( - "Rejected command to commit funds. Account {} does not exist.", - input.account_number - )); - }; - - if old_state - .reserved_funds - .contains_key(&input.wire_transfer_id) - { - Ok(vec![Event::new( - FundsCommitted::TYPE, - STREAM, - &FundsCommitted { - customer_id: input.customer_id, - account_number: input.account_number.to_string(), - wire_transfer_id: input.wire_transfer_id.to_string(), - }, - )]) - } else { - error!( - "Rejecting command to commit funds, account {} does not have a wire transfer hold for {}", - &input.account_number, input.wire_transfer_id - ); - Ok(vec![]) - } -} - -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, - }, - )]) -} - -pub(crate) fn handle_withdraw_funds( - input: WithdrawFunds, - state: Option, -) -> Result { - let Some(state) = state else { - return Err(anyhow::anyhow!( - "Rejected command to withdraw funds. Account {} does not exist.", - input.account_number - )); - }; - - if state.available_balance() < input.amount as u32 { - error!( - "Rejecting command to withdraw funds, account {} does not have sufficient funds. Available {}", - &input.account_number, state.available_balance() - ); - Ok(vec![]) - } else { - Ok(vec![Event::new( - FundsWithdrawn::TYPE, - STREAM, - &FundsWithdrawn { - note: input.note, - account_number: input.account_number.to_string(), - amount: input.amount, - customer_id: input.customer_id, - }, - )]) - } -} - -pub(crate) fn handle_wire_funds( - input: WireFunds, - state: Option, -) -> Result { - let Some(state) = state else { - return Err(anyhow::anyhow!( - "Rejected command to wire funds. Account {} does not exist.", - input.account_number - )); - }; - - if state.available_balance() < input.amount as u32 { - error!( - "Rejecting command to wire funds, account {} does not have sufficient funds. Available {}", - &input.account_number, state.available_balance() - ); - Ok(vec![]) - } else { - Ok(vec![Event::new( - WireTransferInitiated::TYPE, - STREAM, - &WireTransferInitiated { - note: input.note, - account_number: input.target_account_number, - target_routing_number: input.target_routing_number, - target_account_number: input.account_number, - amount: input.amount, - customer_id: input.customer_id, - wire_transfer_id: input.wire_transaction_id, - }, - )]) - } -} - -pub(crate) fn handle_deposit_funds( - input: DepositFunds, - state: Option, -) -> Result { - if state.is_none() { - return Err(anyhow::anyhow!( - "Rejected command to deposit funds. Account {} does not exist.", - input.account_number - )); - }; - - Ok(vec![Event::new( - FundsDeposited::TYPE, - STREAM, - &FundsDeposited { - note: input.note, - account_number: input.account_number.to_string(), - amount: input.amount, - customer_id: input.customer_id, - }, - )]) -} diff --git a/aggregate/src/events.rs b/aggregate/src/events.rs deleted file mode 100644 index 95541fa..0000000 --- a/aggregate/src/events.rs +++ /dev/null @@ -1,119 +0,0 @@ -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)))) -} - -pub(crate) fn apply_funds_deposited( - input: FundsDeposited, - state: Option, -) -> Result { - let Some(state) = state else { - error!( - "Rejecting funds deposited event. Account {} does not exist.", - input.account_number - ); - return Ok(StateAck::error( - "Account does not exist", - None::, - )); - }; - let state = BankAccountAggregateState { - balance: state.balance + input.amount as u32, - ..state - }; - Ok(StateAck::ok(Some(state))) -} - -pub(crate) fn apply_funds_released( - input: FundsReleased, - state: Option, -) -> Result { - let Some(state) = state else { - error!( - "Rejecting funds released event. Account {} does not exist.", - input.account_number - ); - return Ok(StateAck::error( - "Account does not exist", - None::, - )); - }; - let state = state.release_funds(&input.wire_transfer_id); - Ok(StateAck::ok(Some(state))) -} - -pub(crate) fn apply_funds_committed( - input: FundsCommitted, - state: Option, -) -> Result { - let Some(state) = state else { - error!( - "Rejecting funds committed event. Account {} does not exist.", - input.account_number - ); - return Ok(StateAck::error( - "Account does not exist", - None::, - )); - }; - let state = state.commit_funds(&input.wire_transfer_id); - Ok(StateAck::ok(Some(state))) -} - -pub(crate) fn apply_funds_reserved( - input: FundsReserved, - state: Option, -) -> Result { - let Some(state) = state else { - error!( - "Rejecting funds reserved event. Account {} does not exist.", - input.account_number - ); - return Ok(StateAck::error( - "Account does not exist", - None::, - )); - }; - let state = state.reserve_funds(&input.wire_transfer_id, input.amount as u32); - Ok(StateAck::ok(Some(state))) -} - -pub(crate) fn apply_funds_withdrawn( - input: FundsWithdrawn, - state: Option, -) -> Result { - let Some(state) = state else { - error!( - "Rejecting funds withdrawn event. Account {} does not exist.", - input.account_number - ); - return Ok(StateAck::error( - "Account does not exist", - None::, - )); - }; - let state = state.withdraw(input.amount as u32); - Ok(StateAck::ok(Some(state))) -} - -pub(crate) fn apply_wire_transfer_initiated( - _input: WireTransferInitiated, - state: Option, -) -> Result { - // We don't currently change internal state because of this. The first time a wire transfer - // impacts the the account is when funds are reserved (by the process manager) - Ok(StateAck::ok(state)) -} diff --git a/aggregate/src/lib.rs b/aggregate/src/lib.rs deleted file mode 100644 index efbdc1f..0000000 --- a/aggregate/src/lib.rs +++ /dev/null @@ -1,136 +0,0 @@ -use anyhow::Result; -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; -use wasmcloud_interface_logging::error; - -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_reserve_funds( - &self, - input: ReserveFunds, - state: Option, - ) -> anyhow::Result { - commands::handle_reserve_funds(input, state) - } - - fn handle_release_funds( - &self, - input: ReleaseFunds, - state: Option, - ) -> anyhow::Result { - commands::handle_release_funds(input, state) - } - - fn handle_commit_funds( - &self, - input: CommitFunds, - state: Option, - ) -> anyhow::Result { - commands::handle_commit_funds(input, state) - } - - fn handle_create_account( - &self, - input: CreateAccount, - _state: Option, - ) -> anyhow::Result { - commands::handle_create_account(input) - } - - fn handle_withdraw_funds( - &self, - input: WithdrawFunds, - state: Option, - ) -> anyhow::Result { - commands::handle_withdraw_funds(input, state) - } - - fn handle_wire_funds( - &self, - input: WireFunds, - state: Option, - ) -> anyhow::Result { - commands::handle_wire_funds(input, state) - } - - fn handle_deposit_funds( - &self, - input: DepositFunds, - state: Option, - ) -> anyhow::Result { - commands::handle_deposit_funds(input, state) - } - - // -- Events -- - - fn apply_account_created( - &self, - input: AccountCreated, - _state: Option, - ) -> anyhow::Result { - events::apply_account_created(input) - } - - fn apply_funds_deposited( - &self, - input: FundsDeposited, - state: Option, - ) -> anyhow::Result { - events::apply_funds_deposited(input, state) - } - - fn apply_funds_released( - &self, - input: FundsReleased, - state: Option, - ) -> anyhow::Result { - events::apply_funds_released(input, state) - } - - fn apply_funds_committed( - &self, - input: FundsCommitted, - state: Option, - ) -> anyhow::Result { - events::apply_funds_committed(input, state) - } - - fn apply_funds_reserved( - &self, - input: FundsReserved, - state: Option, - ) -> anyhow::Result { - events::apply_funds_reserved(input, state) - } - - fn apply_funds_withdrawn( - &self, - input: FundsWithdrawn, - state: Option, - ) -> anyhow::Result { - events::apply_funds_withdrawn(input, state) - } - - fn apply_wire_transfer_initiated( - &self, - input: WireTransferInitiated, - state: Option, - ) -> anyhow::Result { - events::apply_wire_transfer_initiated(input, state) - } -} - -const STREAM: &str = "bankaccount"; diff --git a/aggregate/src/state.rs b/aggregate/src/state.rs deleted file mode 100644 index b3aaa9c..0000000 --- a/aggregate/src/state.rs +++ /dev/null @@ -1,69 +0,0 @@ -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 deleted file mode 100644 index 8c5e7ec..0000000 --- a/aggregate/wasmcloud.toml +++ /dev/null @@ -1,6 +0,0 @@ -name = "BankAccountAggregate" -language = "rust" -type = "actor" - -[actor] -claims = ["cosmonic:eventsourcing", "wasmcloud:builtin:logging"] diff --git a/assets/bank-account-wormhole.png b/assets/bank-account-wormhole.png new file mode 100644 index 0000000..644d5d5 Binary files /dev/null and b/assets/bank-account-wormhole.png differ diff --git a/bank_wadm.yaml b/bank_wadm.yaml deleted file mode 100644 index 4d86eb0..0000000 --- a/bank_wadm.yaml +++ /dev/null @@ -1,89 +0,0 @@ -apiVersion: core.oam.dev/v1beta1 -kind: Application -metadata: - name: bank-account - annotations: - version: v0.0.1 - description: "A concordance bank account example" -spec: - components: - - name: catalog - type: actor - properties: - image: - traits: - - type: spreadscaler - properties: - replicas: 1 - - type: linkdef - properties: - target: httpserver - - name: projector - type: actor - properties: - image: file://./projector/build/bankaccount_projector_s.wasm - traits: - - type: spreadscaler - properties: - replicas: 1 - - type: linkdef - properties: - target: concordance - values: - NAME: bankaccount_projector - ROLE: projector - INTEREST: account_created,funds_deposited,funds_released,funds_reserved,funds_withdrawn,wire_transfer_initiated - - type: linkdef - properties: - target: keyvalue - - - name: aggregate - type: actor - properties: - image: file://./aggregate/build/bankaccount_aggregate_s.wasm - traits: - - type: spreadscaler - properties: - replicas: 1 - - type: linkdef - properties: - target: concordance - values: - ROLE: aggregate - INTEREST: bankaccount - NAME: bankaccount - KEY: accountNumber - - - name: processmanager - type: actor - properties: - image: file://.process_manager/build/wiretransfer_processmanager_s.wasm - traits: - - type: spreadscaler - properties: - replicas: 1 - - type: linkdef - properties: - target: concordance - values: - ROLE: process_manager - KEY: wireTransferId - NAME: interbankxfer - INTEREST: '{"start":"wire_transfer_initiated","advance":["funds_reserved","wire_transfer_succeeded","wire_transfer_failed"],"stop":["funds_committed","funds_released"]}' - - - name: concordance - type: capability - properties: - contract: cosmonic:eventsourcing - image: registry.hub.docker.com/cosmonic/concordance:0.1.0 - link_name: default - - name: keyvalue - type: capability - properties: - image: cosmonic.azurecr.io/builtin_keyvalue:0.2.5 - contract: wasmcloud:keyvalue - - name: httpserver - type: capability - properties: - image: cosmonic.azurecr.io/httpserver_wormhole:0.6.2 - contract: wasmcloud:httpserver diff --git a/demo/Makefile b/demo/Makefile new file mode 100644 index 0000000..89e8159 --- /dev/null +++ b/demo/Makefile @@ -0,0 +1,43 @@ +.PHONY: help +.DEFAULT: help + +NATS ?= nats +COSMO ?= cosmo +NATS_URL ?= tls://connect.cosmonic.sh +NATS_CREDS ?= ~/.cosmo/user.creds + +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_\-.*]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +login: ## Authenticate your local machine with Cosmonic + $(COSMO) login + +purge: ## Clear all data from the bank account service + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) stream purge CC_EVENTS -f + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) stream purge CC_COMMANDS -f + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) stream purge KV_CC_STATE -f + +view_events: ## v0.0.0: View the bank account event stream + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) stream view CC_EVENTS + +create_account: ## v0.1.0: Create a new bank account + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) req cc.commands.bankaccount '$(shell jq -c . v0.1.0/create_account_cmd.json)' + +get_balance: ## v0.1.0: Get the balance of the bank account + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) kv get CC_STATE agg.bankaccount.ACCT0001 + +deposit_funds: ## v0.2.0: Deposit $1000 into the bank account. Feel free to modify ./v0.2.0/deposit_funds_cmd.json to deposit more or less. + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) req cc.commands.bankaccount '$(shell jq -c . v0.2.0/deposit_funds_cmd.json)' + +withdraw_funds: ## v0.2.0: Withdraw $1000 from the bank account. Feel free to modify ./v0.2.0/withdraw_funds_cmd.json to withdraw more or less. + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) req cc.commands.bankaccount '$(shell jq -c . v0.2.0/withdraw_funds_cmd.json)' + +create_account_2: ## v0.3.0: Create a second new bank account + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) req cc.commands.bankaccount '$(shell jq -c . v0.3.0/create_account_cmd.json)' + +get_balance_2: ## v0.3.0: Get the balance of the second bank account + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) kv get CC_STATE agg.bankaccount.ACCT0002 + +wire_transfer: ## v0.3.0: Transfer $1000 from the first bank account to the second bank account. Feel free to modify ./v0.3.0/wire_funds_cmd.json to transfer more or less. + NATS_URL=$(NATS_URL) NATS_CREDS=$(NATS_CREDS) $(NATS) req cc.commands.bankaccount '$(shell jq -c . v0.3.0/wire_funds_cmd.json)' + diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..bbc938e --- /dev/null +++ b/demo/README.md @@ -0,0 +1,45 @@ +# Bank Account Application Scripts + +## Prerequisites + +- [NATS CLI](https://github.com/nats-io/natscli#installation) for publishing commands and inspecting the event log +- [jq](https://jqlang.github.io/jq/) for convenience parsing with JSON requests and responses +- A Cosmonic account and the [cosmo](https://cosmonic.com/docs/getting-started/get-the-cli) CLI for creating local account credentials +- [make (optional)](https://www.gnu.org/software/make/) for convenience running the scripts + +## Overview + +This directory contains sample payloads and scripts for interacting with the Bank Account application. As this application +evolves from v0.0.0 to v0.3.0 new functionality is added, so it's recommended to use the appropriate scripts for each version. + +Make sure to follow the steps in the [README](../README.md) to deploy the Bank Account application before running the scripts. Each of the scripts below is also contained in the [Makefile](./Makefile) for convenience if you have `make` installed. + +## v0.0.0 + +As of v0.0.0, the Bank Account application does not contain any functionality. You can deploy the application and view the empty event catalog, but there are no commands to publish. + +## v0.1.0 + +After upgrading to v0.1.0, you can publish the `CreateAccount` command to create a new bank account. The script for this version is [create-account.sh](./v0.1.0/create-account.sh) and will publish the payload in [./v0.1.0/create_account_cmd.json](./v0.1.0/create_account_cmd.json) to create an account `ACCT001`. You can run `make create_account` to run this command. + +Now that an account is created, we can inspect the projection state to see account details with `make get_balance`. + +```json +{ + "balance": 5000, + "min_balance": 0, + "account_number": "ACCT0001", + "customer_id": "CUSTBOB", + "reserved_funds": {} +} +``` + +## v0.2.0 + +After upgrading your application to `v0.2.0`, you can deposit and withdraw funds from your account. Use the commands `make deposit_funds` and `make withdraw_funds` to deposit and withdraw $1000 as many times as you'd like to change your account balance. + +As you're depositing and withdrawing funds, you can check the immutable event log by using `make view_events` and see each of the commands and events that you've published. + +## v0.3.0 + +In the latest version of the application, you can wire transfer money between accounts. Use `make create_account_2` to create a second account, and then `make wire_transfer` to transfer $1000 from ACCT0001 to ACCT0002. diff --git a/demo/v0.1.0/create-account.sh b/demo/v0.1.0/create-account.sh new file mode 100644 index 0000000..e77a5c9 --- /dev/null +++ b/demo/v0.1.0/create-account.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export NATS_URL=tls://connect.cosmonic.sh +export NATS_CREDS=~/.cosmo/user.creds + +nats req cc.commands.bankaccount "`jq -c . create_account_cmd.json`" \ No newline at end of file diff --git a/demo/v0.1.0/create_account_cmd.json b/demo/v0.1.0/create_account_cmd.json new file mode 100644 index 0000000..eae44ea --- /dev/null +++ b/demo/v0.1.0/create_account_cmd.json @@ -0,0 +1,10 @@ +{ + "command_type": "create_account", + "key": "ACCT0001", + "data": { + "accountNumber": "ACCT0001", + "initialBalance": 5000, + "minBalance": 0, + "customerId": "CUSTBOB" + } +} diff --git a/demo/v0.2.0/deposit_funds_cmd.json b/demo/v0.2.0/deposit_funds_cmd.json new file mode 100644 index 0000000..ee614e7 --- /dev/null +++ b/demo/v0.2.0/deposit_funds_cmd.json @@ -0,0 +1,10 @@ +{ + "command_type": "deposit_funds", + "key": "ACCT0001", + "data": { + "accountNumber": "ACCT0001", + "amount": 1000, + "note": "cash deposit", + "customerId": "CUSTBOB" + } +} diff --git a/demo/v0.2.0/withdraw_funds_cmd.json b/demo/v0.2.0/withdraw_funds_cmd.json new file mode 100644 index 0000000..8892892 --- /dev/null +++ b/demo/v0.2.0/withdraw_funds_cmd.json @@ -0,0 +1,10 @@ +{ + "command_type": "withdraw_funds", + "key": "ACCT0001", + "data": { + "accountNumber": "ACCT0001", + "amount": 1000, + "note": "atm withdrawal", + "customerId": "CUSTBOB" + } +} diff --git a/demo/v0.3.0/create_account_cmd.json b/demo/v0.3.0/create_account_cmd.json new file mode 100644 index 0000000..ed4fb89 --- /dev/null +++ b/demo/v0.3.0/create_account_cmd.json @@ -0,0 +1,10 @@ +{ + "command_type": "create_account", + "key": "ACCT0002", + "data": { + "accountNumber": "ACCT0002", + "initialBalance": 5000, + "minBalance": 0, + "customerId": "CUSTALICE" + } +} diff --git a/demo/v0.3.0/wire_funds_cmd.json b/demo/v0.3.0/wire_funds_cmd.json new file mode 100644 index 0000000..9efe2d5 --- /dev/null +++ b/demo/v0.3.0/wire_funds_cmd.json @@ -0,0 +1,12 @@ +{ + "command_type": "wire_funds", + "key": "ACCT0001", + "data": { + "accountNumber": "ACCT0001", + "amount": 1000, + "customerId": "CUSTBOB", + "wireTransactionId": "XFER0001", + "targetRoutingNumber": "NA", + "targetAccountNumber": "ACCT0002" + } +} diff --git a/eventcatalog/README.md b/eventcatalog/README.md index c5f69e4..086c479 100644 --- a/eventcatalog/README.md +++ b/eventcatalog/README.md @@ -1 +1,19 @@ -# My Event Catalog \ No newline at end of file +# Bank Account Event Catalog + +This is the event catalog for the bank account service. You can either run the event catalog locally or compile it into a Cosmonic component. + +## Running the Event Catalog + +```bash +npm install +npm run start +``` + +## Compiling Cosmonic Component + +```bash +npm install +npm run build +cd actor +wash build +``` diff --git a/eventcatalog/actor/.keys/bankaccountcatalog_module.nk b/eventcatalog/actor/.keys/bankaccountcatalog_module.nk new file mode 100644 index 0000000..cd5db4b --- /dev/null +++ b/eventcatalog/actor/.keys/bankaccountcatalog_module.nk @@ -0,0 +1 @@ +SMAD6GHCFKOHSU4XKTRH3INRCUVRGA4CZNGCPKMGXO4RNJ4OBOW2IC2QQU \ No newline at end of file diff --git a/eventcatalog/actor/Cargo.lock b/eventcatalog/actor/Cargo.lock index 6554242..59b59d6 100644 --- a/eventcatalog/actor/Cargo.lock +++ b/eventcatalog/actor/Cargo.lock @@ -250,7 +250,7 @@ dependencies = [ [[package]] name = "bankaccountcatalog" -version = "0.1.0" +version = "0.3.0" dependencies = [ "async-trait", "futures", diff --git a/eventcatalog/actor/Cargo.toml b/eventcatalog/actor/Cargo.toml index 6b77c66..9a307db 100644 --- a/eventcatalog/actor/Cargo.toml +++ b/eventcatalog/actor/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bankaccountcatalog" -version = "0.1.0" -authors = [""] +version = "0.3.0" +authors = ["Cosmonic Team"] edition = "2021" [lib] @@ -13,7 +13,6 @@ async-trait = "0.1" futures = "0.3" serde_bytes = "0.11" serde_json = "1.0" - wasmbus-rpc = "0.14" wasmcloud-interface-httpserver = "0.11" rust-embed = "8.0.0" diff --git a/eventcatalog/actor/README.md b/eventcatalog/actor/README.md deleted file mode 100644 index 64d23e4..0000000 --- a/eventcatalog/actor/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Fruit Jokes Microservice - -This microservice, built in Rust, serves random fruit jokes to users. Users can also contribute their own jokes by sending a `POST` request. - -## Features - -- **Add Jokes**: Send a `POST` request with your joke, and it'll be added to the collection of fruit jokes. -- **Retrieve Random Jokes**: A `GET` request to the service returns a random fruit joke. - -## Dependencies - -This microservice leverages the following wasmCloud capabilities: - -- `wasmbus_rpc::actor::prelude::*` for general Actor support. -- `wasmcloud_interface_httpserver` to serve the jokes via HTTP. -- `wasmcloud_interface_keyvalue` for storing the jokes in a set. -- `wasmcloud_interface_numbergen` to generate random numbers to retrieve random jokes. - -## Endpoints - -- `POST /`: Add a new joke to the collection. Ensure the body contains the joke text. -- `GET /`: Retrieve a random fruit joke from the collection. - -## Constants - -- `JOKES_KEY`: This key is used to store the jokes in a set. -- `FALLBACK_JOKE`: In case a joke isn't found, this fallback joke will be returned. - -## Error Handling - -If the sent joke in a `POST` request cannot be parsed, the microservice responds with a cheeky error message: "That joke was bad, I'm not even going to store it". - -## Usage - -Deploy this microservice with wasmCloud, and it's ready to serve jokes and collect new ones. - -## Contributing - -Feel free to submit pull requests or raise issues if you find any. Every joke is appreciated, but keep them fun and light-hearted! diff --git a/eventcatalog/actor/build/bankaccountcatalog.wasm b/eventcatalog/actor/build/bankaccountcatalog.wasm deleted file mode 100755 index 6082324..0000000 Binary files a/eventcatalog/actor/build/bankaccountcatalog.wasm and /dev/null differ diff --git a/eventcatalog/actor/build/bankaccountcatalog_s.wasm b/eventcatalog/actor/build/bankaccountcatalog_s.wasm deleted file mode 100644 index 7effc34..0000000 Binary files a/eventcatalog/actor/build/bankaccountcatalog_s.wasm and /dev/null differ diff --git a/eventcatalog/actor/wasmcloud.toml b/eventcatalog/actor/wasmcloud.toml index 33b196f..c6549ac 100644 --- a/eventcatalog/actor/wasmcloud.toml +++ b/eventcatalog/actor/wasmcloud.toml @@ -3,4 +3,5 @@ language = "rust" type = "actor" [actor] +key_directory = "./.keys" claims = ["wasmcloud:httpserver"] diff --git a/eventcatalog/events/AccountCreated/index.md b/eventcatalog/events/AccountCreated/index.md deleted file mode 100644 index 454140b..0000000 --- a/eventcatalog/events/AccountCreated/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -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 deleted file mode 100644 index 32d1bd2..0000000 --- a/eventcatalog/events/AccountCreated/schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$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/CommitFunds/index.md b/eventcatalog/events/CommitFunds/index.md deleted file mode 100644 index ec7caa0..0000000 --- a/eventcatalog/events/CommitFunds/index.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: CommitFunds -summary: "A request to commit funds under hold to a wire transfer" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' -producers: - - 'Wire Transfer Process Manager' -tags: - - label: 'command' -externalLinks: [] -badges: [] ---- -A request to commit the funds currently on hold for a given wire transfer. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/CommitFunds/schema.json b/eventcatalog/events/CommitFunds/schema.json deleted file mode 100644 index dc6a4db..0000000 --- a/eventcatalog/events/CommitFunds/schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/CommitFunds.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "CommitFunds", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer performing the withdrawal" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - } - }, - "required": ["accountNumber", "customerId", "wireTransferId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/CreateAccount/index.md b/eventcatalog/events/CreateAccount/index.md deleted file mode 100644 index ba54d68..0000000 --- a/eventcatalog/events/CreateAccount/index.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -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 deleted file mode 100644 index 8dabe27..0000000 --- a/eventcatalog/events/CreateAccount/schema.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$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/events/DepositFunds/index.md b/eventcatalog/events/DepositFunds/index.md deleted file mode 100644 index c744127..0000000 --- a/eventcatalog/events/DepositFunds/index.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: DepositFunds -summary: "A request to deposit funds into an account" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' -tags: - - label: 'command' -externalLinks: [] -badges: [] ---- -Requests the deposit of a specified amount into the account. This command can fail to process if the parameters are invalid. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/DepositFunds/schema.json b/eventcatalog/events/DepositFunds/schema.json deleted file mode 100644 index ad2a2cc..0000000 --- a/eventcatalog/events/DepositFunds/schema.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/DepositFunds.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "DepositFunds", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "amount": { - "type": "integer", - "description": "The amount to deposit" - }, - "note": { - "type": "string", - "description": "An optional note to be associated with the deposit" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer performing the deposit" - }, - "transferId": { - "type": "string", - "description": "A unique ID identifying the transfer transaction if applicable" - } - }, - "required": ["accountNumber", "amount", "customerId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/FundsCommitted/index.md b/eventcatalog/events/FundsCommitted/index.md deleted file mode 100644 index dfa1b00..0000000 --- a/eventcatalog/events/FundsCommitted/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: FundsCommitted -summary: "Indicates that reserved funds were committed and withdrawn" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' - - 'Wire Transfer Process Manager' -producers: - - 'Bank Account Aggregate' -tags: - - label: 'event' -externalLinks: [] -badges: [] ---- -Indicates that previously held funds were withdrawn from the account. In the interest of simplicity, this example doesn't support partially committed funds or funds that are required to clear in increments of some small value. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/FundsCommitted/schema.json b/eventcatalog/events/FundsCommitted/schema.json deleted file mode 100644 index 06eb023..0000000 --- a/eventcatalog/events/FundsCommitted/schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/FundsCommitted.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "FundsCommitted", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - } - }, - "required": ["accountNumber", "customerId", "wireTransferId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/FundsDeposited/index.md b/eventcatalog/events/FundsDeposited/index.md deleted file mode 100644 index 5093f99..0000000 --- a/eventcatalog/events/FundsDeposited/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: FundsDeposited -summary: "Indicates funds have been deposited into an account" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' - - 'Bank Account Projector' -producers: - - 'Bank Account Aggregate' -tags: - - label: 'event' -externalLinks: [] -badges: [] ---- -Indicates that funds have been deposited into an account. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/FundsDeposited/schema.json b/eventcatalog/events/FundsDeposited/schema.json deleted file mode 100644 index e3a8211..0000000 --- a/eventcatalog/events/FundsDeposited/schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/FundsDeposited.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "FundsDeposited", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "amount": { - "type": "integer", - "description": "The amount deposited" - }, - "note": { - "type": "string", - "description": "An optional note to associated with the deposit" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer that performed the deposit" - } - }, - "required": ["accountNumber", "amount", "customerId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/FundsReleased/index.md b/eventcatalog/events/FundsReleased/index.md deleted file mode 100644 index af8ed44..0000000 --- a/eventcatalog/events/FundsReleased/index.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: FundsReleased -summary: "Indicates that reserved funds were released" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' - - 'Wire Transfer Process Manager' - - 'Bank Account Projector' -producers: - - 'Bank Account Aggregate' -tags: - - label: 'event' -externalLinks: [] -badges: [] ---- -Indicates that held funds were released as part of a failed or canceled transfer. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/FundsReleased/schema.json b/eventcatalog/events/FundsReleased/schema.json deleted file mode 100644 index f23c698..0000000 --- a/eventcatalog/events/FundsReleased/schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/FundsReleased.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "FundsReleased", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - } - }, - "required": ["accountNumber", "customerId", "wireTransferId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/FundsReserved/index.md b/eventcatalog/events/FundsReserved/index.md deleted file mode 100644 index cc6378d..0000000 --- a/eventcatalog/events/FundsReserved/index.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: FundsReserved -summary: "Indicates funds have been placed on hold for a wire transfer" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' - - 'Wire Transfer Process Manager' - - 'Bank Account Projector' -producers: - - 'Bank Account Aggregate' -tags: - - label: 'event' -externalLinks: [] -badges: [] ---- -Indicates that the funds to be used in a wire transfer have been reserved/placed on hold. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/FundsReserved/schema.json b/eventcatalog/events/FundsReserved/schema.json deleted file mode 100644 index 5c28d49..0000000 --- a/eventcatalog/events/FundsReserved/schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/FundsReserved.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "FundsReserved", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "amount": { - "type": "integer", - "description": "The amount reserved" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - } - }, - "required": ["accountNumber", "amount", "customerId", "wireTransferId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/FundsWithdrawn/index.md b/eventcatalog/events/FundsWithdrawn/index.md deleted file mode 100644 index b1dbc81..0000000 --- a/eventcatalog/events/FundsWithdrawn/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: FundsWithdrawn -summary: "Indicates a successful withdrawal of funds" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' - - 'Bank Account Projector' -producers: - - 'Bank Account Aggregate' -tags: - - label: 'event' -externalLinks: [] -badges: [] ---- -Indicates funds have been withdrawn from the account - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/FundsWithdrawn/schema.json b/eventcatalog/events/FundsWithdrawn/schema.json deleted file mode 100644 index 8f6f7ec..0000000 --- a/eventcatalog/events/FundsWithdrawn/schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/FundsWithdrawn.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "FundsWithdrawn", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "amount": { - "type": "integer", - "description": "The amount withdrawn" - }, - "note": { - "type": "string", - "description": "An optional note associated with the withdrawal" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer that performed the withdrawal" - } - }, - "required": ["accountNumber", "amount", "customerId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/ReleaseFunds/index.md b/eventcatalog/events/ReleaseFunds/index.md deleted file mode 100644 index 3c39437..0000000 --- a/eventcatalog/events/ReleaseFunds/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: ReleaseFunds -summary: "A request to release funds from a wire transfer" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' -producers: - - 'Wire Transfer Process Manager' -tags: - - label: 'command' -externalLinks: [] -badges: [] ---- -Requests that funds held for a given wire transfer are to be released. Note that this command can be rejected if no such -wire transfer is known to the aggregate. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/ReleaseFunds/schema.json b/eventcatalog/events/ReleaseFunds/schema.json deleted file mode 100644 index 3c7d79b..0000000 --- a/eventcatalog/events/ReleaseFunds/schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/ReleaseFunds.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "ReleaseFunds", - "type": "object", - "description": "A request to release funds from a wire transfer", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - } - }, - "required": ["accountNumber", "customerId", "wireTransferId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/ReserveFunds/index.md b/eventcatalog/events/ReserveFunds/index.md deleted file mode 100644 index bb71ec4..0000000 --- a/eventcatalog/events/ReserveFunds/index.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: ReserveFunds -summary: "A request to place wire transfer funds on hold" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' -producers: - - 'Wire Transfer Process Manager' -tags: - - label: 'command' -externalLinks: [] -badges: [] ---- -A request to place on hold the funds to be involved in a given wire transfer - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/ReserveFunds/schema.json b/eventcatalog/events/ReserveFunds/schema.json deleted file mode 100644 index 14ec612..0000000 --- a/eventcatalog/events/ReserveFunds/schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/ReserveFunds.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "ReserveFunds", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "amount": { - "type": "integer", - "description": "The amount to reserve" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer performing the withdrawal" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - } - }, - "required": ["accountNumber", "amount", "customerId", "wireTransferId"] - } - \ No newline at end of file diff --git a/eventcatalog/events/WireFunds/index.md b/eventcatalog/events/WireFunds/index.md deleted file mode 100644 index 42423cd..0000000 --- a/eventcatalog/events/WireFunds/index.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: WireFunds -summary: "A request to wire funds to another account at another bank" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' -tags: - - label: 'command' -externalLinks: [] -badges: [] ---- -Requests the wiring of a specified amount to another account at another bank. This command can fail to process if the parameters are invalid or if the source account does not have sufficient funds. This will result in the _holding_ of the funds until the wire is completed or cancelled. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/WireFunds/schema.json b/eventcatalog/events/WireFunds/schema.json deleted file mode 100644 index 7838c57..0000000 --- a/eventcatalog/events/WireFunds/schema.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/WireFunds.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "WireFunds", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The source account number" - }, - "amount": { - "type": "integer", - "description": "The amount to be transferred to the target account" - }, - "note": { - "type": "string", - "description": "An optional note to be associated with the wire transfer" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer performing the transfer" - }, - "wireTransactionId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - }, - "targetRoutingNumber": { - "type": "string", - "description": "The routing number of the target bank" - }, - "targetAccountNumber": { - "type": "string", - "description": "The account number of the target account" - } - }, - "required": [ - "accountNumber", - "amount", - "customerId", - "wireTransactionId", - "targetRoutingNumber", - "targetAccountNumber" - ] -} diff --git a/eventcatalog/events/WireTransferFailed/index.md b/eventcatalog/events/WireTransferFailed/index.md deleted file mode 100644 index 6ddb412..0000000 --- a/eventcatalog/events/WireTransferFailed/index.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: WireTransferFailed -summary: "Indicates that a wire transfer process failed" -version: 0.0.1 -consumers: - - 'Wire Transfer Process Manager' -tags: - - label: 'event' - - label: 'external' -externalLinks: [] -badges: [] ---- -This event is published from an external source to indicate that the wire transfer process failed. -Note that this event doesn't have any internal information like customer ID because it originates from outside the system. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/WireTransferFailed/schema.json b/eventcatalog/events/WireTransferFailed/schema.json deleted file mode 100644 index 6252642..0000000 --- a/eventcatalog/events/WireTransferFailed/schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/WireTransferFailed.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "WireTransferFailed", - "type": "object", - "properties": { - "note": { - "type": "string", - "description": "An optional note describing the reason for failure" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - }, - "targetRoutingNumber": { - "type": "string", - "description": "The routing number of the target bank" - }, - "targetAccountNumber": { - "type": "string", - "description": "The account number of the target account" - } - }, - "required": [ - "accountNumber", - "amount", - "customerId", - "wireTransferId", - "targetRoutingNumber", - "targetAccountNumber" - ] - } - \ No newline at end of file diff --git a/eventcatalog/events/WireTransferInitiated/index.md b/eventcatalog/events/WireTransferInitiated/index.md deleted file mode 100644 index 5f128e9..0000000 --- a/eventcatalog/events/WireTransferInitiated/index.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: WireTransferInitiated -summary: "Indicates that a wire transfer process has begun" -version: 0.0.1 -consumers: - - 'Bank Account Projector' - - 'Wire Transfer Process Manager' - - 'Bank Account Aggregate' -producers: - - 'Bank Account Aggregate' -tags: - - label: 'event' -externalLinks: [] -badges: [] ---- -Indicates that the **process** of a wire transfer has been initiated. External stimuli from a gateway can then emit events to indicate the completion (successful or otherwise) of this process. Funds involved in the transfer are _reserved_ from the account, but not yet _withdrawn_. The funds will either be released or fully withdrawn pending the outcome of the transfer. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/WireTransferInitiated/schema.json b/eventcatalog/events/WireTransferInitiated/schema.json deleted file mode 100644 index e7ee980..0000000 --- a/eventcatalog/events/WireTransferInitiated/schema.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/WireTransferInitiated.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "WireTransferInitiated", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The source account number" - }, - "amount": { - "type": "integer", - "description": "The amount to be transferred to the target account" - }, - "note": { - "type": "string", - "description": "An optional note to be associated with the wire transfer" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer performing the transfer" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - }, - "targetRoutingNumber": { - "type": "string", - "description": "The routing number of the target bank" - }, - "targetAccountNumber": { - "type": "string", - "description": "The account number of the target account" - } - }, - "required": [ - "accountNumber", - "amount", - "customerId", - "wireTransferId", - "targetRoutingNumber", - "targetAccountNumber" - ] - } - \ No newline at end of file diff --git a/eventcatalog/events/WireTransferSucceeded/index.md b/eventcatalog/events/WireTransferSucceeded/index.md deleted file mode 100644 index e5406e0..0000000 --- a/eventcatalog/events/WireTransferSucceeded/index.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: WireTransferSucceeded -summary: "Indicates that a wire transfer process completed successfully" -version: 0.0.1 -consumers: - - 'Wire Transfer Process Manager' -tags: - - label: 'event' - - label: 'external' -externalLinks: [] -badges: [] ---- -Indicates that the given wire transfer succeeded. This event has no internal information because it originates from outside the system. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/WireTransferSucceeded/schema.json b/eventcatalog/events/WireTransferSucceeded/schema.json deleted file mode 100644 index d8e88b7..0000000 --- a/eventcatalog/events/WireTransferSucceeded/schema.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/WireTransferSucceeded.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "WireTransferSucceeded", - "type": "object", - "properties": { - "note": { - "type": "string", - "description": "An optional note" - }, - "wireTransferId": { - "type": "string", - "description": "A unique ID identifying the wire transfer transaction" - }, - "targetRoutingNumber": { - "type": "string", - "description": "The routing number of the target bank" - }, - "targetAccountNumber": { - "type": "string", - "description": "The account number of the target account" - } - }, - "required": [ - "wireTransferId", - "targetRoutingNumber", - "targetAccountNumber" - ] - } - \ No newline at end of file diff --git a/eventcatalog/events/WithdrawFunds/index.md b/eventcatalog/events/WithdrawFunds/index.md deleted file mode 100644 index 7234da9..0000000 --- a/eventcatalog/events/WithdrawFunds/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: WithdrawFunds -summary: "A request to withdraw funds from an account" -version: 0.0.1 -consumers: - - 'Bank Account Aggregate' -tags: - - label: 'command' -externalLinks: [] -badges: [] ---- -Requests the withdrawal of a specified amount from the account. This command can fail to process if the parameters are invalid or if the account does not have sufficient funds. - -Note that there is a design decision here. You can allow the withdrawal to go through even if there is insufficient funds, and then also emit an overdraft event. Or all commands attempting to withdraw below the minimum (or 0 if omitted) are rejected. This is a domain/application decision and -not really something that can be decided by the framework. - - - -## Schema - \ No newline at end of file diff --git a/eventcatalog/events/WithdrawFunds/schema.json b/eventcatalog/events/WithdrawFunds/schema.json deleted file mode 100644 index dc25fbf..0000000 --- a/eventcatalog/events/WithdrawFunds/schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$id": "https://cosmonic.com/concordance/bankaccount/WithdrawFunds.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "WithdrawFunds", - "type": "object", - "properties": { - "accountNumber": { - "type": "string", - "description": "The account number" - }, - "amount": { - "type": "integer", - "description": "The amount to withdraw" - }, - "note": { - "type": "string", - "description": "An optional note to be associated with the withdrawal" - }, - "customerId": { - "type": "string", - "description": "The ID of the customer performing the withdrawal" - } - }, - "required": ["accountNumber", "amount", "customerId"] - } - \ No newline at end of file diff --git a/eventcatalog/package-lock.json b/eventcatalog/package-lock.json index 1529e99..12eeef8 100644 --- a/eventcatalog/package-lock.json +++ b/eventcatalog/package-lock.json @@ -1,12 +1,12 @@ { "name": "eventcatalog", - "version": "0.0.1", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "eventcatalog", - "version": "0.0.1", + "version": "0.0.0", "dependencies": { "@eventcatalog/core": "1.0.1" }, diff --git a/eventcatalog/package.json b/eventcatalog/package.json index d79c648..bc46ce7 100644 --- a/eventcatalog/package.json +++ b/eventcatalog/package.json @@ -1,6 +1,6 @@ { "name": "eventcatalog", - "version": "0.0.1", + "version": "0.0.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 deleted file mode 100644 index d032b60..0000000 --- a/eventcatalog/services/Bank Account Aggregate/index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -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 deleted file mode 100644 index 96ce287..0000000 --- a/eventcatalog/services/Bank Account Projector/index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -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/eventcatalog/services/Wire Transfer Process Manager/index.md b/eventcatalog/services/Wire Transfer Process Manager/index.md deleted file mode 100644 index 5cbc020..0000000 --- a/eventcatalog/services/Wire Transfer Process Manager/index.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Wire Transfer Process Manager -summary: | - The process manager for managing wire transfer processes -tags: - - label: 'procman' ---- - -This process manager is responsible for managing the process of wire transfers. It listens for the `WireTransferInitiated` event and then emits the appropriate commands to continue the process - - - -## Interest -The following indicates the sequential flow of the process manager's interest, which is required for defining link definitions. It's important to note that the process doesn't complete when it receives the fail/succeed events from the outside world. The process is only considered completed when the funds held by the wire transfer are released or committed. - -* `start` - [WireTransferInitiated](../../events/WireTransferInitiated) -* `advance` - [FundsReserved](../../events/FundsReserved), [WireTransferSucceeded](../../events/WireTransferSucceeded), [WireTransferFailed](../../events/WireTransferFailed) -* `end` - [FundsCommitted](../../events/FundsCommitted), [FundsReleased](../../events/FundsReleased) \ No newline at end of file diff --git a/process_manager/.cargo/config.toml b/process_manager/.cargo/config.toml deleted file mode 100644 index 4905f77..0000000 --- a/process_manager/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[build] -target = "wasm32-unknown-unknown" - -[net] -git-fetch-with-cli = true \ No newline at end of file diff --git a/process_manager/.gitignore b/process_manager/.gitignore deleted file mode 100644 index 262ca9a..0000000 --- a/process_manager/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# 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/process_manager/.keys/bankaccount_processmanager_module.nk b/process_manager/.keys/bankaccount_processmanager_module.nk deleted file mode 100644 index 923faca..0000000 --- a/process_manager/.keys/bankaccount_processmanager_module.nk +++ /dev/null @@ -1 +0,0 @@ -SMAMWST3KY5Q422MRVTCLPO32YRXODME3VFF5XKGFAHBFJPSFDBU6TQYJY \ No newline at end of file diff --git a/process_manager/Cargo.toml b/process_manager/Cargo.toml deleted file mode 100644 index e641368..0000000 --- a/process_manager/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "bankaccount-processmanager" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] -name = "wiretransfer_processmanager" - -[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 = { path = "../../../crates/concordance-gen"} -wasmcloud-interface-logging = {version = "0.10.0", features = ["sync_macro"]} -regress = "0.6.0" - -[profile.release] -# Optimize for small code size -lto = true -opt-level = "s" -strip = true diff --git a/process_manager/README.md b/process_manager/README.md deleted file mode 100644 index 8a3c82d..0000000 --- a/process_manager/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Bank Account Sample - Interbank Transfer Process Manager -TBD \ No newline at end of file diff --git a/process_manager/src/lib.rs b/process_manager/src/lib.rs deleted file mode 100644 index 2364e29..0000000 --- a/process_manager/src/lib.rs +++ /dev/null @@ -1,175 +0,0 @@ -use serde::{Deserialize, Serialize}; - -concordance_gen::generate!({ - path: "../eventcatalog", - role: "process_manager", - entity: "wire transfer" -}); - -#[async_trait] -impl WireTransferProcessManager for WireTransferProcessManagerImpl { - async fn handle_funds_released( - &self, - _input: FundsReleased, - _state: Option, - ) -> RpcResult { - // release of funds is the termination of a transfer process - Ok(ProcessManagerAck::ok( - None::, - vec![], - )) - } - - async fn handle_funds_committed( - &self, - _input: FundsCommitted, - _state: Option, - ) -> RpcResult { - // commitment of funds is the termination of a transfer process - Ok(ProcessManagerAck::ok( - None::, - vec![], - )) - } - - async fn handle_funds_reserved( - &self, - _input: FundsReserved, - state: Option, - ) -> RpcResult { - let Some(mut state) = state else { - return Ok(ProcessManagerAck::ok( - None::, - vec![], - )); - }; - state.status = TransferStatus::FundsReserved; - Ok(ProcessManagerAck::ok(Some(state), vec![])) - } - - async fn handle_wire_transfer_succeeded( - &self, - input: WireTransferSucceeded, - state: Option, - ) -> RpcResult { - let Some(mut state) = state else { - return Ok(ProcessManagerAck::ok( - None::, - vec![], - )); - }; - state.status = TransferStatus::TransferCompleted; - let cmd = CommitFunds { - account_number: state.account_number.to_string(), - customer_id: state.customer_id.to_string(), - wire_transfer_id: input.wire_transfer_id.to_string(), - }; - - Ok(ProcessManagerAck::ok( - Some(state), - vec![OutputCommand::new( - CommitFunds::TYPE, - &cmd, - STREAM, - &cmd.account_number, - )], - )) - } - - async fn handle_wire_transfer_initiated( - &self, - input: WireTransferInitiated, - _state: Option, - ) -> RpcResult { - let state = WireTransferProcessManagerState::new(&input); - - let cmd = ReserveFunds { - customer_id: input.customer_id, - account_number: input.account_number, - amount: input.amount, - wire_transfer_id: input.wire_transfer_id.to_string(), - }; - - Ok(ProcessManagerAck::ok( - Some(state), - vec![OutputCommand::new( - ReserveFunds::TYPE, - &cmd, - STREAM, - &cmd.account_number, - )], - )) - } - - async fn handle_wire_transfer_failed( - &self, - input: WireTransferFailed, - state: Option, - ) -> RpcResult { - let Some(state) = state else { - return Ok(ProcessManagerAck::ok( - None::, - vec![], - )); - }; - let cmd = ReleaseFunds { - account_number: state.account_number.to_string(), - customer_id: state.customer_id.to_string(), - wire_transfer_id: input.wire_transfer_id.to_string(), - }; - Ok(ProcessManagerAck::ok( - Some(state), - vec![OutputCommand::new( - ReleaseFunds::TYPE, - &cmd, - STREAM, - &cmd.account_number, - )], - )) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct WireTransferProcessManagerState { - pub wire_transfer_id: String, - pub account_number: String, - pub customer_id: String, - pub amount: u32, - pub target_routing_number: String, - pub target_account_number: String, - pub status: TransferStatus, -} - -#[derive(Clone, Serialize, Deserialize, Debug, Default)] -pub enum TransferStatus { - Requested, - FundsReserved, - TransferInitiated, - TransferCompleted, - TransferFailed, - #[default] - Unknown, -} - -impl WireTransferProcessManagerState { - pub fn to_bytes(self) -> Vec { - serde_json::to_vec(&self).unwrap_or_default() - } -} - -impl WireTransferProcessManagerState { - pub fn new(event: &WireTransferInitiated) -> WireTransferProcessManagerState { - let event = event.clone(); - WireTransferProcessManagerState { - wire_transfer_id: event.wire_transfer_id, - account_number: event.account_number, - customer_id: event.customer_id, - amount: event.amount as u32, - target_routing_number: event.target_routing_number, - target_account_number: event.target_account_number, - status: TransferStatus::Requested, - } - } -} - -const STREAM: &str = "bankaccount"; diff --git a/process_manager/wasmcloud.toml b/process_manager/wasmcloud.toml deleted file mode 100644 index 9d44936..0000000 --- a/process_manager/wasmcloud.toml +++ /dev/null @@ -1,6 +0,0 @@ -name = "WireTransferProcessManager" -language = "rust" -type = "actor" - -[actor] -claims = ["cosmonic:eventsourcing", "wasmcloud:builtin:logging"] diff --git a/projector/.cargo/config.toml b/projector/.cargo/config.toml deleted file mode 100644 index 4905f77..0000000 --- a/projector/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[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 deleted file mode 100644 index 262ca9a..0000000 --- a/projector/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# 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 deleted file mode 100644 index 24f7801..0000000 --- a/projector/.keys/bankaccount_projector_module.nk +++ /dev/null @@ -1 +0,0 @@ -SMADQF4DVD4AUK2WAR5RKDW4G2S4TRNS4MS5ADERYQ6STLK7MANGYNQCPA \ No newline at end of file diff --git a/projector/Cargo.toml b/projector/Cargo.toml deleted file mode 100644 index 4e9bc65..0000000 --- a/projector/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "bankaccount-projector" -version = "0.1.0" -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 = { path = "../../../crates/concordance-gen"} -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 deleted file mode 100644 index 07e2314..0000000 --- a/projector/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# 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 deleted file mode 100644 index 322a2b9..0000000 --- a/projector/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -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 - } - - async fn handle_funds_deposited(&self, input: FundsDeposited) -> Result<()> { - store::record_funds_deposited(input).await - } - - async fn handle_funds_reserved(&self, input: FundsReserved) -> Result<()> { - store::record_funds_reserved(input).await - } - - async fn handle_funds_withdrawn(&self, input: FundsWithdrawn) -> Result<()> { - store::record_funds_withdrawn(input).await - } - - async fn handle_funds_released(&self, input: FundsReleased) -> Result<()> { - store::record_funds_released(input).await - } - - async fn handle_wire_transfer_initiated(&self, _input: WireTransferInitiated) -> Result<()> { - Ok(()) - } -} - diff --git a/projector/src/store.rs b/projector/src/store.rs deleted file mode 100644 index 8acbe59..0000000 --- a/projector/src/store.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::collections::HashMap; - -use crate::*; - -use serde::{Deserialize, Serialize}; -use wasmbus_rpc::actor::prelude::*; -use wasmcloud_interface_keyvalue::{GetResponse, 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(()) -} - -/// Records a deposit by adding a `LedgerLine` to the end of the previously stored -/// ledger and recording the new balance. -pub async fn record_funds_deposited(event: FundsDeposited) -> Result<()> { - debug!("Recording deposit in account {}", event.account_number); - let account_number = event.account_number.to_string(); - let ctx = Context::default(); - - let kv = KeyValueSender::new(); - let ledger_key = format!("ledger.{account_number}"); - - let new_ledger = get(&ctx, &kv, &ledger_key).await.map(|ledger_raw| { - serde_json::from_str::(&ledger_raw).map(|mut ledger| { - let last_balance = ledger.ledger_lines.last().unwrap().effective_balance; - ledger.ledger_lines.push(LedgerLine { - amount: event.amount as u32, - tx_type: TransactionType::Deposit, - effective_balance: last_balance + event.amount as u32, - }); - ledger - }) - }); - if let Some(Ok(ledger)) = new_ledger { - let new_balance = ledger - .ledger_lines - .last() - .map(|l| l.effective_balance) - .unwrap_or(0); - set_ledger(&ctx, &kv, ledger_key, ledger).await; - let balance_key = format!("balance.{account_number}"); - set(&ctx, &kv, balance_key, new_balance.to_string()).await; - } else { - error!("Unable to save projection for deposit on account {account_number}"); - } - - Ok(()) -} - -/// Records a reservation of funds by adding a funds reserved transaction to the end of the -/// ledger and recording the newly adjusted balance -pub async fn record_funds_reserved(event: FundsReserved) -> Result<()> { - debug!( - "Recording funds reservation (interbank) in account {}", - event.account_number - ); - let account_number = event.account_number.to_string(); - let ctx = Context::default(); - - let kv = KeyValueSender::new(); - let ledger_key = format!("ledger.{account_number}"); - - let new_ledger = get(&ctx, &kv, &ledger_key).await.map(|ledger_raw| { - serde_json::from_str::(&ledger_raw).map(|mut ledger| { - let last_balance = ledger.ledger_lines.last().unwrap().effective_balance; - ledger - .holds - .insert(event.wire_transfer_id, event.amount as u32); - ledger.ledger_lines.push(LedgerLine { - amount: event.amount as u32, - tx_type: TransactionType::FundsReserve, - effective_balance: last_balance - event.amount as u32, - }); - ledger - }) - }); - if let Some(Ok(ledger)) = new_ledger { - let new_balance = ledger - .ledger_lines - .last() - .map(|l| l.effective_balance) - .unwrap_or(0); - set_ledger(&ctx, &kv, ledger_key, ledger).await; - let balance_key = format!("balance.{account_number}"); - set(&ctx, &kv, balance_key, new_balance.to_string()).await; - } else { - error!("Unable to save projection for withdrawal on account {account_number}"); - } - - Ok(()) -} - -// Releases previously reserved funds by adding a funds released transaction to the end -/// of the ledger and recording the updated balance -pub async fn record_funds_released(event: FundsReleased) -> Result<()> { - debug!( - "Recording funds release (interbank) in account {}", - event.account_number - ); - let account_number = event.account_number.to_string(); - - let kv = KeyValueSender::new(); - let ledger_key = format!("ledger.{account_number}"); - let ctx = Context::default(); - - let new_ledger = get(&ctx, &kv, &ledger_key).await.map(|ledger_raw| { - serde_json::from_str::(&ledger_raw).map(|mut ledger| { - let last_balance = ledger.ledger_lines.last().unwrap().effective_balance; - let orig_hold = ledger.holds.remove(&event.wire_transfer_id); - ledger.ledger_lines.push(LedgerLine { - amount: orig_hold.unwrap_or_default(), - tx_type: TransactionType::FundsRelease, - effective_balance: last_balance + orig_hold.unwrap_or_default(), - }); - ledger - }) - }); - if let Some(Ok(ledger)) = new_ledger { - let new_balance = ledger - .ledger_lines - .last() - .map(|l| l.effective_balance) - .unwrap_or(0); - set_ledger(&ctx, &kv, ledger_key, ledger).await; - let balance_key = format!("balance.{account_number}"); - set(&ctx, &kv, balance_key, new_balance.to_string()).await; - } else { - error!("Unable to save projection for withdrawal on account {account_number}"); - } - - Ok(()) -} - -/// Records a withdrawal from an account by adding a withdrawal ledger item to the -/// ledger and recording the new balance -pub async fn record_funds_withdrawn(event: FundsWithdrawn) -> Result<()> { - debug!("Recording withdrawal in account {}", event.account_number); - let account_number = event.account_number.to_string(); - - let kv = KeyValueSender::new(); - let ledger_key = format!("ledger.{account_number}"); - - let ctx = Context::default(); - - // Note:the aggregate would prevent the creation of an event that would violate - // business rules, so we can safely do the subtraction here without any guards - - let new_ledger = get(&ctx, &kv, &ledger_key).await.map(|ledger_raw| { - serde_json::from_str::(&ledger_raw).map(|mut ledger| { - let last_balance = ledger.ledger_lines.last().unwrap().effective_balance; - ledger.ledger_lines.push(LedgerLine { - amount: event.amount as u32, - tx_type: TransactionType::Withdrawal, - effective_balance: last_balance - event.amount as u32, - }); - ledger - }) - }); - if let Some(Ok(ledger)) = new_ledger { - let new_balance = ledger - .ledger_lines - .last() - .map(|l| l.effective_balance) - .unwrap_or(0); - set_ledger(&ctx, &kv, ledger_key, ledger).await; - let balance_key = format!("balance.{account_number}"); - set(&ctx, &kv, balance_key, new_balance.to_string()).await; - } else { - error!("Unable to save projection for withdrawal on account {account_number}"); - } - - 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}"); - } -} - -async fn set_ledger( - ctx: &Context, - kv: &KeyValueSender, - key: String, - ledger: AccountLedger, -) { - set(ctx, kv, key, serde_json::to_string(&ledger).unwrap()).await -} - -async fn get(ctx: &Context, kv: &KeyValueSender, key: &str) -> Option { - match kv.get(ctx, key).await { - Ok(GetResponse { - value: v, - exists: true, - }) => Some(v), - Ok(GetResponse { exists: false, .. }) => None, - Err(e) => { - error!("Failed to get {key} from store: {e}"); - None - } - } -} - -#[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 deleted file mode 100644 index 3c15c18..0000000 --- a/projector/wasmcloud.toml +++ /dev/null @@ -1,6 +0,0 @@ -name = "BankAccountProjector" -language = "rust" -type = "actor" - -[actor] -claims = ["cosmonic:eventsourcing", "wasmcloud:keyvalue", "wasmcloud:builtin:logging"] diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 337e533..0000000 --- a/scripts/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# โš ๏ธ NOTE -These scripts have not yet been updated to the new model. They will be updated soon. - -See the [bank_wadm.yaml](../bank_wadm.yaml) file for the most up to date configuration and startup. \ No newline at end of file diff --git a/scripts/create_account_cmd.json b/scripts/create_account_cmd.json deleted file mode 100644 index b1160e8..0000000 --- a/scripts/create_account_cmd.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "command_type": "create_account", - "key": "ABC123", - "data": - { - "accountNumber": "ABC123", - "initialBalance": 4000, - "minBalance": 100, - "customerId": "CUSTBOB" - } -} \ No newline at end of file diff --git a/scripts/deposit_cmd_1.json b/scripts/deposit_cmd_1.json deleted file mode 100644 index 5493e86..0000000 --- a/scripts/deposit_cmd_1.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "command_type": "deposit_funds", - "key": "ABC123", - "data": - { - "accountNumber": "ABC123", - "amount": 3000, - "note": "cash deposit", - "customerId": "CUSTBOB" - } -} \ No newline at end of file diff --git a/scripts/deposit_cmd_2.json b/scripts/deposit_cmd_2.json deleted file mode 100644 index 13b2fe4..0000000 --- a/scripts/deposit_cmd_2.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "command_type": "deposit_funds", - "key": "ABC123", - "data": - { - "accountNumber": "ABC123", - "amount": 1000, - "note": "cash deposit", - "customerId": "CUSTBOB" - } -} \ No newline at end of file diff --git a/scripts/scenario_1.sh b/scripts/scenario_1.sh deleted file mode 100755 index 9e14112..0000000 --- a/scripts/scenario_1.sh +++ /dev/null @@ -1,19 +0,0 @@ -# /bin/sh -# This script assumes the existence of the nats command line tool and a running NATS server that has JetStream enabled. -# It also requires the following: -# * Concordance capability provider running in a host -# * All relevant actors (agg, pm, proj, etc) running -# * All linkdefs set -# * the jq command installed - -# uncomment if you want to start fresh -# nats stream purge CC_EVENTS -f -# nats stream purge CC_COMMANDS -f - -nats req cc.commands.bankaccount "`cat create_account_cmd.json | jq -c`" -nats req cc.commands.bankaccount "`cat deposit_cmd_1.json | jq -c`" -nats req cc.commands.bankaccount "`cat deposit_cmd_2.json | jq -c`" -nats req cc.commands.bankaccount "`cat withdraw_cmd_1.json | jq -c`" - -nats kv get CC_STATE agg.bankaccount.ABC123 - diff --git a/scripts/withdraw_cmd_1.json b/scripts/withdraw_cmd_1.json deleted file mode 100644 index ff13699..0000000 --- a/scripts/withdraw_cmd_1.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "command_type": "withdraw_funds", - "key": "ABC123", - "data": - { - "accountNumber": "ABC123", - "amount": 2000, - "note": "atm withdrawal", - "customerId": "CUSTBOB" - } -} \ No newline at end of file diff --git a/setup/argocd-application.yaml b/setup/argocd-application.yaml new file mode 100644 index 0000000..5be3ab6 --- /dev/null +++ b/setup/argocd-application.yaml @@ -0,0 +1,15 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: bank-account +spec: + source: + path: . + repoURL: https://github.com/cosmonic/concordance-gitops + targetRevision: HEAD + directory: + recurse: false + destination: + server: "https://kubernetes.default.svc" + namespace: default + project: default diff --git a/wadm.yaml b/wadm.yaml new file mode 100644 index 0000000..3897375 --- /dev/null +++ b/wadm.yaml @@ -0,0 +1,25 @@ +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: bank-account + annotations: + version: v0.0.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 + traits: + - type: spreadscaler + properties: + replicas: 3 + - type: linkdef + properties: + target: httpserver + - name: httpserver + type: capability + properties: + image: cosmonic.azurecr.io/httpserver_wormhole:0.6.2 + contract: wasmcloud:httpserver