Using Documentation-Driven Design to Guide API Decisions
As software design evolves, so do the thought processes behind the design decisions we make as engineers. Some of these development practices are widely known and talked about, such as Test-Driven Design, where changes to code are made in programmatic tests before they’re implemented in actual business logic.
These design practices are helpful for our future selves and for our teammates, because they help us keep our code well-maintained and easily extendable. How do these practices help external audiences, or audiences that aren’t as technical, understand our code? When designing a new API or making impactful changes to an existing API, a Documentation-Driven Design approach can be helpful in guiding the design decisions you make, too.
What is Documentation-Driven Design?
Documentation-Driven Design (DDD) is the practice of writing your technical documentation first, and then using those docs to steer your eventual code implementation.
All developers want to work with simple APIs. But when the source material that’s driving API design is not simple, we want an API that makes a dense subject easier to understand. This is where DDD is especially helpful because it forces you to think about how an outsider understands and uses your work. Because the crux of DDD is based on thinking through your code as an outsider, it’s also important to know your audience when writing your docs.
For many of us at PayPal, our main audience are developers working for merchants to implement ecommerce websites, reconcile money disbursements, and manage customer transactions. When we take a DDD approach to our work, we write guides for an audience that reads our Developer Docs website. This means we write for developers of all levels, working in many languages.
Example of Documentation-Driven Design
Let’s assume we have an API that takes credit card information and securely stores it so merchants can safely keep customers on file (like, say, a Vault 😊). There are several types of cards that can be stored — Visa, Amex, prepaid/gift cards, HSA/FSA cards — and they all may have different rules around when and how they are stored. Let’s use DDD to draft a simple example of how a user might create a card with our API.
This example will be pseudo-coded, but the final draft should be syntactically and functionally correct for the programming language it uses.
}).then(response) => (
// do something with the response
).catch => (
On first read, this seems straightforward, and a developer can move on to implementing this in their API. But let’s take a deeper look at our code and think through this from the perspective of someone who is not familiar with the subject. When reading pseudo-code with that lens, the following questions come to mind:
- Does the name of the function
cardmake sense? Would it be easier to understand if this function was renamed to
createCard? Some languages follow different naming conventions, depending on the language a developer would be working in, this name might need updating.
expirationseems fairly generalized, too. Is this info expected in a specific format? Will this field expect both expiration month and expiration year? Should they be separate fields? Would people based in different countries expect to use this in a different format?
- There is a lot of regulation around handling sensitive data like a card’s primary account number (PAN). Does this implementation assume the developer using this API knows those requirements?
Now we have questions that require deeper thought and more careful implementation. Let’s take this feedback and update our docs accordingly:
}).then(card) => (
// charge card
).catch(error) => (
We’ve clarified our API by splitting
expiration into two different fields:
expirationYear. We’ve also changed our API to indicate that we expect to receive a string that is a tokenization of the card’s PAN, instead of the PAN itself, which is a more secure way for folks to use our API. But how does a user get a tokenized string? Looks like we should create a way for users to tokenize card data, too:
tokenize(response, error).then => (
}).then(response).then => (
// send the response to server
).catch(error) => (
DDD creates an opportunity to look at your code from a different lens, which helps you write code with greater clarity and empathy. With the above example, we walked through some ways that DDD can drive your API design, as well. I hope this brief walkthrough helps you in your doc-driven ventures!