We’ve built a lot of software over the years at Narrative Science. From StatsMonkey to Quill to Lexio, each product has required tight collaboration and strong alignment on goals and requirements. As engineers, we don’t just write code. We are equally responsible as any other function for the success of our products. Part of that responsibility involves thinking strategically and thoughtfully about our process for building software. It’s not enough to tell the rest of the company to “leave us alone – we got this”. A big part of working in a high-functioning organization is communicating a plan—designing the technical implementation of a new feature is no different.
How do we communicate a plan? The rest of this post contains one major part of our company’s technical design review process: the design doc. It is a template our engineers use to show their plan for a new feature. The design doc starts with an overview explaining the purpose of the template. The rest of the sections contain questions for the engineer to answer and go into detail.
A lot of engineers think that the most important section is the Proposal. This makes sense, right? We’re problem solvers at heart and this contains the details of our solution. It has code snippets, REST API interfaces, and diagrams! Real engineering, manifested on a page.
In reality, the sections that have the most impact are the Problem Statement, Goals, and Requirements. Here is where we must be crystal clear about what we’re solving for and why. We could go on to propose a brand new architecture or introduce a new tool to elegantly solve a difficult technical challenge. But what if the solution is for a problem that the business doesn’t actually have? Nailing these first few sections is the crux of the document. Everything flows from there.
This document is a template and guide for writing technical design docs. You should use this template when you need to propose a new system or “large” feature.
The purpose of a design doc is to communicate your solution to a problem that as a team we are trying to solve. That means this doc needs to educate and convince your peers that your proposal is the right solution to pursue. As an author, remember that you have been thinking about the problem a lot more than the reader. It is imperative that you provide background and context so reviewers can reasonably evaluate your solution. Only after introducing the problem, goals, and requirements should you start to dig into more technical details.
Note that this is a guide and not a set of required fields. It isn’t intended to be burdensome or heavyweight. If a section doesn’t make sense for your design or audience then feel free to briefly explain why it’s not applicable.
Once you feel you have the content nailed down, take a pass at the formatting and structure of the content. Ensure that:
- Bulleted and ordered lists are used when enumerating a group of items
- Important points are highlighted through the use of appropriately nested headers, bolding, italics, etc.
Introduce the problem being solved: why it is important, how are the existing solutions (if any) insufficient? What triggered you to write this doc? What is the core product challenge in front of you? The rest of the doc lays out your plan to address the stated problem.
List of relevant docs that readers should be familiar with before reading this. This should include previous design docs, external website links, JIRA epic links, etc.
Enumerate the high level goals. They should be succinct and clear and amount to no more than a handful. Goals should communicate the point of what’s being done.
These may feel like duplicates of requirements depending on the system — that’s OK. In that case if there is a really important functional and nonfunctional requirement that drives all the others, call it out here.
Enumerate the requirements of the design. A good place to start is summarizing the product requirements and adding any that are specific to the technical design. Requirements should communicate what has to be done to reach the goals. Broadly speaking, functional requirements define what a system is supposed to do and non-functional requirements define how a system is supposed to be.
A functional requirement defines a function of a system or its component. A function is described as a set of inputs, the behavior, and outputs. Functional requirements define what a system is supposed to accomplish.
Generally, functional requirements are expressed in the form “system must do <requirement>”. They can also be expressed as user stories, e.g. “As a ___ I want to ___ so that I can ___”. These should be in the form of a bulleted list.
A non-functional requirement (NFR) is a requirement that specifies criteria that can be used to judge the operation of a system, rather than specific behaviors.
Generally, non-functional requirements are in the form of “system shall be <requirement>”. They can also be expressed as user stories, e.g. “As a ___ I want to ___ so that I can ___”. These should be in the form of a bulleted list.
Out of Scope
Call out, if any, requirements that are specifically out of scope of this document and not going to be solved by this design proposal. Denote if something will be in scope in the near future or discussed in a separate proposal. These should be in the form of a bulleted list.
These are architectural principles and patterns that were considered when making decisions. You don’t need to list all of them, but, if there are a couple that you feel are more relevant to your component, do so.
Most problems have probably already been solved somewhere else. Take an opportunity to study those approaches and either look for things to be learned from, mimicked, or used directly (i.e. buy before build).
Put the meat of the design here. This isn’t a checklist or exhaustive so include things that make sense.
This will include things like:
- Overview of the solution
- Performance considerations
- Scalability considerations
- Security considerations
- Monitoring (alarms, metrics)
Scoping, Phasing, and Estimates
Describe the best way to develop this solution. How would we phase it? Are there other dependencies we need to worry about? What is the high level scope / effort that this will take to build?
We have a number of established testing strategies (unit, component, integration, end-to-end, and performance). Is there anything special that needs to be considered to test this feature? What strategy are we going to take to fully test this feature? Do we need performance tests? Are there new datasets that are going to be needed?
Backwards Compatibility and Release Plan
Existing projects and features need to continue to work. How will this design impact them? What is the plan to migrate existing projects to the new system? How will users take advantage of this feature? What is the rollback strategy if the release goes south? What new feature flags will be created?
When coming up with a release plan, consider the riskiness of this feature. Risk is influenced by the chance that this will break existing functionality, the ability to isolate the changes behind a feature flag, need for database updates/migrations, and ability to back out of the changes required for this feature. The release plan needs to limit this risk as much as possible.
Which team will own the new feature/system proposed in this doc? Among the owners, what is the role breakdown by person and responsibility? Is there going to be a transition over time to a new owner? If so, what is the timeline and how will that happen?
What are you not sure about yet? What do you need to find out or what is blocking you from answering the open questions? These should be in the form of a bulleted list.
Our engineering teams have other ways to facilitate technical designs, including a smaller, more informal memo template; a few Slack channels; and of course, good old-fashioned whiteboards and meetings. Ultimately, the format of the design process must be fluid. We must adapt to changing contexts and the needs of our team. The pandemic has pushed us, like most tech companies, to lean on asynchronous communication like design docs and Slack, even more than the past. We don’t see this changing anytime soon. If it does though, we’ll figure it out together.
Finally, having a process just for the sake of it doesn’t help anyone! The purpose of our technical design system is to help us build higher quality software, faster. It’s for us. The process itself is a work in progress, just like our products.