Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Something Smells IF-y

Avatar for yuvke yuvke
February 12, 2018
35

Something Smells IF-y

A cleaner approach to Feature Toggles

Avatar for yuvke

yuvke

February 12, 2018
Tweet

Transcript

  1. Product Team wants to roll out a feature gradually •

    Have it tested first by internal users before being released • Make sure it doesn’t hurt performance • A/B Testing
  2. New Feature == Feature Branch • Add the feature in

    a new branch. • Test in new branch. • Merge back to master / trunk • Release to production
  3. New Feature == Feature Branch • Add the feature in

    a new branch. • Test in new branch. • Merge back to master / trunk • Release to production
  4. Feature Toggles under 128 chars • Put the branch in

    the code • Decide which branch to use on runtime • when test is over - clean it up
  5. toggle example setup code var treatment = client.getTreatment(userData, toggleName); if

    (treatment) { // insert code here to show on treatment } else { // insert code here to show off treatment }
  6. Before const toppingsFactory = require('./toppings'); module.exports = function(userData) { //

    .... response.dough = 'thick'; response.toppings = toppingsFactory(); // module business logic // ........ return response; } pizzaOptions.js
  7. A simple toggle pizzaOptions.js const toppingsFactory = require('./toppings'); const doughFactory

    = require('./dough'); module.exports = function(userData) { // .... var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.dough = doughFactory(); } else { response.dough = 'thick'; } response.toppings = toppingsFactory(); // module business logic // ........ return response; }
  8. A simple toggle pizzaOptions.js const toppingsFactory = require('./toppings'); const doughFactory

    = require('./dough'); module.exports = function(userData) { // .... var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.dough = doughFactory(); } else { response.dough = 'thick'; } response.toppings = toppingsFactory(); // module business logic // ........ return response; }
  9. A simple toggle pizzaOptions.js const toppingsFactory = require('./toppings'); const doughFactory

    = require('./dough'); module.exports = function(userData) { // .... var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.dough = doughFactory(); } else { response.dough = 'thick'; } response.toppings = toppingsFactory(); // module business logic // ........ return response; }
  10. “Just one more thing” pizzaOptions.js const toppingsFactory = require('./toppings'); const

    doughFactory = require('./dough'); module.exports = function(userData) { // .... var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.dough = doughFactory(); } else { response.dough = 'thick'; } response.toppings = toppingsFactory(userData); // module business logic // ........ return response; }
  11. “Just one more thing” toppings.js module.exports = function(userData) { var

    treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { return getLightToppings(); } else { return getAllToppings(); } }
  12. “Just one more thing” toppings.js module.exports = function(userData) { var

    treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { return getLightToppings(); } else { return getAllToppings(); } }
  13. “Just one more thing” toppings.js module.exports = function(userData) { var

    treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { return getLightToppings(); } else { return getAllToppings(); } }
  14. “We need to fix this!” pizzaOptions.js const toppingsFactory = require('./toppings');

    const doughFactory = require('./dough'); module.exports = function(userData) { // .... var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.dough = doughFactory(); } else { response.dough = 'thick'; } response.toppings = toppingsFactory(); // module business logic // ........ return response; }
  15. “We need to fix this!” pizzaOptions.js const toppingsFactory = require('./toppings');

    const doughFactory = require('./dough'); module.exports = function(userData) { // .... var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.dough = doughFactory(); } else { response.dough = 'thick'; } response.toppings = toppingsFactory(); // module business logic // ........ if (!treatment) { // remove irrelevant toppings } return response; }
  16. “We need to fix this!” pizzaOptions.js const toppingsFactory = require('./toppings');

    const doughFactory = require('./dough'); module.exports = function(userData) { // .... var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.dough = doughFactory(); } else { response.dough = 'thick'; } response.toppings = toppingsFactory(); // module business logic // ........ if (!treatment) { // remove irrelevant toppings } return response; }
  17. Ugh resHandler.js const pizzaOptionsFactory = require('./pizzaOptions'); module.exports = function(userData) {

    response = pizzaOptionsFactory(userData); var treatment = client.getTreatment(userData, 'doughOptions'); if (treatment) { response.deliveryOptions = ['delivery', 'pickup']; } else { response.deliveryOptions = ['delivery'] } return response; } Unreadable Unmaintainable Untestable
  18. Abstraction Layers • Whenever we want different behaviours - we

    need a layer on top with a uniform interface. • The interface is making the choice which implementation to use on runtime.
  19. We need an abstraction layer • We can start adding

    them to our code • Or… Maybe we already have one?
  20. Enter Multiquire • Instead of writing temporary code - write

    the final code, in a variant of the file
 • On load time - load (require) both versions to memory • On runtime - automatically decide which one should be used

  21. Our Project index.js payment.js delivery.js email.js dough.js pizzaOptions.js pizzaOptions _doughOptions.js

    toppings.js toppings _doughOptions.js resHandler.js resHandler _doughOptions.js
  22. Our Project index.js moduleG.js moduleE.js moduleH.js moduleD.js moduleB.js moduleB _var1.js

    moduleA.js moduleA _var1.js moduleF.js moduleF.js moduleF_ var1.js
  23. Let’s try this again const toppingsFactory = require('./toppings'); module.exports =

    function(userData) { // .... response.dough = 'thick'; response.toppings = toppingsFactory(); // module business logic // ........ return response; } pizzaOptions.js
  24. Let’s try this again pizzaOptions_doughOptions.js const toppingsFactory = require('./toppings'); const

    doughFactory = require('./dough'); module.exports = function(userData) { // .... response.dough = doughFactory(); response.toppings = toppingsFactory(); // module business logic // ........ return response; }
  25. Let’s try this again pizzaOptions_doughOptions.js const toppingsFactory = require('./toppings'); const

    doughFactory = require('./dough'); module.exports = function(userData) { // .... response.dough = doughFactory(); response.toppings = toppingsFactory(); // module business logic // ........ return response; }
  26. Let’s try this again resHandler.js const pizzaOptionsFactory = require('./pizzaOptions'); module.exports

    = function(userData) { response = pizzaOptionsFactory(userData); response.deliveryOptions = ['delivery'] return response; }
  27. Let’s try this again resHandler.js const getPizzaOptionsFactory = multiquire('./pizzaOptions'); module.exports

    = function(userData) { pizzaOptionsFactory = getPizzaOptionsFactory(userData) response = pizzaOptionsFactory(userData); response.deliveryOptions = ['delivery'] return response; }
  28. Let’s try this again resHandler.js const getPizzaOptionsFactory = multiquire('./pizzaOptions'); module.exports

    = function(userData) { pizzaOptionsFactory = getPizzaOptionsFactory(userData) response = pizzaOptionsFactory(userData); response.deliveryOptions = ['delivery'] return response; }
  29. Let’s try this again resHandler.js const getPizzaOptionsFactory = multiquire('./pizzaOptions'); module.exports

    = function(userData) { pizzaOptionsFactory = getPizzaOptionsFactory(userData) response = pizzaOptionsFactory(userData); response.deliveryOptions = ['delivery'] return response; }
  30. Let’s try this again resHandler.js const getPizzaOptionsFactory = multiquire('./pizzaOptions'); module.exports

    = function(userData) { pizzaOptionsFactory = getPizzaOptionsFactory(userData) response = pizzaOptionsFactory(userData); response.deliveryOptions = ['delivery'] return response; }
  31. Inside Multiquire multiquire.js function multiquire(moduleName) { var path = getAbsolutePath(moduleName);

    // ['doughOptions'] var variants = readVariantFiles(path); var moduleVersions = loadVersions(variants); return function(userData) { let selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant]; }; }
  32. Inside Multiquire multiquire.js function multiquire(moduleName) { var path = getAbsolutePath(moduleName);

    // ['doughOptions'] var variants = readVariantFiles(path); var moduleVersions = loadVersions(variants); return function(userData) { let selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant]; }; }
  33. Inside Multiquire multiquire.js function multiquire(moduleName) { var path = getAbsolutePath(moduleName);

    // ['doughOptions'] var variants = readVariantFiles(path); var moduleVersions = loadVersions(variants); return function(userData) { let selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant]; }; }
  34. Inside Multiquire multiquire.js function multiquire(moduleName) { var path = getAbsolutePath(moduleName);

    // ['doughOptions'] var variants = readVariantFiles(path); var moduleVersions = loadVersions(variants); return function(userData) { let selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant]; }; }
  35. Inside Multiquire multiquire.js function multiquire(moduleName) { var path = getAbsolutePath(moduleName);

    // ['doughOptions'] var variants = readVariantFiles(path); var moduleVersions = loadVersions(variants); return function(userData) { let selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant]; }; }
  36. Summary • Feature Toggles are cool • But they can

    get messy if we’re not using abstraction layers to manage our branching • CommonJS already gives us a very good start point • Using multiquire we can create variation files and use the relevant file on runtime