Skip to content

Instantly share code, notes, and snippets.

@carsonwright
Created April 30, 2018 20:23
Show Gist options
  • Save carsonwright/c1abd331499300979c09b593e879cae6 to your computer and use it in GitHub Desktop.
Save carsonwright/c1abd331499300979c09b593e879cae6 to your computer and use it in GitHub Desktop.

What is different now? Why are we changing how we're doing things?

Goals for 2016 - 2017

Build out a more robust solution

Goals for 2018 +

Make that prototype maintainable, and make it so that no 1 person is mission critical to project upkeep.

How?

  1. Always being Explicit
    1. Require files in a readable, obvious maner (always use an index)
    2. Self Document your code (i.e. name vars and functions as what they do)
    3. Write DRY Confident Code
    4. It should read like a book, you should be able to follow it as if its a story all the way from the bootstrap file in. This should happen with as few unnecessary tangents as possible, flowing from one function to another without breaking the reader out of the main storyline. And characters should not appear out of nowhere as if they've always been there.
  2. Modularity of Files
    1. Building features / code as if they will be reused elsewhere even if it doesn't seem likely.
    2. Write code using the Onion Method. Make sure your code is built up of many reusable packages ( / files) that are required from the outside in.
  3. Universal Truth
    1. If a thing is a thing for 1 user, it should be expected for all users even if it is not turned on.
    2. Customizations don't mean a different truth per organization, it just means more granular switches within features for all clients.
    3. UI should be explicit and organized so that when showing and hiding fields and features, they continue to make sense in the global context of said universal truth.
  4. Correct Assumptions
    1. Always assume the client is right about their problem but not about their solution.
    2. Rubber duck the smallest of problems.
    3. Always ask questions, be persistent.
    4. Ignorance is bliss, always keep your modules ignorant of the outside world, don't assume context.
  5. Client Centered
    1. We will always triage based on current clients first, then clients we wish to onboard.
    2. Every feature or bug fix should be implemented with the idea that the client has to use it, and that they may not be the most adept at computer related activities.
    3. Don't give users a map, give them turn by turn directions, make sure each element of the ui flows into the next automatically leading them to where they should be.
    4. Not all features the client will ask for are features that the Clients in general will be better off having, what is the over all effect of any feature or bug on the group as a whole, and then work the problem from there down to the individual client.
  6. Clarity of Goals
    1. We will not execute on anything until we have received clear goals.
    2. We will pursue the source of the goal until we reach a clear understanding.
    3. We will note said clear understanding in our assigned "card", "story", or "task".
  7. Side effects First
    1. When we assess a feature request or bug fix it must be assessed by side effects first, functionality second.
    2. Side effects should always be the priority meaning current functionality should never degrade

Always be Explicit

Foreign code (Code from a file outside the one you're working in)

1 WAY NOT TO DO IT! (DO NOT USE GLOBAL IMPORTS)

Global (magical / not explicit)

This is the least explicit, and hardest to trace way to accomplish the use of foreign code

Example

    module.exports = ()=>{
        Users.find()
    }

2 WAYS TO DO IT

System Tree (semi explicit)

This is a mediocre, and simple solution for the inclusion and use of foreign code

Example

    module.exports = (system)=>{
        system.controllers.Users.find()
    }

Local Requirement (Most Explicit)

This is a tedious but the most explicit and easiest to trace way to include and use of foreign code.

Example

    const {Users} = require('/src/models');
    module.exports = ()=>{
        Users.find()
    }

Rules for Requiring files

Always make your requirements at the top of the file and all definitions should appear after that.

Do NOT auto require files!

If you auto require a file, on requirement it may fail, and be difficult to trace. A whole lib might not load instead of explicitly saying what file was erroring.

For Example

const files = {}
["a", "b"].forEach((key)=>{
    files[key] = require(key)
})

Always use an index file

items/
        index.js
        a.js
        b.js
        c.js
        d.js
const a = require('./a')
const b = require('./b')
const c = require('./c')
const d = require('./c')

modules.exports = {
    a,
    b,
    c,
    d
}

Always use the Onion!

One way dependencies

Code on the inside should never require code or resources on the outside, but code on the outside can and should require code and resources on the inside.

For example

Layer 1 (Data Layer)

Private Model Helpers Public Models

Layer 2 (Security Layer)

Public Serializers Helpers Public Parameter Collection Helpers ()

Layer 3 (Arbiter Layer)

Private Controller Helpers Public Controllers

For the Front End See Elemental Design Guide (onion) for Front End

Always use async / await "instead of" promises (I know its still a promise).

Use synchronous async reducers instead of evented programming when possible.

    /events/
            /helpers
                action.js
            index.js
            a.js
            b.js
            c.js

action.js

module.exports = (events)=>{
    const eventHandler = async ({type, payload} = {})=>{
        const eventReducers = async (i, action)=>{
            if(events[i]){
                action = await events[i]({
                    type: action.type,
                    payload: {...action.payload}
                })

                return eventReducers(
                    i + 1, 
                    action 
                )
            }else {
                return action
            }
        }
        return await eventReducers(0, {
            type,
            payload
        })
    }
    return eventHandler;
}

index.js

    const eventHandler = require('/events/helpers/eventHandler')
    const a = require('a')
    
    const reducers = eventHandler([
        a,
        b,
        c
    ])
    module.exports = ({type, payload})=>{
        reducers({
            type,
            payload
        })
    }

a.js

    const {vetter} = require('/integrations')
    module.exports = async ({type, payload} = {})=>{
        switch(type){
            case 'CREATE_RESERVATION':
                const response = await vetter.reservations.create({
                    provider: {
                        id: payload.externalId,
                        name: payload.name
                    }
                })

                return {
                    type,
                    payload: {
                        ... payload,
                        vetter: response
                    }
                }

            default:
                return {
                    type,
                    payload
                }
        }
    }

Instead of Event Hooks

EventThing.emit('abc', other)


EventThing.on('abc', (data)=>{
    // Do some stuff
})

Why not Events

They do not always properly error within a readable context. They are aften async anonymous functions sometimes even run in another process or next tick and will at times error in a way that is difficult to debug.

A function that is run from a file in the "standard" way will error on line x

A function that is anonymous and runs from an event binding will error from the event binding context instead of the file the initial function was defined in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment