Strategy pattern

Aug 04, 2021 / 2 min read

Summary

  • Definitions
  • Usage

Definitions

TODO

Without strategy pattern

    // Don't do this
    const main = async (processor, order) => {
        if (processor === 'coinbase') {
          withCoinbase(order)
        } else if (processor === 'paypal') {
          withPayPal(order)
        } else if (processor === 'payexpress') {
          withPayExpress(order)
        } else {
          throw new Error({ message: `Processor '${processor}' is unknown to checkout method.` })
        }
    }

With strategy pattern

  • Define different use cases
  const withCoinbase = (order: IOrder): Promise<ICheckout> => {
    return new Promise(async (resolve, reject) => {
      // do something
    })
  }

  const withPayExpress (order: IOrder): Promise<ICheckout> => {
    return new Promise(async (resolve, reject) => {
      // do something
    })
  }

  const withPayPal (order: IOrder): Promise<ICheckout> {
    return new Promise((resolve, reject) => {
      // do something
    })
  }
  • Define strategies
  const getStrategies = (): {processor: string, handler: (order:IOrder) => void}[] => {
    return [
      { processor: 'coinbase', handler: withCoinbase },
      { processor: 'paypal', handler: withPayPal },
      { processor: 'payexpress', handler: withPayExpress }
    ]
  }
  • Use it in your main function as follow
    const main = async (processor) => {
        const strategy = getStrategies().find(strategy => strategy.processor === processor)
        if (!strategy) {
          throw new Error({ message: `Processor '${processor}' is unknown to checkout method.` })
        }
        const checkout = await strategy.handler(order)
    }

Full source code

    // Define handlers
    const withCoinbase = (order: IOrder): Promise<ICheckout> => {
        return new Promise(async (resolve, reject) => {
          // do something
        })
    }

    const withPayExpress (order: IOrder): Promise<ICheckout> => {
        return new Promise(async (resolve, reject) => {
        // do something
        })
    }


    const withPayPal (order: IOrder): Promise<ICheckout> {
        return new Promise((resolve, reject) => {
        // do something
        })
    }

    // Define strategies with handlers
    const getStrategies = (): {processor: string, handler: (order) => void}[] => {
        return [
          { processor: 'coinbase', handler: withCoinbase },
          { processor: 'paypal', handler: withPayPal },
          { processor: 'payexpress', handler: withPayExpress }
        ]
    }

    // Simpler usage in main
    const main = async (processor): Promise<ICheckout> => {
        const strategy = getStrategies().find(strategy => strategy.processor === processor)
        if (!strategy) {
          throw new Error({ message: `Processor '${processor}' is unknown to checkout method.` })
        }
        return strategy.handler(order)
    }

Another example: security policies


const userAgentChanged = ({ userAgent, token }) => ({
  test: () => {
    return userAgent && token.user_agent !== userAgent
  },
  execute: () => {
    // should not be possible so deny access
    console.log('user agent changed from %o to %o', token.user_agent, userAgent)
    throw new AuthenticationException({
      message: `We're detecting awkward account activity. Resetting your current session for security concern.`,
    })
  }
})

const userIpChanged = ({ ip, token }) => ({
  test: () => {
    return ip && token.ip !== ip
  },
  execute: () => {
    // IP can change because of network change
    console.log('[security] IP changed from %o to %o', token.ip, ip)
  }
})

const userIpAndUserAgentChanged = ({ ip, userAgent, token }) => ({
  test: () => {
    return userAgent && ip && token.user_agent !== userAgent && token.ip !== ip
  },
  execute: () => {
    // TODO: should lock. Same as when velocity change is too big
    console.log('[security] IP & user agent changed')
  }
})


const userVelocityCheck = ({ ip, token }): SecurityStrategy => ({
  test: () => {
    // if velocity > 300km in the last 1h then flag deny access
    // TODO
    return false
  },
  execute: () => {
    // TODO: calculate velocity = delta(currentLocation - previousLocation)
  }
})

const securityChecks = (options, strategies) => {
  const {token, ip, userAgent} = options
  for (const strategy of strategies) {
    if (strategy.test()) {
      strategy.execute()
    }
  }
}

Tweet