Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft idea for a SSoT State Management plugin #94

Open
avickers opened this issue Mar 20, 2019 · 6 comments
Open

Draft idea for a SSoT State Management plugin #94

avickers opened this issue Mar 20, 2019 · 6 comments

Comments

@avickers
Copy link

It will borrow heavily from Vuex as a foundation. This will go as far as State, Mutations, Actions.

let store = {
   state: {
      myStoredObs: ko.observable(false)
   },
   mutations: {
      toggleState: () => this.state.myStoredObs(!this.myStoredObs())
   },
   actions: {
      toggle: () => this.mutations.toggleState()
   }
}
// ko.plugin(name, { store: 'myStore' }) ??
let vm = {
   myLocalObs: ko.observable(),
   myObs: ko.getState('myStoredObs'),  // read only ko.observable subscribed to state
   whenClicked: ko.dispatch('myStore','toggle',{})
}

But here is where things get interesting

let store = {
    collections: {
      restaurants: ko.collection({ indices: ['cuisine'] })
   },
   views: {
      restaurantsByRating: this.collections.restaurants.view({ sort: 'rating' }),
      indianByRating: this.collections.restaurants.view({
         find: { 'cuisine': { $eq: 'indian' }},
         sort: 'rating',
         defaultLimit: null
      })
   }
}
// Off camera insert hundreds or thousands of documents into the restaurants collection from your backend

let vm = {
   restaurants: ko.view('indianByRating',{ limit: 25, offset: 50 }) // An observableArray (subscribed to the store view) containing the 51st-75th most highly rated Indian restaurants.
}

Have a restaurants('frenchByRating',{ limit: 10 }) button, and boom! [provided the view was defined] your ko foreach now shows the top 10 French restaurants.

State and Collections can be persisted to IndexedDB. Should be blazing fast thanks to LokiJS.

OK, tell me how terrible of an idea this is.

@brianmhunt
Copy link
Member

@avickers Thanks for posting this. I'll be mulling it. Is there a handy guide for SSoT that explains the key concepts?

@corlaez
Copy link

corlaez commented Mar 22, 2019

Hi @avickers! It is nice to see this issue I have some suggestions. (Nice Machu Picchu picture BTW)

First I want to share a state management library I just made for knockout: Overko. Just as you mentioned you were inspired by Vuex I have been inspired by Overmind.js. The Overmind influence gives my implementation a wonderful TypeScript support but the code is simpler since it relies in Knockout observables to do the hard job while it keeps a familiar api for Knockout users.

This is the state definition in Overko:

export interface GenderOption {
  name: string;
  value: string;
}
export interface Todo {/*...*/}

const state = {
  todos: {
    list: [] as Todo[],
    input: ""
  },
  gender: {
    checkGender: [] as string[],
    options: [
      { name: "Male", value: "1" },
      { name: "Female", value: "2" }
    ] as GenderOption[]
  }
};

export default state;

Overko will wrap the arrays and other values with Observable and ObservableArray if needed (You can still define your observables to writte a getter for instance). Moreover you can then implement actions and type them with Action or pass a property to your component and type it with Connect (I am not sure how property passing translates to TKO)

I don't think we have to separate actions and mutations. An api that uses only actions is very fluent in my opinion. I am also not sure if it is really necessary to separate state, collections and views (Disclaimer I am just getting familiar with knockout and I am not aware of tko's api) I prefer a free form state that could take in all of that.

Actions example:

import { Action } from ".";
import Todo from "./state";

export const addTodo: Action<Todo> = ({ state }, todo) => {
  state.todos.list.push(todo);
};

I think that state could be free form and we let the users of the library to organize their state as they wish. Actions would be enough in my opinion (vs mutations and actions). One last feature of Overmind that I really like is that it encourages to write side effects separately and provides an interface to mock them easily.

Effects are simple functions but that need to rely on changing stuff (like Dates) or on things that won't exist on a test environment (like window object). Overko and Overmind provide an easy way to override these effects on a testing environment. These functions can be nested in objects as well. Example:

export const setTodos = todos => {
  window.localStorage.setItem(localStorageItem, ko.toJSON(todos));
};

This is a demo of the library. I know that I still have a lot to polish in this library since my Knockout knowledge is very reduced, but I think that it could serve to illustrate what could be made for a possible tko state management implementation inspired in Overmind.js.

https://github.com/lrn2prgrm/pyout-frontend/tree/overkoDemo

Sorry for not being very aware of tko features and having also limited knowledge of Knockout api's but I think that I could contribute to the discussion with Overko and it's Overmind inspiration. Please let's keep up the discussion!

@corlaez
Copy link

corlaez commented Mar 22, 2019

@brianmhunt He is talking about state containers like Redux, Vuex, Overmind, etc.

The SSoT (Single Source of Truth) objective is to separate the app state from the component states, and provide an easy way for components to read the app state (sometimes called connecting the component). This helps to share state between components and simplifies your UI code while decoupling it from your business logic.

In the past people could store state in DOM itself, in that case you need to read and write the dom each time which can be troublesome. That is not the case of Knockout which used the viewModels, but I saw that Knockout used ${root} and ${parent} that tightly couple the components instead. Connected components will only make assumptions on the container, instead of creating multiple places where state is defined and having the component look who is my father, who is parent grandparent which breaks composability.

Of course you can always make use of local state, where it makes sense. $root $parent is available in Vuex but it is not in React. I am not aware if tko still supports root or parent or other features but I am based on what I know from Knockout.

For more on this you could read docs from Redux, Vuex, Overmind, etc. I will share this video where Christian explains what Overmind tries to solve: https://youtu.be/82Aq_ujnBQw?t=348

@avickers
Copy link
Author

Thanks, Armando. I think Christian does a better job of explaining it than Facebook did.

Brian, here is a text and graphics based overview of the Flux pattern.

Implementing Overmind for Knockout sounds like a great idea. Using a familiar tool would make migration easier for developers from other frameworks.

@brianmhunt
Copy link
Member

Got it, that's helpful for helping me mull this. 😁

@avickers
Copy link
Author

avickers commented Mar 24, 2019

@lrn2prgrm,

I think that Overko is great because it's really a typical modern state management library that does what people will expect.

The reason I joked about people telling me my idea was terrible is because I was proposing expanding beyond just a Flux-lite pattern library and bolting on an in-memory/IndexedDB Document db with concepts from both Mongo and CouchDB.

In this case, rather than exclusively address the problem that the Flux pattern is, I would also be trying to help with consuming data directly from the back-end and persisting/caching it locally.

That's where the Collections and Views come in.

The idea being that a developer could make a coarse level of querying to their document database, and then dump include_docs: true from Mongo, Couch, Firestore, etc. into a Collection. Props that required high performance searching/sorting would be indexed. (These could be nested.)

The in-memory db uses indexing and binary trees, and is blazing fast. Index scans at over 1M ops/sec. Dirty queries ~300-500k ops/sec.

Views would essentially be Computeds for arrays of Javascript objects (Documents). They would handle Filtering, Mapping, and Sorting needs.

Why do this?

  • much faster than using Array.prototype derived solutions (no jank);
  • potentially better than lots of specific queries to the back-end for several reasons:
    • avoids network latency;
    • potentially lower overall bandwidth usage;
    • less $$$ paid to AWS/GCP etc
  • benefits of caching (offline functionality, faster reloads under stale-while-revalidate)

Trade offs would be non-trivial extra page weight and that each index adds about 20% to memory footprint for the collection.

While something like Overko would probably be an improvement for any Knockout app of sufficient complexity, the additional functionality that I highlight should often but wouldn't always be desirable; however, since it would be doing things in a NoSQL DocDB way, it's opinionated, and that's a dirty word around these parts. 😆 Might well work better as two discrete plugins.

PS: I also spent a few days in Miraflores when I visited Peru!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants