DDD.Migration (vicander v0.1.1)

Copy Markdown View Source

Snapshot-based migration generation for aggregates stored by DDD.Repository.Ecto.

generate/2 compares an aggregate's current columns with the snapshot saved by the previous run and writes a new timestamped migration containing only the difference (create table the first time). Nothing already generated is overwritten.

Safeguards:

  • Removing a column or changing its type loses data, so it is refused unless allow_destructive: true (--allow-destructive on the Mix task).
  • A rename must be explicit: renames: %{"old" => "new"} (--rename old:new) or field :name, :string, renamed_from: :old_name on the field. Otherwise it is a remove plus an add.
  • Edits inside a nested jsonb entity do not change columns. Old rows keep the old shape; the serializer fills defaults for missing keys when loading.

drift/3 compares an aggregate with the real table columns, for use in a test.

Summary

Functions

Columns the Ecto adapter maps an aggregate to (created_at / updated_at included).

The adapter(s) among modules that store aggregate, as DDD.Repository.Ecto records them. mix vic.gen.migration skips an aggregate whose adapters all use a schema you own.

Renames declared on fields with renamed_from:, as %{old => new}.

Default table name for an aggregate (matches DDD.Repository.Ecto).

True when applying the diff can lose data.

Diff two column lists.

Compare an aggregate's columns with the real table. Returns :ok or {:error, %{missing_in_db: [...], extra_in_db: [...]}}.

True when the diff changes nothing.

Generate the migration for aggregate.

The names of the identity fields (["id"], or the parts of a composite identity).

Compare an aggregate with the Ecto schema you wired it to (schema: on DDD.Repository.Ecto). Report only: nothing is written. Returns :ok or {:error, %{missing_in_schema: [...], extra_in_schema: [...]}}.

Compare the Ecto schema an adapter was wired to with the real table (schema: on DDD.Repository.Ecto; the table is the schema's). Report only. Returns :ok or {:error, %{missing_in_db: [...], extra_in_db: [...]}}. Needs the database; for a test.

Types

column()

@type column() :: %{required(String.t()) => String.t() | boolean()}

Functions

columns(aggregate)

@spec columns(module()) :: [column()]

Columns the Ecto adapter maps an aggregate to (created_at / updated_at included).

custom_schema_adapters(aggregate, modules)

@spec custom_schema_adapters(module(), [module()]) :: [map()]

The adapter(s) among modules that store aggregate, as DDD.Repository.Ecto records them. mix vic.gen.migration skips an aggregate whose adapters all use a schema you own.

declared_renames(aggregate)

@spec declared_renames(module()) :: %{required(String.t()) => String.t()}

Renames declared on fields with renamed_from:, as %{old => new}.

default_table(aggregate)

@spec default_table(module()) :: String.t()

Default table name for an aggregate (matches DDD.Repository.Ecto).

destructive?(map)

@spec destructive?(map()) :: boolean()

True when applying the diff can lose data.

diff(old, new, renames \\ %{})

@spec diff([column()], [column()], %{required(String.t()) => String.t()}) :: map()

Diff two column lists.

Returns %{added: [column], removed: [column], renamed: [{old, new}], changed: [{name, old_type, new_type}]}.

drift(aggregate, table, repo)

@spec drift(module(), String.t(), module()) :: :ok | {:error, map()}

Compare an aggregate's columns with the real table. Returns :ok or {:error, %{missing_in_db: [...], extra_in_db: [...]}}.

empty?(arg1)

@spec empty?(map()) :: boolean()

True when the diff changes nothing.

generate(aggregate, opts \\ [])

@spec generate(module(), keyword()) :: {:ok, :unchanged | Path.t()} | {:error, term()}

Generate the migration for aggregate.

Options: :table, :snapshot_dir (default priv/ddd_snapshots), :migrations_path (default priv/repo/migrations), :repo (default: the configured repo), :renames, :allow_destructive, :timestamp.

Returns {:ok, :unchanged}, {:ok, migration_path} or {:error, reason}.

identity(aggregate)

@spec identity(module()) :: [String.t()]

The names of the identity fields (["id"], or the parts of a composite identity).

schema_drift(adapter, renames \\ %{})

@spec schema_drift(module(), %{required(String.t()) => String.t()}) ::
  :ok | {:error, map()}

Compare an aggregate with the Ecto schema you wired it to (schema: on DDD.Repository.Ecto). Report only: nothing is written. Returns :ok or {:error, %{missing_in_schema: [...], extra_in_schema: [...]}}.

renames is %{"aggregate_field" => "schema_field"} for the fields your to_domain/1 and from_domain/1 map by hand. An adapter with a generated schema is always :ok.

table_drift(adapter)

@spec table_drift(module()) :: :ok | {:error, map()}

Compare the Ecto schema an adapter was wired to with the real table (schema: on DDD.Repository.Ecto; the table is the schema's). Report only. Returns :ok or {:error, %{missing_in_db: [...], extra_in_db: [...]}}. Needs the database; for a test.