Skip to content

ActualLab.Fusion vs Microsoft Orleans ​

Orleans is Microsoft's virtual actor framework for building distributed systems. Both Orleans and Fusion enable scalable .NET applications, but they represent fundamentally different programming models.

The Core Difference ​

Orleans is an actor-based runtime. State is partitioned into "grains" (virtual actors) with identity. Each grain is single-threaded and location-transparent. You think in terms of entities with isolated state and message passing.

Fusion is a compute-based caching layer. State is derived from compute methods with automatic dependency tracking. You think in terms of functions that produce cached results and invalidate when inputs change.

Orleans Approach ​

csharp
// Grain interface
public interface IUserGrain : IGrainWithStringKey
{
    Task<User> GetUser();
    Task UpdateUser(UpdateRequest request);
}

// Grain implementation — single-threaded, isolated state
public class UserGrain : Grain, IUserGrain
{
    private User _state;

    public override async Task OnActivateAsync(CancellationToken ct)
    {
        _state = await _db.Users.FindAsync(this.GetPrimaryKeyString(), ct);
    }

    public Task<User> GetUser() => Task.FromResult(_state);

    public async Task UpdateUser(UpdateRequest request)
    {
        _state.Name = request.Name;
        await _db.Users.UpdateAsync(_state);
        // How do other parts of the system know this changed?
        // You must publish events or use Orleans Streams
    }
}

// Client usage
var userGrain = client.GetGrain<IUserGrain>(userId);
var user = await userGrain.GetUser();

// To get updates, you need Orleans Streams or observers
await userGrain.SubscribeToUpdates(myObserver);

Fusion Approach ​

csharp
// Compute service — no actor identity, just methods
[DeferredInvalidationMode(DeferredInvalidationMode.Local)]
public class UserService : IComputeService
{
    [ComputeMethod]
    public virtual async Task<User> GetUser(string userId, CancellationToken ct)
        => await _db.Users.FindAsync(userId, ct);

    [ComputeMethod]
    public virtual async Task<UserWithOrders> GetUserWithOrders(string userId, CancellationToken ct)
    {
        var user = await GetUser(userId, ct);           // Dependency tracked
        var orders = await GetUserOrders(userId, ct);   // Dependency tracked
        return new UserWithOrders(user, orders);
    }

    [CommandHandler]
    public virtual async Task UpdateUser(UpdateUserCommand cmd, CancellationToken ct)
    {
        await _db.Users.UpdateAsync(cmd.Id, cmd.Data, ct);
        Invalidation.Defer(() => _ = GetUser(cmd.Id, default));  // All dependents notified automatically
    }
}

// Client — automatic updates, no subscription setup
var computed = await Computed.Capture(() => userService.GetUser(userId));
await foreach (var c in computed.Changes(ct))
    Console.WriteLine($"User updated: {c.Value.Name}");

Where Each Excels ​

ActualLab.Fusion is better at ​

  • Automatic dependency tracking across computations
  • Real-time client synchronization without manual streams
  • Simpler mental model (functions, not actors)
  • Computed values that span multiple data sources
  • UI-focused applications (especially Blazor)
  • No cluster membership or placement complexity

Orleans is better at ​

  • Partitioned state with clear ownership boundaries
  • Long-running stateful workflows (grains stay active)
  • Distributed systems with strong isolation requirements
  • Event sourcing via grain persistence providers
  • Geo-distributed deployments with Orleans clusters

Conceptual Comparison ​

ConceptOrleansFusion
Core abstractionGrain (virtual actor)Computed<T> (cached value)
State modelPer-grain isolated stateDependency graph of computed values
IdentityGrain ID (entity-centric)Method + arguments (query-centric)
ConcurrencySingle-threaded per grainConcurrent (thread-safe caching)
DistributionCluster with grain placementSingle server (+ Operations Framework)
Client updatesOrleans Streams / ObserversAutomatic via invalidation
Scaling unitGrain activationComputed value cache

When to Use Each ​

Choose Orleans when: ​

  • Building entity-centric distributed systems (IoT, gaming, trading)
  • State naturally partitions by entity ID
  • You need millions of independent stateful actors
  • Long-running workflows with durable state
  • Geo-distributed deployment is required
  • Team is comfortable with actor model concepts

Choose Fusion when: ​

  • Building data-driven applications with complex queries
  • UI must stay synchronized with server state
  • Computed values depend on multiple data sources
  • You want automatic cache invalidation
  • Simpler deployment (no cluster management)
  • Blazor or MAUI client applications

The Mental Model Difference ​

Orleans thinks in entities:

User:123 ←── grain holds state for this specific user
User:456 ←── different grain, different state, different server maybe

Fusion thinks in computations:

GetUser("123")     ←── cached result
GetUser("456")     ←── cached result
GetDashboard("123") ←── depends on GetUser("123"), auto-invalidates

Dependency Tracking ​

Orleans grains are isolated — a grain doesn't automatically know when another grain's state changes:

csharp
// Orleans: Manual notification required
public class DashboardGrain : Grain, IDashboardGrain
{
    public async Task<Dashboard> GetDashboard()
    {
        var userGrain = GrainFactory.GetGrain<IUserGrain>(_userId);
        var user = await userGrain.GetUser();
        // If user changes, dashboard doesn't know unless you wire up streams
        return new Dashboard(user);
    }
}

Fusion tracks dependencies automatically:

csharp
// Fusion: Automatic dependency tracking
[ComputeMethod]
public virtual async Task<Dashboard> GetDashboard(string userId, CancellationToken ct)
{
    var user = await GetUser(userId, ct);  // Dependency registered
    return new Dashboard(user);
    // When GetUser invalidates, GetDashboard invalidates too
}

Scaling Characteristics ​

Orleans:

  • Horizontal scaling via grain distribution across silos
  • Each grain activation consumes memory on one server
  • Millions of grains spread across cluster
  • Cluster membership protocol for coordination

Fusion:

  • Vertical scaling with efficient caching
  • Computed values shared across all requests
  • Operations Framework for multi-server invalidation propagation
  • No cluster membership needed for basic scenarios

Using Both Together ​

Orleans and Fusion can complement each other:

csharp
// Orleans grain for stateful entity logic
public class OrderGrain : Grain, IOrderGrain
{
    public async Task<Order> PlaceOrder(OrderRequest request)
    {
        // Orleans handles the stateful workflow
        var order = await ProcessOrder(request);

        // Notify Fusion to invalidate cached views
        await _fusionInvalidator.InvalidateUserOrders(request.UserId);
        return order;
    }
}

// Fusion for cached views and real-time UI
public class OrderViewService : IComputeService
{
    [ComputeMethod]
    public virtual async Task<OrderSummary[]> GetUserOrders(string userId, CancellationToken ct)
    {
        // Read from database, cached by Fusion
        // UI clients automatically updated when invalidated
        return await _db.Orders.Where(o => o.UserId == userId).ToArrayAsync(ct);
    }
}

The Key Insight ​

Orleans is an actor runtime — excellent for systems where state naturally partitions by entity identity and you need millions of independent, isolated stateful objects distributed across a cluster.

Fusion is a caching and synchronization layer — excellent for applications where you need computed values to stay fresh, dependencies to cascade automatically, and clients to see updates in real-time.

If your problem is "I have millions of IoT devices and each needs isolated state," Orleans is the right tool. If your problem is "I have a dashboard that shows aggregated data and it needs to update when any underlying data changes," Fusion solves that elegantly.

Many large systems use both: Orleans for entity-level stateful logic, Fusion for cached views and real-time UI synchronization.