Atomic Counter - MySQL-like AUTO_INCREMENT Implementation in DynamoDB

When inserting data into NoSQL stores like DynamoDB, we often utilize libraries like UUID and ULID to create unique identifiers. This approach works perfectly unless you encounter the following situations:

  1. Migration from Legacy Systems: When migrating data from legacy systems, handling serial numbers becomes complex.
  2. Short Hash Requirements: If you need to pass serial numbers as query parameters and require shorter hashes, UUIDs or ULIDs might not be suitable.

The main issue arises from the character length of the hash generated by UUIDs or ULIDs, which is typically longer than using an integer number.

A more effective solution is utilizing atomic counters through DynamoDB’s UpdateCommand. Below is an example. In this illustration, I’ve considered a single table design and included the entity as an argument. This approach accommodates the creation of serial numbers for various entities such as Invoices and Employees.

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, UpdateCommand } from '@aws-sdk/lib-dynamodb';
import { ApiError } from '../../../shared/errors/api-error';

const translateConfig = {
  marshallOptions: {
    convertEmptyValues: false,
  },
};

const documentClient = DynamoDBDocumentClient.from(new DynamoDBClient(), translateConfig);

export async function getNextSerialNumber(entity: string) {
  const command = new UpdateCommand({
    TableName: process.env.TABLE,
    Key: { PK: `AUTOINCREMENT#${entity.toLocaleUpperCase()}`, SK: `AUTOINCREMENT#${entity.toLocaleUpperCase()}` },
    UpdateExpression: 'SET incrementCounter = if_not_exists(incrementCounter, :initial) + :increment',
    ExpressionAttributeValues: {
      ':increment': 1,
      ':initial': 100,
    },
    ReturnValues: 'ALL_NEW',
  });

  try {
    const data = await documentClient.send(command);
    return data.Attributes?.incrementCounter;
  } catch (error) {
    throw new ApiError(500, { title: 'Internal server error', detail: 'Error generating serial number' });
  }
}