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.

Reply via email to