All project updates are published on its Discord Server; it's also the best place for Q/A.
![]()
![]()
Fusion is a .NET Core library providing a new change tracking abstraction built in assumption that every piece of data you have is a part of the observable state / model, and since there is no way to fit such a huge state in RAM, Fusion:
- Spawns the observed part of this state on-demand
- Holds the dependency graph of any observed state in memory to make sure every dependency of this state triggers cascading invalidation once it gets changed.
- And finally, it does all of this automatically and transparently for you, so Fusion-based code is almost identical to the code you'd write without it.
This is quite similar to what any MMORPG game engine does: even though the complete game state is huge, it's still possible to run the game in real time for 1M+ players, because every player observes a tiny fraction of a complete game state, and thus all you need is to ensure this part of the state fits in RAM + you have enough computing power to process state changes for every player.
Under the hood, Fusion turns any response of your internal and public API
into ~ (Result<T> Response, Task Invalidated)
pair, where the second part tells
when this pair has to be recomputed. But you rarely need to deal with this –
Fusion-based services return regular result types, and these pairs
(actually, IComputed<T>
instances) are created, consumed, and composed into
complex dependency graphs transparently for you.
This is Fusion+Blazor Sample, delivering real-time updates to 3 browser windows:
The sample supports both (!) Server-Side Blazor and Blazor WebAssembly hosting modes – you can switch the mode on its "Home" page.
Check out "Why real-time UI is inevitable future for web apps?" to learn why "Refresh" action is getting obsolete.
A small benchmark in Fusion test suite
compares "raw" Entity Framework Core -
based Data Access Layer (DAL) against its version relying on Fusion.
Both tests run almost identical code - in fact, the only difference is that Fusion
version of this test uses Fusion-provided proxy wrapping the
UserService
(the DAL used in this test) instead of the actual type.
The performance difference looks shocking at first:
The speedup is:
- ~31,500x for Sqlite EF Core Provider
- ~1,000x for In-memory EF Core Provider
Such a speedup is possible because Fusion ensures that every output Fusion service produces or consumes – even the intermediate one – is computed just once and reused without recomputation while it stays consistent with the ground truth.
In other words, Fusion acts as a transparent cache + incremental build system for any computation your code runs, and as you can see, it's fast enough to be able to speed up even a code relying on in-memory EF Core provider by 1000x!
Note that:
- Similarly to real-time updates, you get this speedup for free in terms of extra code.
- You also get almost always consistent cache. It's still an eventually consistent cache, of course, but the inconsistency periods for cache entries are so short that normally don't need to worry about the inconsistencies.
- The speedup you're expected to see in production may differ from these numbers a lot. Even though the results presented here are absolutely real, they are produced on a synthetic test.
Check out "The Ungreen Web: Why our web apps are terribly inefficient?" to learn more about the benefits of this.
Fusion provides three key abstractions:
- Compute Services are services exposing methods "backed" by Fusion's version of "computed observables". Compute Services are responsible for "spawning" parts of the state on-demand.
- Replica Services - remote proxies of Compute Services. They allow remote clients to consume ("observe") the parts of server-side state. They typically "substitute" similar Compute Services on the client side.
- And finally,
IComputed<T>
– an observable Computed Value that's in some ways similar to the one you can find in Knockout, MobX, or Vue.js, but very different, if you look at its fundamental properties.
IComputed<T>
is:
- Thread-safe
- Asynchronous – any Computed Value is computed asynchronously; Fusion APIs dependent on this feature are also asynchronous.
- Almost immutable – once created, the only change that may happen to it is transition
to
IsConsistent() == false
state - GC-friendly – if you know about
Pure Computed Observables
from Knockout, you understand the problem.
IComputed<T>
solves it even better – dependent-dependency relationships are explicit there, and the reference pointing from dependency to dependent is weak, so any dependent Computed Value is available for GC unless it's referenced by something else (i.e. used).
All above make it possible to use IComputed<T>
on the server side –
you don't have to synchronize access to it, you can use it everywhere, including
async functions, and you don't need to worry about GC.
But there is more – any Computed Value:
- Is computed just once – when you request the same Computed Value at the same time from multiple (async) threads and it's not cached yet, just one of these threads will actually run the computation. Every other async thread will await till its completion and return the newly cached instance.
- Updated on demand – once you have an
IComputed<T>
, you can ask for its consistent version at any time. If the current version is consistent, you'll get the same object, otherwise you'll get a newly computed consistent version, and every other version of it is guaranteed to be marked inconsistent. At glance, it doesn't look like a useful property, but together with immutability and "computed just once" model, it de-couples invalidations (change notifications) from updates, so ultimately, you are free to decide for how long to delay the update once you know certain state is inconsistent. - Supports remote replicas – any Computed Value instance can be published,
which allows any other code that knows the publication endpoint and publication ID
to create a replica of this
IComputed<T>
instance in their own process. Replica Services mentioned above rely on this feature.
Real-time typically implies you need one or another flavor of event-driven architecture (CQRS, event sourcing, actors, etc.). And all these options are more complex than a simple and familiar request-response model, which Fusion allows you to use.
Besides that, Fusion solves a complex problem of identifying and tracking dependencies automatically for any method that uses Fusion-based services (+ its own logic) to produce the output, and implementing this without Fusion is not only hard, but quite error prone problem.
Of course you still can use events, event sourcing, CQRS, etc. - you'll just need maybe 100× fewer event types.
Check out how Fusion differs from SignalR – this post takes a real app example (Slack-like chat) and describes what has to be done in both these cases to implement it.
Yes. MMORPG example provided earlier hints on how Fusion-based apps scale. But contrary to games, web apps rarely have a strong upper limit on update delay – at least for a majority of content they present. This means you can always increase these delays to throttle down the rate of outgoing invalidation and update messages, and vice versa. In other words, Fusion-based web apps should scale much better than MMORPG.
Compute Services is where a majority of Fusion-based code is supposed to live. CounterService from HelloBlazorServer sample is a good example of such a service:
Lime-colored parts show additions to a similar singleton service you'd probably have in case when real-time updates aren't needed:
[ComputeMethod]
indicates that anyGetCounterAsync
result should be "backed" by Computed Value. This attribute works only when you register a service as Compute Service in IoC container and the method it is applied to is virtual.Computed.Invalidate
call finds a Computed Value "backing" the most recentGetCounterAsync
call with the same arguments (no arguments in this case) and invalidates it - unless it doesn't exist or was invalidated earlier. We have to manually invalidate this value becauseGetCounterAsync
doesn't call any other Compute Services, and thus its result doesn't have any dependencies which otherwise would auto-invalidate it.
Counter.razor is a Blazor Component that uses
CounterService
:
Again, lime-colored parts show additions to a similar Blazor Component without real-time updates:
- It inherits from LiveComponentBase - a small wrapper over
ComponentBase
(base class for any Blazor component), which addsState
property and abstractComputeStateAsync
method allowing to (re)compute theState.Value
once any of its dependencies changes. LiveComponent<T>.State
property is a Live State - an object exposing the most current Computed Value produced by a computation (Func<...>
) and making sure it gets recomputed with a controllable delay after any of its dependencies change.- As you might guess,
ComputeStateAsync
definesState.Value
computation logic in anyLiveComponentBase<T>
descendant.
Blue-colored parts show how State
is used:
State.LastValue
is the most recent non-error value produced by the computation. It's a "safe pair" toState.Value
(true most recent computation result), which throws an error ifState.Error != null
.State.Error
contains an exception thrown byComputeStateAsync
when it fails, otherwisenull
.
That's almost literally (minus IoC registration) all you need to have this:
And if you're curious how "X seconds ago" gets updated,
notice that ComputeStateAsync
invokes TimeService.GetMomentsAgoAsync
,
which looks as follows:
In other words, ComputeStateAsync
becomes dependent on "moments ago"
value, and this value self-invalidates ~ at the right moment triggering
cascading ComputeStateAsync
invalidation.
"Simple Chat" is a bit more complex example showing another interesting aspect of this approach:
Since any event describes a change, Fusion's only "invalidated" event ("the output of f(...) changed") allows you to implement a reaction to nearly any change without a need for a special event!
"Simple Chat" features a chat bot that listens to new chat messages and responds to them:
ChatService
source code doesn't have any special logic to support chat bots -
similarly to CounterService
, it's almost the same as a similar service that
doesn't support any kind of real-time behavior at all:
But since ChatService
is a Compute Service, you can implement a "listener"
reacting to changes in GetMessagesAsync
output relying on e.g. Live State -
and
that's exactly what ChatBotService
does.
- Check out Tutorial, Samples, or go to Documentation Home
- Join our Discord Server or Gitter to ask questions and track project updates.
- The Ungreen Web: Why our web apps are terribly inefficient?
- Why real-time UI is inevitable future for web apps?
- How similar is Fusion to SignalR?
- How similar is Fusion to Knockout / MobX?
- Fusion In Simple Terms
P.S. If you've already spent some time learning about Fusion, please help us to make it better by completing Fusion Feedback Form (1…3 min).