Diffo.Provider.Specification (Diffo v0.3.0)

Copy Markdown View Source

Ash Resource for a TMF Service or Resource Specification.

A Specification identifies the kind of a TMF Service or Resource Instance. Every instance carries a relationship to exactly one Specification node in the graph, established at build time and changeable via Diffo.Provider.respecify_instance/2.

Identity

A Specification is uniquely identified by {name, major_version}. The id is a stable UUID4 that is the same across all environments for a given {name, major_version} pair — it is typically declared as a constant in the Instance Extension DSL and committed to source control.

Versioning

Diffo uses semantic versioning for Specifications with three independent mechanisms:

ChangeMechanismInstance impactIntended usage
Patchnext_patch_specification!/1None — internal fixCorrections to metadata: description wording, category typos
Minornext_minor_specification!/1None — all instances immediately reflect new versionBackward-compatible additions: new optional characteristics, new enum values
MajorNew module, new id, new major_versionInstances stay on old spec until explicitly migratedBreaking changes

What constitutes a breaking change is deliberately vague — it depends on the specification domain and may require negotiation between provider and consumers.

Major version lifecycle

Major versions are decoupled across the provider/consumer boundary:

  1. Provider publishes V2 — deploys a new Instance kind module (e.g. BroadbandV2) with the same specification name, a new id, and major_version: 2. V1 and V2 coexist; both can be used to create instances.
  2. Consumers adopt at their own pace — each consumer (e.g. an RSP) decides when to start creating V2 instances and when to migrate existing V1 instances.
  3. Provider withdraws V1 — removes the V1 module. Existing V1 instances remain in the graph and continue to operate; the domain API for creating new V1 instances is removed.
  4. Consumers complete migration — each consumer migrates remaining V1 instances to V2 via Diffo.Provider.respecify_instance/2, handling any breaking data changes (e.g. remapping or removing an enum value) before or as part of the respecification.

create upsert behaviour

create_specification/1 uses upsert? true on the {name, major_version} identity. Calling it for an existing {name, major_version} pair preserves any attributes not supplied — a second call without category leaves the existing category intact.

An Ash Resource for a TMF Service or Resource Specification

Summary

Functions

Derives the catalog prefix from the type

Validates that the keys in the provided input are valid for at least one action on the resource.

Same as input/1, except restricts the keys to values accepted by the action provided.

Types

t()

@type t() :: %Diffo.Provider.Specification{
  __lateral_join_source__: term(),
  __meta__: term(),
  __metadata__: term(),
  __order__: term(),
  aggregates: term(),
  calculations: term(),
  category: term(),
  created_at: term(),
  description: term(),
  href: term(),
  id: term(),
  instance_type: term(),
  major_version: term(),
  minor_version: term(),
  name: term(),
  patch_version: term(),
  tmf_version: term(),
  type: term(),
  updated_at: term(),
  version: term()
}

Functions

catalog(type)

Derives the catalog prefix from the type

Examples

iex> Diffo.Provider.Specification.catalog(:serviceSpecification) :serviceCatalogManagement

iex> Diffo.Provider.Specification.catalog(:resourceSpecification) :resourceCatalogManagement

default_short_name()

input(opts)

@spec input(values :: map() | Keyword.t()) :: map() | no_return()

Validates that the keys in the provided input are valid for at least one action on the resource.

Raises a KeyError error at compile time if not. This exists because generally a struct should only ever be created by Ash as a result of a successful action. You should not be creating records manually in code, e.g %MyResource{value: 1, value: 2}. Generally that is fine, but often with embedded resources it is nice to be able to validate the keys that are being provided, e.g

Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()

input(opts, action)

@spec input(values :: map() | Keyword.t(), action :: atom()) :: map() | no_return()

Same as input/1, except restricts the keys to values accepted by the action provided.

primary_key_matches?(left, right)