-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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
Is redux conflating actions with events? #891
Comments
Thakns @gaearon. My apology for not going through the existing issues first. I'm at least glad that I'm not the only one that sees the benefit of an alternative model, even though a decision has been made to keep things the way they are. |
Isn’t the separation between action creators and actions in Flux/Redux more akin to the separation between actions and events in event sourcing? |
@BurntCaramel Not quite. In CQRS / Event Sourcing, actions (more commonly known as commands) are intended for action handlers (or command handlers), which know how to handle specific actions, and produce events as a result. Events are intended for persistence of state deltas, and for reconstructing state from a series of events. In redux, there is no distinction between actions and events. They all go through the same path (the middleware pipeline), and eventually they hit the reducers. It's up to you to assign specific semantics to those actions. In some cases actions will be intended for thunk/promise middleware to perform async actions, and those never hit the reducers. Other times actions are pure objects, and eventually hit the reducers. And even in the latter case, redux doesn't prescribe whether those objects that hit the reducers are commands or events. It's up to the application to make that distinction. While this un-opinionated part of redux may be liberating for some people, I (and I guess some other people as well) find that it doesn't fit my mental model of having a clear distinction between commands that are intended for executing business logic, and events that are intended for persisting and reconstructing state. Edit: Regarding action creators, those are not action handlers. They are intended for creating action objects, not handling those actions. |
@khaledh I used to share many of your thoughts some time ago. I'm a backend guy that knows (mostly in theory) ES / CQRS / DDD, and I've implemented some of the concepts in our startup framework nearly 2 years ago (see details). In the framework I build, there are commands and events. A command handler transform the command to events. There are also sagas. The thing is that when we transpose these backend concepts to the frontend world, there are some adaptations to be done because the context is different. In backend DDD / CQRS, the commands are actually representing the user intent. The command handler / AggregateRoot can choose to refuse to execute that command. When an application UX is well-designed, this generally happens when there's a concurrency issue: otherwise it would not be really good UX to display a button to the user for a forbidden action. The role of the aggregate root is to maintain consistency in a collaborative domain. Note that concurrency issues happen because of the async nature of network communications: the UI the user uses to emit its intent may eventually be stale. In frontend / Flux, the UI is synchronous, and the user intents are sent "serially" to the app. There's no such thing as "collaboration" on the UI so there's no need to make any AggregateRoot to maintain consistency. The UI is synchronized to the UI state (with React) and when the user emit its intent, that intent is thus never stale (relative to the UI state, but it may relative to the backend state). The thing to understand is that the DDD domain you have on the backend is very different from the DDD domain you would use in the frontend. So, for me the frontend, due to its synchronous nature, does not reject any command. Thus there is no need for AggregateRoot, and the command handlers always accept the commands and translate them to events (even for failure feedbacks, because it's still UI state modifications). With that in mind, if you rename CommandHandler to ActionCreator, and Event to Action, you have Flux. And a Saga could just be a Store/reducer that receive events and trigger an ActionCreator. You might argue that sometimes the command handler needs to be stateful to decide which events to emit. I agree with that but actually in most cases you can provide that data to your React component as well, and fire the appropriate ActionCreator. Again I'm still using commands and events and it works fine, but it really is a lot of boilerplate (maintaining commands, events and command handlers, where there seem to be a one-to-one relationship with all those...). |
My 2 cents follow. Event Sourcing model:
Facebook Flux model:
Redux model:
Personally, I see no significant difference except the terminology. |
@sompylasar That's a nice summary I think and is also aligned with how I see it. I do think it is a little bit unfortunate that action has been settled in flux and redux, I understand why the choose it in redux to make it easier for the broader audience that understand flux to also understand redux. Using event instead of action would, in my mind, make much more sense. The reasoning is simple: an action is something you do and an event is something that has happened. In the redux scenario I just map my mind to use the terminology illustrated in previous comment. |
@mastoj Yes, same thing. Please re-read and fix the words in your comment though to avoid even more confusion:
|
@khaledh I think you make a lot of sense. I was thinking of just this. If we were using events rather than commands, we'd only dispatch event descriptions ("user-banned", "resource-requested", etc) and all the corresponding reducers that are interested in the action would react accordingly. Also, doing so would decouple the action from the reducer. Actions at that point would be global. |
I'm very new to redux, so bear with me.
I have some background in event sourcing, and the way it works is similar to redux, but not quite the same. In redux (or Flux in general), there is the concept of an action, that presuambly represents some intent either by the user or the system. There is also the concept of a reducer that responds to those actions by applying each one to the current state to generate a new state.
However, in event sourcing (ES for short), the concept of an action is distinct from the concept of an event. The typical pattern in ES is that there is an apply function that derives new state from an existing state and an event. So apply is equivalent to a reducer in redux. However, apply relies on a series of events to derive new state, not actions. Actions, on the other hand, are executed by an execute function, which takes the current state and an action, and produces zero or more events. (A good example can be found here, scroll to the Aggregate Module section.)
This separation between actions and events is fundamental to wrapping one's head around intentions (actions) to change state, and facts (events) that need to be reconciled into state due to the fact that those intentions were actually carried out.
By conflating the two together, it causes artifacts like creating async actions and async middlewares, while it would be much easier to always keep actions as plain objects, and create async action handlers instead.
In addition, async actions/middlewares tend to produce other actions in response to the original action. The way I look at it, those async actions/middlewares should produce events, not actions, as a result of executing the original action. Those events will then be reconciled by the reducer in the typical manner. This is a much easier to understand pattern.
Middlewares are supposed to implement cross-cutting concerns, like logging or security, not application logic such as action handling.
Obviously I may be a bit biased towards ES, but I'd love to hear your opinion about what it has to offer.
The text was updated successfully, but these errors were encountered: