Fusion: Current State and Upcoming Features

Fusion — a small library created as an attempt to challenge the existing architecture of high-load and real-time apps — is going to be 4 months old soon. Here is what happened over these months:
- We’ve got 300+ stars on GitHub
- Total # of downloads of “Stl.Fusion” NuGet package approaches 14K.
- Fusion Samples — initially a single Blazor WASM app project — got hybrid mode (WASM + Server-Side Blazor), authentication, and session tracking. But more importantly, a set of simpler feature samples was added — console “HelloWorld”, SSB-only “HelloWorldBlazor”, and “Caching” showcasing how Fusion can speed up a regular API endpoint.
- Fusion Tutorial was completed — it covers all essential features now, and we’ll certainly add more there in future (authentication is likely the next feature to be covered there). “Scaling Fusion Services” is the part I’m really proud about — it’s the only part of the tutorial that currently doesn’t have much code, but addresses probably the most important question: “will it scale, and if yes — how?”
- Test coverage increased from 54% to 67%. Hopefully, we’ll pass 70% mark over the next week.
- Not sure how many bugs were fixed, but interestingly, only a few of them were really major.
- AFAIK there are no real-world production apps relying on Fusion yet (except inside ServiceTitan), but seemingly, it’s too early to even expect this, assuming the novelty of concept, the age of a project , and finally, the fact its Tutorial was finished just on the last week :) (though parts 1–4 were there for a couple months already).
Let’s talk about what’s upcoming.
Distributed Invalidation
If you read “Scaling Fusion Services”, you know that Fusion doesn’t offer much to help you implementing distributed invalidation — right now you have to do this on you own, and that’s the main core feature that’s missing now.
We’ll add APIs helping to implement it quite soon.
Delta Updates for Replicas
Most of updates should be delivered as diffs describing what’s changed — rather than snapshots of a new state.
Here is what we want to have: let’s say we have GetChatPage(someArgs) method, which returns an array of chat messages: ChatMessage[]. And your client has a replica of v1 of this array — let’s say it is {m1, m2, m3}. Now, user edits m2 so it becomes m2a, and it server-side GetChatPage(someArgs) starts to output {m1, m2a, m3}. Right now an update to the output of GetChatPage(someArgs) comes as a snapshot — i.e. as {m1, m2a, m3}. But ideally, we want to transfer just the delta: m2a = new { … }; root[1] = m2a.
What’s described above is certainly possible. But is it possible to implement the computation of such a set of instructions more efficiently — ideally, with O(changedNodeCount) rather than O(nodeCount) time complexity?
Interestingly, yes: assuming the graph we serialize is composed of immutable objects (this is quite frequent in Fusion’s case), and only a small portion of these objects changes on updates, you can compare two graphs of such objects by:
- Navigating both graphs using BFS or DFS in the same order
- Matching the sub-nodes of the same parent node using referential equality. And since immutable objects have to be recreated to be updated, equality of a node means equality for the whole subtree. This is what allows such a comparison to stop early.
- Serializing every node that isn’t an identical copy — possibly, partially (i.e. just store what’s updated), since we still can match it to the original object (e.g. by key/ID).
Notice that:
- It’s actually quite similar to what React or Blazor does when it renders a component: it similarly skips child components those properties weren’t changed from the last render, and as a result, spends CPU time only on what’s changed.
- The problem actually isn’t tied to Fusion — what we need here is a “Delta Serializer”, so most likely this part will be implemented as a separate package to be used by Fusion, but anyone else too — immutability is becoming increasingly popular nowadays, and C# 9’s records and init-only properties should help to make it standard.
- Immutability isn’t an absolutely necessary property here — you can compare mutable objects the same way and conclude they are identical when the whole subtree is identical, use their identifiers match them, etc.; immutability just helps to speed up the comparison here by spending just O(1) on identical subtrees. So most likely graphs of mutable objects will be supported too — as well as custom comparers for keys and subtrees.
This feature was actually a part of the original vision of how updates should work. But it was obvious we could add it at any time later, so taking into account the resource constraint, it was logical to postpone it.
What this means for you:
- Don’t worry too much about returning lists, sets, or dictionaries of objects (instead of IDs of these objects) from compute services having replicas— in future the updates to such structures will be delivered nearly as efficiently as the updates to corresponding lists of IDs.
- And if you don’t use Replica Services at all (e.g. if you use Server-Side Blazor only), it won’t impact you at all as well.
New Publisher-Replica Communication Channels
Right now Fusion offers just one way of publishing Compute Services and querying them via Replica Services:
- You must create an ASP.NET MVC controller that uses [Publish] filter to publish Compute Service
- Client-side Replica Service calls it using RestEase client under the hood
- And finally, text-based WebSockets channel is used to deliver the invalidation and update messages.
The main benefit of this protocol is its compatibility: you can query such Fusion services not only from Blazor WASM clients, but even from JS code; moreover, the controllers return JSON and publish nothing by default, so you can safely use them as API controllers too.
But the further we go, the higher will be the need for more specific solutions:
- Internal services / microservices: controllers are great when you want explicit publication with a high level of control over serialization, but isn’t it fine for an internal microservice to simply publish the whole compute service — e.g. on a common GRPC endpoint?
- Faster serialization & binary protocol: currently Fusion uses JSON.NET and text-based WebSocket messages. We consider adding MessagePack support and binary protocol to improve serialization performance. For the sake of clarity, you can add custom text-based serializers even now, but there are no examples, so MessagePack example would also show all of that.
- Transports other than plan WebSocket. Ideally, it should be possible to use e.g. any message queue here, and even though internally everything is at least 95% ready for this, there are no examples + configuration helpers.
More Samples
Obviously :)
Speaking of samples, note that you can contribute your own examples as well — your pull request to Stl.Fusion.Samples is all we need!
You Can Help Us!
And even though submitting a pull request to Stl.Fusion repository is a great way to do this, what’s more valuable now is helping us to spread the word about it. In particular, you can:
- Share a post about Fusion — either write your own or re-share one of our posts
- Talk about Fusion on your local .NET meetup or inside your company — feel free to PM me on Facebook if you’d be preparing for this, I’ll be happy to help with materials.
- Invite me to talk about Fusion on your local .NET meetup — and possibly, about engineering at ServiceTitan as well (you decide). We aren’t as well-known among developers as GitLab, Asana, Auth0, MailChimp, Segment, or Figma, but we’re ranked higher than any of these companies on Forbes 2020 Cloud 100 list. I’ll definitely be able to talk via Zoom or Google Hangouts; coming onsite is possible too — but due to COVID, a lot depends on your location here.
Please don’t hesitate to contact me if you want to try any these or other options.