{"action":"created","issue":{"url":"https://api.github.com/repos/yelouafi/redux-saga/issues/8","labels_url":"https://api.github.com/repos/yelouafi/redux-saga/issues/8/labels{/name}","comments_url":"https://api.github.com/repos/yelouafi/redux-saga/issues/8/comments","events_url":"https://api.github.com/repos/yelouafi/redux-saga/issues/8/events","html_url":"https://github.com/yelouafi/redux-saga/issues/8","id":123459831,"number":8,"title":"Sagas should rather be totally autonomous","user":{"login":"slorber","id":749374,"avatar_url":"https://avatars.githubusercontent.com/u/749374?v=3","gravatar_id":"","url":"https://api.github.com/users/slorber","html_url":"https://github.com/slorber","followers_url":"https://api.github.com/users/slorber/followers","following_url":"https://api.github.com/users/slorber/following{/other_user}","gists_url":"https://api.github.com/users/slorber/gists{/gist_id}","starred_url":"https://api.github.com/users/slorber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/slorber/subscriptions","organizations_url":"https://api.github.com/users/slorber/orgs","repos_url":"https://api.github.com/users/slorber/repos","events_url":"https://api.github.com/users/slorber/events{/privacy}","received_events_url":"https://api.github.com/users/slorber/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":6,"created_at":"2015-12-22T11:03:37Z","updated_at":"2015-12-24T11:35:00Z","closed_at":null,"body":"Hello,\r\n\r\nI've seen the real world where some sagas need to be stateful to know if the data needs to be fetched or not:\r\n\r\n```\r\nexport default function* root(getState) {\r\n\r\n  const getUser = login => getState().entities.users[login]\r\n  const getRepo = fullName => getState().entities.repos[fullName]\r\n  const getStarredByUser = login => getState().pagination.starredByUser[login]\r\n  const getStargazersByRepo = fullName => getState().pagination.stargazersByRepo[fullName]\r\n\r\n  yield fork(watchNavigate)\r\n  yield fork(watchLoadUserPage, getUser, getStarredByUser)\r\n  yield fork(watchLoadRepoPage, getRepo, getStargazersByRepo)\r\n  yield fork(watchLoadMoreStarred, getStarredByUser)\r\n  yield fork(watchLoadMoreStargazers, getStargazersByRepo)\r\n}\r\n\r\n// Fetches data for a User : user data + starred repos\r\nfunction* watchLoadUserPage(getUser, getStarredByUser) {\r\n  while(true) {\r\n    const {login, requiredFields = []} = yield take(actions.LOAD_USER_PAGE)\r\n\r\n    yield fork(loadUser, login, getUser(login), requiredFields)\r\n    yield fork(loadStarred, login, getStarredByUser(login))\r\n  }\r\n}\r\n\r\n// load user unless it is cached\r\nfunction* loadUser(login, user, requiredFields) {\r\n  if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) {\r\n    yield call(fetchUser, login)\r\n  }\r\n}\r\n\r\n// load next page of repos starred by this user unless it is cached\r\nfunction* loadStarred(login, starredByUser = {}, loadMore) {\r\n  if (!starredByUser.pageCount || loadMore)\r\n    yield call(\r\n      fetchStarred,\r\n      login,\r\n      starredByUser.nextPageUrl || firstPageStarredUrl(login)\r\n    )\r\n}\r\n```\r\n\r\n\r\nI think we already discussed that but I think the Saga should be a totally autonomous process that listen for events and perform effects.\r\n\r\nThe problem here for me is that `getState().entities.users[login]` is actually a state that has the purpose of being displayed to the UI, as it is computed by Redux reducers. So basically you are coupling the way a Saga may perform effects to the UI state. Your saga is not really stateful, but it can use state provided by a dependency (the UI state).\r\n\r\nI think the Saga should not know anything about the UI state at all. Refactoring the layout of the UI state should not need to perform any modification to the saga logic.\r\n\r\nIn backend systems, sagas can be distributed across a cluster of machines, and the saga can't really (or efficiently) query synchronously the state of the app as it may be stored on other machines. That's why Sagas are stateful and decoupled on the backend.\r\n\r\nMaybe we should not force the user to use this decoupling as it introduces more complexity, but at least give the opportunity for the Saga to really be stateful, instead of reusing the UI state provided by getState. A simple possibility would be to register a reducer to the Saga for example.\r\n\r\n\r\nSee for example a saga implemented in Java here: http://www.axonframework.org/docs/2.0/sagas.html\r\n\r\n```java\r\npublic class OrderManagementSaga extends AbstractAnnotatedSaga {\r\n\r\n    private boolean paid = false;\r\n    private boolean delivered = false;\r\n    private transient CommandGateway commandGateway;\r\n\r\n    @StartSaga\r\n    @SagaEventHandler(associationProperty = \"orderId\")\r\n    public void handle(OrderCreatedEvent event) {\r\n        // client generated identifiers (1)\r\n        ShippingId shipmentId = createShipmentId();\r\n        InvoiceId invoiceId = createInvoiceId();\r\n        // associate the Saga with these values, before sending the commands (2)\r\n        associateWith(\"shipmentId\", shipmentId);\r\n        associateWith(\"invoiceId\", invoiceId);\r\n        // send the commands\r\n        commandGateway.send(new PrepareShippingCommand(...));\r\n        commandGateway.send(new CreateInvoiceCommand(...));\r\n    }\r\n\r\n    @SagaEventHandler(associationProperty = \"shipmentId\")\r\n    public void handle(ShippingArrivedEvent event) {\r\n        delivered = true;\r\n        if (paid) {\r\n            end(); (3)\r\n        }\r\n    }\r\n\r\n    @SagaEventHandler(associationProperty = \"invoiceId\")\r\n    public void handle(InvoicePaidEvent event) {\r\n        paid = true;\r\n        if (delivered) {\r\n            end(); (4)\r\n        }\r\n    }\r\n\r\n    // ...\r\n\r\n}\r\n```\r\n\r\nAs you can see, the OrderManagementSaga is created after every OrderCreatedEvent (so many OrderManagementSaga can live at the same time in the system, but this probably does not apply to frontend sagas). These sagas are stateful and have the `paid` and `delivered` attributes.\r\n\r\nThis is just the Saga code, but you can guess that there's in the system another item called `Shippement` that stores an attribute `delivred`.\r\n\r\nThis may seem surprising but it is not a problem if the global system stores the same data in multiple places. Each place can pick the data it needs from the events. This permits to avoid introducing new dependencies. The only real shared dependency all the components have is the event log.\r\n\r\nThe current approach of using Redux' getState() in Sagas for me is a bit similar to using `waitFor` of Flux. It works but creates coupling that can be avoided.\r\n\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/yelouafi/redux-saga/issues/comments/167096508","html_url":"https://github.com/yelouafi/redux-saga/issues/8#issuecomment-167096508","issue_url":"https://api.github.com/repos/yelouafi/redux-saga/issues/8","id":167096508,"user":{"login":"slorber","id":749374,"avatar_url":"https://avatars.githubusercontent.com/u/749374?v=3","gravatar_id":"","url":"https://api.github.com/users/slorber","html_url":"https://github.com/slorber","followers_url":"https://api.github.com/users/slorber/followers","following_url":"https://api.github.com/users/slorber/following{/other_user}","gists_url":"https://api.github.com/users/slorber/gists{/gist_id}","starred_url":"https://api.github.com/users/slorber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/slorber/subscriptions","organizations_url":"https://api.github.com/users/slorber/orgs","repos_url":"https://api.github.com/users/slorber/repos","events_url":"https://api.github.com/users/slorber/events{/privacy}","received_events_url":"https://api.github.com/users/slorber/received_events","type":"User","site_admin":false},"created_at":"2015-12-24T11:35:00Z","updated_at":"2015-12-24T11:35:00Z","body":"@timdorr it is not because it's written in the doc in a simple way to make it easy to understand for event-sourcing new-comers that is it an absolute truth :)\r\n\r\nBrowsers and backend systems are not so different: they manage state. The main difference is that the frontend receives the user intent synchronously so it generally handles that intent based on an up-to-date state. I'm pretty sure frontend and backend will be more and more similar in the future, and don't forget than @gaearon has also been influcend by the [Turning the database inside out](https://medium.com/@dan_abramov/my-inspiration-ce454ab65f33) talk which is about backend primarily :)\r\n\r\n\r\n\r\n> Your UI is simply a function on state, i.e. React(state) = view. Replaying an event log to compute that view doesn't make any sense. You should let your state container (Redux) handle that computation of final state so that React can render it.\r\n\r\nAbsolutely not. It does make a lot of sense and it permits to implement features like time-travel. You know what, backend guys are doing time-travel for decades :) The saga concept itself is from the backend / eventsourcing world.\r\n\r\nInstead of thinking `React(state) = view`, you should consider `React(Redux(eventlog)) = view`\r\n\r\nIf Redux is claimed to be the source of truth it is probably to be simpler to understand, but Redux treats itself the event-log as the source of truth. The beauty of this is that you can use this event log for many other usages:\r\n\r\n- You can sync 2 Redux stores that are on 2 different browser (for example imagine someone taking remote control of your local redux app for assistance...)\r\n- You can project that event log in other systems\r\n- You can send that event log to the backend and compute bigdata statistics based in UI usage\r\n- so many possibilities...\r\n\r\n> Absolutely! You may have non-visible state that needs to be managed. Take analytics data for instance. You might collect that into your state to occasionally ship back to your server.\r\n\r\nIf you ship the event log to the server directly instead of computing the analytics on the client, you are still able to implement reducers in the backend to compute these analytics (in the language of your choice btw!). You never loose any data. \r\n\r\nIf you have an app in production for 1 year, and you want to introduce a new analytics that count the TodoCreated actions for a given user. If you compute the analytics on the frontend, then you will start with a counter value = 0. If you ship the event log to the backend, and want to introduce that statistic, you have 1 year of historical event-log to compute a counter value: you don't start at 0 but you have your new stat instantatenously!\r\n\r\nRedux is just a system to project an event-log (source of truth) into an easy-to-comsume state (projection of source of truth) for view applications like React. Nothing forces you to use a single projection at a time of your event log.\r\n"}}