Mon 26 May 2025
Conventional Wisdom
There are two things that are impressive when looking at a software project. 1. The simplicity and 2 the consistency.
It can be overwhelming jumping into an organisation or project where the conventions are all over the place. Sometimes it's better if there's just one way to do things.
You're going to be battling with product and business problems, on top of that you're now having to deal with a team expecting XML and another team expecting JSON.
The more consistency and standardisation in a work place the easier it is to dive into a project and get familiar with the hard parts. Working across teams or on more than one code base can be a lot easier once the ways of working become familiar.
We can compare this to picking up a new board game, there's an initial overhead of learning the instructions and how the game plays out. Moving to a new team should feel like you're changing the board but the rest plays out the same way. Going between teams shouldn't feel like jumping between Dune Imperium and Risk1.
There are many decisions that need to be made in software and not having a generally agreed set of conventions adds more overhead to decisions. The downside is that there's a bit of a cold start issue as we get familiar to new guidelines2, but the payoff is worth it.
Having code that is consistent across the codebase improves comprehension for all of engineering
Software Engineering at Google (pg 173)
Having guidelines and rules help us lift the standard of engineering within an organisation and speeds up decision making on minor things like naming if we have defined our expectations.
How Stripe Builds APIs
When building APIs, internally or externally there are a few things it would be good to agree on upfront.
The Stripe API is generally considered to be good; after all, their API is their business. Many documentation tools even use "stripe-like" in their marketing. Stripe has rules and guidelines, which they follow when constructing APIs and they're usually backed up by solid reasoning.
Here are a few of their suggestions:
Avoid Jargon
The example they give is using an industry specific term for
an API property like card.pan
instead of card.number
.
Most people are familiar with a card "number" there are
fewer people familiar with a card "pan".
Accessible vocabulary can allow you to reach more users, you shouldn't gate keep your product and fence off your services to people that have insider knowledge.
Abbreviations are another example of this and should be called out e.g. GTM, you probably thought of Go To Market but I could have been talking about Google Tags Manager.
Your engineers might not come from the same industry and should ideally have diverse backgrounds. This can be played as a strength; if you notice someone asking for clarity on a phrase or word, perhaps you can find phrasing in your answer that is a more suitable term for the API.
Nested Structure
There will always be surprises with any integration.
I've witnessed a 200
status code returning
the message "Server Error"
.
Having properties such as account_number
and
account_created_at
is another one I've seen. Stripe avoids this
by opting for nested
structures, so in this case they would be returning:
account: {
created_at: <timestamp>,
number: 10
}
Properties as Enums
This one prepares us for the future since having a
property like canceled
being either true or false can get
in the way when you introduce more state. We can
avoid filling up our objects with a myriad of redundant
booleans by sticking to an enum from the beginning.
Express Changes with Verbs
Stripe also tends to use clear verbs if a state change will
occur when you hit that endpoint for example:
/paymant/:id/capture
.
Timestamps
Having all properties that are related to timestamps
suffixed with *_at
. This allows you to distinguish the
type, which is harder had you gone with "created
" as
this could be confused for a boolean.
Currencies
These should be represented in the lowest denomination, for example the pound is 100 pence, so we should be passing £10 as the integer 1000. This also helps against pesky floating point arithmetic. When providing a monitary amount when should also provide an indication as to which currency it is. E.g. "GBP" or "USD".
Metrics
When you're only worried about a single service it seems like defining metric conventions is a wasted effort. Metrics are often an after thought and usually always end up in a mess.
Defining a naming convention for your metrics, tags, and services is crucial to have a clean, readable, and maintainable telemetry data.
DataDog
DataDog has some good insight into how to start providing guidelines for metric names.
Avoid abbreviations
For the same reason mentioned above, these might have multiple meanings and in the metric world you don't want to confuse things like Status Code and Service Charge.
Namespaces
Namespaces are one honking great idea -- let's do more of those!
python -c 'import this'
They recommend having the metrics prefixed with the service or application that's generating those metrics.
Unified Tagging
Using standard tags like env
, service
and version
can
help your new service get off the ground quicker with
dashboards that are already written to aggregate your metrics
around those tags.
Avoid Overly Specific Metrics
If you have a metric for the number of requests you can
start tagging your metrics with "method
" which can be either
POST
, GET
et al. you shouldn't need to create a new metric
called post_requests
to specifically capture post
requests.
Cardinality
The other thing to keep in mind when creating metrics, which
is useful for a guideline doc, is reminding ourselves that
each unique key-value pair represents a new time series,
so we should ensure we don't have tags with high cardinality.
Avoid using tags like user_id
as you'll be storing
n
time series, having a bounded set is better; for example
request methods have at most ~5 values.
Databases
Software engineers are often required to give names to tables and columns in databases. You can generally take a look at the other tables and columns to get an idea for existing naming conventions but I'd lean more cautiously here as all you need is a single column out of line and the conventions go to shit.
Naming Tables
Should they be plural or singular? Well according to stack overflow the second answer with 388 votes says "Plural table names" and the first answer with 388 votes says "Singular names for tables". So it beats me... Just pick one.3
Naming Columns
Some people want to have the table name in the primary key,
like user.user_id
these people shouldn't write software.
Having all tables with the convention of id
as the primary
key for that table I find makes most sense, having this in
common across all tables keeps things simple.
Timestamps
Like with an API the tables are also going to contain
timestamps and having a mix of column conventions to
identify these can be a pain. Typically I've gone with the
suffix of *_at
. E.g. created_at
, completed_at
. I've
also seen created_date
or date_completed
. Similar with
foreign keys these should all be tablename_id
where
tablename
is the other table you're referencing.
Another convention that's typically followed with databases
is ensuring all tables have a created_at
and updated_at
column from the start. Some people even push to having
deleted_at
as one of those default columns.
Indexes
I've noticed an unspoken convention of naming indexes like
so: user_account_id_idx
which would be an index on the
account_id
column in the user
table. You can also suffix
it with the type of index; _include
, _gin
, _brin
.
Guidelines
Some of these can seem pretty straight forward if you've been writing code for a while, but you have to remember that not everyone would be exposed to these rules. There also exist engineers who believe they're better than any convention or guideline.
At the outset code reviews are the place to help form these conventions, they're certainly not for architectural design changes, the time for that has long past.
Setting out some initial guidelines can help you scale and
onboard. Having to select request.statusCode
and
request.status_code
because you're naming things different
across services will only get worse as the company gets
older (it doesn't even need to scale, we're battling with
time).
Some of these guidelines might follow just simple stylistic reasoning, but some of them allow you to get dashboards with very little effort or they help you avoid potential hurdles as the project grows.
Guidelines aren't here to hold developers back from self expression they're in place to help us work together and grow organisational learnings as well as provide a baseline for engineering standards.
How do you ensure your organisation is learning if you're not adding or molding your guidelines, does every new project start from zero?