A junior developer
coding real-time UI
next day after this talk

But first, let's talk about
incremental builds

Initial state

Edit "Services.cs"

Artifacts to rebuild

Build "App.exe"

How the hell this is relevant to real-time?

Imagine your app is a composition of functions...

// UI
string RenderAppUI() { 
  // ...
  uiBuilder.Append(RenderUserInfo(userId));
  uiBuilder.Append(RenderCartTotal(cartId));
  // ...
  return uiBuilder.ToString();
} 

string RenderUserInfo(string userId) {
  var user = UserServiceClient.GetUser(userId);
  return $"<div class="user-info">user.Name</div>";
}

string RenderCartTotal(string cartId) {
  var cartTotal = CartServiceClient.GetCartTotal(cartId);
  return $"<div class="cart-total">Total: {cartTotal}$</div>";
}

Why don't we write everything this way?

We'll hit every possible threshold:

  1. Recompute everything → saturate CPU
  2. Chatty client-server RPC → saturate NIC

Valid points. It won't work. Can we go now?

Not yet! Let's draw a call graph for this function:

decimal GetCartTotal(string cartId) 
{
    var cartTotal = 0M;
    var cart = Carts.Get(string userId); // Carts is ICartService
    var specialOffers = SpecialOffers.GetActive(); // etc.
    foreach (var item in cart.Items) {
        cartTotal += item.Price * item.Quantity;
        cartTotal -= specialOffers.Max(
            o => o.GetDiscount(item.Product.Id, item.Price, item.Quantity))
    }
    return cartTotal;
}
(*) We ignore such minor issues as the absence of async code – for now.









THEY CONVERGED!

A new Incremental Builder build you must!

  1. Recompute everything → saturate CPU
    May the Cache be with you!
  2. Chatty client-server RPC → saturate NIC
    And the Client-Side Cache too!

Add caching behavior using a Higher Order Function

Add caching behavior using a Higher Order Function

Add caching behavior using a Higher Order Function

Func<TIn, TOut> ToCaching<TIn, TOut>(Func<TIn, TOut> fn)
  => input => {
    var key = Cache.CreateKey(fn, input);
    if (Cache.TryGet(key, out var output)) return output;
    lock (Cache.Lock(key)) { // Double-check locking
      if (Cache.TryGet(key, out output)) return output;

      output = fn(input);

      Cache[key] = output;
      return output;
    }
  }

var cachingGetCartTotal = ToCaching(Carts.GetCartTotal);
var cartTotal = cachingGetCartTotal.Invoke("cart1");

A tiny issue

To make it work, fn must be a pure function.

⇒ You saw a Vaporware Version™ of ToCaching.

Solutions*

Plan 😈: Purify every function!

Plan 🦄: Add dependency tracking + cascading invalidation

(*) I'm absolutely sure there are other solutions. But there is no more space on this slide, so...

Plan 🦄: Add Dependency Tracking + Cascading Invalidation

Func<TIn, TOut> ToAwesome<TIn, TOut>(Func<TIn, TOut> fn)
  => input => {
    var key = Cache.CreateKey(fn, input);
    if (Cache.TryGet(key, out var computed)) return computed.Use();
    lock (Cache.Lock(key)) { // Double-check locking
      if (Cache.TryGet(key, out computed)) return  computed.Use();

      computed = new Computed(fn, input, key);
      using (Computed.ChangeCurrent(computed))
        computed.Value = fn(input);

      Cache[key] = computed;
      return computed.Use();
    }
  }

Dependency capture

public TOut Use()
{
  if (Computed.IsInvalidating) { // Will explain this later
    Invalidate(); 
    return default;
  }
  // Use = register as a dependency + "unwrap" the Value
  Computed.Current.AddDependency(this);
  return Value;
}

Invalidation

public void Invalidate() 
{
  if (ConsistencyState.Invalidated == Interlocked.Exchange(
      ref _state, ConsistencyState.Invalidated))
    return;
  Cache.Remove(Key);
  foreach (var usedBy in UsedBySet) // Dependants
    usedBy.Invalidate();
  OnInvalidated();
}

public static Disposable Computed.Invalidate()
{
  var oldIsInvalidating = Computed.IsInvalidating;
  Computed.IsInvalidating = true;
  return Disposable.New(() => 
    Computed.IsInvalidating = oldIsInvalidating);
}

Plan 🦄: Example

public class ProductService 
{
  Product Get(string productId) => ProductStore.Fetch(product.Id);
  Product GetFeatured() => ProductStore.List(p => p.IsFeatured);

  void Update(Product product) 
  {
    var oldProduct = ProductStore.Fetch(product.Id);
    ProductStore.Update(product);

    // Invalidation logic
    using (Computed.Invalidate()) {
      Get(product.Id);
      if (oldProduct.IsFeatured || product.IsFeatured)
        GetFeatured();
    }
  }
}

Initial state

Invalidate Products.Get("carrot")

Invalidate Prices.Get("apple")

Call SpecialOffers.GetActive()

⚒️ Incrementally-Build-Everything Decorator

"So, tell me, my little one-eyed one, on what poor, pitiful, defenseless planet has my monstrosity been unleashed?"

Dr. Jumba Jookiba, #1 scientist in my list

Superpowers acquired:

  • Everything is cached
    and (re)computed incrementally
  • Dependencies are captured automatically
  • So we invalidate just what's produced externally!
  • It's a transparent abstraction that doesn't change functions' signatures, code, and even their output!*
(*) Except different timings & tiny periods of inconsistency.

Do we really need delegates?

We don't. Making DI container to provide a proxy implementing such decorators is a 🍰

So it can be absolutely transparent!

Can I use this now?

Not quite:

  • No async/await, thread-safety
  • We need GC-friendly cache and UsedBySet
  • No actual impl. of Computed
  • Etc.

Boring technical problems!
– Elon Musk*

Let me show 100+ more slides first!

(*) Pretty sure he said this at least once





What about eventual consistency?
What about React and Blazor?

We need to go deeper!


What is worse than
eventual consistency?
Permanent inconsistency.


Two eventually consistent systems were left at your doorstep.
Which one you should marry?

                #1

#2      


How this is relevant to real-time, again?

Real-time updates require you to...

  • Know when a result of a function changes
    Invalidate all the things!
  • Recompute new results quickly
    Incrementally build all the things!
  • Send them over the network
    Blazorise and AspNetCorise all the things?
  • Ideally, as a compact diff to the prev. state
    Diff can be computed in O(diffSize) for immutable types (details).

"There are only two hard things in Computer Science: cache invalidation and naming things."
– Phil Karlton


See, we've made a meaningful progress with an easy one!

WHAT ABOUT...

Blazor is:

  • .NET running in your browser on top of WASM!
  • 100% compatible with .NET 5:
    • Expression.Compile(...), Reflection, Task<T>, etc. – it just works!
    • Nearly all of your managed code will run on Blazor too.
  • (Blazor Components, React Components) ≍
    (🦄,🦌) – same, but better!
    Oh, this is so Microsoftey!

Blazor – cons:

  • 1🧵 for now – but JS developers live with this 💩 for 25 years, SO WE CAN!
  • No JIT / AOT yet, MSIL is interpreted.
    .NET 6, don't disappoint us!
    10🧵 x 20x AOT ≃ 200x🚀
  • Even a small project downloads 2…4 MB of .NET .dlls (gzipped!) - and that's after linking with tree shaking.
    Cmon, it's 21 century – size doesn't matter.
    At least online.

React Component - UI markup example

class App extends React.Component {
  render = () => 
    <div class="main">
      <Welcome name="World" />
      <Clock />
    </div>
}

See it on CodePen: https://codepen.io/alexyakunin/pen/xxRzgaK?editors=1111

React Component - (de?)compiled version

class App extends React.Component {
  constructor(...args) {
    super(...args);
    _defineProperty(this, "render", () =>
        // render() generates Virtual DOM
        React.createElement("div", { class: "main" },
          React.createElement(Welcome, { name: "World" }),
          React.createElement(Clock, null))
          );
  }
}

Blazor Component - UI markup example

<div class="main">
    <Welcome Name="World"/>
    <Clock />
</div>

See it on BlazorREPL: https://blazorrepl.com/repl/wluHuIOv061KS9Vt31

Blazor Component - decompiled version

  public class Index : ComponentBase
  {
    protected override void BuildRenderTree(RenderTreeBuilder __builder)
    {
      // BuildRenderTree(...) generates Virtual DOM too,
      // just relying on builder
      __builder.OpenElement(0, "div"); // Notice: OpenElement
      __builder.AddAttribute(1, "class", "main");
      __builder.OpenComponent<Welcome>(2); // Notice: OpenComponent
      __builder.AddAttribute(3, "Name", "World");
      __builder.CloseComponent();
      __builder.AddMarkupContent(4, "\r\n    ");
      __builder.OpenComponent<Clock>(5);
      __builder.CloseComponent();
      __builder.CloseElement();
    }
  }

React and Blazor are "make"-like incremental builders for your UI

Just specialized ones – designed to incrementally update DOM
(actually, any UI control tree) after any component's render() that
actually just defines the new desirable UI state.

w:300px


  

Remember Caching Decorator with Dependency Tracking?

Func<TIn, TOut> ToAwesome<TIn, TOut>(Func<TIn, TOut> fn)
  => input => {
    var key = Cache.CreateKey(fn, input);
    if (Cache.TryGet(key, out var computed)) return computed.Use();
    lock (Cache.Lock(key)) { // Double-check locking
      if (Cache.TryGet(key, out computed)) return  computed.Use();

      computed = new Computed(fn, input, key);
      using (Computed.ChangeCurrent(computed))
        computed.Value = fn(input);

      Cache[key] = computed;
      return computed.Value;
    }
  }

Fusion's version of this decorator is:

  • GC-friendly
  • Async, thread-safe
  • Uses AOP-style decoration
  • Relies on immutable* IComputed<T>
  • Distributed
  • Supports multi-host invalidation / scales horizontally
  • And much more!
(*) Almost immutable

Fusion's IComputed<T>

A bit simplified version:

interface IComputed<T> {
  ConsistencyState ConsistencyState { get; } 
  T Value { get; }
  Exception Error { get; }
  
  event Action Invalidated; // Event, triggered just once on invalidation
  Task WhenInvalidated(); // Alternative way to await for invalidation
  void Invalidate();
  Task<IComputed<T>> Update(); // Notice it returns a new instance!
}

TodoApp: ITodoService API

Note: CancellationToken argument is removed here & further to keep things simple.

public interface ITodoService
{
    // Commands
    [CommandHandler]
    Task<Todo> AddOrUpdate(AddOrUpdateTodoCommand command);
    [CommandHandler]
    Task Remove(RemoveTodoCommand command);

    // Queries
    [ComputeMethod]
    Task<Todo?> TryGet(Session session, string id);
    [ComputeMethod]
    Task<Todo[]> List(Session session, PageRef<string> pageRef);
    [ComputeMethod]
    Task<TodoSummary> GetSummary(Session session);
}

TodoApp: API models / DTOs

public record Todo(string Id, string Title, bool IsDone = false)
{
    // Default .ctor() is needed for JSON deserialization
    public Todo() : this("", "") { } 
}

public record TodoSummary(int Count, int DoneCount)
{
    public TodoSummary() : this(0, 0) { }
}

// Commands

public record AddOrUpdateTodoCommand(Session Session, Todo Item) : ISessionCommand<Todo>
{
    public AddOrUpdateTodoCommand() : this(Session.Null, default(Todo)!) { }
}

public record RemoveTodoCommand(Session Session, string Id) : ISessionCommand<Unit>
{
    public RemoveTodoCommand() : this(Session.Null, "") { }
}

SimpleTodoService: query methods

public class SimpleTodoService : ITodoService
{
    private ImmutableList<Todo> _store = ImmutableList<Todo>.Empty;

    [ComputeMethod]
    public virtual async Task<Todo?> TryGet(Session session, string id)
        => _store.SingleOrDefault(i => i.Id == id);

    [ComputeMethod]
    public virtual async Task<TodoSummary> GetSummary(Session session)
    {
        var count = _store.Count();
        var doneCount = _store.Count(i => i.IsDone);
        return new TodoSummary(count, doneCount);
    }
}

SimpleTodoService: command handler

[CommandHandler]
public virtual async Task<Todo> AddOrUpdate(AddOrUpdateTodoCommand command)
{
    // I'll explain further why this line is needed
    if (Computed.IsInvalidating()) return null!;

    // Apply changes
    var (session, todo) = command;
    if (string.IsNullOrEmpty(todo.Id))
        todo = todo with { Id = Ulid.NewUlid().ToString() };
    _store = _store.RemoveAll(i => i.Id == todo.Id).Add(todo);

    // Invalidate what's impacted
    using var _ = Computed.Invalidate();
    TryGet(session, todo.Id).Ignore();
    GetSummary(session).Ignore();
    return todo;
}

SimpleTodoService: registration

var services = new ServiceCollection();
var fusion = services.AddFusion();

// ~ Like service.AddSingleton<ITodoService, SimpleTodoService>()
fusion.AddComputeService<ITodoService, SimpleTodoService>();

// ...
var serviceProvider = services.BuildServiceProvider()

 Click to play/pause  TodoApp, v1: SimpleTodoService running on the client 

TodoSummaryBadge.razor

Auto-updating "done / total". There is no other code related to this!

@inherits ComputedStateComponent<TodoSummary>
@inject ITodoService Todos
@inject Session Session

@{
    var summary = State.ValueOrDefault ?? new();
}

@if (summary != null) {
    <Badge Color="Color.Success"><b>@summary.DoneCount</b> done</Badge>
    <Badge Color="Color.Primary"><b>@summary.Count</b> total</Badge>
}

@code {
    protected override Task<TodoSummary> ComputeState()
        => Todos.GetSummary(Session);
}

TodoPage.razor (p. 1)

@page "/todo"
@inherits ComputedStateComponent<Todo[]>
@inject ITodoService Todos
@inject Session Session
@inject CommandRunner CommandRunner
@inject ILogger<TodoPage> Log

@{
    Log.LogInformation(
        "Rendering, State.Computed.Version = {Version}", 
        State.Computed.Version);

    var error = State.Error;
    var todos = State.ValueOrDefault ?? Array.Empty<Todo>();
}

TodoPage.razor (p. 2)

<h1>Todo List</h1>
<StateOfStateBadge State="@State" />
<Text>
    Updated: <MomentsAgoBadge Value="LastStateUpdateTime" />
</Text>

<AuthorizeView>
    <NotAuthorized><SignInDropdown Why="to use this page" /></NotAuthorized>
    <Authorized>
        <WhenException Exception="error" />
        <WhenCommandError Exception="CommandRunner.Error" />
        
        <TodoSummaryBadge/>
        @foreach (var todo in todos) {
            <TodoItemView @key="@todo.Id" Value="@todo" CommandRunner="@CommandRunner"/>
        }
        @if (HasMore) {
            <Button Clicked="_ => LoadMore()">Load @PageSize more</Button>
        }
        <Form @onsubmit="_ => Create()">
            <Button Type="@ButtonType.Submit"><Icon Name="@FontAwesomeIcons.PlusSquare"/></Button>
            <input @bind="NewTodoTitle" @bind:event="onchange" />
        </Form>
    </Authorized>
</AuthorizeView>

TodoPage.razor (p. 3)

protected override async Task<Todo[]> ComputeState()
{
    var items = await Todos.List(Session, PageSize + 1);
    HasMore = items.Length > PageSize;
    if (HasMore)
        items = items[0..PageSize];
    LastStateUpdateTime = DateTime.UtcNow;
    return items;
}

private void LoadMore()
{
    PageSize *= 2;
    State.Recompute();
}

private void Create()
{
    var todo = new Todo("", NewTodoTitle);
    NewTodoTitle = "";
    CommandRunner.Call(new AddOrUpdateTodoCommand(Session, todo));
}

CommandRunner.Call

Invokes CommandR-based command execution pipeline for the specified command
and exposes an error (if any) via its property rather than throwing it.

public async Task<TResult> Call<TResult>(ICommand<TResult> command)
{
    Error = null;
    try {
        return await Commander.Call(command);
    }
    catch (Exception e) {
        Error = e;
        return default!;
    }
    finally {
        if (Component is StatefulComponentBase { UntypedState: IComputedState state })
            // This call just "speeds up" the update that follows user action 
            // by decreasing IComputedState's update delay to zero.
            state.ApplyUserCausedUpdate(); 
    }
}

MomentsAgoBadge.razor

Another auto-updating component.

@inherits ComputedStateComponent<string>
@inject IFusionTime _fusionTime

@State.Value

@code {
    [Parameter] 
    public DateTime Value { get; set; }

    protected override Task<string> ComputeState()
        => _fusionTime.GetMomentsAgo(Value) ;
}

IFusionTime

Fusion-style time service invalidating its outputs after certain time period.

public interface IFusionTime
{
    [ComputeMethod]
    Task<DateTime> GetUtcNow();
    [ComputeMethod]
    Task<DateTime> GetUtcNow(TimeSpan updatePeriod);
    [ComputeMethod]
    Task<string> GetMomentsAgo(DateTime time);
}

Can one Fusion service call another Fusion service?

MUST, NOT CAN!
Pros: No need to invalidate anything!
Cons: None.

Built-in Fusion Services:

  • IFusionTime
  • IKeyValueStore
    • DbKeyValueStore (EF-backed)
    • InMemoryKeyValueStore
  • ISandboxedKeyValueStore
  • IAuthService and IServerSideAuthService (EF-backed, in-memory)

IKeyValueStore

public interface IKeyValueStore
{
    [CommandHandler] Task Set(SetCommand command);
    [CommandHandler] Task SetMany(SetManyCommand command);
    [CommandHandler] Task Remove(RemoveCommand command);
    [CommandHandler] Task RemoveMany(RemoveManyCommand command);

    [ComputeMethod] Task<string?> TryGet(string key);
    [ComputeMethod] Task<int> Count(string prefix);
    [ComputeMethod] Task<string[]> ListKeySuffixes(
        string prefix,
        PageRef<string> pageRef,
        SortDirection sortDirection = SortDirection.Ascending);
}

ISandboxedKeyValueStore

Same as IKeyValueStore, just requiring Session everywhere to
constraint keys to ~ "user folder".

public interface ISandboxedKeyValueStore
{
    [CommandHandler] Task Set(SandboxedSetCommand command);
    [CommandHandler] Task SetMany(SandboxedSetManyCommand command);
    [CommandHandler] Task Remove(SandboxedRemoveCommand command);
    [CommandHandler] Task RemoveMany(SandboxedRemoveManyCommand command);

    [ComputeMethod] Task<string?> TryGet(Session session, string key);
    [ComputeMethod] Task<int> Count(Session session, string prefix);
    [ComputeMethod] Task<string[]> ListKeySuffixes(
        Session session,
        string prefix,
        PageRef<string> pageRef,
        SortDirection sortDirection = SortDirection.Ascending);
}

TodoService: command handler method

public class TodoService : ITodoService
{
    private readonly ISandboxedKeyValueStore _store;
    private readonly IAuthService _authService;

    [CommandHandler]
    public virtual async Task Remove(RemoveTodoCommand command)
    {
        if (Computed.IsInvalidating()) return;

        var (session, id) = command;
        var user = await _authService.GetUser(session);
        user.MustBeAuthenticated();

        var key = GetTodoKey(user, id);
        var doneKey = GetDoneKey(user, id);
        await _store.Remove(session, key);
        await _store.Remove(session, doneKey);
    }

    private string GetTodoKey(User user, string id) => $"{GetTodoKeyPrefix(user)}/{id}";
    private string GetDoneKey(User user, string id) => $"{GetDoneKeyPrefix(user)}/{id}";
    private string GetTodoKeyPrefix(User user) => $"@user/{user.Id}/todo/items";
    private string GetDoneKeyPrefix(User user) => $"@user/{user.Id}/todo/done";

    // ...

TodoService: query method

[ComputeMethod]
public virtual async Task<TodoSummary> GetSummary(Session session)
{
    var user = await _authService.GetUser(session);
    user.MustBeAuthenticated();

    var count = await _store.Count(session, GetTodoKeyPrefix(user));
    var doneCount = await _store.Count(session, GetDoneKeyPrefix(user));
    return new TodoSummary(count, doneCount);
}

 Click to play/pause  TodoApp, v2: TodoService + IKeyValueStore running on the client 

Can we replicate IComputed on a remote host?

public class ComputedReplica<T> : IComputed<T> 
{
    ConsistencyState ConsistencyState { get; }
    T Value { get; }
    Exception Error { get; }
    event Action Invalidated;
    
    public ComputedReplica<T>(IComputed<T> source) 
    {
        (Value, Error) = (source.Value, source.Error);
        ConsistencyState = source.ConsistencyState;
        source.Invalidated += () => Invalidate();
    }

    // ...
}

Do the same, but deliver the invalidation event via RPC!

Your Web API call

Fusion Web API call

Fusion Replica Service

Replica Service: controller

[Route("api/[controller]/[action]")]
[ApiController, JsonifyErrors]
public class TodoController : ControllerBase, ITodoService
{
    private readonly ITodoService _todos;
    
    [HttpPost]
    public Task<Todo> AddOrUpdate([FromBody] AddOrUpdateTodoCommand command)
        => _todos.AddOrUpdate(command);

    [HttpGet, Publish]
    public Task<Todo?> TryGet(Session session, string id)
        => _todos.TryGet(session, id);

    // ...
}

Replica Service: client definition

It's not the actual type you consume - the actual runtime-generated replica service (AKA Fusion client) implements the same ITodoService. ITodoClient type just maps its endpoints to the web API relying on RestEase under the hood.

[BasePath("todo")]
public interface ITodoClient 
{
    [Post(nameof(AddOrUpdate))]
    Task<Todo> AddOrUpdate([Body] AddOrUpdateTodoCommand command);
    [Post(nameof(Remove))]
    Task Remove([Body] RemoveTodoCommand command);

    [Get(nameof(TryGet))]
    Task<Todo?> TryGet(Session session, string id);
    [Get(nameof(List))]
    Task<Todo[]> List(Session session, PageRef<string> pageRef);
    [Get(nameof(GetSummary))]
    Task<TodoSummary> GetSummary(Session session);
}

Replica Service: registration

var services = new ServiceCollection();
var fusion = services.AddFusion();

// Adding & configuring services required for Replica Services to operate
var baseUri = new Uri(builder.HostEnvironment.BaseAddress);
var apiBaseUri = new Uri($"{baseUri}api/");
var fusionClient = fusion.AddRestEaseClient((_, o) => {
    o.BaseUri = baseUri;
    o.MessageLogLevel = LogLevel.Information;
});
fusionClient.ConfigureHttpClientFactory((c, name, o) => {
    var isFusionClient = (name ?? "").StartsWith("Stl.Fusion");
    var clientBaseUri = isFusionClient ? baseUri : apiBaseUri;
    o.HttpClientActions.Add(client => client.BaseAddress = clientBaseUri);
});

// Adding actual replica service
fusionClient.AddReplicaService<ITodoService, ITodoClient>();
 Got Fusion
 Get API-first design as a bonus! 

 Click to play/pause  TodoApp, v3: Server-side TodoService, Blazor Server + WASM, debugging, console client 

 Click to play/pause  TodoApp, v4: Multiple hosts 

HelloCart: DbProductService.Edit (command)

[CommandHandler]
public virtual async Task Edit(EditCommand<Product> command)
{
    var (productId, product) = command;
    if (Computed.IsInvalidating()) {
        // This block is "replayed" on every host!
        TryGet(productId).Ignore();
        return;
    }

    await using var dbContext = await CreateCommandDbContext();
    var dbProduct = await dbContext.Products.FindAsync((object) productId);
    if (product == null) {
        if (dbProduct != null)
            dbContext.Remove(dbProduct);
    }
    else {
        if (dbProduct != null)
            dbProduct.Price = product.Price;
        else
            dbContext.Add(new DbProduct { Id = productId, Price = product.Price });
    }
    await dbContext.SaveChangesAsync();
}

HelloCart: DbProductService.TryGet (query)

[ComputeMethod]
public virtual async Task<Product?> TryGet(string id)
{
    await using var dbContext = CreateDbContext();
    var dbProduct = await dbContext.Products.FindAsync((object) id);
    if (dbProduct == null)
        return null;
    return new Product() { Id = dbProduct.Id, Price = dbProduct.Price };
}

(Local)ComposerService

Client-side or server-side? You decide - the code & behavior is +/- identical!

See the power of distributed incremental build in action!
Live demo: https://fusion-samples.servicetitan.com/composition
Source code: ComposerService, LocalComposerService.

public virtual async Task<ComposedValue> GetComposedValue(Session session, string parameter)
{
    var chatTail = await ChatService.GetChatTail(1);
    var uptime = await TimeService.GetUptime(TimeSpan.FromSeconds(10));
    var sum = (double?) null;
    if (double.TryParse(parameter, out var value))
        sum = await SumService.GetSum(new [] { value }, true);
    var lastChatMessage = chatTail.Messages.SingleOrDefault()?.Text ?? "(no messages)";
    var user = await AuthService.GetUser(session);
    var activeUserCount = await ChatService.GetActiveUserCount();
    return new ComposedValue($"{parameter} - server", uptime, sum, lastChatMessage, user, activeUserCount);
}

Caching performance

Most important part of the performance test:

public virtual async Task<User?> TryGet(long userId)
{
  await using var dbContext = DbContextFactory.CreateDbContext();
  var user = await dbContext.Users.FindAsync(new[] {(object) userId});
  return user;
}

// Many readers, 1 (similar) mutator
async Task<long> Reader(string name, int iterationCount)
{
    var rnd = new Random();
    var count = 0L;
    for (; iterationCount > 0; iterationCount--) {
        var userId = (long) rnd.Next(UserCount);
        var user = await users.TryGet(userId);
        if (user!.Id == userId)
            count++;
        extraAction.Invoke(user!); // Optionally serializes the user
    }
    return count;
}

Caching performance

Sqlite EF provider: 16,070x

With Stl.Fusion:
  Standard test:
    Speed:      35708.280 K Ops/sec
  Standard test + serialization:
    Speed:      12481.940 K Ops/sec
Without Stl.Fusion:
  Standard test:
    Speed:      2.222 K Ops/sec
  Standard test + serialization:
    Speed:      2.179 K Ops/sec

In-memory EF provider: 1,140x

With Stl.Fusion:
  Standard test:
    Speed:      30338.256 K Ops/sec
  Standard test + serialization:
    Speed:      11789.282 K Ops/sec
Without Stl.Fusion:
  Standard test:
    Speed:      26.553 K Ops/sec
  Standard test + serialization:
    Speed:      26.143 K Ops/sec

And that's just plain caching, i.e. no benefits from "incrementally-build-everything"!

Caching Sample & more data points on caching

The same service converted to Replica Service:

  • 20,000 → 130,000 RPS = 6.5x throughput
    With server-side changes only, i.e. regular Web API client.
  • 20,000 → 20,000,000 RPS = 1000x throughput!
    If you switch to Fusion client (so-called "Replica Service")
RestEase Client -> ASP.NET Core -> EF Core Service:
  Reads: 20.46K operations/s

RestEase Client -> ASP.NET Core -> Fusion Proxy -> EF Core Service:
  Reads: 127.96K operations/s

Fusion's Replica Client:
  Reads: 20.29M operations/s

How 10x speed boost looks like?

Limits are meant to be broken.

Fusion vs Redis, memcached, ...

💯 Almost always consistent
🚀 Local = 1000x faster:
     ❌🚝 No network calls
     ❌💾 No serialization/deserialization
     ❌👪 Reuse vs deep copy on use
🧱 Incrementally-Build-Everything™
🙀 Supports "swapping" to ext. caches.

Fusion vs SignalR

📪 Automatic & transparent pub/sub
🦄 "X is invalidated" vs all 🦌 SignalR events
💯 Guaranteed eventual consistency

And:
 λ  Substance (build) vs form (update)
     (think React vs jQuery)
🤖 Web API-first design
🐳 Sub-1ms responses, CQRS, multi-host,
     custom update delays → high-load ready
🧬 Single abstraction on the client, server, ...

Fusion vs gRPC

... but Dory is
DAMN FAST!

On a serious note: MessagePack-based protocol is one of planned improvements.
No plans to support GRPC in the observable future due to lack of support for generic type serialization there.

Flux

Redux

MVC

MVC + KO/MobX

Fusion

Fusion

Fusion

Fusion vs Flux, Redux, MVC, MobX, ...

🚀 Real-time & sub-1ms responses
0️⃣-coupled UI components & 👙-frontends
🧬 Single abstraction everywhere
💯 Guaranteed eventual consistency
💃 CQRS, multi-host mode, bells, whistles.

Forget about:
👾 Reducers, stores
🤬 Observable models. Of observable models.
     Of other observable models.
✉ Server-side push notifications

Fusion and Blazor

Blazor WASM 👰 or Blazor Server 💃?

👰 provides the best experience
but drags 2MB+ 🧳 everywhere...
and you need a 🦸 to debug 👰

💃 rules on low-end 📱,
easy to debug, but provides inferior
experience otherwise.

👯 Fusion to the rescue!

Discovery consists of looking at the same thing as everyone else and thinking something different.
― Albert Szent-Gyorgyi

The price you pay for Fusion

Money: thanks to ServiceTitan, Fusion is free and open-source (MIT license)

CPU: free your CPUs! The torture of making them to run recurring computations again and again must be stopped!

RAM is where the price is really paid. So keep this under control, remember about GC pauses. Check out how Fusion scales horizontally and allows you to "swap" the data in RAM to external caches.

Learning curve: relatively shallow in the beginning, but getting steeper once you start to dig deeper. ~ Like for TPL with its ExecutionContext, ValueTask<T>, etc.

Other risks: "We wants it, we needs it. Must have the precious!"

If you need a real-time UI or a robust caching, Fusion is probably the lesser of many evils you'll have to fight otherwise.*



(*) Fusion creator's opinion, totally unbiased.

Does real-time matter?

On a serious note: Real-Time is #1 Feature Your Next Web App Needs

It is not the strongest of the species that survives, nor the most intelligent; it is the one most adaptable to change.

— Charles Darwin














Thank you!