All project updates are published on its Discord Server; it's also the best place for Q/A.
Fusion is a .NET 5 / .NET Core 3.1 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:
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
(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
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.
Play with these samples hosted in a small 1-core K8s cluster right now!
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
(the DAL used in this test) instead of the actual type.
The performance difference looks shocking at first:
The speedup is:
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!
"The Ungreen Web: Why our web apps are terribly inefficient?" lits more light on why this matters.
Fusion provides three key abstractions:
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.
IsConsistent() == falsestate
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:
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.
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.
Check out "Scaling Fusion Services" part of the Tutorial to see a much more robust description on how Fusion scales.
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 any
GetCounterAsyncresult 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.Invalidatecall finds a Computed Value "backing" the most recent
GetCounterAsynccall 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 because
GetCounterAsyncdoesn'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
Again, lime-colored parts show additions to a similar Blazor Component without real-time updates:
ComponentBase(base class for any Blazor component), which adds
Stateproperty and abstract
ComputeStateAsyncmethod allowing to (re)compute the
State.Valueonce any of its dependencies changes.
LiveComponent<T>.Stateproperty 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.
State.Valuecomputation logic in any
Blue-colored parts show how
State is used:
State.LastValueis the most recent non-error value produced by the computation. It's a "safe pair" to
State.Value(true most recent computation result), which throws an error if
State.Error != null.
State.Errorcontains an exception thrown by
ComputeStateAsyncwhen it fails, otherwise
And if you're curious how "X seconds ago" gets updated,
which looks as follows:
In other words,
ComputeStateAsync becomes dependent on "moments ago"
value, and this value self-invalidates ~ at the right moment triggering
"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 -
CounterService, it's almost the same as a similar service that
doesn't support any kind of real-time behavior at all:
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).