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?


  1. The board game. 

  2. With Dune: Imperium it took me around 2 or 3 playthroughs 

  3. I prefer singular since you're going to be writing queries like user.name not users.name and what are you going to do for address? addresses yuck!. How about person and people, people.name lol. 

Socials
Friends
Subscribe