Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

thoughts on modularity, real re-use, app-wide coordination #66

Closed
ghost opened this issue Feb 21, 2016 · 4 comments
Closed

thoughts on modularity, real re-use, app-wide coordination #66

ghost opened this issue Feb 21, 2016 · 4 comments

Comments

@ghost
Copy link

ghost commented Feb 21, 2016

Hi Elm community,

This is not an issue, just a discussion I would like to have on building modular, well-defined applications under the elm architecture. I will try to keep it brief, which will require a lot of hand-waving.

The most important pieces of any application architecture I have implemented always seem to turn out the same:

  1. modularity / re-use with good separation of concerns
  2. the ability to clearly and predictably communicate between components
  3. the ability to concisely and predictably coordinate multiple components to perform major context switches / transitions.

These three pieces enormously impact the refactor-ability of the codebase, the velocity of developers in that codebase, and the lifespan/maintainability of the entire application.

Modularity / Re-use

Ideally an entire application is built from the bottom up from small, loosely coupled, reusable components. If these components can be completely unaware of their place in an application, and only serve a small, defined purpose with a minimal api surface, you are on the right track to a highly maintainable and refactor-able code base.

Clear and predictable communication

If each component is modular, you can build up a tree hierarchy of components where communication between depths or leaves or branches happens only across a small interface at defined intersections. No matter the form of communication - be it a flux architecture, a messaging bus, or pub/sub, this "bounded" communication also keeps you on the right track to a highly maintainable and refactor-able code base.

The ability to coordinate context switches

Most apps have major contextual chunks that form concrete or logical boundaries in which to partition your components. Sometimes this is as simple as main navigation tabs, and sometimes it is more nuanced (consider following a deep link to a different part of your application). If your architecture does not offer a way to reliably and conveniently swap between these contexts, you are going to end up with a lot of fragile code that has to be hand-held through state re-hydration, serialization, branching within contexts, and all sorts of other tedious and error-prone bandaids.

Where does elm fit in?

I have been fooling around with the elm architecture for a little bit, and have tried to familiarize myself with its strengths and weaknesses. It seems relatively similar to the redux pattern (immutable, explicit top->bottom action flow, predictability) with perhaps the major difference being that truly reusable components are much simpler.

Modularity

Using Signals and forwardTo, parents provide their reusable children the information necessary to "just work" so that children can be completely unaware of the rest of the application. There are plenty of (mostly) clear examples where this can be extended to allow lists of lists of entirely dynamic reusable components (the "Model with a context" example is still pretty unclear to me).

One issue I bumped into almost immediately using this pattern is a similar but slightly more advanced use case: when a parent does not merely want to forward its child's actions, but is interested in a subset of those actions.

For example, consider a simple chat interface with a MessageBox , a Post, and a Chat component to hold everything together. What I want to be able to do is pipe InputChanged into the MessageBox without needing to know the details, but when MessageSent appears, capture some details about that action inside of the parent. A rough sketch would be something like...

type Action
    = MessageBoxAction MessageBox.Action

update : Action -> Model -> Model
update act model =
    case act of
        MessageBoxAction msg ->
            case msg of
                -- for a particular action, do something before forwarding it on to the child
                MessageBox.Action.MessageSent ->
                    let
                        updatedMessage = MessageBox.update msg model.currentMessage
                        newPost = Post.init msg model.posts
                    in
                        { model |
                            posts = model.posts :: [ newPost ]
                            , currentMessage = updatedMessage
                        }
                -- for any unspecified actions, just forward to the child
                _ ->
                    { model |
                        currentMessage = MessageBox.update nested model.currentMessage
                    }

This won't compile. I can think of a few other ways to achieve the same end, but none of them are a good idea because they either burden the reusable component with unnecessary requirements or end up with circular and difficult-to-trace "on update do another update" patterns.

I feel like if a cleaner general pattern can be reached (and it might be simple and I'm just missing it), then truly reusable components are actually possible in elm.

Communication

This is pretty inherit in elm. Everything is explicit, and the communication lines are well drawn down a component hierarchy. The biggest challenge I believe will be maintaining a small API surface across components. Sometimes the temptation is strong to update on actions that probably aren't any of your component's business. This eventually carves out an application where changing one action name, or removing a single component can break everything in that hierarchy subtree.

            root
        a           b

    c   d         e   f    g

    h   i          j   k   l

"Update H" needs to be piped from a -> c -> h. In a naive implementation, changing the action to "Update h" requires also changing a and c. I think elm avoids this through the use of a generic "On some child action just pipe the message through and update the model".

Context

I have no idea how elm might handle something like this. I believe the excellent work done over at redux-saga handles context switching in one of the most elegant ways I have seen. Because redux, like the elm architecture, is basically an event-sourced architecture, the concept of sagas "just works". When SOME_TRANSITIONING_EVENT arrives, you can utilize the api of components to predictably transition the application step by step.

The problem with sagas, though, is they are by-nature imperative and not functional. It would be great to hear some thoughts on how the elm architecture handles this case.

@BenBals
Copy link

BenBals commented Mar 3, 2016

As you have seen with MessageBoxAction msg an action can hold a value. Why not make MessageBox.Action.MessageSent hold the send message as a string? But I myself am very new to Elm too, so feel free to submit better solutions.

@romdarom
Copy link

romdarom commented Jun 21, 2016

Hi, i'm trying to build websites using elm.
As far as modularity is concerned, Why not allow simple communications between modules, like subscriptions do?
This would allow one to build apps bottom up.
For instance, i want an input component, and an "accumulator of inputs" component
When enter is hit, the whole input is piped to listening components ...
The Top down approach will have the "supervisor" (Top most component) have to store a lot of information ...
If the hierrachy is deeply nested, then it might get tedious to maintain "sense" in that top most component ...
What you guys think?

@q13
Copy link

q13 commented Aug 26, 2016

Expect more discussion.

@evancz
Copy link
Owner

evancz commented Sep 4, 2016

Here's the beginning of advice on this kind of stuff: http://guide.elm-lang.org/reuse/

We will have more resources coming in the next few months, but that is the gist. If you are thinking about components, if you are thinking about parents and children and communication, something has already gone wrong.

Also, this community uses forums like elm-discuss and the Elm slack for discussions like this. Issues are for tracking concrete problems and work, not for discussions.

@evancz evancz closed this as completed Sep 4, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants