Flux inspired architecture for Blaze (would like your design opinions!)


#1

After building a React Native app with Flux I noticed how nice it was to use that workflow once you got used to it. Suddenly it wasn’t difficult to pinpoint data and state issues!

I think I might have found a pattern that still reaps the benefits and has a lot less boilerplate. No dispatcher, no event listeners, just two methods. This is because we’re using reactivity to re-render instead of events. It also makes your Blaze templates really easy to unit test!

I’d really love to hear your opinions on this. Do you think it makes it easier to reason about the data flow or does it just add noise? In a small app you could even skip actions and call the store directly.

Some principals are:

  • Templates never mutate data directly
  • Templates gather data and trigger/call actions
  • Data mutation happens in one place (Store/Domain object)
  • Stores shouldn’t update other Stores, this happens in Actions

Edit I would not recommend this for React, it’s better to use flux instead! However for Blaze it still works nicely to keep everything predictable.

Ok now to the code. These are from this branch of the current React/Blaze Leaderboard app:

Actions are triggered in the templates:

Template.leaderboard.events({
  'click .inc'() {
    var playerId = Session.get("selectedPlayer");
    PlayerActions.incrementScore(playerId, 5);
  }
});

Actions call the stores directly to mutate the data. If this were a bigger app like Spotify, other stores would want to know about the 'pressedPlayButton' action.
PlayerActions = {
  incrementScore: function(playerId, amount) {
    PlayersDomain.handleIncrementScore(playerId, amount);
    // FooDomain.handlePlayerIncrementScore(playerId, amount);
    // BarDomain.handlePlayerIncrementScore(playerId, amount);
  }
};

Finally the store can update the data. I wasn't sure if I should stick to Domain or keep the more React oriented 'Store' name. They're basically the same thing as Stores are groups of models within a domain. Thoughts?
PlayersDomain = {
  // returns Number of docs updated
  handleIncrementScore: function(docId, amount) {
    return Players.update({_id: docId}, {$inc: {score: amount}});
  },

  ...
};

Lastly templates can fetch data from the Domain with: `PlayersDomain.getAll()`
PlayersDomain = {
  // returns Array of players document Objects
  getAll() {
    return Players.find({}, { sort: { score: -1, name: 1 } });
  },

  ...
};

Full Domain example:
PlayersDomain = {
  // returns Array of players document Objects
  getAll: function() {
    return Players.find({}, { sort: { score: -1, name: 1 } });
  },

  // returns doc id String
  getSelectedPlayerId: function() {
    return Session.get("selectedPlayer");
  },

  // returns full name String
  getSelectedName: function() {
    var selectedId = Session.get("selectedPlayer");
    var player = Players.findOne({_id: selectedId});
    return player && player.name;
  },

  // shouldn't be called directly from a template, only an action
  handleSelectPlayer: function(id) {
    Session.set("selectedPlayer", id);
  },

  // shouldn't be called directly from a template, only an action
  // returns number of docs updated
  handleIncrementScore: function(docId, amount) {
    return Players.update({_id: docId}, {$inc: {score: amount}});
  }
};

Using Meteor models?
Which view layer performs better? Blaze vs React
Meteor Best Practices
Do You Have a Pattern for Multi Step Checkout?
#2

very nice. It looks a lot like Reflux.js


#3

Oooh, this looks really interesting - this is definitely something I have thought about as well. I think this is a great opportunity to define a low-boilerplate structure for next-generation applications on top of Meteor’s primitives.

I would love to see a more detailed discussion/comparison to other things, the stuff you have here seems to really make sense.

Is there a particular reason you chose the React/Blaze leaderboard example?

Also, is it possible to encode some of this into a package somehow, so that there is a standard place to define these things? (guidelines are great, guidelines enforced with code are even better!)


#4

Yea I agree, I think it would be nice to have some kind of basic low-level methodology that everyone used… or was the ‘Meteor way’.


[quote="sashko, post:3, topic:6651"] Also, is it possible to encode some of this into a package somehow, so that there is a standard place to define these things? (guidelines are great, guidelines enforced with code are even better!) [/quote]

Yep! To start i’m refactoring React-ive Meteor to use this and will document it there for that context. I also want to write a Blog post about it soon in a more general sense for Blaze.

I’m not sure how to roll this into a package… too bad we can’t make a package that hooks into the CLI for boilerplate generation :smile: I’ve been thinking of overhauling Meteor Generate and to include a generator for Actions and Stores (or just adding new methods to existing stores).

At the very least I think creating a boilerplate repo with a great ReadMe would work as people could meteor create and then clone in the boilerplate. Having this setup with a router (FlowRouter?) would be nice too.


[quote="sashko, post:3, topic:6651"] Is there a particular reason you chose the React/Blaze leaderboard example? [/quote] Just started tinkering after refactoring the official one :smile: I also like that it's very small and easy to wrap your head around the Flux one way data flow concept.

Me too! I hope the community can help shape it into something that works well for Meteor. I’ve used this setup in one project for a few weeks but haven’t run into enough edge cases yet.


#5

+1 vote to stick with ‘Domain’. The concept of Store is very synonymous with our concept of Collection and would muddle things, I suspect.


#6

@awatson1978 Do you think the lack of a Dispatcher would be in issue? I can’t really foresee any major problems when you have latency compensation with MiniMongo. The Dispatcher queues up handlers to be fired in order but if it’s setup like below they would always fire in order.

PlayerActions = {
  incrementScore: function(playerId, amount) {
    PlayersDomain.handleIncrementScore(playerId, amount);
    CurrentPageDomain.handlePlayerIncrementScore(playerId, amount);
    CartDomain.handlePlayerIncrementScore(playerId, amount);
  }
};

I agree. Since stores are normally just transient state in the form of an array… it might be confusing if you’re fetching from the Minimongo cache and reactive state like Session or ReactiveDict. Also since we’re not firing ‘change’ events like a Flux store would, some might expect it to do so, instead we’re relying on Tracker to re-run when that data changes.

Good point!


#7

Actually, I rather like the concept of a Dispatcher object and API. In some sense, the entire Tracker API is designed to create objects like Dispatcher. And for MultiActor patterns that uses Action objects, a Dispatcher queue seems like it could be a first-class object, up there with Session and Collection. Or, rather, since Session is an ultra-light wrapper around Reactive.Dictionary; I can easily envision Dispatcher being a similar ultra-light wrapper; but maybe around Collection instead. (Maybe Dispatcher’s responsibility is to check that objects going into the Collection have an actionType?)

That being said, I think we could also do without a Dispatcher if we needed to. Looking through the examples, there were multiple times where I wanted to start swapping out Dispatcher instances and replace them with Session (or Collection, upon further thought). So I don’t think its necessarily going to break things if it’s missing. But it will certainly make for cleaner and more coherent code with MultiActor patterns if it’s around.

Coming from .NET, I’m of the mindset that the core API is still somewhat small; and there’s no reason to get rid of Session (if it gets removed from core, I’ll probably wind up adding it back in via the Clinical Meteor Track). There’s plenty of room for both Reactive, Reactive.Dictionary, Session, and a host of other APIs. Dispatcher seems like it could nicely go in the mix.


#8

Hmm this is interesting. I’m going to dive into trying this out tomorrow to see if it’s worth the extra boilerplate. Alt, my favorite Flux implementation kind of merges actions and the dispatcher for you (which is kind of where the above example was inspired from). However they just handle it behind the scenes. I could see making a simpler version of this if the dispatcher was worth it.

I think the vanilla dispatcher is way too verbose. Basically the dispatcher just buys you the guarantee that the callbacks will be fired in the order they were registered. It also has a waitOn that can handle async needs. Obviously mine lacks async but you could just use a callback instead if needed and still get the firing order correct.

I guess what i’m getting at is that I would want to make sure we’re solving a problem by adding all the complexity vs the below :smiley:

PlayerActions = {
  incrementScore: function(playerId, amount) {
    PlayersDomain.handleIncrementScore(playerId, amount);

    CurrentPageDomain.handlePlayerIncrementScore(playerId, amount, function() {
      // wait for CurrentPage store to finish
      CartDomain.handlePlayerIncrementScore(playerId, amount);
    });    
  }
};

One of the things that worries my a bit with using the dispatcher is that it makes it harder for new programmers to pick up and makes the code harder to follow.

Here’s an example of registering the store to the dispatcher in the FB Todo example (really verbose):

// Register callback to handle all updates
AppDispatcher.register(function(action) {
  var text;

  switch(action.actionType) {
    case TodoConstants.TODO_CREATE:
      text = action.text.trim();
      if (text !== '') {
        create(text);
        TodoStore.emitChange();
      }
      break;

    case TodoConstants.TODO_TOGGLE_COMPLETE_ALL:
      if (TodoStore.areAllComplete()) {
        updateAll({complete: false});
      } else {
        updateAll({complete: true});
      }
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_UNDO_COMPLETE:
      update(action.id, {complete: false});
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_COMPLETE:
      update(action.id, {complete: true});
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_UPDATE_TEXT:
      text = action.text.trim();
      if (text !== '') {
        update(action.id, {text: text});
        TodoStore.emitChange();
      }
      break;

    case TodoConstants.TODO_DESTROY:
      destroy(action.id);
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_DESTROY_COMPLETED:
      destroyCompleted();
      TodoStore.emitChange();
      break;

    default:
      // no op
  }
});

#9

Is not the same Dispatcher posted by @luisherraz some months before: MeteorFlux Flow


#10

The Facebook dispatcher in the comment above is very similar to Luis’s. However after trying out some experiments this morning I doubt i’ll implement it in my ‘methodolgy’. It seems like if that’s needed you’re better off pulling in a regular Flux library and just using that.


#11

i’m really interested on this approach, Flux on meteor, for my next project. In what aspect your solution is different from https://github.com/meteorflux/dispatcher package?


#12

Yeah, my concept of a Dispatcher is a bit different than the Flux Dispatcher, since I spent years using the .NET Dispatcher, and recently wrote an actual vehicle dispatch system (an Uber-like wheelchair accessible vehicle dispatch system). My opinion of the .NET one, was that it was too low-level. And trying to write an Uber clone in Meteor without a Dispatcher was a big hassle.

So, when I say I like the concept of a Dispatcher object, that’s less a vote for the MeteorFluxFlow architecture, per se (which seems quite reasonable, actually), and more of a vote for simply having an API to managing Workflow State in MultiActor applications. Which is mostly what Flux seems to be doing; but not entirely.

For me, a simple wrapper class around Collection would suffice, with an Invoke() method that acts on Action records like the following:

{
  actionType: "VIEW_RECORD",
  timestamp: new Date(),
  callback: function(){ ... },
  payload: {

  }
}

That’s all I’m really looking for in a Dispatcher queue.

Looking at Meteor Flux Flow, the general architecture is workable, but it’s awfully heavy on the callbacks, and the View concept is still a bit borked (not taking into account render pipelines and device viewports). But those are minor things.

tl;dr - I suppose I’m more interested in a Dispatcher being a bit more middleware and oriented toward Actor workflow states (by way of Actions); rather than a lower level system-oriented object for managing template state.


#13

Good question. So the “Meteor Flux” uses a full Flux spec. IMHO this is too heavy. However my solution is just more of a convention to structure where the data mutation logic goes. It eliminates mutating data in the view template and allows other ‘domains’ to act on this action. No library would be needed, you just create a namespace and follow the convention.

This keeps data flowing in one direction and makes it very easy to jump to a file (for instance if a view calls CartActions.add then you can jump to that action file and see what domains (stores) are acting on this. That domain would contain a method to handle the action (like createPost). The domain would also have getter methods to fetch data.

Because Meteor has reactive data structures, if you return one of these the view will automatically update when the data structure changes (no event emitter needed).

To summarize a full call flow it would look like this:

  • View calls action method
  • Actions method calls the Domain to mutate data: (this can call multiple domains that care about the action)
  • Store handles call from action (inserts data)
  • Views update if they called Domain’s getter (ex: PostsDomain.getAll() )

This basically just makes a very clean flow the is predictable and easy to reason about. For a more code oriented the first post above (also an example leaderboard repo).

I also have some of the React-ive Meteor repo (FB wall clone) partially converted over here and a half Blaze half React leaderboard example here


[quote="awatson1978, post:12, topic:6651"] tl;dr - I suppose I'm more interested in a Dispatcher being a bit more middleware and oriented toward Actor workflow states (by way of Actions); rather than a lower level system-oriented object for managing template state. [/quote]

Ah gotcha. Yea the Flux dispatcher is mainly concerned with making sure the the template state stays in order, and also handles ajax requests (which is fuzzy in flux).


#14

Ah!! I see. Some weeks ago I was reading Meteor flux forum posts and github code and think that Dispatcher is not needed cause all needed stuff are already on Meteor but I had not time or knowdledge to join the pieces. Only one was sure, like you say, “no event emitter needed” :smile:
I need some time to digest this “pattern” … But I´m suspect this is the way to go on future Meteor architecture apps.


#15

I think so too! It would be nice if we could just point to ‘The Meteor Way’ and that would be a nice base for all Meteor apps. (also next on deck, handling ‘models’ seems to be more vague!)

I used something similar in Blonk but without the Actions… basically the template would call the FooDomain (which I called a controller). I’m starting to refactor these Actions back in to cleanup some of the ‘cross talk’ that gets messy without them.

I also like how easy it is to pickup (as opposed to React’s Flux which takes several hours at best to understand).

Once I can get all of this documented I was going to go with a name so that it’s easy to point to a convention. Working names:

Cosmic Flux

Solar Flux

Photon Flux


#16

Maybe best to remove “Flux” word? Don´t help too much here.
I think that central point is the inmutable character of view. This is “the cause” and your solution is “the effect” and Flux is the solution for a universe without reactivity.
Then, maybe, can we called …
"The non-mutator view way"
This a half joke :slight_smile: … The important is the “fluxless”


#17

Hey Adam, I didn’t read this one until now : )

The only difference between your workflow and a real Flux flow is that you removed the Dispatcher.

This means, you are using imperative instead of declarative programming in the first step of the Flux “one-way-flow”. In other words, you are calling imperatively the Store functions. In Flux, the Stores decide by themselves when they need to be called:

// Your way
Template.leaderboard.events({
  'click .inc'() {
    var playerId = Session.get("selectedPlayer");
    PlayerActions.incrementScore(playerId, 5);
  }
});

// Flux way
Template.leaderboard.events({
  'click .inc'() {
    var playerId = Session.get("selectedPlayer");
    Dispatcher.dispatch({ 
      actionType: "USER_CLICKED_INCREMENT_BUTTON", id: playerId });
  }
});

If you want to add more stuff when the user clicks on the increment (for example change the background color to red or track it with analytics) you have to modify both your action and your stores. In Flux, you only have to register your new store (for example StoreAnalytics) to the USER_CLICKED_INCREMENT_BUTTON action. That’s the point of the Dispatcher.

The Facebook’s Dispatcher uses a switch and it may look verbose. That’s true. On the other side, it’s very flexible. Anyway, I am sure that anyone willing to use a Flux Dispatcher who wants to avoid that switch can build a nicer API on top of it. My package (meteorflux:dispatcher) is just a simple port of the Facebook’s Dispatcher adapted to Meteor (~2Kb gzipped).

BTW, you don’t need to emit events in Meteor even if you use the Dispatcher, you update the UI using reactive data sources and Tracker/Templates.

If you go with the Dispatcher the flow would look like:

  • View dispatches a declarative action
  • Dispatcher calls all the registered Stores
  • Stores handle call (inserts data)
  • Views update if they called Store’s getter

So it’s pretty similiar.

Which approach is better? Well, that’s up to each person and situation I suppose. I think both can be valid : )


#18

Ahhh very interesting, thanks for clearing this up! Perhaps for the most simple use case the imperative method is easier but for larger apps, opting into a dispatcher might be better.

I think using an API similar to Alt would be ideal in this case.
Here’s how Alt merges the dispatcher into their actions (this is the only Flux lib. i’ve really used).

var alt = require('../alt');

class LocationActions {
  updateLocations(locations) {
    this.dispatch(locations);
  }
}

module.exports = alt.createActions(LocationActions);

and then to register that action:

var alt = require('../alt');
var LocationActions = require('../actions/LocationActions');

class LocationStore {
  constructor() {
    this.locations = [];

    this.bindListeners({
      handleUpdateLocations: LocationActions.UPDATE_LOCATIONS
    });
  }

  handleUpdateLocations(locations) {
    this.locations = locations;
  }
}

module.exports = alt.createStore(LocationStore, 'LocationStore');

#19

Sure, if you like Alt then why not : )

In my opinion you have to choose the pattern and principles you/your team are going to use first and then choose the implementation you are more comfortable with.