enumdb

enumdb loads (id, name) reference tables from a relational database into thread-safe enum caches at service startup.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/enumdb"

Reads reference tables into memory at startup, producing populated enumcache instances.

db, err := enumdb.New(ctx, sqlDB, queries)

One call, at startup, after which every lookup is a map read.

The table shape it expects

Rows scannable into (id int, name string). In practice a numeric primary key called id and a unique string column called name:

CREATE TABLE IF NOT EXISTS `example` (
  `id`       SMALLINT UNSIGNED NOT NULL,
  `name`     VARCHAR(50) NOT NULL,
  `disabled` TINYINT NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `id_UNIQUE` (`id` ASC),
  UNIQUE INDEX `name_UNIQUE` (`name` ASC))
ENGINE = InnoDB;

Both uniqueness constraints matter, because the cache is bidirectional. A duplicate name makes the name-to-ID direction ambiguous, and the database is the only place that can enforce it.

The disabled column illustrates the design: the query you supply selects what to load, so extra columns are free and filtering is your choice. WHERE disabled = 0 loads only active values, which is how a value is retired without deleting a row that historical records still reference.

Queries are supplied, not generated

EnumTableQuery carries the queries rather than the table names, so the package builds no SQL and there is nothing to inject into.

It also means the loader is not restricted to single tables. A query that joins, filters by tenant, or reads from a view works exactly as well, because the package only cares that the result scans into two columns.

Loading once at startup

The whole premise is that these values do not change during a process’s lifetime, which holds for status, type and category tables.

That makes the database the source of truth while removing it from the request path entirely. Compare the alternatives: querying per request turns a twenty-row lookup into a network round trip, and hard-coding the constants in Go means two definitions that drift the moment someone adds a row.

Startup is also where a failure is cheapest. A missing table or a changed schema fails the process before it takes traffic, and a rolling deployment leaves the previous version serving.

Lookup cost afterwards

The resulting enumcache resolves in both directions at roughly 15 nanoseconds, allocation-free. That is four to five orders of magnitude cheaper than the query it replaces, so per-field per-request lookups stop being a cost worth avoiding.

Picking up changes

There is no polling and no invalidation. A value added to the table after startup is not visible to a running process.

For a table that genuinely changes, wrap a reload in periodic and call the bulk setters again. Note the merge semantics documented on enumcache: a bulk set adds and overwrites but does not remove, so a deleted row needs an explicit Delete.

Dependencies

Reaches the SQL driver you register, plus enumcache. The package itself uses database/sql.