Skip to content

Latest commit

 

History

History
95 lines (64 loc) · 5.44 KB

configuration.md

File metadata and controls

95 lines (64 loc) · 5.44 KB

Configuration

A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials.

Managing Secrets

The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.

There are various methods available manage secrets and environment variables. Locally we will use our process manager configuration to handle our local environment variables and the test/staging/production environments will be managed by the platform you use to run your applications. For the majority of this workshop we'll be referencing configuration for the Now platform, but similar ideas exist whether you're deploying to kubernetes, a cloud provider, PAAS (like heroku) or gasp via virtual machines (using Chef, Puppet, Ansible, or otherwise).

Introducing Reader

Reader is a lazy Product Type that enables the composition of computations that depend on a shared environment (e -> a). The left portion, the e must be fixed to a type for all related computations. The right portion a can vary in its type.

I'm sure this description is perfectly accessible for people well versed in category theory and Haskell data types, but for mere mortals like myself it requires further explanation.

The simplest way to understand Reader is that it provides a way to provide read-only data to any function (computation). So, for example, in our application where we have a specific base URL for calls to the football-data API, we may choose to pass this to our functions by wrapping them in a Reader rather than reaching into the process.env global (which is impure).

const Reader = require('crocks/Reader');
const concat = require('crocks/pointfree/concat');

const { ask } = Reader;

// greet :: String -> Reader String String
const greet = greeting => Reader(name => `${greeting}, ${name}`);

// addFarewell :: String -> Reader String String
const addFarewell = farewell => str => ask(env => `${str}${farewell} ${env}`);

// flow :: Reader String String
const flow = greet('Hola')
  .map(concat('...'))
  .chain(addFarewell('See Ya'));

console.log(flow.runWith('Thomas'));
// => Hola, Thomas...See Ya Thomas

console.log(flow.runWith('Jenny'));
// => Hola, Jenny...See Ya Jenny

Working with ReaderT

ReaderT is a Monad Transformer that wraps a given Monad with a Reader. This allows the interface of a Reader that enables the composition of computations that depend on a shared environment (e -> a), but provides a way to abstract a means the Reader portion, when combining ReaderTs of the same type. All ReaderTs must provide the constructor of the target Monad that is being wrapped.

Ok, so in my opinion this is even harder to get into than before but is ultimately the most useful way of interacting with a Reader. What ReaderT is doing is embellishing the behaviour of the underlying Monad with the environment access features of a Reader.

It may not be immediately obvious when using this in simple examples such as those in this workshop, however, as your applications grow and the flows begin to have multiple steps (for example, multiple http requests) then having the ability to access the environment in any discrete step is very helpful indeed. This also allows us to split up the steps into discrete chunks to allow easier testing...

const ReaderT = require('crocks/Reader/ReaderT');
const Maybe = require('crocks/Maybe');
const safe = require('crocks/Maybe/safe');
const isNumber = require('crocks/predicates/isNumber');
const and = require('crocks/logic/and');

const MaybeReader = ReaderT(Maybe);
const { ask, liftFn } = MaybeReader;
const { Just, Nothing } = Maybe;

// add :: Number -> Number -> Number
const add = x => y => x + y;

// Typical Constructor
MaybeReader(safe(isNumber)).runWith(76);
//=> Just 76

MaybeReader(safe(isNumber)).runWith('76');
//=> Nothing

const add10ToEnv = ask(x => x + 10);
const add20ToEnv = ask(x => x + 20); // x will be set to the value provided in runWith
const flow = add10ToEnv.chain(x => add20ToEnv.map(y => x + y));

console.log(flow.runWith(1)); // Just(1 + 10) + Just(1 + 20) = Just 32
console.log(flow.runWith(10)); // Just(10 + 10) + Just(10 + 20) = Just 50

Exercises

  1. Take the hard coded secrets and configuration out of your world cup microservice and retrieve them from the environment. Consider which elements of your application may need to differ between environments (this includes testing environments which run unit and integration tests)
  2. Introduce ReaderT into your application to provide configuration to your Async wrapped side-effects

Further Reading

Next - metrics