Skip to content

Instantly share code, notes, and snippets.

@dwhelan
Created April 23, 2017 20:20
Show Gist options
  • Save dwhelan/3c4c9cf68d878b8993ea1ab79babad11 to your computer and use it in GitHub Desktop.
Save dwhelan/3c4c9cf68d878b8993ea1ab79babad11 to your computer and use it in GitHub Desktop.
Edges

Edges

  • definition
  • taxonomy
  • patterns
  • refactorings

Definition

An edge is a discontinuity along some dimension in our system code.

An edge can exist as essential complexity in the domain. Or an edge can exist as accidental complexity we have added to our code.

Taxonomy

There are different types of edges in our system. Let's take a look at some possible ways to classify edges.

Conditional edge

Control flow statements, such as an if, cause a split in the control flow. This we can call a conditional edge.

Exceptional edge

Throwing an exception creates an exceptional edge where the normal flow is handled in one code block and the exceptional condition is handled in another.

This differs from a conditional edge in that the exception handling code may be in a different file and might even cross the system boundary.

Temporal edge

Temporal edges occur when some part of our code needs to wait, creating an edge between the now code and the future code.

These edges can be challenging to reason about. Javascript promises are an example of an edge mitigation where the complexity of handling asynchronous callbacks has been simplified. Or, as discussed below the edge has been pushed from our code into a promise.

Essential edge

Essential edges exist in our domain regardles of how we choose to express them in our code.

Example

Discounts can only be applied to sales over $100.

In this case, an essential edge divides the processing rules for sales over $100, which can have discounts, from other sales.

Accidental edge

An accidental edge is an edge that we have introduced in our code. These edges don't exist in our domain but have emerged due to the way we wrote the code.

Principles of edge reduction

Essential edges

An essential edge in our domain can only be removed by redefining our domain without the edge.

pull out to the boundary of the system

push in to code deeper with the system

extract edge as a first class domain concept

Refactoring Tactics

Pull edge out

Context

Similar edges may appear throughout our code and can be problematic:

  • they tend to create a proliferation of duplicate checking logic
  • they make it more difficult to reason about program flow

By pulling the edge out we may be able to simplify our code and remove duplication.

Before

function process(operation, connection) {
  if (operation === 'remove') {
    remove(connection);
  } else if (operation === 'update') {
    update(connection);
  } 
}

function remove(connection) {
  if (connection === undefined) {
    throw Error;
  } else {
    connection.remove(...);
    ...
  }
};

function update(connection) {
  if (connection === undefined) {
    throw Error;
  } else {
    connection.update(...);
    ...
  }
};

After

function process(operation, connection) {
  if (connection === undefined) {
    throw Error;

  if (operation === 'remove') {
    remove(connection);
  } else if (operation === 'update') {
    update(connection);
  } 
}

function remove(connection) {
  connection.remove(...);
  ...
};

function update(connection) {
  connection.update(...);
  ...
};

We have moved the conditional edge up one level in our code. It may be possible to continue this outwards until we reach our system boundary.

Validation

Often we need to validate data coming into our system.

Example

We are writing an API to return geographic data based on a postal code. We want to be sure the postal code is valid.

// processor.js

function isValid(postalCode) {
  return /^[A-Z]\d[A-Z]\d[A-Z]\d$/.test(postalCode);
}

function process(postalCode) {
  if (isValid(postalCode)) {
    // valid postal code => process the request
  } else {
    // invalid postal code
    throw new Error();
  }
};

Here we see a conditional edge between the code for valid and invalid postal codes. More importantly, the way in which the error is handled, in this case by throwing an exception, is likely not part of the domain. So, we would like to pull out the edge into a protective shell that validates the postal code before calling process().

// shell.js
const processor = require('processor');

function isValid(postalCode) {
  return /^[A-Z]\d[A-Z]\d[A-Z]\d$/.test(postalCode);
}

function process(postalCode) {
  const processor = ...;

  if (isValid(postalCode)) {
    processor.process(postalCode);
  } else {
    // invalid postal code
    throw new Error();
  }
};
// processor.js

module.exports.process = postalCode => {
  // process the request
};

We have simplified the code in the processor.js file. However, it seems like an SRP violation to have the domain validity checking in a shell layer as this check is really a domain concept. In other words, we would prefer to define domain validations within our core domain but invoke them from the shell.

// validator.js
module.exports.isValid = postalCode => {
  return /^[A-Z]\d[A-Z]\d[A-Z]\d$/.test(postalCode);
}
// shell.js
const validator = require('validator');
const processor = require('processor');

function process(postalCode) {
  if (validator.isValid(postalCode)) {
    processor.process(postalCode);
  } else {
    // invalid postal code
    throw new Error();
  }
};
// processor.js

module.exports.process = postalCode => {
  // process the request
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment