mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.24k stars 141 forks source link

Question: Global state for Blazor Server? #295

Closed gingters closed 2 years ago

gingters commented 2 years ago

Given a Blazor Server application that is connected to some sort of hardware. All concurrently connected user sessions / circuits should display the exact same current hardware state. When hardware state changes, a singleton will be notified and should send an action to update the global hardware state for all users.

Is that possible atm? If yes, how could I make the state a singleton too?

mrpmorris commented 2 years ago

There is a new option in 5.2 to use Singletons, but this was really only added for Maui apps.

Although Fluxor is thread-safe, I am not sure if you should be using it for multi-user server apps or not (I am not against it, just undecided).

I would be interested in hearing how it works out for you. Note though, that Fluxor won't let you mix Singleton + Scoped stores. There is either one per scope, or one for everyone.

I'm wondering if my other library Reducible might be a better option? It should reduce data faster, but doesn't have Effect support.

gingters commented 2 years ago

Hrm. I will try it out. Thing is, other stores should be per scope, so that each user can has features and state for only them. But that hardware store should be global.

That said, when thinking about it, I also could provide a singleton service with an Rx subject, update that with with an immutable state and have all components that display that stuff subscribe to the observable and when when it emits, simply bind to the new state and call StateHasChanged. Probably better than misusing fluxor for that and simply using it as normal for per-user state. But i will check it out. Thanks :)

mrpmorris commented 2 years ago

Yeah, I think Flux is more about "lots of data for a single person not contradicting each other" rather than "synchronising data to multiple users".

Having said that, I think it would be possible. Would the states + actions for the shared/scoped stores be completely different (i.e. could be different assemblies) or would you expect the same actions to update both stores?

Are the state classes uses in the two different store types completely different, or shared?

I'll be able to think something up based on your answers :)

gingters commented 2 years ago

That are great questions 😅

As I think about it, the global (hardware) state would be a distinct state type (i.e. with a [GlobalState] attribute instead of [FeatureState]) and have its very own actions, reducers and possibly also effects. It would work pretty much the same like the feature state, but registered as singleton instead of scopes, so every component that wants to display the state and react to state changes would get injected the same state. Also, in my case, actions to this state would (most likely) not be issued by user interaction, but from a background worker class (IHostedService, so also a singleton) that receives external inputs (i.e. via some message queue like MQTT or from a USB / serial connected hardware).

Actions from a user would only indirectly change the state by issuing separate actions and separate effects could then send commands to the hardware, but the global state update should happen after the fact and only when the hardware reports the actual state change to the system (again, queue or direct connection).

Edit/Update: As of now, the user-specific / normal feature states and the global state would exist in the same assembly. The project is relatively small and should be easy to maintain and handle, and so multiple assemblies to separate out stuff would be a bit overcomplicating stuff.

mrpmorris commented 2 years ago

If a folder has a class that implements IMiddleware or descends from Middleware then it isn't automatically loaded when assemblies are scanned unless you do services.AddFluxor(x => x.AddMiddleware<ThatClass>())

I use this approach in my unit tests: https://github.com/mrpmorris/Fluxor/blob/65a96b579af82387ddddacb50d7c76a3b6b3b377/Tests/Fluxor.UnitTests/DependencyInjectionTests/FeatureDiscoveryTests/DiscoverFeatureClassDescendantsTests/DiscoverFeaturesTests.cs#L43

So, you could separate the two by putting them in different folders, each with their own Middleware class implementation in the folder.

When you create your user-specific store you'd call .AddMiddleware<UserSpecific>() and it will pick up all the user specific stuff.

To have a shared state you could create your own ServiceCollection on which you call AddFluxor(x => x.AddMiddleware<SharedStuff>(), then create a ServiceProvider, then resolve the IDispatcher and then use that.

It would mean, however, that you wouldn't have any shared services across the two.