Updated: 05 Jun 2024, 17:00+02:00

Documenting software design decisions

Table of contents

Learning objectives

After reading through this notebook (and digesting its content), you will know:

  • What a software design decision is,
  • Which use-cases are facilitated by documenting design decisions,
  • How a lightweight structure for documentation (i.e., decision records) looks like, and
  • Which process to follow to get to a design decision.
  • How to annotate text with Markdown syntax - useful to create decision records.

1. Prerequisites

To get most out of this notebook, it is useful if you have a difficult decision affecting your software in mind: one that you have taken in the past, or one that you know must be taken in the future.

Other than that, you don’t have to meet any particular prerequisite.

2. Software design decisions

When developing a new piece of software, we make decisions all the time, e.g.,:

  • How to name a variable
  • Whether to split our code into multiple files
  • Which database to use
  • When to implement a feature
  • etc.

Most decisions are micro decisions, in that we don’t think hard about them or they are inconsequential. For example, with modern code editors like Visual Studio Code, it is a matter of seconds to rename a variable, even across many files.

A few decisions are design decisions (or architecture decisions), something that offers a solution to a non-trivial problem. If we are part of a software development endeavor, we should document such decisions. This helps to:

  1. Create a store of explicit knowledge: among others, this aligns team members and improves decision-making (since you need to write down your arguments).
  2. Communicate with your various stakeholders: why you decided the way you did, and which alternatives you considered.
  3. Carry out the decision: since it has been written down, it is much easier enforced than a vaguely agreed-upon decision in an informal meeting.
  4. Check your software design against new information or changed circumstances: this facilitates to evaluate and possibly revise past decisions further down the road.
  5. Coach new team members: by reading the documented decisions, they will get essential context knowledge.

(I call these the 5C - or five use-cases - of documented design decisions.)

5C of design decisions

5C of design decisions

Some people differentiate design and architecture, wherein the latter is “significant design” only.

In this notebook I chose to write of design, not architecture, because from my experience:

  • People won’t bother to document “insignificant” design - therefore, any documented design decision is an architecture decision by definition.
  • The architecture term itself is not inviting - it erects a mental obstacle which will keep people from contributing.

A documented design decision is an asset, since the use-cases listed above provide continued value over time - but its creation comes at an initial cost: it takes effort to document a design decision. Naturally, we want to minimize cost and maximize value. So we want design decision documentation to be:

  1. Easily accessible for authors and readers alike,
  2. Created only when a decision must be taken at that time, and
  3. Useful in several dimensions (see the 5C above).

While we already elaborated on (3.), we will discuss (1.) and (2.) in the following sections.

3. Decision records

You already know how UML diagrams are useful to describe the structure and behavior of a software system. These diagrams capture the end result, but they don’t show how and why you came up with a particular design, i.e., the rationale of a decision.

Models of software vs decision records

Models of software vs. decision records

Enter decision records (sometimes called architecture decision records, any decision records, or a bunch of other names). A decision record is a concise text file with lightweight structure, which describes a single design decision and its rationale.

These qualities (concise, text file, lightweight) are essential, since they lower the barrier of entry for creation and consumption:

  • Concise: I am more likely to create (or read) a record if it is short rather than long.
  • Text file: I can author (or comment) the record with a text editor of my choice (e.g., from within Visual Studio Code).
  • Lightweight: I am more likely to maintain (or review) a record if it has few structural elements to it.

As decision records accumulate, teams often notice how their overall software design (or software architecture, if you will) becomes more intentional and less ad-hoc: decision records are a low-effort “gateway” to more elaborate documentation formats like arc42.

Some teams never bother to create arc42-like artifacts, since the “design trail” left by a stream of decision records is sufficient to them.

There are many templates available, here is the one proposed for this course:

## 01: [Title]

### Meta

Status
: **Work in progress** - Decided - Obsolete

Updated
: DD-MMM-YYYY

### Problem statement

[Describe the problem to be solved or the goal to be achieved.
Include relevant context information.]

### Decision

[Describe **which** design decision was taken for **what reason** and by **whom**.]

### Regarded options

[Describe any possible design decision that will solve the problem.
Assess these options, e.g., via a simple pro/con list.]

In the following, we briefly discuss the template elements in turn.

3.1. Title

Summarize what the design decision is about. It is up to personal preference whether you describe the problem statement (e.g., How to access the database) or already spell out the solution (e.g., Use SQL for database access).

Indexing design decisions is helpful, since the index numbers can be used as shorthand for later referencing. For example, during team discussion or in other documents, you can simply refer to D-01 instead of calling out the full title.

A well-known example of index usage are the Python Enhancement Proposals. For example, when people refer to the Python style guide, they usually just say (or write) PEP-8.

Example:

## 01: How to access the database - SQL or SQLAlchemy 

3.2. Metadata

We capture two types of information: workflow status and when the record was last updated.

The moment you create a decision record (i.e., the text file), the status becomes work in progress.

Only when agreement was reached it jumps to decided - which could also mean that you rejected either assessed alternative.

Finally, whenever a design decision shall not apply any more, its status becomes obsolete. This could mean that:

  • The decision does not apply to the software any longer (e.g., because of a scope shift);
  • It has been superseded by another decision;
  • While working on the design decision, one very obvious solution emerged and there was never a design decision to begin with (this is an edge case); or
  • No agreement could be reached in the team: at some point it makes sense to close the record, even if unfinished, instead of further dragging it along.

In any case, you should update the decision record with a description why it has become obsolete.

As for the updated field, simply make a habit of updating the date whenever you change something on the text file.

Example:

### Meta

Status
: Work in progress - **Decided** - Obsolete

Updated
: 30-Jun-2024

3.3. Problem statement

Describe the situation that caused the need for a design decision. Typical triggers include:

  • Fundamental decision that sets the course for the whole endeavor (technology- or business-wise).
  • When the decision is taken, it will be difficult to revise it later on.
  • Whether or not to introduce a new element to your technology stack.
  • The design of an interface, which should remain rather stable over time.
  • Solution proposal that would deviate from a given standard or well-known good practice.
  • The decision feels risky, and thus warrants a systematic discussion around it.
  • The team has been discussing the topic for an extended period of time.
  • Many stakeholders are involved, e.g., when deciding on a cross-cutting topic like logging.

Usually, you want to provide as much context as possible - for later communication, to your future self, and as service to new-joiners.

Example:

### Problem statement

Should we perform database CRUD (create, read, update, delete) operations by writing plain SQL 
or by using SQLAlchemy as object-relational mapper?

Our web application is written in Python with Flask and connects to an SQLite database.
To complete the current project, this setup is sufficient.

We intend to scale up the application later on, since we see substantial business value in it.

Therefore, we will likely:

+ Change the database schema multiple times along the way, and
+ Switch to a more capable database system at some point.

3.4. Decision

Write as plainly as possible:

  • Which decision has been taken,
  • Why you are convinced the decision is the right one, and
  • Who decided (name names, not roles or “the team” - since both tend to change).

The why part is arguably the most important one in the whole document. After all, the main rationale for systematically documenting design decisions is to keep a record why a certain decision has been taken, not how the result looks like (for that, you have other artifacts like UML diagrams available).

Avoid pro-forma rationales such as:

  • Following herd instinct: “everybody does it this way” or “that’s how we’ve always done it”.
  • The (technology) choice is the “hip” thing to do right now.
  • There was not enough time to really evaluate the various alternatives.
  • Made-up or apparently unrealistic alternatives, which do not fit the problem statement.

If the best you can come up with are rationales of those types, it is better to set the decision status to obsolete and close the record than to decide on a shaky foundation.

Rationales with substance include:

  • Given the pros and cons of all regarded alternatives, the chosen alternative seems like the logical one (or the least bad one).
  • A conducted proof-of-concept shows that the decision has significant merit to it.
  • Proof is given why the decision will decrease risk, provide value (short-term or long-term), or achieve a specific (business) goal.
  • Just one alternative comes out that fulfils a certain knockout criterion (often, available skills, budget, or time).

Example:

### Decision

We stick with plain SQL.

Our team still has to come to grips with various technologies new to us, like Python and CSS.
Adding another element to our stack will slow us down at the moment.

Also, it is likely we will completely re-write the app after MVP validation.
This will create the opportunity to revise tech choices in roughly 4-6 months from now.

*Decision was taken by:* github.com/joe, github.com/jane, github.com/maxi

3.5. Regarded options

Describe the various alternatives as you investigate them in the decision-making process. It makes sense to present the options in comparison to each other, for example by means of a pro/con list, table or mind-map.

While elaborating a design decision, you will mostly work on and discuss about this section. It is quite common that the mere action of identifying the available options and comparing them will produce a “natural choice”.

Some people prefer a rather formal approach to comparing alternatives, wherein they

  • Settle on evaluation criteria,
  • For each alternative, assign scores to the criteria, and
  • Select the one alternative coming out with the highest score.

In my experience, multi-attribute decision methods of this type can be helpful in guiding decision-making, but they shouldn’t be the decision-making mechanism - not least since criteria and scoring are easily manipulated.

Example:

### Regarded options

We regarded two alternative options:

+ Plain SQL
+ SQLAlchemy

| Criterion | Plain SQL | SQLAlchemy |
| --- | --- | --- |
| **Know-how** | ✔️ We know how to write SQL | ❌ We must learn ORM concept & SQLAlchemy |
| **Change DB schema** | ❌ SQL scattered across code | ❔ Good: classes, bad: need Alembic on top |
| **Switch DB engine** | ❌ Different SQL dialect | ✔️ Abstracts away DB engine |

3.6. Full example

For reference, here is the example in its entirety:

01: How to access the database - SQL or SQLAlchemy

Meta

Status
Work in progress - Decided - Obsolete
Updated
30-Jun-2024

Problem statement

Should we perform database CRUD (create, read, update, delete) operations by writing plain SQL or by using SQLAlchemy as object-relational mapper?

Our web application is written in Python with Flask and connects to an SQLite database. To complete the current project, this setup is sufficient.

We intend to scale up the application later on, since we see substantial business value in it.

Therefore, we will likely:

  • Change the database schema multiple times along the way, and
  • Switch to a more capable database system at some point.

Decision

We stick with plain SQL.

Our team still has to come to grips with various technologies new to us, like Python and CSS. Adding another element to our stack will slow us down at the moment.

Also, it is likely we will completely re-write the app after MVP validation. This will create the opportunity to revise tech choices in roughly 4-6 months from now.

Decision was taken by: github.com/joe, github.com/jane, github.com/maxi

Regarded options

We regarded two alternative options:

  • Plain SQL
  • SQLAlchemy
Criterion Plain SQL SQLAlchemy
Know-how ✔️ We know how to write SQL ❌ We must learn ORM concept & SQLAlchemy
Change DB schema ❌ SQL scattered across code ❔ Good: classes, bad: need Alembic on top
Switch DB engine ❌ Different SQL dialect ✔️ Abstracts away DB engine

4. Decision-making process

Capturing design decisions is a continuous activity (in particular for agile teams), not a one-time effort. Its typical process consists of 3+1 phases, which we will discuss in turn:

  1. Identify and prioritize
  2. Analyze and reach agreement
  3. Polish decision record
  4. Use

3+1 phases of decision process

3+1 phases of the design decision documentation process

4.1. Identify and prioritize

The first activity is to identify the need to take a design decision. For typical triggers, see section “problem statement” above.

Anyone on the team who identifies a potential design decision is encouraged to create a new decision record: this is not the privilege of a dedicated role (like Architect) or tied to seniority.

Upon creating the record, give it an index number and a preliminary title. Often, your first thoughts will circle around the problem statement or potential alternatives. Write those down with 80/20 ambition level, typically within 5-15 minutes.

Next, consider if the design decision needs to be taken now, or whether decision-making can wait. In other words, prioritize against all other tasks that your team has.

Some teams maintain a dedicated Kanban board just for decision records. Others mix pending design decisions into their usual team Kanban board. In any case, your team can (and should) treat design decisions as a work item like any other (e.g., a user story) that must be sized, prioritized and appointed as part of your usual (agile) planning process.

4.2. Analyze and reach agreement

A prioritized design decision proceeds into the analyze phase. Here, you will typically:

  • Redefine the problem statement, add more context, possibly change the title.
  • Identify and elaborate possible alternative options to solve the issue.
  • Find suitable evaluation criteria to compare the alternatives.
  • Collect evidence that support or oppose an alternative.
  • Compare the alternatives and come to a conclusion.

To increase throughput it is advisable to nominate one person who is responsible to drive (and complete) the analysis phase. However, this person should not be the only one contributing. Instead, you want to include as many perspectives as feasible. Not only does this increase acceptance, but it also creates a more nuanced, multi-faceted discussion of the topic at hand.

A final agreement marks the end of analysis. Often in a regular meeting, the persons involved in decision-making find an agreement - this marks the design decision. In agile teams it is common to strive for a consensus-oriented decision-making format. In more traditional organizations, a manager (or a board) will take the decision.

4.3. Polish decision record

Once a design decision exists it usually pays off to invest some editorial effort to polish the decision record. You want to ensure it is written from the perspective of (future) readers, not through the authors’ lens. Things to look out for (and correct) include:

  • Illogical organization of the decision record: Avoid “stream of consciousness”-type writing. Thoughts should be organized in the order that seems logical to an uninvolved reader, not the order which happened to occur to the author(s).
  • Unnecessary long text and repetitions: In the decision-making process it is common to consider some aspects from slightly different perspectives - these can be condensed afterwards. Also, it is often helpful to complement (or replace) prose with a diagram or two.
  • Inconsistent use of terminology: In the heat of the argument, terms can be mixed or applied inconsistently. A finalized decision record should be precise, central terms should be defined properly and used consistently.
  • Assumes too much “insider” knowledge: Remember that decision records are also a means to communicate with (outside) stakeholders, and a learning device for new-joiners. When in doubt, spend a few sentences more on articulating the context in which the decision has been taken.

To enforce that such polishing happens, some teams won’t take a decision until proper editorial quality has been reached: that is, their process is analyze - polish - agree.

4.4. Use

Finally, the design decision has been taken and is documented with a decision record. It now enters the use phase. For a reference of five use-cases, please refer to “software design decisions” above.

5. Basic Markdown syntax

The suggested decision record template uses Markdown. To get you started, a brief primer on the syntax follows.

There are various Markdown flavors. For example, GitHub uses GitHub Flavored Markdown (GFM) on its own web application - but GitHub Pages is configured with Kramdown as its default Markdown interpreter.

All content on this section will work with both GFM and Kramdown, so no need to worry.

5.1. Paragraphs and highlighting

Anything that is not annotated is considered a text paragraph.

To create a new paragraph, leave 1 blank line in between.

Example:

Anything that is not annotated is considered a text paragraph.

To create a new paragraph, leave 1 blank line in between.

Make it a habit to always separate text blocks with a blank line in between. For example:

  • Between heading and paragraph
  • Between paragraph and (un-)ordered list
  • Between paragraph and table
  • Between paragraph and code block

To escape annotations like “*” or “#”, precede them with a \ (backslash.)

Example:

To escape annotations like "\*" or "\#", precede them with a `\` (backslash.)

To highlight with italic, bold, or bold-italic use 1, 2, or 3 * (asteriks).

Example:

To highlight with *italic*, **bold**, or ***bold-italic*** use 1, 2, or 3 `*` (asteriks).

5.2. Headings

A paragraph preceded by 1 or more # (hashtag) plus 1 ` ` (space) is interpreted as a heading - the more #, the lower the heading level.

You must include numbering manually. Also, limit yourself to three heading levels - anything more than that typically compromises readability.

Example:

# Heading

## Sub-heading

### Sub-sub-heading

# 1. Manually numbered heading

5.3. Code

Markdown makes it very easy to include code snippets, which adds to its popularity.

A code block starts with 3 ``` (backticks), optionally followed by the code language for proper code highlighting. On the next line, the code starts. Close a code block with a last line of 3 ```.

Example:

```python
print("Hello, World!")
```

Text indented by 4 ` ` (spaces) will also be interpreted as code - so don’t indent your text.

To add in-line code, enclose it within a pair of ` ` (backticks).

Example:

To implement "Hello, World!" in Python, simply write `print("Hello, World!")`.

5.4. Lists and quotes

To create an unordered list:

  • Leave 1 blank line before the list.
  • Start a list item with 1 + (plus) sign and 1 ` ` (space)
  • … followed by some content.
  • To finish a list, leave 1 blank line after the list.

After that, the text continues.

Example:

To create an unordered list:

+ Leave 1 blank line before the list.
+ Start a list item with 1 `+` (plus) sign and 1 ` ` (space)
+ ... followed by some content.
+ To finish a list, leave 1 blank line after the list.

After that, the text continues.

To create an ordered list:

  1. Do the same as with unordered lists.
  2. But this time, use 1. - the . (point) is important.
  3. Which number you use after the first number doesn’t matter.
  4. The interpreter will auto-increment for you.

Example:

To create an ordered list:

1. Do the same as with unordered lists.
2. But this time, use `1.` - the `.` (point) is important.
1. Which number you use after the first number doesn't matter.
1. The interpreter will auto-increment for you. 

A paragraph with a quote is started with a > (greater than), followed by a ` ` (space).

Example:

> A paragraph with a quote is started with a `>` (greater than), followed by a ` ` (space).

5.5. Tables

Table cells are enclosed by | | (pipes with spaces). The content comes in-between. To separate between table header and table content, include a dedicated line of table cells with at least 3 --- (minus) as separator.

The separators can be re-written to center (| :-: |), right-align (| --: |) or left-align (| :-- |) a table column.

To illustrate, here is the (slightly modified) pro/con table from above:

Criterion Plain SQL SQLAlchemy
Know-how ✔️ We know how to write SQL ❌ We must learn ORM concept & SQLAlchemy
Change DB schema ❌ SQL scattered across code ❔ Good: classes, bad: need Alembic on top
Switch DB engine ❌ Different SQL dialect ✔️ Abstracts away DB engine

Example:

| Criterion | Plain SQL | SQLAlchemy |
| :-: | --: | :-- |
| **Know-how** | ✔️ We know how to write SQL | ❌ We must learn ORM concept & SQLAlchemy |
| **Change DB schema** | ❌ SQL scattered across code | ❔ Good: classes, bad: need Alembic on top |
| **Switch DB engine** | ❌ Different SQL dialect | ✔️ Abstracts away DB engine |

The CommonMark specification misses a table syntax. CommonMark is considered the “common denominator” specification which all other popular Markdown interpreters implement as well. So watch out whether the particular interpreter you are using supports table syntax or not.

To turn some text into a link, simply embrace it with < > (angle brackets), like so: https://www.hwr-berlin.de/.

To provide a separate text description to an URL, use [ ] (square brackets) for the description and ( ) (brackets) for the URL, like so: HWR Berlin.

Example:

<https://www.hwr-berlin.de/>.

[HWR Berlin](https://www.hwr-berlin.de/).

To render an image, provide an explanatory text between ![ ] - notice the ! (exclamation point) - followed by its path between ( ) (brackets). The path could be a relative path on the file system or an absolute path. Illustrative image:

HWR logo

Example:

![HWR logo](https://upload.wikimedia.org/wikipedia/de/9/90/Hochschule_f%C3%BCr_Wirtschaft_und_Recht_Berlin_logo.svg)

5.7. HTML

In principle, you may mix in arbitrary HTML elements. Having said that, try to avoid such HTML injection, except for cases which the Markdown interpreter you use specifically recommends.

A common use-case is the annotation of keyboard commands like Ctrl+S, via the <kbd> HTML element.

Example:

A common use-case is the annotation of keyboard commands like <kbd>Ctrl</kbd>+<kbd>S</kbd>.

Annex: Follow-up recommendations

Congratulations, you have learned about decision records: a lightweight, low-effort way to systematically document software design decisions.

This notebook reflects my personal thinking and experience like no other notebook in this series. Partly, this has to do with the still-emergent research around decision records. Partly, it is the very nature of non-trivial decision-making to be idiosyncratic and contextualized.

So, there is more to learn and more perspectives to be exposed to. I recommend these resources if you want to follow up with the art and science of decision records:

Copyright © 2024 Prof. Dr. Alexander Eck. All rights reserved.