A very short description first:
- Imagine you could create a Knockout model that includes every piece of data you have — not on a single client, but on every one of your servers and clients! And once something changes there, any client (or server) that has a replica of this piece gets notified about the change.
- This is what Fusion offers. And surprisingly, it’s not a toy concept — it’s designed to scale nicely.
Now, let’s talk about specific differences. I assume you already know Fusion abstractions support thread-safety, immutability, async support, etc. — in other words, the “must-have” features for server-side scenarios are there. What else is different?
- The key difference is: Fusion assumes every piece of data you deal with is a part of the model, and since there is no way to fit all of this in RAM, it is designed to “spawn” the observed part of this huge state on-demand, and destroy the unused parts quickly.
- Fusion doesn’t have separate “observable” and “computed observable” concepts. Every state there is “computed observable” because ultimately, it is always computed from some ground truth — you do this by either querying an external data source(e.g. DB — note that “querying” is also an act of computation) or by computing it from other “computed observables”. IComputed<T> is an “envelope” for the state of type T that provides these “computed” and “observable” properties.
- IComputed instances aren’t “nested” — in other words, their values never include other IComputed instances. If you consume some IComputed, you should use its data, but not the “envelope”. This explains why Fusion-based services look simple and do exactly what you see. You may feel hierarchical models are desirable in some cases — and yes, they are, but there are other ways get the same effect in Fusion (e.g. things like using a client-side replica of server-side IComputed as UI model of a table row are totally fine in Fusion app).
- Above also explains why there is just one interface for “computed observable”, and no special “flavors” of it for lists, dictionaries, etc. — if there is no nesting, you don’t need them.
- Fusion supports the most common way you deal with the “model” on the server-side — all you need is to write API / service methods providing access to its parts. Services exposing such methods are called “computed services” (they are tagged with IComputedService + typically they’re singletons). There is no direct analog of this concept in KO / MobX — they don’t need it, because they are designed to keep the whole model in RAM.
- I guess you suspect Fusion endpoints are similar to the regular ones, but return IComputed<T> instead of T, right? No, Fusion’s “computed observables” are rarely seen “walking in plain sight” — in other words, you seldom have to deal with them directly. They are created or pulled from the registry of “live” computed instances by Fusion proxies on every call you make, but never returned directly — what’s returned is their value. The assumption is: if the value is all you need in most cases, why should you bother so much about the envelope? And clean APIs without IComputed<T> look much nicer vs “polluted” ones exposing IComputed<T>. Moreover, most of such APIs are supposed to be asynchronous, so we’re talking about Task<User> (which is already a bit more complicated) vs Task<IComputed<User>>. I understand you might think it’s a bad idea, but believe me, you’ll like this. And yes, Fusion allows you to declare methods returning (Value)Task<IComputed<T>>, so if you’re not convinced — you’re free to try this option.
- Finally, Fusion has a concept of “replica services” — they act like “computed services”, but instead of running the computation locally, they query the remote endpoint matching the method you call and return an IComputed<T>, that behaves like its remote “counterpart”. In particular, it will be invalidated right after the same happens with its remote counterpart, and you can request to update it to get the most current version. There is a fairly complex machinery “wiring” these two things together, but ultimately, they “just work”. And since these replicas are identical to any other IComputed instances, you can invoke replica services from the client-side “computed services” to create a client-side state that depends on them. “Composition” sample shows how the code using replicas on the client is similar to the code using actual server-side services.
But ultimately, Stl.Fusion is a state change tracking abstraction — similarly to Knockout and MobX.