This repository is an architecture workbench that implements the same order workflow in two different styles:
- Microservices, using explicit HTTP service boundaries and service-owned persistence.
- Virtual actors, using Orleans grains that own state and behavior by durable identity.
The comparison focuses on how each architecture expresses state ownership, concurrency, idempotency, compensation, contention, deployment, observability, and evolution. The repository includes a Blazor Workbench UI, deterministic scenarios, topology-aware health, shared OpenTelemetry instrumentation, and a .NET Aspire development environment.
This repository is a teaching and comparison tool. It is not a production reference architecture or a controlled benchmark.
The workbench helps investigate practical architecture questions:
- Who owns inventory state and protects its invariants?
- Where does workflow coordination live?
- How are concurrent requests prevented from over-reserving stock?
- How are duplicate submissions resolved idempotently?
- How is inventory compensated after payment failure or timeout?
- What happens when many requests target one hot product identity?
- How do deployment and operational responsibilities differ?
- How do the architectures affect maintenance, evolution, and team ownership?
Start with the problem statement, then explore the microservices design, virtual actors design, and detailed trade-offs.
Workbench.Ui
-> Workbench.Gateway
-> Microservices
-> Orders.Api
-> Inventory.Api
-> Payments.Api
-> Virtual actors
-> Ordering.Api
-> Ordering.Silo
-> OrderGrain
-> InventoryItemGrain
-> PaymentAccountGrain
Workbench.Gateway sends each scenario through both implementations and returns normalized results to Workbench.Ui.
Orders.Apicoordinates the order workflow.Inventory.Apiowns inventory state and reservation invariants.Payments.Apiowns payment authorization behavior.- Workflow coordination crosses explicit HTTP and persistence boundaries.
See the Microservices folder overview and Microservices design.
OrderGrain(orderId)owns one logical order workflow.InventoryItemGrain(productId)owns inventory for one product identity.PaymentAccountGrain(customerId)owns payment behavior for one customer or account identity.Ordering.Apiis the HTTP entry point and Orleans client, whileOrdering.Silohosts the Orleans runtime.
See the Virtual actors folder overview and Virtual actors design.
Workbench.Ui provides four focused views.
The Scenario runner executes the selected deterministic workflow through both implementations and presents normalized results side by side. It supports scenario defaults and optional advanced inputs for stock, quantity, concurrency, and identity values.
The result cards show request submissions, unique successful orders, rejected submissions, idempotent duplicate responses, remaining inventory, elapsed time, terminal reasons, and explanatory timelines.
See the UI dashboard guide, Scenario guide, and Workbench folder overview.
The Health page combines live readiness and liveness reports with the shared topology model. It organizes resources into groups, nodes, and dependencies, and presents:
- service availability
- direct and aggregate health
- required and optional dependency health
- group health
- unknown or missing observations
Health describes runtime reachability and readiness. It does not prove business correctness.
See Observability and operations, the Health model, and the Topology model.
The Topology page is a text-based explanation of the intended architecture. It describes the Workbench request path, service ownership, actor identities, the Orleans runtime boundary, and dependency relationships.
It does not display live resource state or availability. Runtime topology-aware health belongs on the Health page.
The Trade-offs page provides a concise in-product comparison of the two architecture styles. Detailed reasoning remains in Trade-offs and Organizational scaling and architecture fit.
The workbench includes seven deterministic scenarios:
- Successful order: inventory is available and payment succeeds.
- Insufficient inventory: the workflow is rejected before payment.
- Payment failure compensation: reserved inventory is released after explicit payment failure.
- Payment timeout after reservation: timeout is treated as failure and compensated.
- Concurrent orders: independent orders compete for limited stock.
- Duplicate request: concurrent duplicate submissions resolve to one logical result.
- Hot product contention: many requests target one product identity.
See the Scenario guide for default inputs, expected counts, reason values, architecture interpretation, and operational validation.
The normalized result contract distinguishes attempts from logical outcomes:
- Total request submissions counts attempts sent to one implementation.
- Unique successful orders counts distinct logical orders that completed.
- Rejected submissions counts logical submissions that were rejected.
- Idempotent duplicate responses counts repeated submissions that returned an established result.
- Remaining inventory is the final observed quantity.
- Elapsed time is local workbench feedback, not benchmark evidence.
This distinction is especially important for concurrent and duplicate-request scenarios.
Install the .NET SDK required by the repository and use a suitable .NET development environment.
Confirm the installed SDKs with:
dotnet --list-sdksThe supported development path uses the Aspire AppHost:
dotnet run --project src/Hosting/Hosting.AppHost/Hosting.AppHost.csprojOpen the Aspire dashboard URL printed by the AppHost, then open the Workbench.Ui endpoint from the resource list.
Aspire is used to:
- compose and start the development topology
- provide service discovery and dependency wiring
- expose project endpoints and resource health
- inspect structured logs
- inspect distributed traces
- inspect metrics
- manage resource lifecycle during development
See the Hosting overview and AppHost overview.
src/
Hosting/ Aspire composition and shared service defaults
Microservices/ Orders, inventory, and payment services
Observability/ Shared health and topology models
VirtualActors/ Orleans API, grains, persistence, and silo
Workbench/ Shared contracts, gateway, and Blazor UI
tests/ Workflow, persistence, acceptance, and regression tests
docs/ Architecture, validation, and operational guidance
Each major source area contains a focused README with implementation-specific guidance.
Run the standard validation sequence from the repository root:
dotnet restore microservices-vs-virtual-actors.slnx
dotnet build microservices-vs-virtual-actors.slnx --configuration Release --no-restore
dotnet test microservices-vs-virtual-actors.slnx --configuration Release --no-buildThe test projects provide complementary coverage:
Microservices.Testscovers the HTTP-service workflow.VirtualActors.Testscovers the Orleans workflow and SQLite grain persistence.Workbench.AcceptanceTestscovers externally visible Gateway behavior.Workbench.ScenarioRegressionTestsprotects normalized scenario-result semantics.
The GitHub Actions workflow in .github/workflows/build.yml performs automated build and test validation.
See Local validation and End-to-end validation for the complete validation workflow.
The repository uses shared service defaults and custom scenario instrumentation:
- W3C trace context and .NET
Activity - OpenTelemetry traces and metrics
- structured logging and
X-Correlation-IDpropagation - scenario activities and bounded metrics
- custom trace collection and sampling
- readiness and liveness endpoints
- topology-aware health evaluation
The observability surfaces are complementary:
- Aspire dashboard: detailed development inspection of resources, endpoints, dependencies, logs, traces, metrics, configuration, and lifecycle.
- Workbench Health page: application-specific interpretation of live health through groups, nodes, dependencies, and availability.
- Workbench Topology page: text-based explanation of the intended architecture.
Do not place credentials, connection strings, request bodies, customer identifiers, order identifiers, product identifiers, or idempotency keys in normal telemetry or metric dimensions.
See Correlation and trace context, Observability and operations, and Service defaults.
Recommended reading path:
- Problem
- Microservices design
- Virtual actors design
- Trade-offs
- Scenario guide
- Local validation
- Observability and operations
- Known limitations
See the documentation index for categorized reading paths and links to every detailed document.
Contributions are welcome. Use GitHub Issues for reproducible bugs and concrete feature requests, and GitHub Discussions for questions, observations, and early architecture ideas.
Read CONTRIBUTING.md before making a change.
Keep these guardrails in mind:
- The repository is not a benchmark.
- Local timings depend on the machine, runtime state, persistence, topology, and workload.
- Aspire is the supported development composition, not a production deployment blueprint.
- The sample does not provide production security, recovery, reconciliation, scaling, telemetry retention, alerting, or incident management.
- Health does not prove business correctness.
- The comparison demonstrates trade-offs rather than declaring a winner.
See Known limitations and Out of scope.
The useful question is not whether microservices or virtual actors are universally better. It is how each style expresses and evolves state ownership, concurrency, coordination, compensation, idempotency, deployment, observability, and operational responsibility.
The best fit depends on workload identity, consistency requirements, team ownership, deployment boundaries, platform maturity, and expected evolution. See Trade-offs for the detailed comparison.