Dynalon / reactive-state

Redux-clone build with strict typing and RxJS down to its core. Wrist-friendly, no boilerplate or endless switch statements
MIT License
138 stars 7 forks source link

Making a scalable state solution #2

Closed MVSICA-FICTA closed 7 years ago

MVSICA-FICTA commented 7 years ago

Interesting to come across yet another RxJS state solution that again does not venture far from Redux. This limits the potential of RxJS, however, I do like how reducers can be registered, which allows different types of reducing to be accommodated. The following investigates optional reducer types:

https://github.com/ivan-kleshnin/reactive-states#functional-reducer

In most examples of reducing it is customary to show a minimal piece of state, usually a simple counter. In my case I have multiple types of larger JSON documents that are fully structured with nested nodes. These are coming from a database and one of each document type would be active at a time. Maybe each document is it's own root store having it's own slices and reducers?

If I place each type of document activated/queried for in it's own behaviorSubject or a replaySubject does each of these then become it's own reactive-state store? Exchanging the contents of a behaviorSubject is then just done using next(). This is the pattern used in the ngrx store, the following article is pretty sweet:

https://gist.github.com/btroncone/a6e4347326749f938510

Maybe its just a TypeScript thing but I'm not really thinking of defining my state with an interface declaration. Rather, my state is pre-defined as JSON documents that are loaded, edited or created from scratch via tools. It would be good to have a way to inject new documents/interfaces dynamically, which behaviorSubject.next() is perfect for.

In the examples the interface declaration seems to be more suited for client state, which is one of the six types of state that Victor Savkin brings to light in the link below. It would be most excellent for reactive-state to provide the means to handle each of the types.

https://youtu.be/brCGZ8Lk-HY?t=3m27s

Best and thanks for releasing reactive-state!

Ronsku commented 7 years ago

Hi @MVSICA-FICTA,

When you want to fetch your big JSON data from the backend, does all of this represent the application state? If you want to follow the redux style you should not include anything else into the state than the actual initial state. If you need to fetch stuff from your database later you do it as a side effect (also explained in the wiki here).

Consider on splitting these things into smaller pieces and thinking about if everything you are including into the store is actually affecting the application state. If you want to sync bigger datasets into your application, you could consider using something like indexedDB, redis or depending on the size even local storage.

In redux you should also have only one store and with this library you are able to take advantage of the "slices", they are not separate stores and you should be careful that you don't use them wrong as separate stores (my opinion). . You do not have to use types. This library works well with pure JavaScript afaik.

Maybe you even want to keep the initial load of your site/app as small as possible and just fetch the JSON data from your database when it's actually needed? Just my thoughts

Dynalon commented 7 years ago

I am not sure if I understood your problem correctly, but from your question I'll try to answer it:

First, I believe in the Redux pattern which brings a single Store to the table. Do not create multiple root stores. You should only have a single call to Store.create() in your code - all other "stores" are just slices.

When you say you have large JSON documents coming from a database I will assume that you absolutely must have them available offline, and thus in your browsers memory. As @Ronsku suggested, you might break the documents into other storage options (local storage, repository class in memory) and keep them out of your AppState. You AppState should only contain the stuff necessary for your application to work.

From what I got from your answer, you only need to have a single document of this collection active. That sounds like a perfect case for loadDocumentAction = new Action<string>() which you can use to load a document by a string id into your state (so only one of those documents are in the state, i.e. when viewing or editing): interface AppState { currentJSONDocument: any }; (you can see I use the any here so you do not have to type it as you requested - I would however strongly suggest static typing).

Let me assume now two cases:

(a): You would only need a subset of data, not the full JSON document in your store (that happens when your REST services are designed in a CRUD way where they return multiple properties that are not needed). In this case, break up your JSON document and create a Model designed just for your AppState - which will only contain the parts you need. You can create mapper function let model = fromJSONDocument(json) that will create your Model - the perfect place to do this would be in the reducer that loads the document into the store.

or

(b) you absolutely need the full JSON document in your store (i.e. in a huge editor where you need to be able to edit all properties). In that case, yes, your state will become complex. You will propably end up with a lot of actions and reducers. But since you HAVE to have such a big document in the state, there is no way around it. Not when using Flux, Redux or Reactive-State - your have just complex business demands and need to deal with it.

One way I can think of, if you have no complex logic in your reducers, just a "user might edit every property in whatever way") you can create a simple saveChangesReducer that would replace your active document with a new document (make sure its an immutable operation, possibly deep-cloning your object). That works however only in simple cases. If your business logic becomes complex, your reducer logic will become complex too - that is inevitable.

Last note: You do not need to use static types with Reactive-State. If you use JavaScript, typings are not even there. When using Typescript but do not want to type your JSON document, you can use the any type on the store or any slice: const store: Store<any> = Store.create(); or const slice = store.createSlice<any>("json");.

MVSICA-FICTA commented 7 years ago

@Ronsku Thanks for your advise. Most certainly my JSON documents are kept on a database and then loaded/activated as queries are made. The contents of one of the documents entails what you would call application type state but it is very well organized and I'd likely have to write a lot of slices (which remind me of datalens) just to work with it.

Also, this application state document can be exchanged because it is versioned to represent different levels of the app. So in other words, the actual "initial state" can be swapped. Another important document represents the content of the apps main editor. All document types can be swapped for others of their kind and have many predefined state nodes.

So far this whole Redux like approach in all the approaches of it I have seen seems very mickey mouse compared to what I require. I simply want to make queries and load/activate the result document and then I need a slicing/datalens or CRUD like solution that does not require me to write a million separate actions and reducers/mutators or define another million slices to work with the data.

I'm continually amazed at how many counter type examples I see, no matter what the framework happens to be. In all these examples by the time you get to even a handful of state values you end up with a ton of code just to handle it. There has got to be a more universal way to handle state no matter what type it is and such a solution is what I'm looking for.

Whenever I see code defining actions/reducers I'm thinking of how to squeeze as much of them down to generated/factory like functions. For instance, it should be possible to produce slices on demand dynamically and also to have a generic action that could be reused. There is still far to much boilerplate produced when working with state.

Dynalon commented 7 years ago

@MVSICA-FICTA I currently contract for a large company and we do Angular2 with ngrx state management with thousands of objects in our global state at a time, and lots of FE only developers on the project. If we were not to use ngrx/store and use the unidirectional data flow approach, there would be no clean architecture, every team would create data access/modification differently etc. Using a clean, well-documented approach for data flow (such as the store/action dispatch pattern made famous with Flux and Redux) helps to manage that. And yes, we have thousands of reducers, but they are sliced down and only operate on partial states/object which form only a small part of the full object graph in the state. And the reducers are separated into own functional modules. That is the only way I think we can manage complexity (and reducers can be well tested, as they are pure functions). This especially holds true if you start to create components that operate of sub-properties of an object in the state and is reused everywhere.

Think a Product entity with lots of properties, and a PriceTag visual component which only needs the price, vat and possible discount. The "sliced state" of the Product which only contains pricing information is passed to the PriceTag component, which renders it nicely on the screen. You can test the PriceTag component individually in unit and e2e test, only passing a very small state. I would highly discourage making a PriceTag component which is coupled to the full Product type.

Dynalon commented 7 years ago

I am closing this as this is not really a bug with reactive-state or anything that requires changes to the code; but feel free to continue discussion or open a new issue with suggestions/feature requests/bugs etc.

MVSICA-FICTA commented 7 years ago

@Dynalon Thanks for the insights and strategies. Yes, I like the ngrx store but not Angular. My framework of choice is VueJS and there we have Vuex, which has the same boilerplate problem minus the RxJS.

Because RxJS is a mainstay throughout my app I'm seeking for a Vuex alternative or some way to enhance Vuex with RxJS. As far as having a single store with a beautiful module/substore system that connects to components, I have all that in Vue.

The suggestion I can offer is to think about how to make everything more reusable and eliminate boilerplate wherever possible. On my end I have all app features worked into service modules and things are pretty tight. If reducer types can be swapped and also be as generic as possible then that is the best that can be done. I'm not part of a huge team so I'm working with what I have :)

MVSICA-FICTA commented 7 years ago

@Dynalon Actually, I consider the poor scalability of Redux like systems to be a major bug. I have accumulated a lot of insights into reactive design and have a lot of suggestions but things here appear to be all neat, tidy and locked down. All the best with reactive-state, I'll consider it in light of all the reactive R&D I have in the works!

P.S. I'm looking for an even more "wrist-friendly, without any boilerplate or endless switch statements" solution. For that you have to think outside the box, beyond Redux, something made with pure RxJS and FP from scratch, anything less always hits the same ceiling set by Redux.