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 [email protected].
For more options, visit https://groups.google.com/d/optout.