When I started designing the order workflow for a microservice system, I quickly ran into problems. Orders in our system can transition through more than 20 statuses — from ORDER_CREATED to PAID, SHIPPED, CANCELLED, and beyond.
Each status could require a different service to:
- Perform some business logic (e.g., call another service, validate state)
- Trigger the next transition
- Broadcast updates downstream
At first, I wasn’t sure where the logic should live or how services should coordinate. These were the questions I needed answers to:
- Should there be a central service that owns all order statuses?
- How should status transitions be modeled and persisted?
- How can I avoid bloated
whenstatements while keeping the code modular? - What if multiple statuses share common logic like updating and broadcasting?
- Should class names reflect the design patterns used (like
*Strategy)?
This post is a summary of what I learned while figuring it out.
1. Should One Central Service Manage All Order Statuses?
I initially considered building a central OrderStatusService to coordinate everything. But that quickly felt wrong.
What worked better was event-driven choreography:
- Each service emits events like
PaymentCompletedorItemShipped - The Order Service listens to these events and updates the order status
This approach kept services decoupled and avoided turning the Order Service into a monolith. It also respected service ownership — the service that performs the action should own the event that represents it.
2. Should I Store Only the Latest Status or Keep the Full History?
It was tempting to just store the latest status in the orders table. But I soon realized that both are useful:
orders.status: for quick accessorder_status_history: for traceability, debugging, and analytics
Keeping a history allowed us to reconstruct workflows, support compliance requirements, and debug production issues more easily.
3. How Do I Structure Code When Every Status Has Different Logic?
Originally, I was using when(order.status) and branching logic inside service classes. It got messy fast.
The better approach was combining the State Pattern and Strategy Pattern:
- Create a
OrderStatusHandlerinterface - Implement one class per status (or group of statuses)
- Each handler owns its specific logic and knows how to transition
This made the system easier to extend, test, and reason about. Adding a new status no longer meant touching a massive switch statement.
4. What If Many Statuses Share the Same Final Steps?
For many statuses (e.g., 1–10), the last steps were the same: update the order status and publish an event.
Instead of repeating those steps everywhere, I extracted them into a reusable class: OrderUpdaterAndBroadcaster.
Each handler could:
- Run its specific logic
- Call a shared method to update and broadcast
This reduced duplication and made cross-cutting concerns (like logging or metrics) easier to manage.
5. Should Class Names Reflect the Pattern (e.g., *Strategy)?
I wondered if I should name my handlers something like OrderStatusStrategy. But I realized naming by domain responsibility is clearer.
Calling them OrderStatusHandler made the role of each class obvious — it handles a specific order status. The fact that it uses a pattern internally is an implementation detail, not the point.
Final Thoughts
If you're designing microservices around stateful business processes like orders, here’s what helped me:
- Let each service emit the events it owns
- Let the Order Service respond to those events and manage status updates
- Use handlers to isolate logic per status
- Extract shared behaviors like
update + broadcastinto reusable components - Focus on domain-driven names, not design-pattern terms
This approach helped me reduce complexity, improve testability, and keep services modular without sacrificing clarity.
TL;DR
| Problem | What I Do Now |
| ------------------------- | -------------------------------------------------- |
| Centralized status logic? | ❌ Use event-driven updates in Order Service |
| Store status? | ✅ Both latest and full history |
| Unique logic per status? | ✅ One handler per status |
| Shared final steps? | ✅ Compose reusable logic for update + broadcast |
| Naming? | ✅ Use domain terms like Handler, not Strategy |