I re-read your message, Erik, and my reply and realized that I hadn't
answered all of the questions. The routing I've been working with handles
both commands created by the client side and handled by the service side
and commands created by the client and handled by "normal" external means.
This gets addressed through the type declaration:

type ClientCommand msg
    = Batch (List (ClientCommand msg))
    | PlatformCmd (Platform.Cmd msg)
    | APIFromClient (APICommand msg)


The key point here is in having the platform and API commands split out so
that they can be mapped differently. From the client standpoint, if you can
make a Cmd, then you can call ClientCommand.fromCmd and turn it into a
ClientCommand which can then be batched with API commands. At the point
where the "client" meets the "service" in the model, these get turned back
into Platform.Cmd's (through Task.succeed to set up the delivery of the the
APICommand value to the service).

If I could figure out how to do this with tasks, that might well be simpler
and easier to plug in. I may need to take a pass at that again.

Mark

P.S. I don't really like the words "client" and "service" but they seemed
better at least than "client" and "server" since the latter would suggest
code running on another machine which while this separation enables that
possibility is not something that is happening here. I thought about
"viewModel" and "dataModel" but that seemed to have a certain amount of
verbal noise in saying "model" everywhere.

On Sun, Aug 28, 2016 at 7:52 PM, Erik Lott <mrerikl...@gmail.com> wrote:

> Mark, tell me about this code:
>
> type APICommand msg
>     = AddForum (Error -> msg) (ForumID -> msg) String
>     | DeleteForum (Error -> msg) (() -> msg) ForumID
>     | PostMessage (Error -> msg) (() -> msg) ForumID String
>
>
> type APISubscription msg
>     = ForumList (List (ForumID, String) -> msg)
>     | MessageList (Array.Array String -> msg) ForumID
>
>
> Conceptually, I appreciate what you're trying to do here (create a simple
> system driven by domain messages - AddForum, DeleteForum, etc) , but the
> actual implementation worries me. I'm wondering why you're not simply
> creating an API module instead:
>
> module API exposing(addForum, deleteForum, postMessage)
>
> addForum: String -> Task Http.Error Forum
> // code here...
>
> deleteForum: String -> Task Http.Error Never
> // code here
>
> etc
>
>
> I know that you mentioned that you have some additional complexity (task
> queue, websockets,etc), but I haven't actually seen anything that would
> cause me to abandon the built-in command/subscriptions system. What if you
> need to do something in the page that is not necessarily domain related,
> but requires a task/command - e.g. generate a random number? Are these
> types of situations factored into your design?
>
>
>
>
> On Saturday, August 27, 2016 at 6:14:41 PM UTC-4, Mark Hamburg wrote:
>
>> I'm working on some example code. I'm varying between a moderately
>> fleshed out example of the plumbing but with little backing it up and a
>> fully worked but trivial to the point of not being interesting example. The
>> general model, however, works as follows:
>>
>> • We have a service model and a client model. The idea is that after
>> agreeing on an API, these can then undergo independent development.
>> Furthermore, we should be able to do things like mock the service rapidly
>> and then fill it in with a version that talks to the web service or
>> whatever is needed. Similarly, we could mock the client side quickly if we
>> just wanted to test the service APIs.
>>
>> • The API is represented by a union type: APICommand clientMsg. A
>> typical entry would be something like:
>>
>> | PostMessage (Error -> clientMsg) (() -> clientMsg) ForumId String
>>
>>
>>
>> • APICommand clientMsg is wrapped up inside a ClientCommand clientMsg type
>> that provides standard Cmd-style functionality (batch, map). It also
>> provides a way to embed non-API commands of type Cmd clientMsg. I would
>> have liked to just use a different type of message within Cmd to do this
>> rather than reconstructing map and batch — trivial though they may be —
>> but mapping for platform commands is built around tagging results as they
>> come back and we need to adjust the tagging functions on API commands
>> before they get to the service.
>>
>> • The update function for the client has the signature:
>>
>> clientMsg -> clientModel -> (clientModel, ClientCommand clientMsg)
>>
>> This gets applied at the "root" level when processing messages destined
>> for the client. On the way back, it can turn the client commands into
>> platform commands of type Cmd (RootMsg serviceMsg clientMsg) where the
>> RootMsg type provides for messages bound for the client, messages bound
>> for the service, and API commands bound for the service.
>>
>> • The service processes both its own messages and API commands (in
>> separate update functions in my latest code). Service updates return 
>> (serviceModel,
>> ServiceCommand serviceMsg clientMsg) where ServiceCommand again mirrors
>> Cmd in terms of providing batch and map support. (This could actually be
>> represented as a Cmd (Either serviceMsg clientMsg) but I'm not sure that
>> leveraging Cmd in that case provides more clarity.)
>>
>> • The root update function again maps the service commands appropriately
>> to generate commands of type Cmd (RootMsg serviceMsg clientMsg) which
>> route to the service and client as appropriate.
>>
>> The amount of code is actually pretty small and as discussed above, it
>> provides a very nice separation of concerns between the client side and the
>> server side and allows each to be coded following standard Elm architecture
>> patterns with the caveat that one doesn't actually use Cmd during
>> propagation.
>>
>> So, with that, I would be happy were it not for the fact that it's dawned
>> on me that subscriptions are going to be more difficult because I can't
>> simply wait for the runtime to ask the root model for subscriptions and
>> then plumb it appropriately. This is because the service model will almost
>> certainly need to adjust its state based on the client subscription
>> requests — see some effects managers for examples — and when asked for
>> subscriptions we can't update the root model. So, I will need to perform an
>> internal subscription update after every client model update. This isn't
>> fatal but I could imagine it being inefficient. I could also send a strobe
>> to do this but that might introduce more delay than I'd like. That said,
>> I've wondered whether subscriptions need lazy support akin to what we have
>> for views.
>>
>> Mark
>>
>> P.S. The heart of this work was in making it possible to write an API
>> like the following:
>>
>> type Error
>>     = NoPermission
>>     | NoConnection
>>     | ServiceFailure
>>
>>
>> type ForumID = ForumID String
>>
>>
>> type APICommand msg
>>     = AddForum (Error -> msg) (ForumID -> msg) String
>>     | DeleteForum (Error -> msg) (() -> msg) ForumID
>>     | PostMessage (Error -> msg) (() -> msg) ForumID String
>>
>>
>> type APISubscription msg
>>     = ForumList (List (ForumID, String) -> msg)
>>     | MessageList (Array.Array String -> msg) ForumID
>>
>> Everything else was just the work to allow the client side and the
>> service side to hew as closely as possible to the Elm architecture while
>> getting the appropriate interaction between the two.
>>
>>
>>
>> On Fri, Aug 26, 2016 at 11:55 PM, Charles-Edouard Cady <
>> charlesed...@gmail.com> wrote:
>>
>>> Would it be possible in your architecture to  perform server requests at
>>> the highest level of your application? You would then send a a->Msg to each
>>> sub component. That way server requests would only be performed in one
>>> place where you could fine-tune how you want them performed.
>>> This comes from Richard's answer to a question I had on composability.
>>> Components should only update their own private non-shared state that other
>>> components don't need. Shared states such as your server queue are updated
>>> at the highest level and each sub component merely receives a function to
>>> run the update.
>>> Does that make sense in your context?
>>>
>>> --
>>> 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...@googlegroups.com.
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>
>> --
> 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.
>

-- 
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