kura_repo behaviour (kura v2.20.7)

View Source

Behaviour for defining a repository (database connection).

Implement otp_app/0 to tell Kura which application owns the repo's migrations.

-module(my_repo).
-behaviour(kura_repo).
-export([otp_app/0]).

otp_app() -> my_app.

Configure the database connection under the kura application env. Repos go in a {repos, #{Name => Cfg}} map; Kura starts the pool for each repo automatically during application startup.

[{kura, [
    {repos, #{
        my_repo => #{
            backend => kura_backend_postgres,
            host => "localhost",
            port => 5432,
            database => "my_db",
            user => "postgres",
            password => "secret",
            pool_size => 10
        }
    }}
]}].

The same form scales to multiple repos (e.g. a Postgres primary plus a SQLite analytics store):

[{kura, [
    {repos, #{
        my_repo => #{
            backend => kura_backend_postgres,
            host => "localhost",
            database => "my_db",
            pool_size => 10
        },
        analytics_repo => #{
            backend => kura_backend_sqlite,
            database => <<":memory:">>
        }
    }}
]}].

Each repo's dialect, pool, and driver are resolved from its backend key. Queries through different repos use their own dialects; the query cache is keyed per repo.

For backward compatibility, Kura also accepts the flat single-repo form ({repo, _}/{backend, _}/{host, _} at the kura env level) and the per-app form (application:get_env(OtpApp, RepoModule)). The {repos, ...} map is checked first, then the flat env, then the per-app form. New projects should prefer the map form.

Optionally implement init/1 to modify config at runtime - useful for reading secrets from files, environment variables, or external services:

-module(my_repo).
-behaviour(kura_repo).
-export([otp_app/0, init/1]).

otp_app() -> my_app.

init(Config) ->
    Config#{
        password => list_to_binary(os:getenv("DB_PASSWORD", "postgres"))
    }.

Optionally implement migration_apps/0 to run migrations shipped by other applications against the same repo - an extension that owns part of the schema, for instance. The application owning the repo module is always included, so this lists only the extras:

-module(my_repo).
-behaviour(kura_repo).
-export([otp_app/0, migration_apps/0]).

otp_app() -> my_app.

migration_apps() -> [my_gdpr_extension].

kura_migrator orders them by their OTP applications lists, so a dependency's migrations run first. See kura_migrator:migration_apps/1.

Summary

Functions

Read the repo configuration from application environment.

Return true if the repo is configured read_only => true.

Pick a replica repo for RepoMod from its replicas => [Repo] config.

Callbacks

init(Config)

(optional)
-callback init(Config :: map()) -> map().

migration_apps()

(optional)
-callback migration_apps() -> [atom()].

otp_app()

-callback otp_app() -> atom().

Functions

config(RepoMod)

-spec config(module()) -> map().

Read the repo configuration from application environment.

read_only(RepoMod)

-spec read_only(module()) -> boolean().

Return true if the repo is configured read_only => true.

A read-only repo rejects writes (insert/update/delete and the bulk and soft-delete variants return {error, read_only}). Reads are unaffected. Intended for a replica repo pointed at a read replica.

replica(RepoMod)

-spec replica(module()) -> module().

Pick a replica repo for RepoMod from its replicas => [Repo] config.

Returns a randomly-chosen replica, or RepoMod itself when none are configured. Routing is explicit: the caller runs reads through the returned repo ((kura_repo:replica(MyRepo)):all(Q)), so the caller owns read-after-write consistency. There is no automatic write/read split.