I'm going to try to take the large app design questions and focus them on a more narrow and admittedly contrived example.
Say the people doing the client coding needed to be able to take a URL string and fetch a string via HTTP. (Yes, this is covered in the HTTL module. Bear with me. I'm trying to keep the example simple.) Dealing with tasks all over the place muddies up the client architecture that would otherwise focus on commands for external operations. So, we define:
getStringCommand :
(Http.Error -> msg)
-> (String -> msg)
-> String
-> Cmd msg
This is easy to write given the standard libraries.
But now it turns out we would like to execute these one at a time. We might generate any number of these commands during a single update call, but the mechanics of their execution demand that we not start the HTTP fetch for one until the HTTP fetch for the previous has finished. (I said it was contrived. Maybe we want to automatically fail subsequent commands if the first one fails.)
From what I understand of effect managers, we could write an effect manager to do this but the documentation around effect managers discourages reaching for them as a solution. They are identified as being for library writers and though this serialized string-fetcher seems a bit like a library in its usage, it also feels like a chunk of general app functionality. Or maybe the backend needs to use web sockets instead of HTTP and we would like to use the web sockets effects manager as part of the implementation.
One way to address this is to replace commands with requests, recognize string fetch requests when we reach a certain point in the model hierarchy, and process them accordingly generating commands as we move up the rest of the hierarchy. This has been covered in previous posts to the discussion list. The downside to this is that it doesn't interoperate well with code that wants to speak in terms of commands. One nice thing about effects managers is that the addressing of a command to a particular effect manager is essentially unseen by everything that handles it until we get to the app runner. Having lots of code need to switch from returning commands to returning requests is a very visible consequence of using this service that speaks via requests.
Another way to handle this is by changing update functions so that they still speak commands, but they now have a signature like:
update : Msg -> Model -> (Model, Cmd (Wrapped Msg))
We can then watch for wrapped commands and somehow unwrap the ones that really are looking for work by the sequencer code. That said, I'm waving my hands somewhat fast here and while we now continue to use commands, we don't use them in the way we're used to so I don't know that it's a big win over the requests approach.
Is there a better way to do this that I'm not seeing? The example is contrived but so are most examples. It feels like it gets at the sort of problem for which there ought to be a design pattern — i.e., structure your types and functions like this to solve this sort of problem.
Mark