Skip to content

Repository files navigation

Microservices vs Virtual Actors

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.

What you can explore

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.

Architecture at a glance

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.

Microservices

  • Orders.Api coordinates the order workflow.
  • Inventory.Api owns inventory state and reservation invariants.
  • Payments.Api owns payment authorization behavior.
  • Workflow coordination crosses explicit HTTP and persistence boundaries.

See the Microservices folder overview and Microservices design.

Virtual actors

  • 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.Api is the HTTP entry point and Orleans client, while Ordering.Silo hosts the Orleans runtime.

See the Virtual actors folder overview and Virtual actors design.

Workbench experience

Workbench.Ui provides four focused views.

Scenario runner

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.

Health

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.

Topology

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.

Trade-offs

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.

Scenarios

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.

Result semantics

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.

Run locally

Prerequisites

Install the .NET SDK required by the repository and use a suitable .NET development environment.

Confirm the installed SDKs with:

dotnet --list-sdks

Start with Aspire

The supported development path uses the Aspire AppHost:

dotnet run --project src/Hosting/Hosting.AppHost/Hosting.AppHost.csproj

Open 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.

Repository map

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.

Testing and validation

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-build

The test projects provide complementary coverage:

  • Microservices.Tests covers the HTTP-service workflow.
  • VirtualActors.Tests covers the Orleans workflow and SQLite grain persistence.
  • Workbench.AcceptanceTests covers externally visible Gateway behavior.
  • Workbench.ScenarioRegressionTests protects 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.

Observability in development

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-ID propagation
  • 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.

Documentation

Recommended reading path:

  1. Problem
  2. Microservices design
  3. Virtual actors design
  4. Trade-offs
  5. Scenario guide
  6. Local validation
  7. Observability and operations
  8. Known limitations

See the documentation index for categorized reading paths and links to every detailed document.

Contributing

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.

Scope and interpretation

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.

Key takeaway

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.

About

Hands-on comparison of microservice-style and virtual actor-style designs for the same distributed workflow, including development, testing, deployment, scaling, and operational trade-offs.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages