You, the developer, are a user too
Approaching building systems by thinking of developers as its first users not only ensures smoother engineering processes and developer happiness but also sheds light on unclear parts of the design and enforces good practices from the start.
Products have many users
It’s easy when designing and building systems to forget that they serve more than one user.
Take, for example, a favorite of janky system design interviews everywhere, the URL shortener service. You could naively assume, as many do, that the user is the person that wants their URLs shortened and the person that wants their shortened URL to redirect to a full URL.
But the actual users of the system are altogether different:
- advertisers
- SREs
- paying customers
- customer support agents
- sales
Even when broadening the scope like that, it is easy to miss the earliest user of them all: ourselves, the developer.
Designing for developers
As a developer, the ways we interact with the URL shortener are:
- writing and reading its code
- testing and validating it before release
- communicating with colleagues and stakeholders about the project
- fixing bugs and adding new features
These interactions are, of course, served by external tools (an editor, a source control system, a code review platform, and agile workflows). The actual product design is how the code we write can leverage, augment and benefit from these tools.
I like to approach system design by first listing the interactions users want to have in as technology-agnostic a way as I can. Say hello to user stories!
Centering the developer as a user can lead to stories like these:
- As a developer, I want to be able to get how many errors happened in the component I worked on in the last beta release and correlate them against my pull requests
- As a developer, I want to be able to load-test my software and share the results
- As a developer, I want to share my current work version with other users and gather their feedback
- As a developer, I want to get notified when someone reports a bug on a feature I worked on
- As a developer, I want to spin up a local copy of the production environment and replay the sequence of events that led to an error
Approaching these interactions with a product mindset exposes synergies we might otherwise miss.
All the traditional UX design approaches work wonders: user stories, wireframes, storyboards, domain modeling, and prototypes.
The best about this is: because we are designing for developers, we can take a lot of shortcuts. We can use complex software when necessary; command-line tooling, python notebooks, and excel exports are all fair game. What matters is that developers can do what their stories describe.
Command-Line tools are central to developer-centric product design
I am a fan of command-line tools that allow developers to access valuable information from both development and production systems quickly.
How often has this particular exception been hit in the last seven weeks? How many URLs did the system shorten for each version seeded to beta customers? Can you quickly deploy a branch and share its URL for testing?
Because the command-line tool itself is often just a tiny layer in front of the actual codebase, the design of a command-line tool as a product profoundly influences the architecture of the code.
- Being able to quickly gather the error logs corresponding to a specific git revision and a specific source component requires us to do properly structured logging
- Sharing a work-in-progress with other users requires a quick way to set up and tear down staging environments
The good thing about replacing manual processes with command-line tools is that you can compose them. Processes become easier to compose once you start following a few guidelines:
- Data output gets emitted in structured formats (CSV, JSON, YAML, …)
- Data gets stored immutably in the cloud and referred to by ids (or, in general, basic analytics hygiene). Immutable, persistent data makes reports reproducible across machines and ensures that other users can review and reproduce our findings.
Another advantage of command-line tools is that they offer an “index” into your codebase. Not only do they allow a user to discover what is possible by passing in the -help
flag, but because the script is just a facade to the codebase, it allows a developer to discover which APIs exist and how they are used.
A concrete example: generating a load-test report
Let’s imagine that we are working on the following story:
“As a developer, Anna wants to create a staging environment that matches production two weeks ago and stress it with a synthesized test load that matches black Friday traffic.”
We sketch out what this could look like in a shell script (not how Anna could give this script and its accompanying black-friday.yaml to her colleague Bob, and Bob would have no problem running his version of the load-test, getting his immutable report data back):
RELEASE=$(./tool query -version -date='2 weeks ago' -target production)
DEPLOY_ID=$(./tool deploy -staging -version=${RELEASE})
TEST_ID=$(./tool load-test -target ${DEPLOY_ID} -template black-friday.yaml)
REPORT_ID=$(./tool generate-report -type load-test -from ${TEST_ID})
REPORT_URL=$(./tool get-report-url ${REPORT_ID})
echo "Load test report for ${RELEASE}: ${REPORT_URL}"
From there, it is relatively straightforward to implement the necessary functionality.
It might seem like a lot of effort for something that could potentially be hacked into the shell; in fact, it might be a tremendous amount of effort if you can’t easily create a staging environment, but it will pay off many times throughout the project’s lifetime (due to shortened feedback loops, but that’s a topic for another time).