MeteorFlux Flow

TLDR: The building blocks for a Meteor Flux architecture are already there.

I use a Flux architecture in the new Meteor editor centric app that I develop. I’ve started with space:ui 3.4.3 that already implements a Dispatcher, Stores with reactive state and Action factories that reduce the boilerplate code. I’ve decided to not use the template mediator and use Meteor templates directly with a small base template mixin.

I think the Meteorhacks packages work great in addition:

The iron:router can also work but you should disable the reactive route behavior by using Tracker.afterFlush in the route action.

What I have learned so far:

  • The router should just change the layout and emit an action so that stores can do other app state changes

  • Having the domain specific business logic in one place (Stores) reduced the mental overhead when you must change or add something to it.

1 Like

Hey @CodeAdventure, thanks for chiming in :relaxed:

Meteor is very very simple so I want to keep things as simple as possible. Space:ui remembers me to PureMVC and man, I don’t want that again. I have a hard time reading CS so I didn’t take a exhaust look at it, but I don’t like things like mediators, constants, commands, controllers, sugar classes…

And I don’t feel like going far from regular Meteor is the way to go and the less I introduce, the better.

I think the point of Flux is that you cannot make something difficult to debug and therefore your application doesn’t have to be so tight.

Anyway, that’s my way to see things right now and all this is helping me understand both Meteor and Flux better so I’m going to continue until I have introduced as much elements as I can.

2 Likes

Sure! I’m just trying to figure out myself where every Meteor element should be.

Yes, I think FlowRouter and FlowLayout are going to be great in this. I don’t like FlowComponents that much because I prefer to use the official Blaze.

100% agree.

This is interesting because Flux was not designed with routers in mind.

Why did you come to that conclusion? Did you try other approaches?

I have added user accounts and a few other things:


http://cartflux.meteor.com

  • User Accounts, with a new store (UserStore) to control logins, account creation, errors…

  • UserActions, for login/logout actions. This pattern is suggested by Facebook for async actions:

For example, instead of dispatching the actions in the Template events, you use the UserActions:

Template.LoginForm.events({
  'submit #login-form': function(event){
    event.preventDefault();
    UserActions.login(event.target.user.value, event.target.password.value);
  },

and then…

UserActions = {
  login: function(user,password){
    Meteor.loginWithPassword(user, password, function(error){
      if (error) {
        Dispatcher.dispatch({ actionType: "LOGIN_FAILED", error: error });
      } else {
        Dispatcher.dispatch({ actionType: "LOGIN_SUCCEED" });
      }
    });
  },

This is only for async actions.

  • Library to validate inputs in the client. This pattern is suggested by Facebook as well.

To do things like this:

UserActions = {
  createAccount: function(user){
    if (!Libs.Validations.areValidPasswords(user.password, user.retry_password))
      var error = new Meteor.Error("password-doesnt-match", "It looks like the passwords don't match.");

    if (error)
      Dispatcher.dispatch({ actionType: "CREATE_ACCOUNT_FAILED", error: error });
  • Refactor of the Stores, so now they can be unit tested. I added dependency injection as well.

It’s very simple, instead of creating the object, you create it inside a function and return it:

// Creator
var newOneStore = function(SomeCollection){

  // Reactive Vars
  var something = new ReactiveVar(false);

  // Object
  var OneStore = {
    // Callbacks...
    onSomethingHappened: function(){
      something.set(true);
      SomeCollection.insert({});
    },

    // Getters...
    getSomething: function(){
      return something.get();
    }
  };

  OneStore.tokenId = Dispatcher.register(function(payload){
    switch(payload.actionType){
      case "SOMETHING_HAPPENED":
        OneStore.onSomethingHappened();
        break;
    }
  });

  return OneStore;
};

// Create the instance
SomeCollection = new Mongo.Collection('some-collection');
OneStore = newOneStore(SomeCollection);

Of course you can use this pattern or not.

Just wanted to say, this is my first time visiting forums.meteor.com, on this thread, and it’s just amazing. A great showcase of Meteor. Sorry for hijacking! :slight_smile:

1 Like

@luisherranz thank you very much for this. It has been extremely educational. I’m still ramping up on both React/Flux and separately Meteor. Any chance you can help me understand how what you propose above is different than the MVVM pattern provided by the ViewModel http://viewmodel.meteor.com. Thanks!

@manuel did a great explanatory video. Here:
https://www.youtube.com/watch?v=5LsTUv61cKU

.

MVVM is another design pattern, invented by Microsoft and useful as well. This is a very simplistic image but you can get the idea:

As you can see MVVM is two-way-data-binding and Flux is one-way-data-binding. Flux is this way on purpose so applications are easier to debug.

One of the more important things @manuel is trying to solve with his package is the fact that in Meteor you put logic in the Template Events and that is highly coupled to the UI and harder to test. In MVVM the View has references to the ViewModel but the ViewModel doesn’t know about the View so it is more decoupled and easier to test. This is very well explained in the video.

In this regard, MVVM is good, but Flux solves it as well: When there is an event it just dispatches an action. Then, the stores which need to do something about it, listen and react. All the logic is in the Stores and they are decoupled and easy to test. Controller-Views (Meteor template helpers and events) only dispatch actions and retrieve data.

I prefer the Flux approach because I really like working with notifications. They are more decoupled, easier to follow and easier to maintain.
The problem with notifications is that they can get very messy and unpredictable when they are chained. I had problems in the past with this. Flux doesn’t allow to do it, so I liked the pattern and wanted to give it a try.

I’m no expert on MVVM so if anybody wants to jump in and correct me, is welcomed :blush:

Thank you for the helpful response!

1 Like

I have removed the insecure package.

No big deal, just had to change things like this:

Cart.update(id, {$inc: {quantity: 1}});

to things like this

Meteor.call('CartStore.increaseCartItem', id);

// then...
Meteor.methods({
    'CartStore.increaseCartItem': function(id){
      Cart.update(id, {$inc: {quantity: 1}});
    }
});

For this, I don’t like the whole allow and deny stuff. I think Meteor.methods are easier to understand.
And as they are simulated on the client, you still get latency compensation.

http://cartflux.meteor.com/

I am working with pagination and I noticed that our subscription system and Minimongo are concepts not covered by Facebook’s Flux.

Who should decide which content is available in the client at each moment?
View or Stores? Any opinions?

1 Like

I have updated the example using pagination.

It was easy thanks to Meteor reactivity but I saw some new things. For example, I need to be sure the actual page is less than the total number of pages. If you are in the last page and you delete all products there, you can find yourself in a blank page. User should be redirected to the new last page.

You can check the total number of pages each time you remove a product, but that doesn’t take advantage of Meteor’s reactivity. If instead of that, you can use Tracker.autorun to ensure this is checked each time the total number of pages changes, no matter what, who or when.

Tracker.autorun(function(){
    var total_pages = CatalogStore.getNumberOfPages();
    var actual_page = CatalogStore.getActualPage();
    if (actual_page > total_pages) {
      Session.set("CatalogStore.actualPage", total_pages);
    }
  });

This seems great because it is decoupled and automatic. But right now it’s a lonely piece of code inside the Store. It doesn’t belong anywhere, really.

What do you think?
Can you run into problems if you use it?
Can it save you from running into problems?
Where should we put it?

http://cartflux.meteor.com/

There’s another thing I have noticed which is different from Meteor and Facebook Flux: the waitFor.

Due to Meteor’s reactivity, I looks like I didn’t need to use waitFor even once yet.

They explain its use with this example:

 // Keeps track of which country is selected
 var CountryStore = {country: null};

 // Keeps track of which city is selected
 var CityStore = {city: null};

 // When a user changes the selected city, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'city-update',
   selectedCity: 'paris'
 });

 // This payload is digested by `CityStore`:
 Dispatcher.register(function(payload) {
   if (payload.actionType === 'city-update') {
     CityStore.city = payload.selectedCity;
   }
 });

// When the user selects a country, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'country-update',
   selectedCountry: 'australia'
 });

 // This payload is digested by both stores:
 CountryStore.dispatchToken = Dispatcher.register(function(payload) {
   if (payload.actionType === 'country-update') {
     CountryStore.country = payload.selectedCountry;
   }
 });

 CityStore.dispatchToken = Dispatcher.register(function(payload) {
   if (payload.actionType === 'country-update') {
     // `CountryStore.country` may not be updated.
     Dispatcher.waitFor([CountryStore.dispatchToken]);
     // `CountryStore.country` is now guaranteed to be updated.
 
     // Select the default city for the new country
     CityStore.city = getDefaultCityForCountry(CountryStore.country);
   }
 });

So you use waitFor when you need to wait for another store.

But in Meteor you can use reactive variables, so you will do something like this:

 // Keeps track of which country is selected
 var CountryStore = {};
 CountryStore.country = new ReactiveVar();

 // Keeps track of which city is selected
 var CityStore = {};
 CityStore.city = new ReactiveVar();

 // When a user changes the selected city, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'city-update',
   selectedCity: 'paris'
 });

 // This payload is digested by `CityStore`:
 Dispatcher.register(function(payload) {
   if (payload.actionType === 'city-update') {
     CityStore.city.set(payload.selectedCity); // set reactive value
   }
 });

 // When the user selects a country, we dispatch the payload:
 Dispatcher.dispatch({
   actionType: 'country-update',
   selectedCountry: 'australia'
 });

 // This payload is digested ONLY by `CountryStore`:
 Dispatcher.register(function(payload) {
   if (payload.actionType === 'country-update') {
     CountryStore.country.set(payload.selectedCountry); // set reactive value
   }
 });

 // In Meteor we can use Tracker.autorun instead of Flux waitFor
 Tracker.autorun(function(){
   var country = CountryStore.country.get();
   CityStore.city.set(getDefaultCityForCountry(country));
 )};

Whenever CountryStore.country changes, Tracker.autorun will rerun that function and CityStore.city will be always in sync, even if country has changed by a different event than country-update.

I still wonder how good is this, because it looks very good to me right now.
And I wonder if waitFor would be still useful in Meteor in any case at all.

1 Like

I have included FlowRouter in the shopping cart example.

What I have done is very simple yet, but it looks promising.

I have added a new “kind of store” called CatalogRouter. It’s not called Store because I thought it was too long (CatalogRouterStore) but in a sense, is just another Store which takes care of the Catalog routing.

FlowRouter helps converting url requests to actions:

FlowRouter.route('/products/:page', {
  action: function(params) {
    Dispatcher.dispatch({ actionType: "GO_TO_PRODUCTS_PAGE", page: params.page});
  }
});

After that, the Store takes control. It has useful get methods for the Views, like getActualPage(), getNumberOfPages() or getNextPageUrl(). The views consume them to render the correct pagination, urls, etc.

In my last reply I was talking about using Tracker.autorun to keep coherence. This Store is in charge of that as well. For example, it checks reactively if the user is in a page which doesn’t exist anymore and solves the problem.

And… that’s it.

This is the new Router-Store file:

http://cartflux.meteor.com/

Next step: start messing with the layout and see how Flux, FlowRouter and FlowLayout fit together there.

PS: I have also uploaded it to scalingo because I want to test it. So far so good. Super easy to deploy:
https://cartflux.scalingo.io/

2 Likes

That is some awesome content :blush: keep posting!

1 Like

Some news here.

At this point, I wanted to include dependency injection in the stores (so they are easier to test). At the same time, I run into problems with loading order, globals and circular dependencies.

I have fallen in love with Meteor’s reactivity lately, so I thought, why not to apply it to this problem: I created a package called Reactive Dependency:


I did a very simple example with some stores depending on each other:

It can be used without Flux as well.

Back to the Shopping cart example, these are the things I have done since the last update:

  • I have included the Reactive Dependency package and it’s working great. No more loading order or globals problems.
  • I have done some refactoring of the code after reading the Meteor Style Guide.
    Now I am using var self = this; everywhere and new to create my Stores.
  • I have added a MainRouter which is in charge of retrieving routes.
  • I have added a MainLayout which is in charge of changing the layout. I am using FlowLayout now. Working great.
  • I have created client, both and server folders and move things where they belong.
  • client: views, stores, layouts, routers.
  • both: methods and collections.
  • server: publications.
  • I have added what I call a “Look ahead” publication.

What I mean for a “Look ahead” publication is that it is subscribing to both the previous and next page, instead of only the current page. That means when you go to the next page, the data is already there so it feels realtime.

I am still trying to figure out some things:

  • Who should do subscriptions. Templates? Stores? Routers?
  • Are routes “App State”, “User input” or a mix between both?

If routes are App State, they should be retrievable from Stores. But if they are “User Input” they should dispatch actions.

I have been thinking about the “Everything is a package” pattern as well. And I think it’s great. Meteor itself, Telescope or Atom look are like that. When things start to get complex, it’s better to create a package. So, for example, the “Look ahead” publication probably deserves its own package. But then you can use Flux to organise all the pieces together: keep the App State of your app and populate your Views.


http://cartflux.meteor.com/
https://cartflux.scalingo.io/

I just find this thread and I’m very impressed. I’d love to see how this can be tested with velocity. Would you mind to add tests on your cart repository ?

1 Like

Yes, I wanted to add dependency injection first. So it’s my next step : )

But I am still trying to figure out the best way to deal with routes and layouts as well.

1 Like

I have done another example of you how to pass data between templates for another thread.
It’s very simple but here it is:

http://meteorpad.com/pad/36dwXz9ktQK3SJGgB

And this is the thread:

Maybe I don’t understand your use-case entirely, but we use state-machines for this kind of thing (ecommerce, order state, complex workflows that touch multiple APIs). We find state machines really help manage the complexity.

Here is a Meteor wrapper for a state machine we are using:

1 Like