At Heroku, we maintain lots of Services, and centrally, we make use of feature-flags.

Unfortunately, the primary entity at Heroku is the App, so almost everything hangs off of Apps.

Some entities however, are not App-centric, and storing features for things that are not Apps or Users, is not practical in our centralised Features storage.

Hypothetical Features service

This is not a production service, this is a thought-experiment.

I want to be able to associate arbitrary features with arbitrary resources.

So, for example, I might have a “pipeline” resource, with a uuid “db8e9a0d-b9b1-4e7a-ab21-a2b289da032b”, and we want to associate the “speedups” feature flag with the pipeline, because I’m testing a change that should speedup builds, but might not work for all builds.

This means that I have a minimum of two calls I need to provide:

  • Add feature to a resource identified by (resource type and id)
  • Does this resource have a specific feature?
  • Remove a feature from a resource

Of course, this could become a highly critical piece of production infrastructure, so I need good metrics coming out so I can identify slow-downs, and failures, and thus I need good logging.

Architectural sketch

Architectural Overview

This is just a sketch, modelled after the Ports and Adapters approach.

(I’m terrible at hand-drawing hexagons, so I use circles).

The Features API provides the main entrypoint, providing the basic functionality we need above.

Decoupling it from storage and logging concerns, via the Features Store and Logging ports, means that we can explore different logging and storage mechanisms in the code.

Open Closed Principle

The Hexagonal Architecture is a manifestation of the Open Closed Principle (OCP), which is summarised as:

“software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”

We have the HTTP API adapter, and the gRPC adapter, these talk to the “Features API”, we can change how the “Features API” provides the functionality that these adapters talk to, without having to change them.

The “Features API” is an interface, it might be defined simply (in Go) as

type FeaturesAPI interface {
  ResourceHasFeature(resource, id, feature string) (bool, error)
  AddFeatureToResource(resource, id, feature string) error
  RemoveFeatureFromResource(resource, id, feature string) error
}

And it’s easy to see how our HTTP and gRPC adapters might talk to this.

They don’t need to care how I store the features for a given resource, or indeed, how I log the fact, they talk to the FeaturesAPI and fetch the data from it.

They are open for extension (I can replace the underlying storage and logging) but closed for modification, because I don’t need to change the APIs to replace these.

It is easier to test this code, I can stretch the implementation in ways that usually require mocking, by providing new implementations of the “Features Store”, and “Logger” adapters, perhaps a slow logger, or an in-memory store, or one that fails on concurrent writes, these things are hard to test normally.