Follow an order through separate services for catalog, payments, inventory, and notifications.
Service-oriented architecture (SOA) organizes software into services that provide business functions through defined interfaces. A retailer's website and customer-support tools can use the same ordering or payment service, rather than each implementing those functions separately.
This example follows a customer placing an order. It shows what each service does, how information passes between them, and how the order progresses when a payment or stock reservation fails.
We'll also look at how Encore runs the services and their declared infrastructure locally, then provisions and deploys them in your own AWS or GCP account.
The website uses these services to place an order. A customer-support application could use Orders to look up the same purchase, or Payments to request a refund. The services expose those operations through their interfaces, so callers can use them without directly changing their database records.
Orders saves the price agreed at checkout with the order. Catalog can later change a product's price without changing what that customer purchased. Inventory handles stock reservations, and Payments maintains the connection to the payment provider.
The client submits the items to Orders. Orders checks the request, gets current product information from Catalog, and stores a pending order. It returns an order ID that the client can use to check progress.
Orders then publishes an OrderPlaced event. Inventory receives it and tries to reserve the items. Payments receives it through a separate subscription and asks the payment provider to authorize the amount. These actions can finish in either order.
Each service publishes its result. Orders confirms the purchase when it has both a successful reservation and a payment authorization. Notifications receives OrderConfirmed and sends the customer a confirmation email.
Client → Orders → Catalog │ └─ OrderPlaced ─┬→ Inventory → StockReserved / StockRejected └→ Payments → PaymentAuthorized / PaymentFailed Orders → OrderConfirmed → Notifications
The initial HTTP request doesn't wait for that whole sequence. The pending order records the work in progress, and the client can retrieve its updated status later. In this example, authorization reserves the payment; capturing the funds can be a later step tied to fulfillment.
Suppose Inventory reserves the items but the payment is declined. Orders cancels the purchase and requests release of the reservation. If the payment is authorized but stock is unavailable, it asks Payments to void the authorization.
These follow-up operations undo completed work in another service. They are often called compensating actions. The sequence of local transactions and compensating actions is a saga.
Orders also needs a way to resolve work that never finishes. A pending order can expire after the allowed processing time, triggering cancellation and any necessary compensation. A result arriving after cancellation is handled according to the canceled state, so a late authorization does not confirm the purchase again.
Notifications can retry delivery without changing the order's payment or stock status. A failed email leaves a confirmed order confirmed.
An OrderPlaced event carries the purchase details needed to start processing:
{
"eventId": "evt-order-1042",
"orderId": "order-1042",
"currency": "USD",
"totalMinorUnits": 4990,
"items": [{ "sku": "book-7", "quantity": 2 }]
}
The amount is stored in integer minor units, so 4990 represents USD 49.90. The event records the amount agreed at purchase time. Payments uses the order reference to find the payment details through the application's authorized payment integration; raw card credentials are not included in the event.
The event format is a contract between the services. Adding an optional field can let existing consumers continue working, while changing what an existing field means can require coordinated changes. Keeping the event separate from the Orders database schema lets the service change its storage without changing every consumer.
Messages and requests can be retried. Payments might receive the same order event twice, or a request to the payment provider might time out after the provider has already processed it.
Each service uses a stable identifier for the operation and records the outcome. For changes within its own database, it can save the result and deduplication key in one transaction. Repeating that operation returns the recorded outcome or leaves the completed state unchanged.
An external payment call also uses the provider's idempotency mechanism. If the request times out, Payments can retry with the same operation key rather than create another authorization. The same principle applies to releasing a stock reservation or voiding an authorization more than once.
Orders can fail after saving a purchase but before publishing OrderPlaced. Without a recovery mechanism, the database contains a pending order that Inventory and Payments never hear about.
A transactional outbox saves the event in the same database transaction as the order. A publisher then reads the outbox and delivers the event, retrying when necessary. Consumers continue to handle duplicate delivery because the publisher can fail after sending an event but before recording it as sent.
Encore's transactional outbox guide shows this pattern for an Encore.go application. It keeps the database write and the intent to publish together, so event delivery can resume after a failure.
An order ID connects the business records and logs produced by each service. Distributed tracing shows the requests and message processing involved in a particular attempt, including where time was spent or a call failed.
Orders can also expose how long purchases have remained pending and which result each is waiting for. That lets the team investigate a stuck checkout even when the individual services are responding normally.
SOA emphasizes services that expose reusable business capabilities, often across several applications. A payment service used by the store and customer-support tools is one example. Microservices focus on smaller services with independent deployment and operation within an application.
The example here uses asynchronous messaging and service-managed data, practices also common in microservices. They are implementation choices for this store rather than requirements every SOA system must follow. Some SOA environments use broader services and centralized integration middleware.
With Encore, the application declares its services, databases, and messaging in code. The platform uses those declarations to run the application locally and provision the infrastructure in your own AWS or GCP account.
For this store, Orders declares the topic used to announce a purchase. Inventory and Payments declare separate subscriptions, so both receive each event. Each service can also declare the database it uses for its own records.

The topic declaration in TypeScript defines the event shape and its delivery guarantee:
import { Topic } from "encore.dev/pubsub";
export interface OrderPlaced {
eventId: string;
orderId: string;
currency: string;
totalMinorUnits: number;
items: { sku: string; quantity: number }[];
}
export const orderPlaced = new Topic<OrderPlaced>("order-placed", {
deliveryGuarantee: "at-least-once",
});
Inventory and Payments each add a subscription with a handler for their part of the workflow. Those handlers perform the reservation or authorization and publish the result. The at-least-once guarantee means they must handle retries using the stable identifiers described above.
Developers can run the services and local resources together with encore run and inspect requests and event processing in the local dashboard. The same declarations drive cloud provisioning and deployment, keeping the resource configuration with the application as the order flow changes.
Run in your terminal to get started locally.
No. In this example each service controls its data, but that does not require a separate physical server for each database. The deployment can use shared database infrastructure with separate databases and access permissions.
No. An enterprise service bus is one way to connect services and handle integration. Services can also communicate through direct APIs and messaging, as in the order example.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.