This is following on from the discussion "Private state: A contrived example". It was going to be a reply but it got rather long and seems like a large enough topic by itself.
I'm currently working on a Store service. It's an abstraction over a websocket that provides an API for persisting business models (models that are synced with a remote database), things like addFeed, removeUser etc. It also handles latency compensation --- changes are applied locally to allow immediate UX updates, messages are then sent and confirmed/rejected at the server and the local state amended accordingly. This requires that it have control over the client state of all models that are stored in the backend (so that it can handle switching between optimistic and pessimistic states). Below I've sketched out some different aspects of the Store service that I've been exploring. I've skipped over some of the implementation details to focus on the key aspects but if that's left some areas of confusion let me know and I'll flesh them out. Also bear in mind that I haven't actually implemented this yet so there may be issues that I will hit. It seems a good concrete example of a service though so hopefully we can use it to flush out some of the architectural issues with services. ## Service commands Updates in children may want to issue commands to this service, e.g. addFeed "feed one". Here's how this might work (it's similar to the approach Mark took, folding the commands in with platform commands but with platform / service commands separated out). The service is stored in the root model mainModel = { someViewState , services = { store } } commands to be sent to it are returned from update ( model , Cmd.none , [Services.storeCmd Store.addFeed "feed one"] ) Services.storeCmd returns a constructor that builds an absolute path to the store, so: Services.storeCmd == ServicesMsg StoreMsg -- absolute path from mainModel This allows the root update to route the cmd to the Store. Because each command already contains the absolute path to the service it's targeting there's no need for an equivalent to Cmd.batch, they can simply be appended. ( model , Cmd.batch [cmd1, cmd2] , childServiceCmds ++ [Services.storeMsg Store.addFeed "feed one" ] ) Information is propagated down the tree from the Store by including the required information as a param to the updates e.g. updateChild msg services.store.feeds childModel A more generalised version might be updateChild msg servicesModel childModel ## Subscriptions to Store events I'd like to allow child "components" to subscribe to events like FeedDeleted. For instance if a feed view was showing a feed that was deleted (perhaps another user deleted the feed and that message has been received over the websocket) then rather than show a blank view it would be better to switch to a different feed (with a notification at the top so the user understands what has happened). I'm not sure how I'm going to handle this yet. When events are triggered by messages received over the websocket then it's a case of mapping higher level subscriptions from the child components down to the lower level websocket subscriptions. It seems like it might be an appropriate place for an effects manager but I've been steered away from those... ## Store subscriptions to server Having written this section I'm now not sure how relevant it is to the discussion as it may be specific to the Store, I've included it anyway in case it sparks any useful thoughts. The intention is to allow child "components" to declare the information that they need. For instance assume feed viewer component has currentFeedId == 3, it wants to request that the Store subscribe to feed 3. Internally the Store has subscriptions to websocket messages. The subscriptions that it registers are based on a storeSubscriptions function that sits parallel to the usual subscriptions. Let's say there's a feed viewer which has it's current feed pointing to feed 3. It's storeSubscriptions might look like storeSubscriptions childModel [Store.FeedSubscription childModel.currentFeedId] and in the parent perhaps you'd have storeSubscriptions parentModel parentStoreSubscriptions ++ (Child.storeSubscriptions parentModel.childModel) at the top of the tree the storeSubscriptions are then passed into the Store's subscriptions mainSubscriptions model = Store.subscriptions storeSubscriptionsFromAllChildren model.store -- You received this message because you are subscribed to the Google Groups "Elm Discuss" group. To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.