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

Something smells IF-y (NodeJS Israel)

Avatar for yuvke yuvke
April 22, 2018
43

Something smells IF-y (NodeJS Israel)

April 22nd, 2018

Avatar for yuvke

yuvke

April 22, 2018
Tweet

Transcript

  1. Something smells IF-y A cleaner approach to Feature Toggles nodeJS

    Israel / April 2018 / Also it’s my B-day
  2. Us

  3. 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 }
  4. header.js episodesList.js header.js footer.js show.js gallery.js if (feature) { }

    else { } if (feature) { } else { } if (feature) { } else { } if (feature) { } else { }
  5. header.js episodesList.js header.js footer.js show.js gallery.js if (feature) { }

    else { } if (feature) { } else { } if (feature) { } else { } if (feature) { } else { } Unreadable
  6. header.js episodesList.js header.js footer.js show.js gallery.js if (feature) { }

    else { } if (feature) { } else { } if (feature) { } else { } if (feature) { } else { } Unreadable Unmaintainable
  7. header.js episodesList.js header.js footer.js show.js gallery.js if (feature) { }

    else { } if (feature) { } else { } if (feature) { } else { } if (feature) { } else { } Unreadable Unmaintainable Untestable
  8. show page generation Model request Template Context Response response const

    getShowModel = require('./model/show'); const getShowTemplate = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); await getShowTemplate(request); await getShowContext(request); return getShowResponse(request) }
  9. show page generation Model request Template Context Response response const

    getShowModel = require('./model/show'); const getShowTemplate = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); await getShowTemplate(request); await getShowContext(request); return getShowResponse(request) }
  10. show page generation Model request Template Context Response response const

    getShowModel = require('./model/show'); const getShowTemplate = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); await getShowTemplate(request); await getShowContext(request); return getShowResponse(request) }
  11. show page generation Model const getShowModel = require('./model/show'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); … } /model/show.js module.exports = async function(request) { request.model = await getModelFromDB(); }
  12. Let’s use factories Model const getShowModel = require(‘./model/show'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); … }
  13. Let’s use factories Model const getShowModel = require(‘./model/showFactory'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); … }
  14. Let’s use factories Model const getShowModel = require(‘./model/showFactory'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); … }
  15. Let’s use factories Model const getShowModel = require(‘./model/showFactory'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); … } /model/showFactory.js const showModel = require('./show'); const subsectionsShowModel = require('./subsectionsShowModels'); module.exports = async function(request) { var treatment = client.getTreatment(request.userData, 'subsectionsShowPage'); if (treatment) { request.model = await subsectionsShowModel(request); } else { request.model = await showModel(request); } }
  16. show page generation Model const getShowModel = require(‘./model/showFactory'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowInfo(request); … } /model/showFactory.js const showModel = require('./show'); const subsectionsShowModel = require('./subsectionsShowModels'); module.exports = async function(request) { var treatment = client.getTreatment(request.userData, 'subsectionsShowPage'); if (treatment) { request.model = await subsectionsShowModel(request); } else { request.model = await showModel(request); } }
  17. show page generation Model const getShowModel = require(‘./model/showFactory'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowInfo(request); … } /model/showFactory.js const showModel = require('./show'); const subsectionsShowModel = require('./subsectionsShowModels'); module.exports = async function(request) { var treatment = client.getTreatment(request.userData, 'subsectionsShowPage'); if (treatment) { request.model = await subsectionsShowModel(request); } else { request.model = await showModel(request); } }
  18. show page generation Model const getShowModel = require(‘./model/showFactory'); const getShowTemplate

    = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowInfo(request); … } /model/showFactory.js const showModel = require('./show'); const subsectionsShowModel = require('./subsectionsShowModels'); module.exports = async function(request) { var treatment = client.getTreatment(request.userData, 'subsectionsShowPage'); if (treatment) { request.model = await subsectionsShowModel(request); } else { request.model = await showModel(request); } }
  19. New Feature == Feature Branch • Add the ‘subsections’ feature

    in a new branch image source: http://endoflineblog.com/oneflow-a-git-branching-model-and-workflow
  20. New Feature == Feature Branch • Add the ‘subsections’ feature

    in a new branch image source: http://endoflineblog.com/oneflow-a-git-branching-model-and-workflow
  21. New Feature == Feature Branch • Add the ‘subsections’ feature

    in a new branch • change /model/show.js image source: http://endoflineblog.com/oneflow-a-git-branching-model-and-workflow
  22. New Feature == Feature Branch • Add the ‘subsections’ feature

    in a new branch • change /model/show.js • Merge back to master / trunk image source: http://endoflineblog.com/oneflow-a-git-branching-model-and-workflow
  23. New Feature == Feature Branch • Add the ‘subsections’ feature

    in a new branch • change /model/show.js • Merge back to master / trunk image source: http://endoflineblog.com/oneflow-a-git-branching-model-and-workflow
  24. New Feature == Feature Branch • Add the ‘subsections’ feature

    in a new branch • change /model/show.js • Merge back to master / trunk image source: http://endoflineblog.com/oneflow-a-git-branching-model-and-workflow
  25. Our solution - Multiquire • Duplicate show.js to a new

    file show_subsections.js • Make the changes you want in show_subsections.js
  26. Our solution - Multiquire • Duplicate show.js to a new

    file show_subsections.js • Make the changes you want in show_subsections.js • load both show.js and show_subsections.js to memory
  27. Our solution - Multiquire • Duplicate show.js to a new

    file show_subsections.js • Make the changes you want in show_subsections.js • load both show.js and show_subsections.js to memory • In runtime use the appropriate module according to feature toggle state
  28. const getShowModel = require(‘./model/show’); using require() Our solution - Multiquire

    const getShowModel = multiquire(‘./model/show’); using multirequire() loads ‘show.js’ to memory
  29. const getShowModel = require(‘./model/show’); using require() Our solution - Multiquire

    const getShowModel = multiquire(‘./model/show’); using multirequire() loads ‘show.js’ to memory loads both ‘show.js’ and ‘show_subsections.js’ to memory
  30. show page generation Model Template Context Response const getShowModel =

    require('./model/show'); const getShowTemplate = require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); await getShowTemplate(request); await getShowContext(request); return getShowResponse(request) }
  31. show page generation const getShowModel = require('./model/show'); const getShowTemplate =

    require('./template/show'); const getShowContext = require('./context/show'); const getShowResponse = require('./response/show'); async (request) => { await getShowModel(request); await getShowTemplate(request); await getShowContext(request); return getShowResponse(request) }
  32. now with multiquire const getShowModel = multiquire('./model/show'); const getShowTemplate =

    multiquire('./template/show'); const getShowContext = multiquire('./context/show'); const getShowResponse = multiquire('./response/show'); async (request) => { await getShowModel(userData)(request); await getShowTemplate(userData)(request); await getShowContext(userData)(request); return getShowResponse(userData)(request); }
  33. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; }
  34. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; }
  35. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } modulePath = ‘~/myRepo/model/
  36. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } modulePath = ‘~/myRepo/model/
  37. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } variants = [’subsections’] modulePath = ‘~/myRepo/model/
  38. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } variants = [’subsections’] modulePath = ‘~/myRepo/model/
  39. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } moduleVersions = { subsections: func(request) } variants = [’subsections’] modulePath = ‘~/myRepo/model/
  40. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } moduleVersions = { subsections: func(request) } variants = [’subsections’] modulePath = ‘~/myRepo/model/
  41. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } moduleVersions = { subsections: func(request) } variants = [’subsections’] modulePath = ‘~/myRepo/model/ originalModule = func(request)
  42. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; } moduleVersions = { subsections: func(request) } variants = [’subsections’] modulePath = ‘~/myRepo/model/ originalModule = func(request)
  43. behind the scenes multiquire.js function multiquire(moduleName) { const modulePath =

    getAbsolutePath(moduleName); const variants = readVariantFiles(modulePath, moduleName); const moduleVersions = loadVersions(variants); const originalModule = require(modulePath + moduleName); return function(userData) { const selectedVariant = client.getTreatment(userData, variants); return moduleVersions[selectedVariant] || originalModule; }; }
  44. 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