Skip to content

Commit

Permalink
Python ticketing example
Browse files Browse the repository at this point in the history
  • Loading branch information
gvdongen committed Jul 22, 2024
1 parent 95b7039 commit 756b2cf
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
venv
.venv
__pycache__/
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Restate Example: Ticket reservation system Python

This example shows a subset of a ticket booking system.

Restate is a system for easily building resilient applications using **distributed durable building blocks**.

❓ Learn more about Restate from the [Restate documentation](https://docs.restate.dev).

## Running the example

To set up the example, use the following sequence of commands.

Setup the virtual env:

```shell
python3 -m venv .venv
source venv/bin/activate
```

Install the requirements:

```shell
pip install -r requirements.txt
```

Start the app as follows:

```shell
python3 -m hypercorn example/app:app
```

Start the Restate Server ([other options here]()):

```shell
restate-server
```

Register the service:

```shell
restate dp register http://localhost:8000
```

Then add a ticket to Mary's cart:

```shell
curl localhost:8080/cart/Mary/add_ticket -H 'content-type: application/json' -d '"seat2B"'
```

Let Mary buy the ticket via:
```shell
curl -X POST localhost:8080/cart/Mary/checkout
```

That's it! We managed to run the example, add a ticket to the user session cart, and buy it!
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import restate

from example.cart_object import cart
from example.checkout_service import checkout
from example.ticket_object import ticket

app = restate.app(services=[cart, checkout, ticket])
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from datetime import timedelta

from restate.context import ObjectContext
from restate.object import VirtualObject

from example.checkout_service import handle
from example.ticket_object import reserve, mark_as_sold, unreserve

cart = VirtualObject("cart")


@cart.handler()
async def add_ticket(ctx: ObjectContext, ticket_id: str) -> bool:
reserved = await ctx.object_call(reserve, key=ticket_id, arg=None)

if reserved:
tickets = await ctx.get("tickets") or []
tickets.append(ticket_id)
ctx.set("tickets", tickets)

ctx.object_send(expire_ticket, key=ctx.key(), arg=ticket_id, send_delay=timedelta(minutes=15))

return reserved


@cart.handler()
async def checkout(ctx: ObjectContext) -> bool:
tickets = await ctx.get("tickets") or []

if len(tickets) == 0:
return False

success = await ctx.service_call(handle, arg={'user_id': ctx.key(),
'tickets': tickets})

if success:
for ticket in tickets:
ctx.object_send(mark_as_sold, key=ticket, arg=None)

ctx.clear("tickets")

return success


@cart.handler()
async def expire_ticket(ctx: ObjectContext, ticket_id: str):
tickets = await ctx.get("tickets") or []

try:
ticket_index = tickets.index(ticket_id)
except ValueError:
ticket_index = -1

if ticket_index != -1:
tickets.pop(ticket_index)
ctx.set("tickets", tickets)

ctx.object_send(unreserve, key=ticket_id, arg=None)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import uuid
from typing import TypedDict, List
from restate.context import ObjectContext, Serde
from restate.service import Service

from example.utils.email_client import EmailClient
from example.utils.payment_client import PaymentClient


class Order(TypedDict):
user_id: str
tickets: List[str]


payment_client = PaymentClient()
email_client = EmailClient()

checkout = Service("checkout")


@checkout.handler()
async def handle(ctx: ObjectContext, order: Order) -> bool:
total_price = len(order['tickets']) * 40

idempotency_key = await ctx.run("idempotency_key", lambda: str(uuid.uuid4()))

async def pay():
return await payment_client.call(idempotency_key, total_price)
success = await ctx.run("payment", pay)

if success:
await ctx.run("send_success_email", lambda: email_client.notify_user_of_payment_success(order['user_id']))
else:
await ctx.run("send_failure_email", lambda: email_client.notify_user_of_payment_failure(order['user_id']))

return success
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from restate.context import ObjectContext
from restate.object import VirtualObject


ticket = VirtualObject("ticket")


@ticket.handler()
async def reserve(ctx: ObjectContext) -> bool:
status = await ctx.get("status") or "AVAILABLE"

if status == "AVAILABLE":
ctx.set("status", "RESERVED")
return True
else:
return False


@ticket.handler()
async def unreserve(ctx: ObjectContext):
status = await ctx.get("status") or "AVAILABLE"

if status != "SOLD":
ctx.clear("status")


@ticket.handler()
async def mark_as_sold(ctx: ObjectContext):
status = await ctx.get("status") or "AVAILABLE"

if status == "RESERVED":
ctx.set("status", "SOLD")
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class EmailClient:

def __init__(self):
self.i = 0

def notify_user_of_payment_success(self, user_id: str):
print(f"Notifying user {user_id} of payment success")
# send the email
return True

def notify_user_of_payment_failure(self, user_id: str):
print(f"Notifying user {user_id} of payment failure")
# send the email
return True
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class PaymentClient:

def __init__(self):
self.i = 0

async def call(self, idempotency_key: str, amount: float) -> bool:
print(f"Payment call succeeded for idempotency key {idempotency_key} and amount {amount}")
# do the call
return True

async def failing_call(self, idempotency_key: str, amount: float) -> bool:
if self.i >= 2:
print(f"Payment call succeeded for idempotency key {idempotency_key} and amount {amount}")
i = 0
return True
else:
print(f"Payment call failed for idempotency key {idempotency_key} and amount {amount}. Retrying...")
self.i += 1
raise Exception("Payment call failed")
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
restate_sdk
hypercorn

0 comments on commit 756b2cf

Please sign in to comment.