Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release] Update the examples #23

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ cd resemble-examples/
Create a new Python virtual environment in which to install Resemble
requirements and run an application:

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=32-33) -->
<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=35-36) -->
<!-- The below code snippet is automatically added from ./readme_test.sh -->
```sh
python -m venv ./.resemble-examples-venv
Expand All @@ -148,7 +148,7 @@ Install the Resemble command line tool (`rsm`) via `pip`. This package includes
the `rsm` CLI, the Resemble `protoc` plugin, the proto dependencies required for
Resemble definitions, and the `grpcio-tools` package that provides `protoc`.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=39-39) -->
<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=42-42) -->
<!-- The below code snippet is automatically added from ./readme_test.sh -->
```sh
pip install reboot-resemble-cli
Expand All @@ -166,7 +166,7 @@ requirements include the Resemble backend library, `reboot-resemble`.
Requirements are specific to a particular example application. The following
command will install requirements for the `hello-constructors` application.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=52-52) -->
<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=55-55) -->
<!-- The below code snippet is automatically added from ./readme_test.sh -->
```sh
pip install -r hello-constructors/backend/src/requirements.txt
Expand All @@ -178,7 +178,7 @@ pip install -r hello-constructors/backend/src/requirements.txt
Run the Resemble `protoc` plugin to generate Resemble code based on the protobuf
definition of a service.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=55-55) -->
<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=58-58) -->
<!-- The below code snippet is automatically added from ./readme_test.sh -->
```sh
rsm protoc
Expand All @@ -196,7 +196,7 @@ repository.
The example code comes with example tests. To run the example tests,
use `pytest`, for example:

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=58-58) -->
<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./readme_test.sh&lines=61-61) -->
<!-- The below code snippet is automatically added from ./readme_test.sh -->
```sh
pytest hello-constructors/backend/
Expand Down
82 changes: 82 additions & 0 deletions api/bank/v1/account.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
syntax = "proto3";

package bank.v1;

import "google/protobuf/empty.proto";
import "resemble/v1alpha1/options.proto";

import "bank/v1/errors.proto";

////////////////////////////////////////////////////////////////////////
// Service definitions.

service Account {
option (resemble.v1alpha1.service).state = "AccountState";

rpc Open(OpenRequest) returns (google.protobuf.Empty) {
option (resemble.v1alpha1.method).writer = {
constructor: true,
};
}

rpc Balance(google.protobuf.Empty) returns (BalanceResponse) {
option (resemble.v1alpha1.method).reader = {
};
}

rpc Deposit(DepositRequest) returns (DepositResponse) {
option (resemble.v1alpha1.method).writer = {
};
}

rpc Withdraw(WithdrawRequest) returns (WithdrawResponse) {
option (resemble.v1alpha1.method) = {
writer: {},
errors: [ "OverdraftError" ],
};
}

// An async task to send a welcome email to the customer.
rpc WelcomeEmailTask(google.protobuf.Empty)
returns (google.protobuf.Empty) {
option (resemble.v1alpha1.method) = {
writer: {},
task: true,
};
}
}

////////////////////////////////////////////////////////////////////////
// State types.

message AccountState {
string customer_name = 1;
uint64 balance = 2;
}

////////////////////////////////////////////////////////////////////////
// Request and response types.

message OpenRequest {
string customer_name = 1;
}

message BalanceResponse {
uint64 balance = 1;
}

message DepositRequest {
uint64 amount = 1;
}

message DepositResponse {
uint64 updated_balance = 1;
}

message WithdrawRequest {
uint64 amount = 1;
}

message WithdrawResponse {
uint64 updated_balance = 1;
}
49 changes: 49 additions & 0 deletions api/bank/v1/bank.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
syntax = "proto3";

package bank.v1;

import "google/protobuf/empty.proto";
import "resemble/v1alpha1/options.proto";

import "bank/v1/errors.proto";

////////////////////////////////////////////////////////////////////////

service Bank {
option (resemble.v1alpha1.service).state = "BankState";

rpc SignUp(SignUpRequest) returns (SignUpResponse) {
option (resemble.v1alpha1.method).transaction = {
};
}

rpc Transfer(TransferRequest) returns (google.protobuf.Empty) {
option (resemble.v1alpha1.method) = {
transaction: {},
errors: [ "OverdraftError" ],
};
}
}

////////////////////////////////////////////////////////////////////////

message BankState {
repeated string account_ids = 1;
}

////////////////////////////////////////////////////////////////////////

message SignUpRequest {
string customer_name = 1;
}

message SignUpResponse {
// The ID of the account that has just been opened for the customer.
string account_id = 1;
}

message TransferRequest {
string from_account_id = 1;
string to_account_id = 2;
int64 amount = 3;
}
16 changes: 16 additions & 0 deletions api/bank/v1/errors.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
////////////////////////////////////////////////////////////////////////
// This file contains common errors that may be returned by several RPCs,
// defined in several files.
////////////////////////////////////////////////////////////////////////

syntax = "proto3";

package bank.v1;

////////////////////////////////////////////////////////////////////////

// Error returned when a withdrawal would overdraft the account.
message OverdraftError {
// Amount that we would have overdraft by.
uint32 amount = 1;
}
39 changes: 0 additions & 39 deletions api/hello_world/v1/greeter.proto

This file was deleted.

4 changes: 4 additions & 0 deletions bank/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# The Resemble Bank

This example demonstrates how state machines can interact, including in the
context of a Transaction.
File renamed without changes.
107 changes: 107 additions & 0 deletions bank/backend/src/account_servicer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import asyncio
import logging
from bank.v1.account_rsm import (
Account,
AccountState,
BalanceResponse,
DepositRequest,
DepositResponse,
OpenRequest,
WithdrawRequest,
WithdrawResponse,
)
from bank.v1.errors_rsm import OverdraftError
from google.protobuf.empty_pb2 import Empty
from resemble.aio.contexts import ReaderContext, WriterContext

logging.basicConfig(level=logging.INFO)


class AccountServicer(Account.Interface):

async def Open(
self,
context: WriterContext,
request: OpenRequest,
) -> Account.OpenEffects:
# Since this is a constructor, we are setting the initial state of the
# state machine.
initial_state = AccountState(customer_name=request.customer_name)

# We'd like to send the new customer a welcome email, but that can be
# done asynchronously, so we schedule it as a task.
welcome_email_task = self.schedule().WelcomeEmailTask(context)

return Account.OpenEffects(
state=initial_state,
tasks=[welcome_email_task],
response=Empty(),
)

async def Balance(
self,
context: ReaderContext,
state: AccountState,
request: Empty,
) -> BalanceResponse:
return BalanceResponse(balance=state.balance)

async def Deposit(
self,
context: WriterContext,
state: AccountState,
request: DepositRequest,
) -> Account.DepositEffects:
updated_balance = state.balance + request.amount
return Account.DepositEffects(
state=AccountState(balance=updated_balance),
response=DepositResponse(updated_balance=updated_balance),
)

async def Withdraw(
self,
context: WriterContext,
state: AccountState,
request: WithdrawRequest,
) -> Account.WithdrawEffects:
updated_balance = state.balance - request.amount
if updated_balance < 0:
raise Account.WithdrawError(
OverdraftError(amount=-updated_balance)
)
return Account.WithdrawEffects(
state=AccountState(balance=updated_balance),
response=WithdrawResponse(updated_balance=updated_balance),
)

async def WelcomeEmailTask(
self,
context: WriterContext,
state: AccountState,
request: Empty,
) -> Account.WelcomeEmailTaskEffects:
message_body = (
f"Hello {state.customer_name},\n"
"\n"
"We are delighted to welcome you as a customer.\n"
f"Your new account has been opened, and has ID '{context.actor_id}'.\n"
"\n"
"Best regards,\n"
"Your Bank"
)

await send_email(message_body)

return Account.WelcomeEmailTaskEffects(
state=state,
response=Empty(),
)


async def send_email(message_body: str):
# We're not actually going to send an email here; but you could!
#
# If you do send real emails, please be sure to use an idempotent API, since
# (like in any well-written distributed system) this call may be retried in
# case of errors.
logging.info(f"Sending email:\n====\n{message_body}\n====")
Loading
Loading