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

Action stream consolidation #324

Merged
merged 17 commits into from
Apr 28, 2018

Conversation

markwhitfeld
Copy link
Member

@markwhitfeld markwhitfeld commented Apr 23, 2018

As discussed by the team, we need to consolidate the dispatch and action handler invocation to use the action stream as the mechanism for execution.
This is required for the implementation of Action Pipes (#192 ).

Note: Due to the large changes in this PR, please could you merge into master without squashing the commits. I have taken care to document the decisions along the way in each commit and to keep them relatively small. This will help with potential merge conflicts with other branches too.

@markwhitfeld markwhitfeld force-pushed the action_stream_consolidation branch 2 times, most recently from 4d6f6b4 to 456c6d9 Compare April 24, 2018 10:17
@markwhitfeld markwhitfeld changed the title WIP - DO NOT MERGE - Action stream consolidation Action stream consolidation Apr 24, 2018
@markwhitfeld
Copy link
Member Author

@amcdnl @deebloo @leon Please review. I think that this is ready for merge.

There are some particular points of importance to note:

  • I added a canceled status to the action stream for canceled actions. I went for the US spelling of this (canceled) as opposed to the UK spelling (cancelled). I prefer the UK spelling but most frameworks seem to follow US spelling.
  • The completed/errored/canceled updates to the action stream always happen asynchronously (delayed by a 0ms timer in the observable). This was done to avoid the misordering of events in the stream for subsequent observers. I can go into more detail with you directly if needed.
  • The observable returned by the dispatch method will fire before the completed/errored/canceled updates on the action stream (the reasoning is related to the point above). You will find explicit tests related to this in the action.spec.ts class.
  • When there is an error the value of the error is no longer sent to the dispatch observer. We will need to include the in the ActionContext if this is something we want back. It is actually discussed in issue this.store.dipatch.subscribe(value => {}) in case of action success value is state, otherwise it is error #313 that there should be no returned value. I think that an error should hit the subscriber's error callback (with the actual error) and not the next callback. See Danny's comment here.
  • I extracted the getStateFn, setStateFn and dispatchFn into an InternalStateOperations interface and pushed its creation into state-factory.ts to remove some duplication and to try to satisfy the failing Code Climate error. Code Climate is still unhappy but I'm not sure how much value there would be in pursuing this removal of duplication further.

PS. I was tempted to do some further refactoring around the creation of the StateContext but given that there is an outstanding issue (#311 ) regarding the fact that updateState seem to mutate state I rather left that code so as to avoid merge conflicts.

@deebloo
Copy link
Member

deebloo commented Apr 24, 2018

@markwhitfeld how would this work for something like debounce? the actions stream still doesn't seem to trigger the actual state changes. Maybe I am missing something though. ill be back home tomorrow and would be happy to hop on the call

@markwhitfeld
Copy link
Member Author

markwhitfeld commented Apr 24, 2018

@deebloo I still need to wire up the stream directly to the handler. At the moment the stream is triggering the invokeActions call that the dispatcher used to call.
Essentially I have decoupled the triggering and return value of the action handlers from direct code calls and made it part of the stream processing albeit still a bit procedural inside at the moment.
I am doing this in gradual steps so that we don't arrive at a totally different codebase overnight.

I also see pushing the stream further into the invoke actions code as part of the Action Pipes work. If you see this as something that should be part of this PR then I am happy to continue along that path. My concern would just be around merge hell if this branch sticks around for too long.

@amcdnl
Copy link
Member

amcdnl commented Apr 26, 2018

The observable returned by the dispatch method will fire before the completed/errored/canceled updates on the action stream (the reasoning is related to the point above). You will find explicit tests related to this in the action.spec.ts class.

Its very important this happens after completion.

When there is an error the value of the error is no longer sent to the dispatch observer.

This is fine in my opinion as long as it does error.

Also, just to double check if i do dispatch() of the same action twice really quickly, it will only complete when that EXACT action is completed not one of that type?

Copy link
Member

@amcdnl amcdnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AMAZING OVERALL! Few feedback items.

@@ -7,6 +7,7 @@ import { Observable, Subject } from 'rxjs';
export enum ActionStatus {
Dispatched = 'DISPATCHED',
Completed = 'COMPLETED',
Canceled = 'CANCELED',
Errored = 'Errored'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to make Errored actually ERRORED.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. This is actually related to part of issue #313

return compose([
...plugins,
(nextState, nextAction) => {
if (nextState !== prevState) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the complexity of this function now, we should break it out into its own method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


actionResult$
.pipe(
delay(0) // Force completion onto new task to prevent completion from firing before dispatch event on subsequent observers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very clever! 👏

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

.pipe(
delay(0) // Force completion onto new task to prevent completion from firing before dispatch event on subsequent observers
)
.subscribe(ctx => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this is lambda aka remove {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, although I prefer having them in a statement block because you can then put a breakpoint there if necessary.

.subscribe(ctx => {
this._actions.next(ctx);
});
/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove dead code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

exhaustMap((ctx: ActionContext) => {
switch (ctx.status) {
case ActionStatus.Completed:
return of(this._stateStream.getValue());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the subscribe result should not return a value. The reason is was before was the value is needed for the plugins next().subscribe(state). Not sure how we can make the dispatch().subscribe() not return anything but make the next().subscribe(state=> work too. cc/ @deebloo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amcdnl yeah we would have to change plugins. Which I am certainly open to

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that I will have to leave it like this for now so that I don't break the plugins.

);
})
)
.subscribe(ctx => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a lambda.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, although I prefer having them in a statement block because you can then put a breakpoint there if necessary.

this._actions
.pipe(
filter((ctx: ActionContext) => ctx.status === ActionStatus.Dispatched),
mergeMap(({ action }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we want mergeMap here and not concatMap?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this case I don't think it really matters. as long as it isn't switchMap

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amcdnl Yes definitely! A ConcatMap here would cause an issue where the action completions would wait for any pending previous actions to complete. You would rather want to notify of completion straight away.

this._connected = true;
}

private get rootStateOperations(): InternalStateOperations<any> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getters/Setters should be before methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I moved it to the top.

@amcdnl amcdnl mentioned this pull request Apr 26, 2018
@deebloo
Copy link
Member

deebloo commented Apr 26, 2018

@amcdnl
"Also, just to double check if i do dispatch() of the same action twice really quickly, it will only complete when that EXACT action is completed not one of that type?"

this will only happen if you dispatch the exact same object.

const action = new Action();

store.dispatch(action);
store.dispatch(action)

@amcdnl
Copy link
Member

amcdnl commented Apr 26, 2018

@deebloo - Makes sense. I think I'm ok with that. We could fingerprint each one but that might be overkill.

@markwhitfeld
Copy link
Member Author

@amcdnl I think that the PR is ready now, please review again.
If it is possible please don't squash the commits as I made sure they all took logical steps and documented the progress as I solved various aspects of the PR.

Mark Whitfeld added 4 commits April 27, 2018 22:35
Introduced an OrderedSubject extension of Subject that forces subscribers to receive values in the order that they were pushed
This allowed us to remove the delay(0) hack
@@ -16,11 +16,30 @@ export interface ActionContext {
action: any;
}

export class OrderedSubject<T> extends Subject<T> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deebloo I think you might like this.

@deebloo
Copy link
Member

deebloo commented Apr 28, 2018

idk about not squashing. this is one feature and if it ever needs to be reverted or picked it will be difficult. All of the commits will be listed in the description

@amcdnl
Copy link
Member

amcdnl commented Apr 28, 2018

Ya, I agree w/ @deebloo - I think squash is the right thing given all the commits.

@amcdnl amcdnl merged commit 6337a19 into ngxs:master Apr 28, 2018
@markwhitfeld markwhitfeld deleted the action_stream_consolidation branch April 30, 2018 10:23
@markwhitfeld markwhitfeld restored the action_stream_consolidation branch April 30, 2018 10:27
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

Successfully merging this pull request may close these issues.

3 participants