Skip to content

Commit

Permalink
Add Python Durable RPC example
Browse files Browse the repository at this point in the history
  • Loading branch information
gvdongen committed Dec 20, 2024
1 parent 097ff97 commit d79b5e5
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 4 deletions.
31 changes: 29 additions & 2 deletions python/patterns-use-cases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,37 @@ This example shows an example of:
The example shows how you can programmatically submit a requests to a Restate service.
Every request gets processed durably, and deduplicated based on the idempotency key.

The example shows a [client](src/main/java/my/example/durablerpc/MyClient.java) that receives product reservation requests and forwards them to the product service.
The [Product service](src/main/java/my/example/durablerpc/ProductService.java) is a Restate service that durably processes the reservation requests and deduplicates them.
The example shows a [client](src/durablerpc/client.py) that receives product reservation requests and forwards them to the product service.
The [Product service](src/durablerpc/product_service.py) is a Restate service that durably processes the reservation requests and deduplicates them.
Each product can be reserved only once.

### Running the example
1. [Start the Restate Server](https://docs.restate.dev/develop/local_dev) in a separate shell: `restate-server`
2. Start the service: `python -m hypercorn --config hypercorn-config.toml src/durablerpc/product_service:app`
3. Register the services (with `--force` to override the endpoint during **development**): `restate -y deployments register --force localhost:9080`

### Demo scenario

Run the client to let it send a request to reserve a product:
```shell
python src/durablerpc/client.py product1 reservation1
```

This will give us `{"reserved":true}`.

Let's change the reservation ID and run the request again:
```shell
python src/durablerpc/client.py product1 reservation2
```

This will give us `{"reserved":false}` because this product is already reserved, so we can't reserve it again.

However, if we run the first request again with same reservation ID, we will get `{"reserved":true}` again:
```shell
python src/durablerpc/client.py product1 reservation1
```
Restate deduplicated the request and returned the first response.

## Microservices: Sagas

An example of a trip reservation workflow, using the saga pattern to undo previous steps in case of an error.
Expand Down
Empty file.
27 changes: 27 additions & 0 deletions python/patterns-use-cases/src/durablerpc/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import argparse

import requests

RESTATE_URL = "http://localhost:8080"


# Durable RPC call to the product service
# Restate registers the request and makes sure it runs to completion exactly once
# Could be part of a Flask app or any other Python application
def reserve_product(product_id: str, reservation_id: str):
url = f"{RESTATE_URL}/product/{product_id}/reserve"
headers = {
"idempotency-key": reservation_id,
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers)
print({"reserved": response.json()})


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("product_id", type=str, help="Product ID")
parser.add_argument("reservation_id", type=str, help="Reservation ID")

args = parser.parse_args()
reserve_product(args.product_id, args.reservation_id)
16 changes: 16 additions & 0 deletions python/patterns-use-cases/src/durablerpc/product_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import restate
from restate import ObjectContext, VirtualObject

product_service = VirtualObject("product")


@product_service.handler()
async def reserve(ctx: ObjectContext) -> bool:
if await ctx.get("reserved"):
print(f"Product already reserved {ctx.key}")
return False
print(f"Reserving product {ctx.key()}")
ctx.set("reserved", True)
return True

app = restate.app([product_service])
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Let's change the reservation ID and run the request again:
curl -X POST localhost:5000/reserve/product1/reservation2
```

This will give us `{"reserved":false}` because this product is already reserved so we can't reserve it again.
This will give us `{"reserved":false}` because this product is already reserved, so we can't reserve it again.

However, if we run the first request again with same reservation ID, we will get `{"reserved":true}` again:
```shell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ app.post("/reserve/:productId/:reservationId", async (req: Request, res: Respons
const { productId, reservationId } = req.params;

// Durable RPC call to the product service
// Restate registers the request and makes sure runs to completion exactly once
// Restate registers the request and makes sure it runs to completion exactly once
const products = restateClient
.objectClient<ProductService>({ name: "product" }, productId);
const reservation = await products.reserve(
Expand Down

0 comments on commit d79b5e5

Please sign in to comment.