{"action":"created","issue":{"url":"https://api.github.com/repos/fdecampredon/rx-flux/issues/10","labels_url":"https://api.github.com/repos/fdecampredon/rx-flux/issues/10/labels{/name}","comments_url":"https://api.github.com/repos/fdecampredon/rx-flux/issues/10/comments","events_url":"https://api.github.com/repos/fdecampredon/rx-flux/issues/10/events","html_url":"https://github.com/fdecampredon/rx-flux/issues/10","id":58467444,"number":10,"title":"Discussion: The right API for rx-flux","user":{"login":"fdecampredon","id":864201,"avatar_url":"https://avatars.githubusercontent.com/u/864201?v=3","gravatar_id":"","url":"https://api.github.com/users/fdecampredon","html_url":"https://github.com/fdecampredon","followers_url":"https://api.github.com/users/fdecampredon/followers","following_url":"https://api.github.com/users/fdecampredon/following{/other_user}","gists_url":"https://api.github.com/users/fdecampredon/gists{/gist_id}","starred_url":"https://api.github.com/users/fdecampredon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fdecampredon/subscriptions","organizations_url":"https://api.github.com/users/fdecampredon/orgs","repos_url":"https://api.github.com/users/fdecampredon/repos","events_url":"https://api.github.com/users/fdecampredon/events{/privacy}","received_events_url":"https://api.github.com/users/fdecampredon/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":9,"created_at":"2015-02-21T17:41:38Z","updated_at":"2015-02-24T16:43:41Z","closed_at":null,"body":"The Flux pattern is really interesting because in its essence it is fully reactive. I think it perfectly matches the Rx way of doing things and combined with `rx-react`, `rx-flux` could result in a solid architecture.  \r\n\r\nHowever, I have some problems to figure the right API for `rx-flux`. I want to publish this project on npm but, before that, I want to create an API that will provide this project users **simplicity**, **elegance**, and **freedom**.\r\n\r\nA good library is hard to design. Despite the fact that it is its role to constraint the user to develop in a certain way, to reduce boilerplate and transform the hard into simple, each feature that you offer makes that library less usable for people who don't want to use those features. \r\nSometimes it's better to let the user build their own abstractions on top of your library than trying to solve every use case.\r\n\r\n# The Store\r\nThe only thing I'm pretty happy with actually in `rx-flux` is the store object. When you think about what is a Store in the Flux pattern, the answer is pretty simple : a  `reduce` operation.\r\nAt a given instant, the value *held* by the store is the result of a `reduce` operation over the list of all actions that have been dispatched. \r\nThat perspective make things easy to reason about. For example, if your store state is erroneous it's easy to understand where the problem comes from by recording/replaying action.\r\n`rx-flux` stores mimics exactly that model: a seed value and a set of transformations for each action dispatched.\r\n\r\n```\r\nvar TodoStore = Store.create({\r\n  getInitalValue: () => ({}),\r\n  getOperations: () => \r\n    Rx.Observable.merge(\r\n      todoCreated.map(todo => ({ \r\n        transform: (todos) =>  ({ ...todos, [todo.id]: todo })  \r\n      })),\r\n      ....\r\n    )\r\n}\r\n```\r\n\r\n# Singleton Dispatcher vs Independent Actions\r\n\r\nI tried to reduce all the boilerplate code of the original Flux pattern by creating *function-object* ala [reflux](https://github.com/spoike/refluxjs) and getting rid of the central dispatcher.\r\nAfter experimenting a bit with it, I dislike the result. \r\n\r\nAn `rx-flux` action has too much responsibility; it is the constant, the action creator and the dispatcher at the same time. Worse, that pattern does not provide the *single source of truth* that is the central dispatcher in the original Flux pattern. \r\n\r\nUnless someone completely disagrees and proves that I should not do so, I'm seriously considering getting rid of those *function-object * and reintroducing a global singleton dispatcher. \r\nSure it is a little more verbose, but the single fact that you can record/replay all the actions from a single source worth the deal in my opinion.\r\n\r\n# The ActionCreator problem\r\n\r\nThere is something that I dislike and want to replace in the original Flux pattern: *ActionCreator*.  \r\nIn my point of view, *ActionCreator* is the *garbage* of the original Flux pattern where everything that breaks the unidirectional data flow has been put. \r\nAn *ActionCreator* validates the input and creates the action object - no surprise here - but it also dispatches the action and acts as *command* by performing an async task and dispatching another action depending on the result.  \r\nNow I still think that there is a need of a simple function that creates an action and validates the input but it should not dispatch the created action itself nor it should perform any other tasks.\r\nInstead, I want to let the component dispatch itself the value returned by the action creator and let the `WebService` layer listens dispatched actions and performs async tasks.\r\n\r\n# The case of async task\r\nSo here is the problem that I want to solve from the start and that gives me so much headache: \r\nWhen an action is associated to an async task, if that task fails, the store may have to **revert** its state. \r\n\r\nSimple case are obvious but this can create a lot of complexity. \r\nFor that purpose, an operation consumed by an `rx-flux` store can provide a promise as a *confirmation token* : \r\n```\r\ntodoCreated.map(todo => ({ \r\n  transform: (todos) =>  ({ ...todos, [todo.id]: todo }),\r\n  confirm: aPromise\r\n}))\r\n```\r\nWhen the promise is resolved, the operation is confirmed and in the contrary, if the promise is rejected, the store state is reverted.\r\n\r\nNow the problem is: how we generate that promise.\r\n \r\nIn the current rx-flux version, I have something similar to the *ActionCreator* so it is more or less easy. The *ActionCreator* performs the async task and the promise is just a property of the action object. \r\nHowever, that strategy causes more problems that it solves. \r\nFirstly, as I said, I don't want the *ActionCreator* to be responsible of executing the async task. Also, I think that the action object should be a serializable object. Finally some stores can be interested in that async task failure/success so the result of that task should also dispatch an *action*.  \r\n \r\nThe original Flux pattern promotes 3 constants per action. The first one for the original action, one for failure and one for success. This is a bit verbose but more importantly, it does not provide a way to see how the result/error actions are related to the original one. \r\nReflux has some sort of `async` actions but does not really promote a way of resolving things.\r\n\r\n# Proposals\r\nHere are the different proposals I have thought about. For each one of this, I'll show a little example of a *todo creation*.\r\n\r\n## Simple Dispatcher with `getAsyncToken`\r\n\r\nIn this proposal, each action has an unique id generated by the Dispatcher; that helps creating the relation between success/failure but it produces a lot of boilerplate code.\r\n\r\n```javascript\r\n// AppDispatcher.js\r\n\r\n// The app dispatcher pretty similar to the original flux one, \r\n// except the registration/listening process That is based on rxjs\r\nvar Dispatcher = require('rx-flux').Dispatcher;\r\n\r\nmodule.exports = Dispatcher.create(); \r\n\r\n//TodoConstants.js\r\nvar keyMirror = require('keymirror');\r\n\r\n//Three constant by *async* action, one for the action one for failure one for success\r\nmodule.exports = keyMirror({\r\n  TODO_CREATE: null,\r\n  TODO_CREATE_SUCCESS: null,\r\n  TODO_CREATE_FAILURE: null\r\n});\r\n\r\n//TodoActions.js\r\n\r\nvar TodoContants = require('./TodoContants');\r\nvar uuid = require('uuid');\r\n\r\n// the action creator, responsible for creating the action object and validating the input\r\nmodule.exports = {\r\n  todoCreate(text) {\r\n    if (!text) {\r\n      throw new Error('text should not be empty');\r\n    }\r\n    return {\r\n      type: TodoContants.TODO_CREATE,\r\n      data: {\r\n        id: uuid(),\r\n        text: text,\r\n        completed: false\r\n      }\r\n    }\r\n  },\r\n  \r\n  todoCreateSuccess(action, result) {\r\n    return {\r\n      type: TodoContants.TODO_CREATE_SUCCESS,\r\n      parentAction: action.id // the action is related to the original action through the *id*\r\n      data: result\r\n    }\r\n  }\r\n  \r\n  todoCreateFailure(action, error) {\r\n    return {\r\n      type: TodoContants.TODO_CREATE_FAILURE,\r\n      parentAction: action.id // the action is related to the original avtion through the *actionid*\r\n      data: error\r\n    }\r\n  }\r\n}\r\n\r\n//TodoCommands.js\r\n\r\n\r\nvar TodoConstants = require('./TodoConstants');\r\nvar TodoActions = require('./TodoActions');\r\nvar AppDispatcher = require('./AppDispatcher');\r\nvar WebApi = require(...);\r\n\r\n// Commands are responsible of executing async task\r\nAppDispatcher\r\n  .getObservable(TodoContants.TODO_CREATE)\r\n  .subscribe(function (action) {\r\n    var todo = action.data;\r\n    WebApi.createTodo(todo).then(function (result) {\r\n      //on success we dispatch a *child action*\r\n      AppDispatcher.dispatch(TodoActions.todoCreateSuccess(action, result))\r\n    }, function (error) {\r\n      //on failure we dispatch a *child action*\r\n      AppDispatcher.dispatch(TodoActions.todoCreateFailure(action, error))\r\n    })\r\n  })\r\n\r\n//TodoStore.js\r\nvar Store = require('rx-flux').Store;\r\nvar TodoConstants = require('./TodoConstants');\r\nvar AppDispatcher = require('./AppDispatcher');\r\n\r\nvar todoCreated = (\r\n  AppDispatcher\r\n    .getObservable(TodoContants.TODO_CREATE) // create an observable that filter by action type\r\n    .map(todo => ({ \r\n      transform: (todos) =>  ({ ...todos, [todo.id]: todo }),\r\n       // a promise resolved when a *child* action with the constants is dispatched\r\n      confirm: AppDispatcher.getAsyncToken(  \r\n        TodoContants.TODO_CREATE_SUCCESS, \r\n        TodoContants.TODO_CREATE_FAILURE, \r\n        action.id\r\n      ) \r\n    })),\r\n)\r\n\r\nvar TodoStore = Store.create({\r\n  getInitalValue: () => ({ }),\r\n  getOperations: () => Rx.Observable.merge(todoCreated)\r\n}\r\n\r\n\r\n//component dispatching\r\n\r\n...\r\ncreateTodo(event) {\r\n  var text = event.target.value;\r\n  AppDispatcher.dispatch(TodoActions.createTodo(text)); // the component is responsible for dispatching\r\n}\r\n...\r\n```\r\n\r\n## Dispatcher with integrated `command`\r\n\r\nNow in the second proposal, the Dispatcher provides a 'command' mechanism while this reduces boilerplate code and constraints the usage. I'm not so confident it provides the necessary freedom.\r\nFor this proposal, I'll provide the Dispatcher api (typescript/flow notations).\r\n\r\n```typescript\r\ninterface Dispatcher {\r\n  /**\r\n   * Produce an observable that notify of dispatched action,\r\n   * If type is provided action are filtered by this type\r\n   */\r\n  getObservable(type?: string): Rx.Observable \r\n  \r\n  /**\r\n   * Dispatch an action\r\n   */\r\n  dispatch(action: {type: string, data: any})\r\n\r\n  /**\r\n   * Register a command, a command is a function that return a Promise\r\n   */\r\n  registerCommand(type?: string, command: (action) => Promise<any>);\r\n  \r\n  /**\r\n   * observable exposing 'command' results\r\n   */\r\n  commandResult(type: string): Rx.Observable\r\n  /**\r\n   * observable exposing 'command' errors\r\n   */\r\n  commandError(type: string): Rx.Observable;\r\n  /**\r\n   * observable exposing 'command' completion\r\n   */\r\n  commandComplete(type: string): Rx.Observable;\r\n  /**\r\n   * observable exposing async task status\r\n   */\r\n  commandStatus(type: string): Rx.Observable<boolean>;\r\n \r\n  /**\r\n   * return a promise that resolve with the result of commands associated to the given action type\r\n   */\r\n   getAsyncToken(action: { type: data}): Promise\r\n}\r\n\r\n\r\n\r\n```javascript\r\n// AppDispatcher.js\r\n\r\n// The app dispatcher pretty similar to the original flux one, \r\n//  except the registration/listening process That is based on rxjs \r\n// and the provided command system\r\n\r\nvar Dispatcher = require('rx-flux').Dispatcher;\r\n\r\nmodule.exports = Dispatcher.create(); \r\n\r\n//TodoConstants.js\r\nvar keyMirror = require('keymirror');\r\n\r\n// only one constant for async action, other constant are generated by the Dispatcher,\r\n// in this case TODO_CREATE:result TODO_CREATE:error\r\nmodule.exports = keyMirror({\r\n  TODO_CREATE: null\r\n});\r\n\r\n//TodoActions.js\r\n\r\nvar TodoConstants = require('./TodoConstants');\r\nvar uuid = require('uuid');\r\n\r\n// the action creator, responsible for creating the action object and validating the input\r\nmodule.exports = {\r\n  todoCreate(text) {\r\n    if (!text) {\r\n      throw new Error('text should not be empty');\r\n    }\r\n    return {\r\n      type: TodoConstants.TODO_CREATE,\r\n      data: {\r\n        id: uuid(),\r\n        text: text,\r\n        completed: false\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n//TodoCommands.js\r\n\r\n\r\nvar TodoConstants = require('./TodoConstants');\r\nvar AppDispatcher = require('./AppDispatcher');\r\nvar WebApi = require(...);\r\n\r\n// Here we simply call the WebApi.createTodo that return a promise\r\n// Success/failure action are automaticly created/dispatched\r\nAppDispatcher.registerCommand(TodoConstants.TODO_CREATE, function ({data: todo}) {\r\n  return WebApi.createTodo(todo)\r\n})\r\n\r\n//TodoStore.js\r\nvar Store = require('rx-flux').Store;\r\nvar TodoConstants = require('./TodoConstants');\r\nvar AppDispatcher = require('./AppDispatcher');\r\n\r\nvar todoCreated = (\r\n  AppDispatcher\r\n    .getObservable(TodoContants.TODO_CREATE) // creates an observable filtered by action type\r\n    .map(todo => ({ \r\n      transform: (todos) =>  ({ ...todos, [todo.id]: todo }),\r\n      confirm: AppDispatcher.getAsyncToken(action)  // a promise that resolves to whatever the command associated to the action resolves\r\n    })),\r\n)\r\n\r\nvar TodoStore = Store.create({\r\n  getInitalValue: () => ({}),\r\n  getOperations: () => Rx.Observable.merge(todoCreated)\r\n}\r\n\r\n\r\n//component dispatching\r\n\r\n...\r\ncreateTodo(event) {\r\n  var text = event.target.value;\r\n  AppDispatcher.dispatch(TodoActions.createTodo(text)); // the component is responsible for dispatching\r\n}\r\n...\r\n```\r\n\r\n\r\n# Conclusion\r\n\r\nFlux is a solid pattern that makes our application state easy to reason about. So before publishing `rx-flux`, I really want to grasp the right api. Any proposal/comment/opinion would be greatly appreciated."},"comment":{"url":"https://api.github.com/repos/fdecampredon/rx-flux/issues/comments/75793076","html_url":"https://github.com/fdecampredon/rx-flux/issues/10#issuecomment-75793076","issue_url":"https://api.github.com/repos/fdecampredon/rx-flux/issues/10","id":75793076,"user":{"login":"arqex","id":6509397,"avatar_url":"https://avatars.githubusercontent.com/u/6509397?v=3","gravatar_id":"","url":"https://api.github.com/users/arqex","html_url":"https://github.com/arqex","followers_url":"https://api.github.com/users/arqex/followers","following_url":"https://api.github.com/users/arqex/following{/other_user}","gists_url":"https://api.github.com/users/arqex/gists{/gist_id}","starred_url":"https://api.github.com/users/arqex/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arqex/subscriptions","organizations_url":"https://api.github.com/users/arqex/orgs","repos_url":"https://api.github.com/users/arqex/repos","events_url":"https://api.github.com/users/arqex/events{/privacy}","received_events_url":"https://api.github.com/users/arqex/received_events","type":"User","site_admin":false},"created_at":"2015-02-24T16:43:41Z","updated_at":"2015-02-24T16:43:41Z","body":"The actions in the components is an issue related rather to the user interaction than to the rendering. \r\n\r\nYou usually don't need to listen to component dispatched actions to render your page, but in the rare case you do it in the server, you can create a similar solution using EventEmitter. The key is not to add a propietary piece of code inside the views ( components ) because that would couple them to your library.\r\n\r\nIn your library stores react to action events and the view react to store events. Why don't to make actions react to views events too?\r\n\r\n"}}