Paradigm Overview
Paradigm is an experimental modeling framework that focuses on the formal abstraction relationship between a model and its data. This allows for creation and manipulation of multi-layered structures working at the level of:
- meta-model (data interoperability, schema translation)
- model (database migration, schema versioning)
- data (entity analysis, data validation)
The end goal is to characterize complex heterogeneous systems across multi-step transformation pipelines including schema uptake, code generation, and integration tests. First-class treatment of arbitrary Filesystem objects and Shell operations allow for:
- flexible levels of descriptive granularity
- quick integration of existing tools
- traceability and provenance by default
This provides an ergonomic core for next-generation MBSE tooling.
Structure
Paradigms
Paradigm
- Top-level data model containerParadigm.Package
- Namespace organizationParadigm.Class
- Entity definitionsParadigm.Property
- Typed attributes and referencesParadigm.PrimitiveType
- Basic data typesParadigm.Enumeration
- Constrained sets
Graph (Data)
Paradigm.Graph
Protocol - A set of functions for accessing graph nodesParadigm.Graph.Node
- Standardized form for individual entity instancesParadigm.Graph.MapGraph
- An in-memory graph implementationParadigm.Graph.FilesystemImpl
- Provides folder and file nodes from local storage
Paradigm Operations
For these examples we'll use the provided Metamodel paradigm:
metamodel_paradigm = Paradigm.Canonical.Metamodel.definition()
Abstraction
Paradigm.Abstraction
allows movement between paradigm definitions and their graph representations.
- embed - Embeds a
Paradigm
struct into aParadigm.Graph
(an empty MapGraph by default) - extract - Reconstructs a Paradigm struct from metamodel-conformant graph data
Any valid Paradigm
struct should round-trip:
embedded_metamodel = Paradigm.Abstraction.embed(metamodel_paradigm)
Paradigm.Abstraction.extract(embedded_metamodel) == metamodel_paradigm
Conformance
Paradigm.Conformance.check_graph/2
validates that graph data conforms to its paradigm definition. The conformance checker ensures data integrity by validating:
- Class validity - All nodes reference defined classes
- Property completeness - Required properties are present, unknown properties flagged
- Cardinality constraints - List/single value requirements met
- Reference integrity - All references point to existing nodes of correct classes
- Enumeration values - Values match defined enum options
The embedded metamodel validates against itself:
Paradigm.Conformance.check_graph(metamodel_paradigm, embedded_metamodel)
Transform
Paradigm.Transform
Behavior defines the contract for transform modulesParadigm.Transform.Identity
transform provided for demonstration
The transform module requires a target graph which doesn't necessarily need to be empty.
target_graph = Paradigm.Graph.MapGraph.new()
{:ok, transformed_graph} = Paradigm.Transform.Identity.transform(embedded_metamodel, target_graph, %{})
embedded_metamodel == transformed_graph
Universe Paradigm
Paradigm.Canonical.Universe
is a system-level model treating Paradigm.Graph
and Paradigm.Transform
objects as primitive types. Then the actual built-in operations can be used in transform modules. The Paradigm.Universe
module supports content-addressed graphs, which is useful for round-tripping and equality checks on transforms.
Propagate
The Paradigm.Transform.Propagate
transform module takes Universe
->Universe
where any potential conformance checks or transformations are applied. So all the stuff we did in the above section can be achieved internally in a Universe
-conformant graph:
- Paradigm conformance is checked by
Paradigm.Abstraction.extract/1
andParadigm.Conformance.check_graph/2
. The Universe does not contain Paradigms- only Graphs, with all relationships bootstrapped by the metamodel's self-relationship. - Transforms, registered against source and target paradigms, leave
TransformInstance
elements for tracing the flow of data. So we see, for example, thatParadigm.Transform.Identity
leaves aTransformInstance
with the same source and target id - indicating equality.
metamodel_graph = Paradigm.Canonical.Metamodel.definition()
|> Paradigm.Abstraction.embed()
metamodel_id = Paradigm.Universe.generate_graph_id(metamodel_graph)
universe_instance = Paradigm.Graph.MapGraph.new()
|> Paradigm.Universe.insert_graph_with_paradigm(metamodel_graph, metamodel_id)
|> Paradigm.Universe.register_transform(Paradigm.Transform.Identity, metamodel_id, metamodel_id)
{:ok, transformed_universe} = Paradigm.Transform.Propagate.transform(universe_instance, universe_instance, %{})
Conformance checks and transform results can be observed in transformed_universe
.
Installation
If available in Hex, add paradigm
to your list of dependencies in mix.exs
:
def deps do
[
{:paradigm, "~> 0.1.0"}
]
end
Or install directly from GitHub:
def deps do
[
{:paradigm, github: "roriholm/paradigm"}
]
end
Then run:
mix deps.get
Quick Start
Here's a basic example using the canonical metamodel:
# Get the metamodel paradigm
paradigm = Paradigm.Canonical.Metamodel.definition()
# Embed it into a graph for manipulation
graph_instance = Paradigm.Abstraction.embed(paradigm, Paradigm.Graph.MapImpl)
# Validate that the embedded graph conforms to the metamodel
Paradigm.Conformance.check_graph(paradigm, graph_instance)
# => %Paradigm.Conformance.Result{issues: []}
# Extract back to a Paradigm struct
extracted = Paradigm.Abstraction.extract(graph_instance)
# extracted == paradigm