{"action":"created","issue":{"url":"https://api.github.com/repos/cerebral/cerebral/issues/204","repository_url":"https://api.github.com/repos/cerebral/cerebral","labels_url":"https://api.github.com/repos/cerebral/cerebral/issues/204/labels{/name}","comments_url":"https://api.github.com/repos/cerebral/cerebral/issues/204/comments","events_url":"https://api.github.com/repos/cerebral/cerebral/issues/204/events","html_url":"https://github.com/cerebral/cerebral/issues/204","id":130866932,"number":204,"title":"Modules, lets talk context","user":{"login":"christianalfoni","id":3956929,"avatar_url":"https://avatars.githubusercontent.com/u/3956929?v=3","gravatar_id":"","url":"https://api.github.com/users/christianalfoni","html_url":"https://github.com/christianalfoni","followers_url":"https://api.github.com/users/christianalfoni/followers","following_url":"https://api.github.com/users/christianalfoni/following{/other_user}","gists_url":"https://api.github.com/users/christianalfoni/gists{/gist_id}","starred_url":"https://api.github.com/users/christianalfoni/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/christianalfoni/subscriptions","organizations_url":"https://api.github.com/users/christianalfoni/orgs","repos_url":"https://api.github.com/users/christianalfoni/repos","events_url":"https://api.github.com/users/christianalfoni/events{/privacy}","received_events_url":"https://api.github.com/users/christianalfoni/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":39,"created_at":"2016-02-03T00:32:25Z","updated_at":"2016-02-12T14:29:07Z","closed_at":null,"body":"There has been lots of discussions on \"context\" lately. And I want to summarize a bit on what Cerebral wants to solve, and how some late ideas causes some confusion around that. First of all I just want to say \"modules are GREAT!\". This summary has nothing to do with modules, but how we have introduced the concept of \"context\".\r\n\r\nThe problem Cerebral solves is scalability in applications. Applications change, they change a lot and especially during initial development. You put some state in your app related to a button click and suddenly you want to change the exact same state related to a completely different button, but that button also changes some other state to boot. And suddenly you do not want that button over there anymore, you want it on a completely different page.\r\n\r\nThese changes are very hard to do in traditional frameworks for a couple of reasons:\r\n\r\n1. You hide state inside different parts of your code, not allowing other parts of your code to change that state (Making it difficult for a new button to change the same state as an other button)\r\n\r\n2. You give pieces of your code a specific context, making it dependent on that context to do its job (Making it difficult to move a button to a completely different page)\r\n\r\nThis is the core of what Cerebral wants to solve in terms of reasoning and scalability, and it does do this. Moving components around does not affect at all how you extract state. You can change whatever state you want wherever you want using actions.\r\n\r\nSo lets look at an example:\r\n\r\n```js\r\nfunction action({module, state}) {\r\n  module.state.set('foo', 'bar');\r\n  state.set('some.other.module.foo', 'bar');\r\n}\r\n```\r\n\r\nThis is what we mean by context. We now say that whatever signal this action is used on, and whatever module that signal is registered on, it will change the state of that module. Now flipping it around:\r\n\r\n```js\r\nfunction action({state}) {\r\n  state.set('moduleA.foo', 'bar');\r\n  state.set('some.other.module.foo', 'bar');\r\n}\r\n```\r\n\r\nNow we say that this action, regardless of signal and module, will change the state of `moduleA`. This approach has three benefits:\r\n\r\n1. Its explicit. Reading the action and we know where it does its change\r\n2. We can use the action wherever we want and it will always do the same thing\r\n3. Less mindload as we use the same argument to do any state change\r\n\r\nOkay, this is not the problem, but I want to express how important I think this core idea is :-) Lets move on!\r\n\r\n#### Factories (giving \"context\")\r\nSometimes you do want to reuse the same action, but you want to operate on different paths in the state tree.\r\n\r\n```js\r\nimport actionFactory from './some/actionFactory';\r\n\r\nexport default [\r\n  actionFactory('some.module')\r\n];\r\n```\r\nAnd inside the factory:\r\n```js\r\nfunction factory(path) {\r\n  function action({state}) {\r\n    state.set(`${path}.foo`, 'bar');\r\n  }\r\n\r\n  return action;\r\n}\r\n```\r\n\r\nNow we have given \"context\", but things look pretty good still.\r\n\r\n#### Module context\r\n```js\r\nexport default (module) {\r\n  module.addSignals({\r\n    thisHappened\r\n  });\r\n}\r\n```\r\n\r\nUsing the `module` argument inside the actions will now point to the specific module. This is great as we can isolate some logic, but it does also remove the benefits explained initially. Harder to read an action pointing to module, \"what module?\". Actions can not be reused on other chains. And you can not  compose signals together either.\r\n\r\n### What to do?\r\nI for one do of course not want to impose factory hell on Cerebral developers, but I also want to make sure that everyone sees the benefit of not using context. Ideally there would be a clear line of when to use context and when to not use it. By the end it is of course completely up to the developer, but I think it is wise to only use context where you absolutely have to. Like when reusing modules. But we can help out on context, make it more composable. It will not be as composable as a context-free app, where you could just grab any action, but we can allow composing signals with context.\r\n\r\nA suggestion to this would be:\r\n\r\n```js\r\nexport default [\r\n  myAction,\r\n  'moduleA.somethingHappened',\r\n  someOtherAction,\r\n  'moduleB.somethingHappened'\r\n];\r\n```\r\n\r\nNow, this would run as:\r\n\r\n1. myAction (in context of signal triggered)\r\n2. `moduleA.somethingHappened` signal would be spread and context changed to `moduleA` for those actions\r\n3. someOtherAction (in context of initial signal triggered)\r\n4. `moduleB.somethingHappened` signal would be spread and context changed to `moduleB` for those\r\n\r\n**Important:**\r\n\r\n1. The signals are actually \"inlined\", meaning that they will all run as \"one signal\"\r\n2. If any of the signals are bound to a route, the route will currently not be updated. That can be solved with the suggested `route('moduleA.somethingHappene')` action factory for the router\r\n3. Routes needs to be one signal. That means composing signals on a route will still require a new signal. for example: `{homeRouted: ['home.opened', 'home.news.opened']}`"},"comment":{"url":"https://api.github.com/repos/cerebral/cerebral/issues/comments/183352235","html_url":"https://github.com/cerebral/cerebral/issues/204#issuecomment-183352235","issue_url":"https://api.github.com/repos/cerebral/cerebral/issues/204","id":183352235,"user":{"login":"christianalfoni","id":3956929,"avatar_url":"https://avatars.githubusercontent.com/u/3956929?v=3","gravatar_id":"","url":"https://api.github.com/users/christianalfoni","html_url":"https://github.com/christianalfoni","followers_url":"https://api.github.com/users/christianalfoni/followers","following_url":"https://api.github.com/users/christianalfoni/following{/other_user}","gists_url":"https://api.github.com/users/christianalfoni/gists{/gist_id}","starred_url":"https://api.github.com/users/christianalfoni/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/christianalfoni/subscriptions","organizations_url":"https://api.github.com/users/christianalfoni/orgs","repos_url":"https://api.github.com/users/christianalfoni/repos","events_url":"https://api.github.com/users/christianalfoni/events{/privacy}","received_events_url":"https://api.github.com/users/christianalfoni/received_events","type":"User","site_admin":false},"created_at":"2016-02-12T14:29:07Z","updated_at":"2016-02-12T14:29:07Z","body":"@Guria Been thinking, there is an issue with the event approach. The action and signal argument has to be serializable. Because they are passed to the debugger. I do not think we should attach anything other than execution details on those objects.\r\n\r\nAlso.. I have never seen a pattern where you use events to do mutations back into the thing that triggered the event. Events are traditionally to use information from event to create a side effect, or do a `preventDefault()` or `stopPropagation()`. It just looks a bit unusual... and scary... in my experience to `eventArg.doMutationInScopeOfWhatTriggeredThisEvent()`"}}