Go to docs.restate.dev → Develop → Local Dev https://docs.restate.dev/develop/local_dev#running-restate-server--cli-locally
Download the Restate Server and CLI in your preferred way
restate example java-hello-world-gradle
Virtual Object tracking the cart content of the user.
Keyed by user ID.
- Create a class
CartObject
- Add the @VirtualObject annotation
- Define three handlers
- addTicket(String ticketId) -> boolean success
- expireTicket(String ticketId) -> void
- checkout() -> boolean success
Virtual Object representing a single ticket in the system.
Keyed by ticket ID.
- Create a class
TicketObject
- Define three handlers
- reserve() -> boolean success
- unreserve() → void
- markAsSold() → void
- Create an AppMain class with a main method
- Create an RestateHttpEndpoint
- Bind the two objects to the endpoint
- Listen on default port 9080
- In
build.gradle.kts
change the mainClass (Greeter → AppMain)
application {
mainClass.set("my.example.AppMain")
}
Via the run button in the IDE or via
./gradlew run
restate-server
Or adapt based on the way you installed the server.
Via the Restate CLI:
restate deployments register http://localhost:9080
curl -X POST localhost:8080/TicketObject/ticket1/reserve
curl -X POST localhost:8080/TicketObject/ticket1/unreserve
curl -X POST localhost:8080/TicketObject/ticket1/markAsSold
Add a ticket to Mary’s cart:
curl localhost:8080/CartObject/Mary/addTicket \
-H 'content-type: application/json' \
-d '"seat2B"'
Let Mary buy the ticket:
curl -X POST localhost:8080/CartObject/Mary/checkout
- Get the
“status”
from Restate K/V state - If the status equals
“Available”
, then- set it to
“Reserved”
- return true
- set it to
- Else
- return false
If the status does not equal “Sold”
, then clear it.
If the status equals “Reserved”
, then set it to “Sold”
.
- For each ticket, a permanently living object in Restate
- Requests are executed one-by-one
- Durable, consistent state machine
Try it out by reserving tickets and see the status change:
restate kv get TicketObject ticket1
- Reserve the ticket → TicketObject/reserve
- If success
- Get Set of ticket IDs from K/V state
- Add current ticketID to the state
- Set timer to expire ticket in 15 minutes
- Return reservation success
- Get the tickets from state and check if they are not empty
- If empty, return false
- Do the payment
- Generate a resilient, unique payment identifier
- Do the payment by creating a stub
private boolean pay(String idempotencyKey, double totalPrice){ System.out.println("Paying tickets for " + idempotencyKey + " and price " + totalPrice); return true; }
Throw an exception in the addTicket handler and see how Restate handles retries:
throw new IllegalStateException("The handler failed");
Debug with the CLI:
restate invocations list
restate invocations describe <id>
- If payment was success,
- Call TicketObject/markAsSold for each ticket
- Clear the cart
- Return true (success)
- Else return false (failure)
- Remove the ticket from the tickets set
- If the ticket got removed, then call TicketObject/unreserve
curl localhost:8080/CartObject/Mary/addTicket \
-H 'content-type: application/json' \
-H 'idempotency-key: ad5472esg4dsg525dssdfa5loi' \
-d '"seat2C"'
Check the service logs → no re-execution
Imagine there are multiple websites where tickets for the same concert can be reserved. These other websites send events over Kafka.
Let's hook up the reserve
method to Kafka.
Start up a Kafka cluster
docker compose up
Start up Restate with the configuration file containing the location of our Kafka cluster:
restate-server --config-file restate.toml
Connect the reserve
method with the Kafka topic:
curl localhost:9070/subscriptions -H 'content-type: application/json' \
-d '{
"source": "kafka://my-cluster/reserve",
"sink": "service://TicketObject/reserve",
"options": {"auto.offset.reset": "earliest"}
}'
Publish something to the topic
docker ps
docker exec -ti f7381464eaef /bin/bash
kafka-console-producer --bootstrap-server localhost:9092 --topic reserve
For example: ticket1={}
You should see in the logs that the reserve method got executed.
Try to do the same for addTicket
of the CartService
.