πŸ—³ [Feedback] 1.3 App Structure with Screens & Components: Get ready for A/B/Multivariate testing and continuous improvement


#1

This is the odd idea to first publish an unfinished flow of consciousness to try to get some feedback before investing the time into a full blown medium article. Offer criticism, improvement, ideas and experience as you like. Or already use what ever you find useful now. But higher level stack planning is really falling under the table lately. So I update this post consecutively.

Title: 1.3 App Structure with Screens & Components: Get ready for A/B/Multivariate testing and continuous improvement
Technology: Meteor 1.3, React
Keywords: Meteor, Continuous Improvement, Testing, Variance Testing, Application Structure, Workflow
// Working Paper, v0.1 - 03-17-2016 (thrown together out of internal communication)

This is a longer article about an appropriate folder and file structure, incl. conventions, for successfull continious improvment and A/B/Multivariate testing in Meteor 1.3 React websites & apps. A mathematical / data driven way to find out if a nav-bar should be on the right or left side, or if the background should be green or red to improve a certain goal. Here is what I got so far.


I often see startups struggle to prepare to validate design decisions - or at least - proof that they could do design validations to VCs and other sources of capital. Preparation in this case means to allow the tech stack to

  • isolate visible (screen) variations
  • dynamically map users and allow them β€œto be beta”
  • have a workflow where developers can work on variations
  • without having merge conflicts all the time

In the most extreme way, you could create a zillion app variations and have them delivered by a sticky load balancer. However, this is incredible intransparent and a real challenge for multivariate testing. Especially if there is no dev ops guy doing some automation with a statistical background.

I have now advised - for the first time - not to use webpack and rely on Meteors new module system. So I needed to define a Project Structure that allows continuous improvement on multiple levels

  • server & services
  • api
  • ui
  • ui elements
  • build targets
  • +Variations of all of those

while beeing testable and easily enforced. Not very easy.

I have done those kinds of β€œbest practice” boilerplates a lot to counteract or reduce the fatigue of a fresh dev team. Give them recipes and don’t allow them to introduce unnecessary complexity.

With react, there is a notion that everything is big tree of smart or dumb components. That sounds so much simpler than helpers, templates and weakly defined controllers. While that is true, it does not tell you anything about how to weigh the structure for different kinds of components.

And there are so many different kinds: function, relation, feature, user story, load order, coupling, section, complexity…

I did a lot of research, especially within the react community, to find an approach that proofs solid for parallel development. But before talking about that, let’s take a look at the current structure of the official Meteor 1.3 react todos example (react branch): https://github.com/meteor/todos/tree/react
This repo is as much in progress as this document. It may change a bit to the following illustrations.

Official Meteor 1.3 todos App Structure

The folder folder current folder structure looks like this
apache

.
β”œβ”€β”€ client/
β”‚   β”œβ”€β”€ main.html
β”‚   β”œβ”€β”€ main.js <--------------------------------+
β”‚   └── main.less+---------+     Single          |
β”œβ”€β”€ i18n/               |  |     import to       |
β”‚   └── en.i18n.json    v  v     main.js         |
β”œβ”€β”€ imports/           Styles    in client       |
β”‚   β”œβ”€β”€ api/           in UI/                    |
β”‚   β”‚   β”œβ”€β”€ lists/...                            |
β”‚   β”‚   └── todos/...                            |
β”‚   β”œβ”€β”€ startup/                                 |
β”‚   β”‚   β”œβ”€β”€ client/                              |
β”‚   β”‚   β”‚   └── routes.jsx  +--------------------+
β”‚   β”‚   └── server/
β”‚   β”‚       β”œβ”€β”€ fixtures.js +--------------------+
β”‚   β”‚       β”œβ”€β”€ register-api js +----------------|
β”‚   β”‚       β”œβ”€β”€ reset-passwo d-email.js|---------|
β”‚   β”‚       └── security.js +--------------------+
β”‚   └── ui/                                      |
β”‚       β”œβ”€β”€ components/...                       |
β”‚       β”œβ”€β”€ containers/...                       |
β”‚       β”œβ”€β”€ helpers/...                          |
β”‚       β”œβ”€β”€ layouts/...                          |
β”‚       β”œβ”€β”€ pages/...                            |
β”‚       └── stylesheets/...                      |
β”œβ”€β”€ node_modules/...                             |
β”œβ”€β”€ packages/...                                 |
β”œβ”€β”€ public/...              4 imports to         |
β”œβ”€β”€ resources/...           main.js in           |
β”œβ”€β”€ server/                 serVer/              |
β”‚   └── main.js <--------------------------------+
β”œβ”€β”€ tests/
β”œβ”€β”€ README.md
β”œβ”€β”€ circle.yml
β”œβ”€β”€ mobile-config.js
└── package.json
  • everything lives under the imports/ folder
  • it has three main parts: api, ui, startup
  • files are imported to the server or client by referencing them in a main.js file in the client and server
  • in this structure, the server/main.js has various imports
  • the ui/ folder splits between types of react components: components, containers, layout and pages

It is probably quite obvious that running tests (variations of containers, components and pages) would be quite a headache. Also, since there is no scoping of files into β€œfeatures”, the list of a chat window and the list of a blog element could collide.

The NEW β€œScreens & Components” 1.3 React App Structure

The idea of the following structure is to facilitate the addition of aspects of the app. Since aspects of an app can be defined as different sections (or routes) within an application, the _screens/ folder follows the apps route in its directory structure.

I have forked and adapted the official react todos example to the following illustration. Check it out:
https://github.com/D1no/todos (default Branch: react-screen-component)

The app structure looks now like this:

.
β”œβ”€β”€ client/
β”‚   β”œβ”€β”€ main.html
β”‚   β”œβ”€β”€ main.js      #import "./imports/client.entry";
β”‚   └── main.less    #@import "{}/imports/style.entry.less";
β”œβ”€β”€ i18n/
β”‚   └── en.i18n.json
β”‚ +---------------------- Main Dev Folder -----------------+
β”‚
β”œβ”€β”€ imports/
β”‚   β”œβ”€β”€ #components/ # UI Components & Extensions
β”‚   β”œβ”€β”€ _screens/    # Compositions of UI Components
β”‚   β”œβ”€β”€ api/         # Collections, Methods and Pub/Sub
β”‚   β”œβ”€β”€ lib/         # Helpers & former Meteor Packages
β”‚   β”œβ”€β”€ server/      # Strict security related files
β”‚   β”œβ”€β”€ stylesheets/ # Global styles (if used)
β”‚   β”œβ”€β”€ client.entry.jsx +--->
β”‚   β”œβ”€β”€ server.entry.js  |--->  Loader agnostic / entries
β”‚   └── style.entry.less +--->
β”‚
β”‚ +--------------------------------------------------------+
β”œβ”€β”€ node_modules/...
β”œβ”€β”€ packages/...
β”œβ”€β”€ public/...
β”œβ”€β”€ resources/...
β”œβ”€β”€ server/
β”‚   └── main.js       #import "./imports/server.entry";
β”œβ”€β”€ tests/
β”œβ”€β”€ README.md
β”œβ”€β”€ circle.yml
β”œβ”€β”€ mobile-config.js
└── package.json

Mainly, the imports/ is now the full center of attention and provides only one single file per build target/entity for reference. This facilitates clutter to the outside directories and allows to just switch out the meteor build tool for example the webpack:webpack alternative (referencing the client, server, etc entries).

Also, the imports folder hierarchy is now much flatter.

Screens follow Routes

The _screens/ directory follows the apps router.jsx, which leads to natural scoping of files and an easy way for multiple devs to work on different features without facing pre-mature merge conflicts.

...
imports/
β”œβ”€β”€ #components/...
β”œβ”€β”€ _screens/   <---------------+   Directory structure
β”‚   β”œβ”€β”€ _shared/                    follow routes. Routes
β”‚   β”‚   β”œβ”€β”€ _auth.jsx               follow features.
β”‚   β”‚   β”œβ”€β”€ _auth.less
β”‚   β”‚   β”œβ”€β”€ _notFound.jsx
β”‚   β”‚   └── _notFound.less
β”‚   β”œβ”€β”€ join/
β”‚   β”‚   └── Join.jsx
β”‚   β”œβ”€β”€ lists/            +-----+   Feature Lists (Specific)
β”‚   β”‚   β”œβ”€β”€ ListsContainer.jsx  |
β”‚   β”‚   β”œβ”€β”€ _Header.jsx         |   lists are a Feature
β”‚   β”‚   β”œβ”€β”€ _Header.less        |   of the App and are
β”‚   β”‚   β”œβ”€β”€ _Lists.jsx          |   made available under
β”‚   β”‚   β”œβ”€β”€ _Lists.less         |   the app.com/lists route.
β”‚   β”‚   β”œβ”€β”€ _TodoItem.jsx       |
β”‚   β”‚   └── _TodoItem.less      |   Acceptance Test that
β”‚   β”œβ”€β”€ signin/           +-----+   lists do work as expected
β”‚   β”‚   └── SignIn.jsx
β”‚   β”œβ”€β”€ _App.jsx          +-----+   Feature App (Global)
β”‚   β”œβ”€β”€ _App.less               |
β”‚   β”œβ”€β”€ _ListList.jsx           |   feature of using
β”‚   β”œβ”€β”€ _ListList.less          |   routes for navigation
β”‚   β”œβ”€β”€ _Menu.jsx               |   welcome messages,
β”‚   β”œβ”€β”€ _Menu.less              |   connectivity status
β”‚   β”œβ”€β”€ AppContainer.jsx        |   etc.
β”‚   └── routes.jsx        +-----+
β”œβ”€β”€ api/...
β”œβ”€β”€ lib/...
β”œβ”€β”€ server/...
β”œβ”€β”€ stylesheets/...
β”œβ”€β”€ client.entry.jsx
β”œβ”€β”€ server.entry.js
└── style.entry.less
...

Neglecting the awesome React chrome extension, it’s now pretty straight forward for a new developer - tasked to β€œchange the lists background to blue” - to find the relevant sceen and ui components without knowing the full application. The only thing he has to know, is that he needs to follow the directory structure according to the _screens/routes.jsx.

import React from 'react';
import { Router, Route, browserHistory } from 'react-router';

// route components
import AppContainer from './AppContainer.jsx';
import ListContainer from './lists/ListsContainer.jsx';
import SignIn from './signin/SignIn.jsx';
import Join from './join/Join.jsx';
import _notFound from './_shared/_notFound.jsx';

export const renderRoutes = () => (
  <Router history={browserHistory}>
    // Dictating folder structure
    <Route path="/" component={AppContainer}>
      <Route path="lists/:id" component={ListContainer}/>
      <Route path="signin" component={SignIn}/>
      <Route path="join" component={Join}/>
      <Route path="*" component={_notFound}/>
    </Route>
  </Router>
);

The routes.jsx is a key file within the project and is the Nr. 1 source of merge conflicts for new features and parallel programming.

Variable & Filenaming Conventions

for now copyed from the readme.md of the https://github.com/D1no/todos


  • Each react component file is
  • UpperCase if it exports a Class
  • lowerCase if it exports a const or a function
  • A screen component is prefixed with an _ underscore, if it is a partial screen element that either belongs to the next non-_ upper-level screen component or is shared among various, down level screens (like a _layout variant) inside a _shared/ folder
  • Variables have to be declared, default exported and imported as the file name.

For example, you now easily understand the role and location of various components

      <div className="page lists-show">
        <_Header list={list}/>
        <div className="content-scrollable list-items">
          {loading ? <message title="Loading tasks..."/> : Todos}
        </div>
      </div>

Note: This structure paradigm resulted out of a recent, large, assisted Meteor 1.3 restructure. This example illustrates the main points for discussion and further adaption. The _ and # proof helpful to quickly spot relevant components in long import trees and enforce a semantic order.


Multi-Dev Workflow

Assuming an agile, user story driven workflow, it is common to split tickets (user stories) among mutliple developers to get going quickly. A user story can be

  • an improvement to an existing feature
  • or the addition of a new one

Going back to the original project structure with a ui folder, it is clear that multiple devs will add and change files in every folder. Always. With the β€œScreens & Components” structure, a new dev could create a feature - for example a dashboard - by creating a new dashboard folder under the _screens/ folder and recycling existing components available to the app. Probably adding (rather than changing) new ones as well.

The β€œdashboard developer” adds the dashboard to the routes.jsx as a new route:

      (...)
      <Route path="dashboard" component={Dashboard} />
      (...)

This is probably going to be the only merge conflict for the integration of the feature and a good hook-moment for QA.

Unit & Acceptance Testing

ToDo: Unit tests live next to components. Feature / acceptance tests live within the _screens folder next to its feature domain tested with chimp. Discussed here: [Solved]: Meteor acceptance test

Value of Variance Testing (A/B/Multivariate)

Variance testing is a big part of proper software development and the interface of the business guys and the techs. And usually the part where arguments end and start between an CEO and CTO or a Product Owner and a Tech Lead.

Therefore, having a transparent variance testing set-up that is well understood from sales to junior dev is the corner stone of a great product development. Because the user feedback of the variance needs to flow back from the customer, through the people and mapped successfully to the test cases and resulted data.

One of the most obvious ways you know it works is this:

Sales: My customers hate the beta and want to go back to the old system!

It’s clear to which variation the feedback belongs to: Some beta! This is a very rough A/B test - you know now that B sucks but without some more qualitative information from the sales guy, you will need an intern sorting and aggregating all the praise and complaints to get some insight. And still, you may just listen to the loudest voice.

So it makes sense to use 4 different β€œBetas”. Beta1, Beta2, Beta3, Beta4… with lesser variations so you know what probably sucks, if all the sales people of β€œBeta3 Customers” hear β€œthey hate the beta and want to go back!”. Further splitting up those groups and variations leads you at some point to testing id’s or testing objects. Google runs hundreds of testing objects in parallel to validate variation models.

But they can do that because they don’t have to worry about

  • enough traffic per-variation to make meaningful, statistical comparisons (significance)
  • having benchmark conversion rates out of past experience
  • about a large number of goals (in fact a relative low number of goals - talking about the search engine)

However, beeing able to easily do variation testing for a start up means

  • less technical considerations and debates (just say whatever and test it)
  • on average, the risk profile of users / pages views is limited and weighted
  • it forces to state a desired improvement (formulate a goal - hint: β€œeasy to use” is a pretty hard to measure goal)
  • learning quickly what engages users

Variations on the Application Level with the Screens & Component Structure

Its simple. In order to do variance testing you have to create variations. Now think about your application, stack or thought out app. How do you test if a green login button is better than a blue one?

Well, you maybe got some ideas already. Its React. So we could create a button with a property color and have our test variation randomly decide to render with a 50:50 chance either green or blue.

      (...)
      <Button color={ Math.floor(Math.random() * 2) == 0) ? 'green' : 'blue' } />
      (...)

A user will now randomly see a green or blue button. You could track this variation with some self written code, Google Analytics or other third parties (more on that later).

Now I want to also test a lighter background color of the text field, round corners of the dialog and a different font with the green option. And I want to add a pink option now. And now it is getting tricky: We have to inject in all those places (components) variation generators - that stay consistent - and track those outcomes. Very hairy.

What we can do instead: We track variations of the user experience. And the user experience is strongly defined by our routes (the beauty of dynamic applications) and reflected by our _screens/ directory tree.

So in the case of the todos example, we simply copy our login screen. Three times. And create our variations by changing

...
β”œβ”€β”€ _screens/
β”‚   β”œβ”€β”€ _shared/...     Starting point:
β”‚   β”‚
β”‚ +-------------------- Our Join Feature --------+
β”‚ | β”‚                                            |
β”‚ | β”œβ”€β”€ join/           Lets run 3 variations to |
β”‚ | β”‚   └── Join.jsx    validate the login flow. |
β”‚ | β”‚                                            |
β”‚ +----------------------------------------------+
β”‚   β”‚
β”‚   β”œβ”€β”€ lists/...
β”‚   β”œβ”€β”€ signin/...
β”‚   β”œβ”€β”€ ...
β”‚   └── routes.jsx
...

To this:

...
β”œβ”€β”€ _screens/
β”‚   β”œβ”€β”€ _shared/...     With 3 Screen Versions
β”‚   β”‚
β”‚ +-------------------- Our Join Feature --------+
β”‚ | β”‚                                            |
β”‚ | β”œβ”€β”€ join/  # 3 Variations are tested         |
β”‚ | β”‚   β”œβ”€β”€ 01_green/                            |
β”‚ | β”‚   β”‚    └───── Join.jsx  # Variation 1      |
β”‚ | β”‚   β”œβ”€β”€ 02_blue/                             |
β”‚ | β”‚   β”‚    └───── Join.jsx  # Variation 2      |
β”‚ | β”‚   └── 03_pink/                             |
β”‚ | β”‚        └───── Join.jsx  # Variation 3      |
β”‚ | β”‚                                            |
β”‚ +----------------------------------------------+
β”‚   β”‚
β”‚   β”œβ”€β”€ lists/...
β”‚   β”œβ”€β”€ signin/...
β”‚   β”œβ”€β”€ ...
β”‚   └── routes.jsx
...

We simply copy pasted the Join.jsx and apply the variations discussed above. This has a few advantages to the method before:

  • a variation in itself is nothing special and could come from another repo with branches etc
  • a variation can be changed at will without worrying about killing atomic variations
  • a front-end developer has to know nothing about variation testing to make changes to a component and certiantly wont trip over noise such as Math.floor(...

Running Variations with the Router

What? What? Yes -> Running variations over a router sounds awful at first. After all, a router should be a URI, right?

A Uniform Resource Identifier - something that by definition does not change. Well, starting with the invention of cookies which changed the user’s experience on hitting the server, the immutable characteristic of a URI already faded.

Today we have dynamic routers where we ensure that a route delivers rather a constant experience (meeting an expectation) - like www.myapp.com/register - but is not linked to specific content no more. On apps we often don’t even see routes.

The routes.jsx file becomes our key place to run variations and is the flipboard of continuous improvement. We simply vary the content underneath an expected experience to gather data about user behavior. Like so

import React from 'react';
import { Router, Route, browserHistory } from 'react-router';

// route components
import AppContainer from './AppContainer.jsx';
import ListContainer from './lists/ListsContainer.jsx';
import SignIn from './signin/SignIn.jsx';
import _notFound from './_shared/_notFound.jsx';

/* Join Experiment (3 Variations)
Google Docs: (some URL to a Google Docs document explaining the reasoning)
Owner: Person XYZ / email

Primary Goals
1) maximise the login speed of our users and
2) prefer facebook login
3) reduce bounce rate
 */
import Join01_green from './join/01_green/Join.jsx';
import Join02_blue from './join/02_blue/Join.jsx';
import Join03_pink from './join/03_pink/Join.jsx';

const joinVariations = [Join01_green, Join02_blue, Join03_pink];
// We could add a more complex variation picker at will
const Join = joinVariations[Math.floor(Math.random() * joinVariations.length)];

/*
Router
 */
export const renderRoutes = () => (
  <Router history={browserHistory}>
    <Route path="/" component={AppContainer}>
      <Route path="lists/:id" component={ListContainer}/>
      <Route path="signin" component={SignIn}/>
      // We could pass a testing id down the route for better tracking
      <Route path="join" component={Join}/>
      <Route path="*" component={_notFound}/>
    </Route>
  </Router>
);

Tests are now run β€œautomatically” and can be easily removed or lowered by only touching the router.jsx. This should be done together with a guy from the business side, the tech side and/or the experiment owner.

Performance

Right now I expect to hear something like this:

Are you crazy?!? If you copy & past Screens like that you will have a hell of a lot of duplicate code served to the client! That will certainly kill load times! Especially with many experiments of large screens such as the Lists Screen! We need webpack for dynamic requires!

And you are right and wrong at the same time. Let’s talk about wepback: Webpack exists to lazy load new code. That means, if you have a complex app with many features, you are able to lazy load additional aspects of your application to not bombard the client on its first landing. And that is awesome. And that is what I hope comes soon to meteor.

But here we talk about duplicate code.

Yes! That’s the problem! It’s not DRY!

Exactly. It’s a problem but for someone else, who already solved this perfectly well: gzip compression

Meteor, also all load balancers, are able to compress data before it is send over the wire. For that gzip is used. A compression algorithm which is supported natively in browsers since Internet Explorer 6. The sole role of this algorithm is to tokenize duplicate strings to expand them at native speed again on the other side.

This is what gzib is made for and the right tool for variations: Mostly little changes in overall similar code (=strings). The overhead of having a few KB - expanded - on the client can be neglected if you load just one picture.

In an ad’hock experiment: Duplicating the β€œlists” feature of the todos app 50 times while changing three props randomly (color, sorting, inline css) via a pre-run-script resulted in a 0,74% larger transfer volume after gzib.

Since I often catch premature load optimisations, read: Only the gzip Size Matters by TJ VanToll
https://www.tjvantoll.com/2014/01/27/only-the-gzip-size-matters/

The network layer is much better suited for quick wins in load times. Also check your http headers etc. rather than trying to solve this in a reflex over the application layer with added complexity.

How to track variations

This is still a ToDo

  • Basic in App
  • Google Analytics (A/B Testing)
  • Optimizely (Multivariate Testing)

Okay, this is the status quo. I wanted to roll it out - at least to the community - before Meteor 1.3 lands to give people the chance to review their stack and maybe find some inspiration in this approach.

If you do testing, continuous improvement, variations validation or anything related to this post, please share your experience.

If you happen to struggle with a stack strategy or need help finding a proper implementation / workflow - feel free to contact me.


Advices on refactoring my app and rethinking architecture
Meteor File Structure
Redux boilerplate reduction - laziness or not? Switching to MobX
[HIRING] React & Meterial UI with Meteor (Front-End Developer)
#2

I’m not experienced enough to comment on the proposed file structure, but the content was fascinating and very educational to me.

A few questions sprung to mind: Do you try to ensure that a particular user consistently sees the same variant, it could be jarring for a user to see a blue button initially and then a green one later for example? If so, how do you go about achieving that?


#3

I would throw LaunchDarkly into your experiment. We use it and it’s pretty rad. NOTE: it’s a premium product.

You can use it to run experiments and have the switch outside of the app.

We managed to get rid of our QA environment all together by deploying straight to production and having features disabled by default (but enabled for Xolv.io staff). This means we get to see the latest and greatest updates, and can roll them out to users that signed up for the beta updates, then to everyone.


#4

Looks pretty solid.

The _screens naming convention is a good one, as it avoids view and is similar to screenshot. Also, for what it’s worth, workflow can be useful synonym for _screens.

It does look like the majority of the files are getting littered with underscores; so maybe invert the convention and assume everything is a partial by default, unless there’s a Screen suffix on the name? Otherwise, it’s a very similar approach to what we’re doing, and A/B testing with the Router definitely seems to be the right way to go.

β”‚   β”œβ”€β”€ lists/            +-----------+   Feature Lists (Specific)
β”‚   β”‚   β”œβ”€β”€ ListsContainerScreen.jsx  |
β”‚   β”‚   β”œβ”€β”€ Header.jsx                |   lists are a Feature
β”‚   β”‚   β”œβ”€β”€ Header.less               |   of the App and are
β”‚   β”‚   β”œβ”€β”€ Lists.jsx                 |   made available under
β”‚   β”‚   β”œβ”€β”€ Lists.less                |   the app.com/lists route.
β”‚   β”‚   β”œβ”€β”€ TodoItem.jsx              |
β”‚   β”‚   └── TodoItem.less             |   Acceptance Test that
β”‚   β”œβ”€β”€ signin/           +-----------+   lists do work as expected
β”‚   β”‚   └── SignInScreen.jsx

One thing that came to mind is that this naming convention is conducive to generating automated screenshots. Have you set up your $CIRCLE_ARTIFACTS directory yet? We take screenshots of our application during test runs, which are offloaded into the Artifacts tab. We can then pick those screenshots up, and drop them into a program like InVisionApp.com to implement an A/B Multivariant Design Lifecycle. (It doesn’t generate A/B metric analysis; but works for stakeholders who have yes/no authority.)

In the above screenshot, we’re testing some A/B variants of whether we should have navbars hidden or visible by default; and whether to default to FullScreen/PageScreen by default. We also often prefix the steps with N-* to create ordered workflows.


#5

Very clear writing & structure. Would love to join up and help you with writing a Meteor React App standard

I think having a guide in place would help immensely with those partaking the task of restructure or starting a new app. For example, you’re right - I’m the lead developer of a small startup and design decisions are often daunting and feel risk-bound. Having a strong but followable structure like above, it’s very easy for me to direct other members.

Seth


#6

True - most of the design is done on the go. The one above is done one a MVP. I belive this is the way to go for finding good continuous integration workflows. Just check yours :slight_smile: