Skip to main content

Use database constraints for rules a serializer cannot enforce alone

2026-11-11 16:36:24.041461+00:00
Use database constraints for rules a serializer cannot enforce alone

Two requests can both ask whether a slug is available, receive "yes" and then insert the same slug. Application validation improves the error message, but the database must enforce the rule shared by concurrent writers.

Concurrent inserts meet the database uniqueness boundary; the duplicate receives a conflict.

Put the invariant in the schema

For a toy Django application, make a slug unique within its workspace:

from django.db import models

class Document(models.Model):
    workspace_id = models.PositiveIntegerField()
    slug = models.SlugField()

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["workspace_id", "slug"],
                name="document_workspace_slug_unique",
            )
        ]

Create and apply the migration before relying on the constraint. First check existing data for duplicates; the migration cannot make conflicting rows disappear on its own.

An application service can translate the expected conflict into a useful domain error. Catch the exception outside the atomic block so subsequent database work does not run inside a broken transaction:

from django.db import IntegrityError, transaction

def create_document(workspace_id, slug):
    try:
        with transaction.atomic():
            return Document.objects.create(
                workspace_id=workspace_id, slug=slug
            )
    except IntegrityError:
        if Document.objects.filter(
            workspace_id=workspace_id, slug=slug
        ).exists():
            raise ValueError("That slug already exists in this workspace")
        raise

This fragment assumes the model above. In a production API, prefer a stable validation response and inspect the violated constraint where the database adapter supports it. An IntegrityError can have causes beyond this particular uniqueness rule.

Test the database rule directly

Insert a synthetic workspace-one document, then try another with the same slug and workspace. The second write should fail. The same slug in workspace two should remain valid.

A concurrency test needs separate database connections and realistic transaction boundaries. A single sequential serializer test proves the friendly validation path, not that concurrent writers are contained. Exercise the database backend used for deployment.

Decide whether slug comparison is case-sensitive. The basic constraint follows the database's configured comparison semantics; a product rule treating "Guide" and "guide" as identical needs a corresponding normalized value or expression-based constraint.

Return a conflict message that helps the user choose another slug. Log unexpected constraint failures internally so a new schema issue is not mistaken for ordinary duplicate input. The Nirogidhara case study gives related application context. Django's constraint reference and PostgreSQL's constraint documentation explain the schema mechanisms.