Making a large scale app with vue.js (part 1): modularize your store!

First, I apologize if my language is sometimes poor, I’m french and I don’t natively speak English.

Recently I reacted about an article from Anthony Gore: Is Vue.js a Good Choice for a Large-Scale Application? He was telling, and it’s true, that like any other framework, you have to consider exactly what are your needs when building a large scale application. So I told about my own experience building a big app with vue.js, but I stayed in generalities and didn’t give details on the actions I had to perform to keep things understandable and maintainable.

That’s why I wanted to start this series, where I’ll give more details on what to do. This is the part one, talking about how to organize and manage your data store, where take place the API calls as well as the data management.

In a second part, we go further by removing specific data processing from the store by encapsulating it in objects prototypes, what is done in an OO way by class objects if you work with ES2015. This permits you to have an abstraction layer between your frontend and the backend, making easier future evolutions which will not fail to appear.

In the third part, we’ll see how to manage backend resources even in the case there are multiple backends, each one with its own authentication rules and error reporting protocol. This is a needed abstraction layer to build a permanently evolving app.

Then in a fourth part, we’ll see how to manage sudden and profound changes in the look and feel of the user interface by building a layout system, which will bring more flexibility to meet the expectations of designers.

Finally, in the last part, we’ll discuss the importance of testing and to build effective non-regression tools, as on a huge application it’s unthinkable to manually test anything that can break from one feature add-on to another.

Doing it all, you will have a good basis for growing an ambitious application.

Caveats in a big application

What are the dangers waiting for you starting a large scale app? Not necessarily technical flaws. If you’re able to make working a little application, there are no reasons you can’t do it in a large one. Technologies are the same.

But when you build a little thing, there are chances that you start with a better-defined goal, on a more stable basis and generally with a reduced team, or even alone…

On a large application, unfortunately, you will certainly have to begin something with a perimeter and a scope at light years of where it will evolve later, insofar as it will involve the conflicting wills not only of the highest management bodies you work for but also of clients who will have requirements up to the budget they intend to spend.

In addition, you will have to develop this application with a sometimes large team, composed of personalities of various sensibilities and skills, and sometimes even with offshore developers.

So for me, the difficulties of building a large scale application are :

  • Be prepared to review the architecture, the look and the behavior of the front end several times, as long as all stakeholders have differences on the purpose of the product, which can take a very long time …
  • Be prepared to change several times the ways to access the data, because the backend team will have to face the same problem…
  • Be prepared to manage code from developers with diametrically opposed ways of thinking, and be prepared to accept that you will not be able to rewrite all the code they made that you don’t like… You will only be able to define guidelines, which will be understood, or not…
  • Be prepared to spend a lot, but a LOT of time writing and debugging tests…
  • The customers and the product managers will only see the frontend part of the application because they can’t imagine all the stuff that goes under the hood to make it work. So as a frontend lead, be prepared to be the one they ask anything, and also the one they blame for everything… And be prepared that they’ll never understand why you say that “little” feature is hard to implement…
  • Be prepared that the estimates you make on new features ALWAYS magically transform to deadlines, with big events if they are outdated…
  • Be prepared to be very very flexible in all those things you need to be ready for …

For all the human and psychologic caveats, it’s difficult to help you, this depends too much on each one’s path in its life… But technically, you can start with a good basis: the big thing is before all to have a great flexibility in the app’s code, thinking from the earliest steps at the several refactos that will have to be done.

The first good reflex: moving data handling from components to the store

Building a large scale app, it’s quickly obvious that the handling of your data models can’t be done directly inside each component using that data. Everyone can understand that common operations have to be kept centralized in a place easy to maintain and to access, from anywhere in the application.

A data store, and with vue.js especially the one proposed by vuex is the perfect candidate to do such a thing. You can put in it all the data you need, access it from everywhere in your app via getters, modify it from everywhere via mutations and also perform asynchronous operations such as sending data to an API or retrieve it from via actions.

You can even imagine to put in your mutations/actions very specific handling, like validations on the data, data transform methods or special getters to get it at different formats for different purposes, etc…

But if your application is becoming a fat thing, with a lot of different kind of data to use, and maybe hundreds of endpoints on different APIs, this can very quickly lead to a situation where you can get your store definitions files becoming huge, and so on difficult to understand and to maintain.

Modularisation of the vuex store

Fortunately, the vuex team thought to a really pleasant solution: you can build your store by modules.
The vuex documentation firstly encourages new users to separate state, getters, mutations, and actions in different files, which is a good thing when the data store is not too big. It also presents modules, but without true guidelines to manage them, as there’s a lot of ways to handle complexity, each one with pros and cons.

I’ll present here one of these ways, assuming I’m not sure it’s the best one, but being certain that it works in a large scale app, with a team of several developers with different points of view using and modifying it. I insist on this point because even if the technical stability is very important, unless you’re working alone, the consistency of the team’s workflow is just as important.

That said, after a lot of different tries, what I advise today is to make one module for each data model used in the application, and more accurately one module for each CRUD endpoint of the app’s backend(s).

Let’s take a simple and common case study to illustrate it: an old good todo list… The app has to manage two data models: users and todos. There is a backend exposing an API where there are endpoints serving these models :

In this case, the best is to make two vuex modules. One for the user data model and another for the todos data model. I intentionally not detailed the code to perform actions as it’s not the main subject and we have a lot to say…

As you can see, the modules have been namespaced. This allows to have the same actions names between both modules, for example for the CRUD verbs of the data (create, read, update, delete).

Let’s now assembly these modules in our store definition file :

You can then add your store to the application in its main.js file in the traditional way. At that time, inside each component of your project, you are able to access all this stuff really easily (from inside any SFC) :

By organizing your store like you gain several advantages :

  • Having one file per data model, the code is not spread around and stays consistent, being so more readable and maintainable.
  • If one day the backend team tells you “on the next API version, we change all the user resource”, you know you only have one file to update.
  • You keep files at a reasonable size. If, however, one of your modules becomes too large (my own threshold is about 250 lines, but it’s up to everybody's feeling), split it again into a few coherent subsets (for example, by separating state, getters, mutations, and actions in 4 other files) and import and assemble them into your module file .
  • You can have a consistent getters and methods naming model across different data models, keeping things coherent when the same action is performed on different objects, but each one with its own special stuff to be done.

Avoid helper calls to the store

You certainly saw that in the store calls examples above I used only the explicit form. Before managing a large scale app, I was thinking the vuex helpers mapGetters, mapMutations, mapActions were a beautiful thing, and I started the app using it massively. But when it grew with hundreds of store calls, spread into hundreds of components, I quickly understood that it was a flaw… When you make something like ...mapGetters(['getter1', 'getter2', etc. ], and you call later in your code this.getter1, there is a time where the <script> part of components are large enough to not see all at a glance, and these calls are too difficult to distinguish from component’s own methods and properties. In use, it brought too much confusion, especially with the freshly and shortly assigned developers, who didn’t have time to explore the store. In addition, you have to know, component by component, which store calls you’ll need, to place the good ones in the mapFunctions. So I went in three days refacto to remove it all everywhere, using only standard store calls this.$store.getters, this.$store.commit and this.$store.dispatch. Everything had since became more clear, and you can see at first sight if you call a getter, a mutation or an action. You also don’t have to ask you if a call is needed or not, they are all available.

I think this is the reason why the Vue.js guidelines recommend to name mutations and actions with caps letters, to make a significant difference from component’s properties. But for me, it looks awful (sorry, it’s my neurosis…), and I’m far better with explicit store calls. At first, the team’s devs were doubtful, but after I’ve done it, they all agreed with me and now they preach in turn the good word about it …

Thank you very much for reading. You can find the next part here.

Best regards.