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

Creating a complex business rule (exemple based on counters) #50

Closed
slorber opened this issue Dec 3, 2015 · 6 comments
Closed

Creating a complex business rule (exemple based on counters) #50

slorber opened this issue Dec 3, 2015 · 6 comments

Comments

@slorber
Copy link

slorber commented Dec 3, 2015

Hi,

I really like the ELM architecture that permits to bubble up the state and redispatch it to the tree in the update function.

Comparing it to React, this approach seems to permit to easily replace setState of React components (local state) and put that state in a global model / atom.

However now imagine my app's domain is counters.
It is composed of:

  • A pair of counters
  • A list of counters

The business rule

Now I'd like to create a new component that will display a value based on the following rule:

  • The value should increment by one on any counter increment
  • The value should decrement by one on any counter decrement
  • The value should never be more than 3 and less than 0 (if we increment with value 3, the value stays 3)

Now imagine the pair has actions:

type Action
    = Reset
    | Top Counter.Action
    | Bottom Counter.Action

And the list has actions

type Action
    = Insert
    | Remove
    | Modify ID Counter.Action

A sequence of actions like:

  • Top Increment
  • Top Increment
  • Modify 1 Increment
  • Bottom Increment
  • Bottom Increment
  • Modify 2 Decrement

The value for this sequence of action should be 2.

This business rule I want to implement might seem weird in this case but in the real world we often have to implement such rules...

The problem

How to create a model that receive these actions and then permit ti diplay that value?

The problem I see is that as the counter incrementations are "embedded" into parent actions, then we somehow need to pattern-match on the parent actions like Top / Bottom / Modify, before being able to see if the counter action is an increment.

It feels wrong to me, because tomorow if I need to add a new TupleX of counter, or Pyramid of counters or whatever with a strange shape, I don't want to have to manage additional pattern matching to compute the value I want.

If a simple "business event" (because counters is my business) increment was fired, then it would be much easier to listen to all these counter actions from any place of my app.

The idea would also be that to add my new view displaying the value I want, I should not need in any way to modify existing code normally. This is also the idea behind ideas like CQRS where you can spawn a new query view / index from an existing event log without having to rework at all the way events are emitted (because you can't erase the past anyway...)

Example needed

So here all the examples of this repository explains well how to handle local state.
However I do not see any example on how to implement real world business rules.

To compare with React I understand how to replace setState with ELM, but I don't understand (by reading this tutorial) how to replace Flux.

I think that to solve my problem described above, I should not need to have a global understanding of the app inner workins. I should be able to see there is some kind of a global/business actions union-type with Increment and Decrement, and then just decide to listen from them, without having to know if these actions are fired from a pair or list of counters because my business rule simply don't care about that.

I'm pretty sure it is largely possible to do that in ELM and an example would be welcome from an experienced ELM user :)

I'm thinking of something like this:

view : Signal.Address Action -> Signal.Address BusinessAction -> Model -> Html

Can I make the counter view fire 2 actions in 2 distinct adresses like that? So that my counter click is fired both for local state update and also global / business listening from other components?

Thanks

@evancz
Copy link
Owner

evancz commented Dec 7, 2015

Check out my response in this thread. I suspect that's what you want. If not, do you mind emailing elm-discuss with a very specific example of what you need to do? From there, we can help you with the exact code you need.

This is kind of a newer pattern, so it's not "an official thing" by any means. But I bet it'll do what you want.

@evancz evancz closed this as completed Dec 7, 2015
@slorber
Copy link
Author

slorber commented Dec 7, 2015

Hi,

If you reference the pattern update : Action -> Model -> ( Model, Effects Action, whateverYouWant ) it does not look to solve the problem I try to figure out.

The problem is not in the update function for me, the problem is that I want somehow all counter actions to be fired in a "global" way (like a global business events bus) and to be able to listen from any increment from anywhere in my app without having to touch the existing code: just plug a view and listen from increments.

I will try to make an example but I don't have time right now to do so :'(

@evancz
Copy link
Owner

evancz commented Dec 7, 2015

For some additional context, sharing this question with elm-discuss will get it in front of more people who can help out. More people read that.

Also, the thing you want to do feels like a weird thing to me. It sounds like you have a very specific scenario in mind that is motivating this line of questioning. Can you describe that scenario in the elm-discuss post? From there, I think we can help more. I feel like this may be an XY problem where the expected solution may not be the one that makes sense in Elm. I am not sure at all though! Knowing the particular scenario will help us figure out the best way to achieve it in Elm.

@slorber
Copy link
Author

slorber commented Dec 7, 2015

@evancz understood that and will post to the discuss group instead. I'd like to explain my ideas better than that before but I don't have time to do so right now.

I have 2 years of production experience with React and something similar to Flux (actually we invented flux before it come out because we have experience with event-sourcing / cqrs and ddd). What my experience show me is that generally when state is updated in a React / Flux application it is mainly for 2 reasons:

Local component update

In this case, only the component has interest in that state mutation and nobody else cares

I mean in React generally if we implement a simple counter we would put the counter value in the component state.

In ELM what I've seen so far is that an event is fired, and wrapped, and wrapped, and wrapped, goes to the "main mailbox" and then is injected into the main update function which propagates progressively to the update function of the counter, and then we redisplay everything.

I like the ELM model better as it permits to externalize the state of the counter to a model, while in the React world we would tend to couple that logic to the view framework. However ELM system has more boilerplate but I'm ok with that as the typesystem is better than javascript

Non-local component update

In this case, the counter should be able to increment its local state, but other views/widgets in the app should be able to update themselves too according to the increment events (like in the weird business rule I designed above)

With React, for that you would use Flux or something similar. To be able to support complex setups like lists of pairs of counters etc, you would probably fire increment actions with a counterId.

The same action/event can be listened from many places (or "stores"/"reducers") to update the state in multiple places.

With ELM, all the examples I've seen so far with counters only mutates the state in a single place: the local counter model. So if we need to plug a new view that depends in the counter increment events, it is not clear how ELM should be used to solve that problem. This need does not happen often in simple examples like TodoMVC or list of counters, but is a real need in complex applications

Imagine a setup like that, where what I want to do is add that weird-count-value div:

<div class="list-of-list-of-counters"></div>
   <div class="list-of-counters">...</div>
   <div class="list-of-counters">...</div>
   <div class="list-of-counters">...</div>
</div>
<div class="pair-of-counters"></div>
   ...
</div>
<div class="weird-count-value"></div>
   something between 0 and 3, based on the interactions with the counters
</div>

I understand that in the ELM counter examples, the events are actually bubbling to the "main mailbox" (at least it is how I suppose StartApp.Simple works). So I guess anyone can plug a new update function here to listen to the events (or signals) and compute a new model called WeirdCountValue from the signals.

However in the examples I've seen, the thing bothering me is that when the mailbox receives the signal they are wrapped, so if I want to know how much increments there are globally in my app (for example), I have to unwrap the actions (sometimes multiple time) before even knowing it's an increment or decrement.

In CQRS / eventsourcing the point is that you should be able to look at some kind of global event log and be able to compute new models from that, without having to understand the implementation details of the existing app. The WeirdCountValue model should not have to assume anything from how the counters are weirdly nested in my app, but should just be able to listen for increments and decrements.

This stuff is easy with Flux because we don't wrap actions as they bubble to the top so we can just plug a new listener like that:

var weirdCountValue = 0;

SomeFluxEventBus.listen(function(event) {
   if ( event.name === "INCREMENT_COUNTER" ) {
      if ( weirdCountValue < 3 ) {
          weirdCountValue = weirdCountValue + 1;
      } 
   }
   if ( event.name === "DECREMENT_COUNTER" ) {
      if ( weirdCountValue > 0 ) {
          weirdCountValue = weirdCountValue - 1;
      } 
   }
});

I would like to do the same with any counter example you've done but I don't know which is the idiomatic way.

I thing the wrapping of actions is good for local state but not really helpful it the app model should be updated in multiple places.

Summary

What I think I want is a mailbox that receives directly actions like Increment or Decrement instead of wrapped actions.

Does it make any sense? What is the idiomatic way to achieve that in ELM?

@mbylstra
Copy link

@sorber did you post this to elm-discuss? Could you please paste a link to the thread here if you did?

@slorber
Copy link
Author

slorber commented Feb 29, 2016

@mbylstra no I did not. Don't hesitate to do so if you want and i'll be happy to join the conversation.

Also note some interesting discussions going on there: jarvisaoieong/redux-architecture#1

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

3 participants