These are chat archives for jdubray/sam

28th
May 2017
Johan Alkstål
@johanalkstal
May 28 2017 08:01 UTC
Something I'm wondering about is model.present, and a good pattern for it to know what is being presented, in order to evaluate the validity of the presented data.
Any thoughts?
Slađan Ristić
@sladiri
May 28 2017 09:39 UTC

@johanalkstal JJ talked about passing in a hint to the model, if it is absolutely required. I have not yet build more than a small proof of concept with SAM, but this might be related to how you think about the Interface to your app. Maybe it is more complicated than my naive view:

For example, take an app with two buttons and their actions: increase => 1, decrease => -1. Maybe one might be tempted to want to check inside the model, that an increase only presents positive values, but this is not the responsibility of the model here. The model could check, if the presented value (or its sum with the model's value) exceeded some limit, and reject it then.

So the model is not concerned with the user clicking the wrong button or entering an invalid negative amount for increase, that would be a concern for validation and the action maybe? @jdubray
What I tried was to give the hint inside the user's UI, so validate the input, or disable actions/buttons when required. The user is then informed that an action would be rejected. Should the user force an "invalid" action, "never trust the user input", then the model would be there to reject it still.

These UI hints seem to "break" a principle of SAM maybe, that that the presenter should not decide what the model would do with a value. In this case, preventing client action because of invalid input, which is checked in the UI instead of the model. But it looks like a usability feature to do it anyway.

But maybe I missed your usecase. :)

Daniel Neveux
@dagatsoin
May 28 2017 09:54 UTC
@sladiri I think @johanalkstal wants to now how to detect which type of data is presented.
For this, you can use a switch:
1- The action present {type: INCREASE, amount}
2- The model.present function is like
const present = data => {
  // By using a switch you can ensure to get the correct data type
  switch(data.type){
    case INCREASE: 
      //do stuff
     break;

    case DECREASE: 
      //do stuff
     break;

    case RESET: 
      //do stuff
     break;

  }
}
Slađan Ristić
@sladiri
May 28 2017 10:02 UTC
I see, I think JJ called this unit-of-work too.
It looks a bit "odd" (in SAM) and reminds me of Redux, but I guess there are situations where you cannot avoid this pattern?
Slađan Ristić
@sladiri
May 28 2017 10:16 UTC
To be more clear, the model should not be responsible to distinguish between these cases, but the different actions would be that distinction. Each action would ultimately present values to the model, which would check them to preserve its own consistency ("only" do CRUD), rather than to check what it means to increase for example.
That is what I understood as ideomatic SAM, if it helps. :)
Daniel Neveux
@dagatsoin
May 28 2017 10:18 UTC

to distinguish between these cases

You mean the different case of the switch ?

Slađan Ristić
@sladiri
May 28 2017 10:18 UTC
@dagatsoin Yes
Daniel Neveux
@dagatsoin
May 28 2017 10:22 UTC
I don't understand why the model should not do the distinction between those cases. It is just a kind of casting and can't be done in the action.
Slađan Ristić
@sladiri
May 28 2017 10:38 UTC
I thought that this would be a separation of concern which SAM gives you. But rules are meant to be broken, I still have to try out SAM more.
Daniel Neveux
@dagatsoin
May 28 2017 11:17 UTC

I will try to explain my point of view:

Take a role playing game:

  • The model is a Creature. It can be hit or healed.
  • The way it is hit or healed is not important for the model. It could be done with a potion, a distant weapon, a close attack, etc. It is the concern of the Action.

This is cool because you can write as many action as you need: DrinkPotion, HitWithCloseWeapon, HitWithDistantWeapon, etc...
but on the model side, you need a mechanism at some point to help the model understand the only two things is care about:

  1. what is happening to him (healed or hit)
  2. how much it will hit or healed. Say healthPoints or damagePoints

So that means that could write different types of data that are presented. Such as

{
  type: HEAL_WITH_POTION,
  healthPoints: 34
}
Daniel Neveux
@dagatsoin
May 28 2017 11:29 UTC
So the separation of concern is respected as the action does not know what a model is and vis versa.
Slađan Ristić
@sladiri
May 28 2017 11:37 UTC

Hm, I would try to define heal like { healthDelta: 34, someItemId: 'foo' } (but I have not built a game like this). Then the model would just check, do we have the item required for a positive healthDelta for example, and probably maintain a maximum hitpoint value.

I guess again, the case distinction or casting would not absent, but a bit different inside the model like this. It is a slightly different pattern maybe:
if (healthDelta && someItemId) { ... remove item and change health }

This is different in that the action "knows more" about the model, but the separation as I understood is preventing the action from directly setting the value in the model, but just proposing them.

Slađan Ristić
@sladiri
May 28 2017 11:54 UTC
Heal should actually be just { healthDelta: 50 } and the model would then check if there is an item to heal for that amount, and remove it too. You could distinguish by the number if it is a hit, without needing to maintain switch case strings. A magic-heal could be distinguished by being healthDelta: 50, manaDelta: -10 }.
Johan Alkstål
@johanalkstal
May 28 2017 12:26 UTC
@dagatsoin was correct about the question I was asking. if an action sends the a presented value, the model has to somehow know what part of the model is this value relevant for, in order to know what to do with it (validate/accept/reject). Some kind of information like a Flux-like action is needed, { type: ACTION_TYPE, payload: value }. Or specific model.present functions for different actions. model.presentUser.
Even if the value is an object of { healthDelta: 20, manaDelta: -10 }, it requires an analysis of the value shape, in order to know what to do with it.
So some "hint" seems necessary.
Daniel Neveux
@dagatsoin
May 28 2017 12:28 UTC

it requires an analysis of the value shape, in order to know what to do with it.

It is the point

Johan Alkstål
@johanalkstal
May 28 2017 12:31 UTC
Are you folks experimenting with vanilla JS, or something else? :)
Daniel Neveux
@dagatsoin
May 28 2017 12:36 UTC
I use Typescript
Daniel Neveux
@dagatsoin
May 28 2017 12:54 UTC

@sladiri
indeed there is two main ways to present data to the model:

  • use typed structures data
  • use specific functions

The advantages of using structures (it is the command pattern, is not?):

  • you can serialize the args and send it to the server to have only actions on client and model on the server. Or to syncing purpose (all multiplayer games use this convention for syncing client/server)
  • you keep a strong separation between actions/model because the API surface is very thin (the present function)

Using specific function could work but the api is way larger (model.presentPotionToUser, model.presentHitToUser, etc.), so more chance to break during updates.

Johan Alkstål
@johanalkstal
May 28 2017 12:54 UTC
Yes I feel the same way.
Jean-Jacques Dubray
@jdubray
May 28 2017 12:56 UTC
@johanalkstal @sladiri TLA+ states that an action should return exact property values you want the model to accept. It should be like a yes/no decision. I think that's the right approach, the model should be focused on integrity of its state and not concerned why it got there (event, validation, transformation, enrichment which are the responsibility of the action). There is a side benefit of presenting such proposal, an action can trigger multiple acceptors compared to a direct call where the action knows exactly which acceptor it wants to invoke (vuex, redux, elm...). I know that style is not popular but I believe that's the correct interaction between actions and model/mutation.
I would relax a bit the constraint that the proposal must contain exact property values. It's ok if the model does a bit of computation/transformation before it accepts or rejects the new property values. It's more important to decouple the event from the model (why the model changes vs how the model changes), that aspect should not be compromised.
as @sladiri any "additive" operation cannot be represented as a proposal, you have to create an "additive" proposal. I don't think there is any way around that.
I do pass the action name with the proposal, but I am yet to find a case where I need it.
Jean-Jacques Dubray
@jdubray
May 28 2017 13:03 UTC

So the model is not concerned with the user clicking the wrong button or entering an invalid negative amount for increase, that would be a concern for validation and the action maybe?

yes, that being said, you may find a bit challenging that the action cannot query the model, you have to make sure that all data that an action will need to validate/transform the event will be passed via the view as V=f(M), that's because SAM is based on a well defined "step" (event->Action->Model->State->view) where the mutation is synchronized, so it would be a gross violation of the pattern is the action could access the current value of the model. It can only access the values from the last known state representation.

The user is then informed that an action would be rejected.

yes, that's the correct way to do it, there is no point in trying invoking an action if you know it's not allowed, it's very different from the model accepting/rejecting property values. In no ways the two intersect, because SAM has a well-defined state representation, you have an ability to infer which action are allowed in that state representation. However, due to concurrency issues, an action maybe triggered based on a stale state representation. SAM-SAFE deals with all these concerns.

Johan Alkstål
@johanalkstal
May 28 2017 13:06 UTC
@jdubray My question was more about "how does the model know what the context is for the yes/no decision"? Simple example, action returns { email: 'joe@mail.com' }, is it an email change? Is it a new user? What's the context? How do you think that question should be handled?
Jean-Jacques Dubray
@jdubray
May 28 2017 13:06 UTC

These UI hints seem to "break" a principle of SAM maybe, that that the presenter should not decide what the model would do with a value.

Not at all, that's a completely different concern. State Representation controls which actions are allowed or not. The model operates like a database and makes sure at any time the integrity of the property values are not compromised.

@johanalkstal The model is the only piece of code allowed to do any kind of application state mutation, so the context is the model.
In your example, the proposal's semantics have to be as clear as possible to trigger the right behavior. As I mentioned "additive" operation (+1, add user,...) need to be specific
It's not ok for the action to propose the new value of a counter, when you just try to increment it.
Johan Alkstål
@johanalkstal
May 28 2017 13:09 UTC
And how are those operations specified?
Jean-Jacques Dubray
@jdubray
May 28 2017 13:09 UTC
The purpose of the action is to decouple the events from the model, but you cannot decouple the actions from the model. That being said, the coupling should allow for a many-to-many relationship between actions and acceptors
Johan Alkstål
@johanalkstal
May 28 2017 13:10 UTC
Continuing on the example the user wants to update their email?
Jean-Jacques Dubray
@jdubray
May 28 2017 13:10 UTC
@johanalkstal they are specified as {incrementBy:+1}
Johan Alkstål
@johanalkstal
May 28 2017 13:11 UTC
Okay. So the action value could be { newEmail: 'joe@mail.com' } and the model looks at the shape of the value, sees the newEmail key and validates the email value?
Jean-Jacques Dubray
@jdubray
May 28 2017 13:11 UTC
All my acceptors start with a condition like:
if (proposal.email) { ... }
as I said, people generally don't like this style, though I believe it's the most appropriate one for the action-model interface
Johan Alkstål
@johanalkstal
May 28 2017 13:12 UTC
Just trying to understand the API of actions and acceptors.
Jean-Jacques Dubray
@jdubray
May 28 2017 13:12 UTC
The advantage of using a proposal like email or newEmail is that many acceptors can respond to that one (e.g. the primary email acceptor). If the user don't have a primary email yet, the newEmail value will be assigned automatically
@johanalkstal it's ok to ask questions!
Don't take my answers for the gospel, happy to hear criticism. I just speak from experience. I have been applying the pattern for more than a year on big projects and I feel comfortable with my recommandation.
but there might be better ways.
Johan Alkstål
@johanalkstal
May 28 2017 13:14 UTC
I'm at a level of seeking understanding. :) Not knowledgable enough yet to come with critique. ;)
Jean-Jacques Dubray
@jdubray
May 28 2017 13:15 UTC
For me the interface between action and model works a bit like SQL, actions gives "data commands" to the model and the model decides ultimately what to do.
Johan Alkstål
@johanalkstal
May 28 2017 13:15 UTC
You mention "acceptors" in plural. Does that mean you have multiple equivalencies of model.present?
Jean-Jacques Dubray
@jdubray
May 28 2017 13:15 UTC
SQL has similar issues with update/insert
No there is only one entry point to a model, though it's ok for an action to present the proposal to several model (that's really cool!)
It's heavy to have a fully query language, but the spirit of the interface should be similar.
I talk about acceptors as a way to modularize your model code.
"updates" are acceptors
The model code assembles all the acceptors and executes them in sequence, like a pipeline: https://github.com/jdubray/startbootstrap-clean-blog/blob/master/sam/model.js
Jean-Jacques Dubray
@jdubray
May 28 2017 13:20 UTC
that's one way to achieve the many-to-many relationship.
Johan Alkstål
@johanalkstal
May 28 2017 13:20 UTC
I'm using a basic scenario for experimenting with SAM and using a single state tree. It's a basic login form.
{
  isAuthenticated: false,
  loginForm: {
    email: '',
    invalid: false
  }
}
Jean-Jacques Dubray
@jdubray
May 28 2017 13:20 UTC
VueX for instance requires that the action calls a specific mutation, which IMHO is not ideal.
yes

@sladiri

It looks a bit "odd" (in SAM) and reminds me of Redux, but I guess there are situations where you cannot avoid this pattern?

yes, I think that's too strong, but whatever works for you, it's not critical.

@sladiri

Each action would ultimately present values to the model, which would check them to preserve its own consistency ("only" do CRUD), rather than to check what it means to increase for example.

yes, exactly

Johan Alkstål
@johanalkstal
May 28 2017 13:23 UTC
So as the user types an email string, each input would send an action of { loginFormEmail: value }? The model can then update both loginForm.email and loginForm.invalid?
Daniel Neveux
@dagatsoin
May 28 2017 13:26 UTC
yes, the action can validate the format of the email thx to a regex and present a data such as { loginFormEmail: string, valide: boolean} receiving that, the model mutates loginForm.email and loginForm.invalid
Johan Alkstål
@johanalkstal
May 28 2017 13:29 UTC
What should happen on a rejection of a proposal, if the proposed data comes from an input that at the same time is bound to a model value?
A contrived example would be if an input is bound to model.number but the user inputs letters instead of numbers?
If I reject the input, would that still be reflected in the input somehow?
<input value=model.number> and user enters letters, that are not allowed because only numbers are accepted
Jean-Jacques Dubray
@jdubray
May 28 2017 13:32 UTC
So practically the action will make the call to authenticate the user and propose that the user is authenticated.
If the email is invalid, the action will propose to display an error, the model will accept or not that proposal (most likely accept).
The advantage of decoupling action from model is that for instance you could have multiple login actions that use the same model. One action could limit the domain from which you can login (e.g. gmail) or have different validation rules such you need to have an avatar before you can sign up.
SAM is just a factoring of the code, nothing less nothing more and there could be, in specific cases, reasons why you factor some logic in the action vs the model, but in general you will have very good reasons for choosing one or the other. The key is to have these two areas, when you have just one (like a reducer) you tend to be quite random about that choice.
Johan Alkstål
@johanalkstal
May 28 2017 13:40 UTC
The action to display en error on invalid could come from a nap?
Jean-Jacques Dubray
@jdubray
May 28 2017 13:40 UTC

@johanalkstal

Some kind of information like a Flux-like action is needed, { type: ACTION_TYPE, payload: value }. Or specific model.present functions for different actions. model.presentUser.

as I mentioned earlier, I also pass an action type, but practically I never have to use it. I also recommend never using multiple present methods, unless for one case, where you need to update "system" properties that are irrelevant to the state representation.

It's ok in that case to have another present method.
Johan Alkstål
@johanalkstal
May 28 2017 13:41 UTC
Okay
Jean-Jacques Dubray
@jdubray
May 28 2017 13:42 UTC
@johanalkstal I use vanillaJS (ES6) and TypeScript for Angular2.
Johan Alkstål
@johanalkstal
May 28 2017 13:43 UTC
@jdubray Would love more comparisons in http://sam.js.org/#frameworks :)
I've been dabbling with Meiosis for the last week.
Jean-Jacques Dubray
@jdubray
May 28 2017 13:45 UTC
In a nutshell, the comparison fits in a couple diagrams:
1/ this is everyone else (including Meiosis)
blob
SAM takes this red box and factors it slightly differently
blob
The only framework today that comes closer is Vuex, which interestingly uses the same TLA (State,Action,Mutation) and uses proposals between action and mutation, but not data proposals, an action calls directly a specific mutator with the proposal
Jean-Jacques Dubray
@jdubray
May 28 2017 13:51 UTC
SAM is just a pattern, you can use it with any framework, you can choose your favorite way to wire the elements of the pattern, you can even choose the architecture (where actions, model and state run).
This is another view of the pattern
blob
Daniel Neveux
@dagatsoin
May 28 2017 13:52 UTC
For vuex you can pass a payload with a mutator name.store.commit('increment', 10)
Jean-Jacques Dubray
@jdubray
May 28 2017 13:53 UTC

Yes, exactly

Here is why you can choose your architecture much more efficiently (SAM collapses application architecture from a 6 layer model, to 4 -> that's huge)

Slađan Ristić
@sladiri
May 28 2017 13:53 UTC
Oh, I saw that, yes, looks handy
Jean-Jacques Dubray
@jdubray
May 28 2017 13:53 UTC
blob
Gunar Gessner
@gunar
May 28 2017 17:13 UTC
Love this last diagram