Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • Jul 15 16:31
    dependabot[bot] labeled #87
  • Jul 15 16:31
    dependabot[bot] opened #87
  • Jul 15 16:31

    dependabot[bot] on npm_and_yarn

    Bump svelte from 3.0.0-beta.20 … (compare)

  • Jul 15 16:31
    dependabot[bot] labeled #86
  • Jul 15 16:31
    dependabot[bot] opened #86
  • Jul 15 16:31

    dependabot[bot] on npm_and_yarn

    Bump svelte from 1.64.1 to 3.49… (compare)

  • Jul 07 17:48

    dependabot[bot] on npm_and_yarn

    (compare)

  • Jul 07 17:48
    dependabot[bot] closed #79
  • Jul 07 17:48
    dependabot[bot] commented #79
  • Jul 07 17:48
    dependabot[bot] labeled #85
  • Jul 07 17:48
    dependabot[bot] opened #85
  • Jul 07 17:48

    dependabot[bot] on npm_and_yarn

    Bump moment from 2.24.0 to 2.29… (compare)

  • Jul 06 19:01

    dependabot[bot] on npm_and_yarn

    (compare)

  • Jul 06 19:01
    dependabot[bot] closed #78
  • Jul 06 19:01
    dependabot[bot] commented #78
  • Jul 06 19:01
    dependabot[bot] labeled #84
  • Jul 06 19:01
    dependabot[bot] opened #84
  • Jul 06 19:01

    dependabot[bot] on npm_and_yarn

    Bump moment in /angular4-Bootst… (compare)

  • Jun 03 02:06
    dependabot[bot] labeled #83
  • Jun 03 02:06
    dependabot[bot] opened #83
Aaron W. Hsu
@arcfide
I would have expected the persistence to be done first and only after successful persistence assuring that the data is faithfully retained for the View to render.
Jean-Jacques Dubray
@jdubray
@arcfide what example are you looking at?
There is definitely a choice to be made on when the state representation is computed and shared with the view. The reason for that is NAP (next-action-predicate). My interpretation of how this should be done is that the predicate that is firing based on the current control state (by returning true/false) decides whether the view can be shared or not. If not, this is equivalent to a synchronous reaction to the proposal. The model is in a state where an action needs to happen and depending on the result, (say assuming no next-action fires) then the state representation can be computed and released.
Jean-Jacques Dubray
@jdubray
Theoretically the model is synchronized (and really must be for SAM to work correctly), however it is your choice if your application can handle synchronized persistence or if you need to rely on the next-action to persist the model. In theory it should always be done in the next-action, in practice, if you are nor expecting any event while you persist your model state, then you can do it in the model. Does that make sense?
Aaron W. Hsu
@arcfide
@jdubray I'm looking at this: https://sam.js.org/#wiring
Aaron W. Hsu
@arcfide
The way I'm seeing the reading of that code, say, where you to one action that will then trigger another action is a sequence of operations in time as: action1 → present → state → NAP → action2 → present → state → NAP → Render → persist → persist → ⊥. So, in this case, it seems that the rendering will occur before persistence and that the persist functions will get called twice on the same state, with the first state before the second action triggering never being persisted.
Is that a correct interpretation?
I understand the idea of not rendering until there are no next actions to perform. This could result in a long chain of actions that need to occur before the view is updated again. The question I'm wondering about is the double persist calls as well as the call to render before the calls to persist happen. In my head, it seems like we should never render data to the user that we haven't verified has succeeded in persisting on the backend. No?
Jean-Jacques Dubray
@jdubray
@arcfide The wiring will depend on your architecture. SAM is highly isomorphic and you can run parts or all of the pattern on the client and/or the server. I have shown it with SAM-SAFE https://github.com/jdubray/sam-safe
Jean-Jacques Dubray
@jdubray
There is no one way to wire the pattern and will depend on optimizations/trade-offs you want to make. My general assumption for classical UIs is that the user is only interested in a UI change once the persistence of the state has been completed (with or without errors), that's why I generally recommend invoking any persistence API directly from the model, though it is incorrect, it's just an approximation. In the case where you want to continue processing events -> action -> proposals you have to adopt a different strategy. The strategy that always work is the one I described above where the next-action predicate controls the rendering, though you will quickly run into some hard questions depending on the events you want to process while persisting the state. I would not be too concerned about too many NAP loops, in practice that does not happen. That's why I created SAM-FSM because NAP usually maps well to automatic actions in a finite state machine. What SAM brings to that picture is that it positions your code precisely with respect to the FSM. I don't know if you tried to "code" with an FSM paradigm in the past but in general you quickly hit a wall because FSMs don't align well with structured programming, it's one or the other. SAM makes them coexist nicely. Dr. Lamport doesn't like to talk about FSM, he talks about this PC (program counter) variable, but in essence, that's the control state of an FSM weaved into a TLA+ specification.

So I would not be concerned by that, in practice I have not seen it happening:

This could result in a long chain of actions that need to occur before the view is updated again

And not every proposal would result in a persistence call, there are parts of the application state that you do not persist. But again it depends on your architecture (client/server) and which parts of the pattern run where. When the model is on the server, you need to persist its entire state regardless, so that it can be retrieved to process every proposal in a synchronized fashion. When the model runs in the browser, there is no need to persist its entire state. You would only persist user entries (either directly in the model when the UX allows it) or via next-actions.
Aaron W. Hsu
@arcfide
@jdubray Thanks for taking the time to try to explain this, but I think the core issue is still being lost. The core issue isn't about how many action chains or the like occur between rendering, it's the inconsistency I'm seeing between the example wiring logic and the claim you made above and elsewhere in the sam.js.org page that the rendering would only happen once the persistence operation was completed. In the example wiring, the persistence will only ever happen after rendering, not before if I read the code correctly. That's my core misunderstanding, because everything else says that it should be the other way around.
Aaron W. Hsu
@arcfide
Secondary to the above, is a separate issue I have, which is trying to understand the statement you made about persisting in the model not being "correct" (from a theoretical standpoint). I believe from this and reading above in your previous responses that you are saying, "in theory it should always be done in the next-action," but that's ambiguous to me because the term next-action isn't precisely constrained enough for me yet. Did you mean there that there should be dedicated code in the Next Action Predicate (NAP) that handles persistence, or that persisting the model should be considered a separate Action that is triggered automatically from the NAP due to changes in the model state? Or, another way of interpreting the above might be that you have persistence occurring in the State(M) function before or after calling the NAP(SR).
Thirdly, as a separate issue, it's not clear to me why persistence in the model is theoretically approximate versus the other (currently ambiguous) alternative. I could accept that it is, simply because the pattern says so, but you seem to indicate that there's something deeper there.
I should say that I've used FSMs in the past and found them quite helpful, and I didn't find them particularly difficult to use, though I used Powell's Cleanroom SWE approach to sequence-based enumeration to design and work with FSMs, as I found it the most helpful there.
Aaron W. Hsu
@arcfide
My comment about long chains wasn't a value judgment one way or the other, just an attempt to verify that I correctly understand the semantics of the pattern, which is that the triggering of an action can occur via NAP theoretically for a long time. However, actions do not have a guaranteed order of arrival (because many could be submitted at a single time, and the actions are theoretically asynchronous), so the NAP triggered action would just be going onto a "queue" of actions for the model to continue to process. So, the view could trigger two actions A1 and A2, and when A1 is processed (each action is process synchronously by the model, though their arrival order is non-determinate, yes?) it could trigger the NAP to launch A3, which may or may not arrive to the model before A2 is queued and acted on, yes? So the order of action processing in such a situation could be A1 → A2 → A3 or it could be A1 → A3 → A2, yes? I just want to make sure that I understand the relationship and where the synchronization boundaries really are, and such.
Aaron W. Hsu
@arcfide
Finally, it seems that if one were designing a GUI, there are GUI level states that need to be retained in the model that might not be totally related to your semantic model, but rather are necessary for updating visual rendering of the GUI, which in the SAM pattern would ultimately be retained either in the model or derived from the model via the State function, yes? So, for instance, I'm thinking here of a waiting/loading bar or spinner. If you click a button that need to fetch new data in the model and populate some fields, then you would want the loading UI to show up to show that the request is in progress, but you couldn't have the View update itself to change to the loading bar, nor the Action that is triggered, because neither are supposed to mutate the View/SR, but rather, the action needs to run through the model, and the model might respond with a new state property Updating: True or the like, and then the View would render based on that new property, and then when the model was fully updated, a new state representation would be triggered, yes? However, in this case, I wonder how this two phase rendering would be appropriately (theoretically correctly) handled in the pattern. Would the model just initialize the update first to trigger the waiting UI and then wait for a second action to retrieve the data (since the model has to be synchronous) or would the model somehow send two updated states at different times from that single action request?
Aaron W. Hsu
@arcfide
As an example, say I want to click a button, have the button transform to a "submitting" icon of some sort while I wait for data to be submitted, and then the new UI based on the submitted data to be loaded in. The View can't update itself with a submitting icon, only the State() function changing the View can do that. So there needs to be some path through the model to change the UI to a submitting icon and then to the new UI based on the data. So, what would that look like for actions and state changes? Would it be Upload() -- [Model] -→ SubmittingState -→ GetUploadUI() -- [Model] -→ NewUIState ? Or would it be Upload() -- [Model] -→ SubmittingState ... [Model] --→ NewUIState, where no separate action is triggered but the Model somehow produces two things? I'm thinking for instance about how you might handle long running updates to the model, say, where your model contains some large dataset table you're displaying and then the user does a query over that data that needs to be filtered and computed and potentially pull in data from another area of the model, so the model needs to update that dataset table somehow based on that new query, but the computation of that new data could be a little long to compute.
Jean-Jacques Dubray
@jdubray

@arcfide

in the example wiring, the persistence will only ever happen after rendering,

I agree, I am not disputing that, what's important to note is that the pattern first appeared in 2016 and since then lots of discussions have happened and I have produced a lot of materials that may sometimes conflict with the current view of the pattern. It was shaped with discussions such as this one by the community, not just by me.

persisting the model should be considered a separate Action that is triggered automatically from the NAP due to changes in the model state

That would be my view, though, as I mentioned you have to distinguish between persisting the entire application state (model.persist()) and side effects of the current actions (persisting the data from a form, after proposal and application state mutation).

In some cases it's perfectly fine to render and persist in others it's not, the UX and temporal logic principles should drive the decisions, there is no particular conflict between the two.
Jean-Jacques Dubray
@jdubray

However, actions do not have a guaranteed order of arrival

Nor they should. I have written some sample code (in SAM-SAFE for instance) for debouncing or cancellation of long-running operations. The model should always operate in synchronized mode.

So, it's yes, yes, yes, you should not assume a particular order, though the addition of sam-fsm can help you keep track of the control state and keep some proposals on an inboud queue. The pattern itself does not answer all these questions, it only provides an organization of the code that is easier to reason about for application state mutation. When you have a case where order is important and you want to keep the proposals until you are in the state of processing them, then you have to implement that mechanism yourself, the pattern does not dictates how that would happen.
Jean-Jacques Dubray
@jdubray

derived from the model via the State function

Yes, I would say the best approach is always to derive the state representation (as in representational state transfer) from the model rather than mixing the two, but that separation is not always possible nor optimal.

@dagatsoin may want to join the conversation, he may have different views, he has built a UI framework on the SAM pattern, so he might have more insights. My role in the community is more on trying to answer more theoretical questions and produce a reference implementation of the pattern. I don't build apps for a living, I am more a back-end / integration / b2b software engineer/architect. I came to SAM from the perspective of general state management for Business Process Engines (back in 2015, long story).
Jean-Jacques Dubray
@jdubray

I'm thinking here of a waiting/loading bar or spinner

Yes, that's the tough case :-), last time I checked, even Redux was spinning it before entering Redux and the rendering of the application state would them stop it. What's original about SAM is the positioning of API calls with respect to the pattern (queries in actions and synchronous updates in the model).

That's my 2016 implementation of a REACT spinner https://github.com/jdubray/sam-samples/blob/master/react-spinner/index.html
SAM supports parent/child relationships between instances of the pattern, I believe that's another way to do it, but I don't want to hide that's a corner case, not as a failure of temporal logic but as an HTML paradigm inconsistency (non declarative corner cases).
Jean-Jacques Dubray
@jdubray

The View can't update itself with a submitting icon, only the State() function changing the View can do that.

Yes, and again, if HTML was designed properly we would not have these questions, it mixes paradigms and therefore your implementation has to deal with that. You can't use a state representation, aka V = f(M), with a non declarative behavior.

You may want to take a look at my fishing game implementation, that's the most interactive example I have: https://github.com/jdubray/sam-samples/blob/master/js-rectangle/rectangle.html
It's hard for me to answer the submitting order because it depends on how you implement it. Perhaps that blog sample (with updates on the backend) would help visualize how it could be done: https://github.com/jdubray/sam-samples/blob/master/crud-blog/blog.js
Jean-Jacques Dubray
@jdubray

on how you implement it

I meant declaratively or programmatically.

david kaye
@dfkaye_twitter
@arcfide These are good questions and constraints you raise. I don't think there's only one right way to do this. I'll address the view scenario (where a button click results in 1) a request for data, and 2) a loading state assignment). We'll assume the view knows how to set a status element's text from the status property of the state representation. From the view, the flow starts with an onclick call to Action({ action: "loadData" }). That action function would then open a request and propose a new model property { step: "status", value: "busy" or "loading" }, and the model would update itself, pass its new values to the state which would notify the view of the new status. Since the request for data is asynchronous , its response is handled with a promise handler or a callback that will eventually propose the response data as an update to the model { step: "update", value: data }. You can let the model decide whether to set a new status "updated" or send it yourself with the proposal, { step: "status", value: "done" }. That's a bare bones flow, but note that it does not require a next action predicate/function call from the state.
@arcfide here's a code gist of what that might look like: https://gist.github.com/dfkaye/0f5a16b738a6d59847d84d24f6976523
Aaron W. Hsu
@arcfide
@dfkaye_twitter and @jdubray Interesting, thanks for this. I think I've identified an implicit expectation that I did not have in my mind. Say that we have a datastore of some sort (say, an RDBMS or key-value store), in the theoretical architecture in my head, I'm imagining the model as mapping over and encapsulating this datastore entirely. So, I was imaging, say, that there was maybe a data property that represents the actual persisted data (maybe implemented as some pointer to an RDBMS) and then maybe a data_view property that contained the data that was actually being presented to the UI based on a set of filters or the like. So, if I had a query, say, to filter the data with some search term, I was expecting that the model would be responsible for accepting a state update request to the data_view based on a set of filters (say, {search_terms: 'abc 123'}), so an acceptor might update the search query property, and then a reactor would apply a query function over the data property to produce the data_view property. I was imagining that this would occur within the model. Same as if an item was deleted or added from data, where this would result in a change in the data_view property after the data property was updated, all within the model. The data property would be the only thing persisted in this case. In this case, the synchronous nature of the model makes me wonder how it would handle the potentially long computation of computing the filter, for example. However, reading the above, it sounds to me like the model in the SAM pattern described above, where actions can query some data, that the model does not actually represent the actual data of the application (such as the tables in the RDBMS), which now makes me somewhat confused, as I was thinking of the SAM pattern as a whole systems architecture pattern describing both the backend services and the frontend UI where the model would represent and encapsulate things like the RDBMS or other backend datastore and backend services. Have I misunderstood something here?
Thank you for the examples, I will probably take a deeper look into them, though I have looked at a few of them already.
Aaron W. Hsu
@arcfide
Okay! I think I might have made some progress in understanding this.
Aaron W. Hsu
@arcfide
Let me see if I have this right. The struggle I was having was with understanding where certain logic/computation ought to occur in the "theoretically correct" model. I was under the initial impression that computation and logic over application state would be done inside the model. But if I understand correctly, this is not correct. The model is only in charge of changes to the model, not computation on the model, yes? I had the expectation that the computational aspects were constrained to a single area of the pattern, but if I am understanding this correctly, I now think this is likely incorrect. Instead, I believe the pure pattern would generally segregate computations into two different kinds, both of which are handled in the Action and State modules, not in the Model. Computations that receive new information from 3rd party or user-derived sources, such as new information submitted through the View, are process and refined via the actions, which are then fed as updates to the Model. However, computation that is done over the model (that is, with the model as input) are not done with Actions, because Actions should not be aware of the model. Instead, if something needs to be computed or derived from the model, that is more correctly computed in the State function. This segregation of the computational parts ensures that the flow of data is always going from View/3rd Party → Action → Model → State → View → * in a single linear control flow with a single directional arrow capable of representing the flow of data. So there is no bidirectional flow of information back through the system, but only a single unidirection flow of data through a loop with what can be thought of as two nodes in the architectural graph feeding data into the Action module (View and "External" data sources that are not controlled by us).
Aaron W. Hsu
@arcfide
So, for example, we could consider the two cases of things we might want to do to a list of data in a View. We might want to update it with new data or totally replace it. This updating would feed new data into an Action which would then present an update proposal of the new data to the model. Perfectly fine. But what if we also have a search feature and want to filter over an existing list of data? We shouldn't try to filter the data in the Action module, nor should that filtering be done or retained in the Model. Say this searching might also take a long time, so we want to add a spinner. So, when we submit a "filter" action, we have a couple of options. 1) We could submit the filtering action and then the model could update itself with the new filters, but not the resulting filtered data. Then the State function would take that data, see a new filter, and send a state update to the View that it has begun filtering, thus triggering the spinner. Then, it could continue to work on filtering the data until the data is fully filtered, at which point it could update the View again with the new data. 2) We could enqueue two different actions, the first being a spinner activation action and then a filtering action which runs the filtering computation in the State function. This would mean that the State function isn't sending two state representations one after the other, but it increases complexity on the front of enqueuing two different actions, while also incurring the obligation to ensure that the two actions arrive and are handled by the model in the correct order.
Aaron W. Hsu
@arcfide
Now that I'm thinking about this more, it occurs to me that filtering for short-lived or otherwise small results might work fine in the State, but we may need some way to cache the search results, which would make it part of the model. That suggests to me that maybe this should be solved by initially updating the model with the filter parameters, which would trigger the State function to move to a "search_notstarted" state, where we would then trigger a next action to begin the filtering, which would trigger an action that would update the model with a status "searching" and the State would then produce a spinner and the control state would move to "search_inprogress" and then when the filtering action finally finishes, it could store the search results in the model and then the State function would render that, for example, with pagination of the search results or something like that.
That gives three different ways of doing filtering, I think: 1) Filter in State, send two View updates; 2) Use a spinner action and a filter action, filter in State; 3) Use filter_start, filter_inprogress, and do_filter action, trigger do_filter in NAP, which fires filter_inprogress and does the filtering, while State function does no filtering.
For short lived or easily computed filters, the first two seem to have the advantage of not storing the search results. The third action has the benefit of not computing the filter in the State function, as well as allowing the search results to be stored in the model. Are these all "correct" according to the pattern, and are they all equally good, or what might be the appropriate way to deal with this?
Aaron W. Hsu
@arcfide
I guess a variation on #3 would be to fire a filter_do from a filter_inprogress action.
A challenge to #3 that I see is that you probably wouldn't want to call the action with the stateRepresentation, but would instead want to call it with the model data (or a subset of it), which I presume doesn't present a problem? In the examples I see the NAP being defined as a function over the state representation and not over the model.
Jean-Jacques Dubray
@jdubray

So, if I had a query, say, to filter the data with some search term, I was expecting that the model would be responsible for accepting a state update request to the data_view based on a set of filters (say, {search_terms: 'abc 123'}), so an acceptor might update the search query property, and then a reactor would apply a query function over the data property to produce the data_view property

The way I would implement that specific scenario is actually doing the query in the action and proposing the search results to the model to update the application state. That's kind of the beauty of SAM (as I see it) you can position queries and mutations very precisely, in relation to both the persisted state and the overall application state. Based on user preferences (application state) you could even sort/filter the search results in the model as you update the application state with the search results. That way neither the API nor the action (search) need to know anything about the application state. That is in sharp contrast with the event-handler pattern which need to know everything. I consider today the event-handler pattern an anti-pattern. Works in small scale (in scope) but as your application becomes more complex, its complexities grow exponentially. In other words, SAM suggests using an encapsulated global application state and I feel today that's a much better approach.

Jean-Jacques Dubray
@jdubray
Any mutation happens in the model, nothing else, nothing more. Computed values that react to application state mutations (automatic variables) should be handled separately, I would suggest in the state representation. Actions are "pure" functions with respect to the model, there is an application/user event that is translated optionally into an intent and the intent is mapped to an action. The action will transform the event data into a proposal and as I mentioned I view it as a very good place to make queries so that you can pass the results to the model (that's why I use quotes for pure, people don't like that I define pure that way). As Dr. Lamport would say there is no such a thing as a pure function in computer science since calling a function as overall side effects, there are just not generally visible to the developer.

computation that is done over the model (that is, with the model as input) are not done with Actions, because Actions should not be aware of the model

Exactly, that's the essence of SAM, there is all that business logic that ends up in reducers in Redux or in event-handlers and SAM says, wait a second, they are different and you are better off decoupling them from each other, it's much easier to test and maintain.

1) We could submit the filtering action and then the model could update itself with the new filters, but not the resulting filtered data. Then the State function would take that data, see a new filter, and send a state update to the View that it has begun filtering, thus triggering the spinner.

That's one way to do it, but again I would not running any query from the model, only mutations.

Jean-Jacques Dubray
@jdubray

2) We could enqueue two different actions, the first being a spinner activation action and then a filtering action which runs the filtering computation in the State function. This would mean that the State function isn't sending two state representations one after the other, but it increases complexity on the front of enqueuing two different actions, while also incurring the obligation to ensure that the two actions arrive and are handled by the model in the correct order.

Like with any pattern, there are trade-offs and contexts where SAM is easier to use. I want to emphasize that if HTML was 100% declarative that would make SAM the best choice for managing the application state. That being said, it's easy enough to deal with these corner cases programmatically. As an extreme, I created this 19 LOC library to look like react and of course you can implement a SAM structure/wiring below. I want to emphasize that SAM does not require any library, I created one because it helps visualize the pattern and its qualities, but once you understand the role and responsibilities of the pattern, everything should be easy to code. (https://medium.com/@metapgmr/hex-a-no-framework-approach-to-building-modern-web-apps-e43f74190b9c)