Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 99 additions & 1 deletion documentation/dsls/DSL-Diffo.Provider.Instance.Extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ module at compile time via persisters and are introspectable at runtime via
payload and an enabled/disabled default.
- `parties do` — the party roles that instances of this kind relate to, with multiplicity,
reference, and calculation options.
- `places do` — the place roles that instances of this kind relate to, mirroring `parties do`
in structure and options.

## behaviour

Expand All @@ -39,7 +41,7 @@ See `Diffo.Provider.BaseInstance` for full usage documentation including generat


## structure
Defines the structural shape of the Instance — its specification, characteristics, features, and parties
Defines the structural shape of the Instance — its specification, characteristics, features, parties, and places

### Nested DSLs
* [specification](#structure-specification)
Expand All @@ -51,6 +53,9 @@ Defines the structural shape of the Instance — its specification, characterist
* [parties](#structure-parties)
* party
* parties
* [places](#structure-places)
* place
* places


### Examples
Expand All @@ -69,6 +74,10 @@ structure do
parties do
party :provider, MyApp.Provider
end

places do
place :installation_site, MyApp.GeographicSite
end
end

```
Expand Down Expand Up @@ -326,6 +335,95 @@ Declares a plural party role on this Instance
Target: `Diffo.Provider.Instance.Extension.PartyDeclaration`


### structure.places
List of Instance Place roles

### Nested DSLs
* [place](#structure-places-place)
* [places](#structure-places-places)


### Examples
```
places do
place :installation_site, MyApp.GeographicSite
places :coverage_areas, MyApp.GeographicLocation, constraints: [min: 1]
place :billing_address, MyApp.GeographicAddress, reference: true
end

```




### structure.places.place
```elixir
place role, place_type
```


Declares a singular place role on this Instance





### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`role`](#structure-places-place-role){: #structure-places-place-role .spark-required} | `atom` | | The role name, an atom |
| [`place_type`](#structure-places-place-place_type){: #structure-places-place-place_type } | `any` | | The module of the Place kind. A BasePlace-derived resource. |
### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`reference`](#structure-places-place-reference){: #structure-places-place-reference } | `boolean` | `false` | If true, no direct PlaceRef edge is created; the place is reachable by graph traversal. |
| [`calculate`](#structure-places-place-calculate){: #structure-places-place-calculate } | `atom` | | Name of an Ash calculation on this resource that produces the place at build time. |





### Introspection

Target: `Diffo.Provider.Instance.Extension.PlaceDeclaration`

### structure.places.places
```elixir
places role, place_type
```


Declares a plural place role on this Instance





### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`role`](#structure-places-places-role){: #structure-places-places-role .spark-required} | `atom` | | The role name, an atom |
| [`place_type`](#structure-places-places-place_type){: #structure-places-places-place_type } | `any` | | The module of the Place kind. A BasePlace-derived resource. |
### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`reference`](#structure-places-places-reference){: #structure-places-places-reference } | `boolean` | `false` | If true, no direct PlaceRef edge is created; the place is reachable by graph traversal. |
| [`calculate`](#structure-places-places-calculate){: #structure-places-places-calculate } | `atom` | | Name of an Ash calculation on this resource that produces the place at build time. |
| [`constraints`](#structure-places-places-constraints){: #structure-places-places-constraints } | `keyword` | | Multiplicity constraints on the number of places in this role, e.g. [min: 1, max: 3] |





### Introspection

Target: `Diffo.Provider.Instance.Extension.PlaceDeclaration`





Expand Down
49 changes: 49 additions & 0 deletions documentation/dsls/DSL-Diffo.Provider.Party.Extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,54 @@ Target: `Diffo.Provider.Party.Extension.PartyRole`



## places
Declares the roles this Party kind plays with respect to Places

### Nested DSLs
* [role](#places-role)


### Examples
```
places do
role :headquartered_at, MyApp.GeographicSite
end

```




### places.role
```elixir
role role, place_type
```


Declares a role this Party kind plays with respect to Places





### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`role`](#places-role-role){: #places-role-role .spark-required} | `atom` | | The role name, an atom |
| [`place_type`](#places-role-place_type){: #places-role-place_type } | `any` | | The module of the related Place resource |






### Introspection

Target: `Diffo.Provider.Party.Extension.PlaceRole`





<style type="text/css">.spark-required::after { content: "*"; color: red !important; }</style>
100 changes: 78 additions & 22 deletions documentation/how_to/use_diffo_provider_extension.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ In this 'Diffo Provider Instance Extension' livebook you will learn about:
* Using the Assigner
* Composing a Resource from partially assigned Resources
* Declaring domain Parties using the Party Extension
* Declaring domain Places using the Place Extension

### Installing Neo4j and Configuring Bolty

Expand Down Expand Up @@ -146,7 +147,7 @@ Diffo also has an inbuilt Spark DSL extension [Diffo.Provider.Instance.Extension

The extension has two top-level sections:

**`structure do`** — describes the static shape of the Instance kind: its TMF Specification, Characteristics, Features, and Party roles. All declarations are baked into the module at compile time and introspectable at runtime via generated functions (`specification/0`, `characteristics/0`, `features/0`, `parties/0`) and `Diffo.Provider.Instance.Info`.
**`structure do`** — describes the static shape of the Instance kind: its TMF Specification, Characteristics, Features, Party roles, and Place roles. All declarations are baked into the module at compile time and introspectable at runtime via generated functions (`specification/0`, `characteristics/0`, `features/0`, `parties/0`, `places/0`) and `Diffo.Provider.Instance.Info`.

**`behaviour do`** — declares which Ash actions should be wired for instance lifecycle management. Declaring `create :name` injects `:specified_by`, `:features`, and `:characteristics` arguments onto that action, and the `BuildBefore`/`BuildAfter` changes registered on `BaseInstance` automatically handle specification upsert, feature and characteristic creation, party validation, and graph relationship wiring for every create action. You write the action body for your domain-specific accepts and arguments; the structural wiring is handled for you.

Expand Down Expand Up @@ -208,6 +209,10 @@ defmodule Diffo.Compute.Cluster do
party :operator, Tenant
party :manager, Engineer
end

places do
place :data_centre, Diffo.Compute.DataCentre
end
end

behaviour do
Expand Down Expand Up @@ -507,6 +512,60 @@ defmodule Diffo.Compute.Engineer do
end
```

## Place Extension

`Diffo.Provider.BasePlace` is an Ash Resource Fragment for domain-specific Place kinds, mirroring `BaseInstance` and `BaseParty`. It provides common Place attributes — `id`, `href`, `name`, `type`, `referred_type` — and the `Diffo.Provider.Place.Extension` DSL, which lets a Place kind declare the roles it plays with respect to Instances, Parties, and other Places.

`type` defaults to `:PlaceRef` and is typically set in the `build` action to the concrete place type (`:GeographicSite`, `:GeographicLocation`, or `:GeographicAddress`). When `referred_type` is present, `type` must be `:PlaceRef` — meaning this Place is a reference rather than a physical location.

The `Diffo.Provider.Place.Extension` DSL cheat sheet is at [DSL-Diffo.Provider.Place.Extension](https://hexdocs.pm/diffo/DSL-Diffo.Provider.Place.Extension.html).

### Defining Place kinds

We'll add a `DataCentre` Place kind to our Compute domain. Clusters are hosted at a data centre; the `instances do` block records that relationship from the DataCentre's perspective.

```elixir
defmodule Diffo.Compute.DataCentre do
@moduledoc """
DataCentre in the Compute domain
"""

alias Diffo.Provider.BasePlace
alias Diffo.Compute

use Ash.Resource,
fragments: [BasePlace],
domain: Compute

resource do
description "A Compute Data Centre"
plural_name :data_centres
end

jason do
pick [:id, :href, :name, :type]
compact true
rename type: "@type"
end

outstanding do
expect [:id, :name, :type]
end

actions do
create :build do
accept [:id, :href, :name]
change set_attribute(:type, :GeographicSite)
end
end

instances do
role :data_centre, Diffo.Compute.Cluster
role :data_centre, Diffo.Compute.GPU
end
end
```

### Compute Domain

With all resources defined we can now declare the `Diffo.Compute` domain, which exposes a typed API for each resource:
Expand All @@ -525,6 +584,7 @@ defmodule Diffo.Compute do
alias Diffo.Compute.Cluster
alias Diffo.Compute.Tenant
alias Diffo.Compute.Engineer
alias Diffo.Compute.DataCentre

resources do
resource GPU do
Expand Down Expand Up @@ -561,6 +621,11 @@ defmodule Diffo.Compute do
define :get_engineer_by_id, action: :read, get_by: :id
define :list_engineers, action: :read
end

resource DataCentre do
define :create_data_centre, action: :build
define :get_data_centre_by_id, action: :read, get_by: :id
end
end
end
```
Expand Down Expand Up @@ -591,27 +656,18 @@ alias Diffo.Provider.Instance.Party

### Creating a Cluster

We'll use a helper module to set up the data centre place:
First we create the data centre — our `DataCentre` resource uses `BasePlace`, so it is managed via the Compute domain API like any other domain resource:

```elixir
defmodule Diffo.Compute.Test do
alias Diffo.Provider
alias Diffo.Provider.Instance.Place

def create_data_centre_place do
dc =
Provider.create_place!(%{
id: "NXTM2",
name: :dataCentreId,
href: "place/compute/NXTM2",
referred_type: :GeographicSite
})

%Place{id: dc.id, role: :dataCentre}
end
end
alias Diffo.Provider.Instance.Place

{:ok, dc} = Compute.create_data_centre(%{id: "NXTM2", name: "NextDC M2"})
```

places = [Diffo.Compute.Test.create_data_centre_place()]
Now build the cluster, passing the data centre as a place and our party members by id and role:

```elixir
places = [%Place{id: dc.id, role: :data_centre}]
parties = [
%Party{id: tenant.id, role: :operator},
%Party{id: engineer.id, role: :manager}
Expand Down Expand Up @@ -682,10 +738,10 @@ What happens when I request a specific assignment from an instance to which the

### What Next?

In this tutorial you've used Diffo's Provider Instance Extension to define a Compute domain with a composite Cluster resource comprised of assigned GPU cores, and the Provider Party Extension to define Tenant and Engineer party kinds that operate and manage those resources.
In this tutorial you've used Diffo's Provider Instance Extension to define a Compute domain with a composite Cluster resource comprised of assigned GPU cores, the Provider Party Extension to define Tenant and Engineer party kinds that operate and manage those resources, and the Provider Place Extension to declare where instances and parties exist geographically.

`BaseParty` follows the same pattern as `BaseInstance` — domain-specific party resources use it as a fragment and write their own `build` action for domain-specific attributes. No manual wiring is needed.
`BaseParty` and `BasePlace` follow the same pattern as `BaseInstance` — domain-specific resources use them as fragments and write their own actions for domain-specific attributes. No manual wiring is needed.

A `BasePlace` extension for domain-specific Place kinds (such as a DataCentre with its own attributes) follows the same pattern and will be added in a future release.
Domain-specific Place kinds (such as a DataCentre with its own attributes) use `BasePlace` as a fragment and declare their roles via `instances do`, `parties do`, and `places do` sections on `Diffo.Provider.Place.Extension`. Party kinds similarly declare their place roles via `places do` on `Diffo.Provider.Party.Extension`.

If you find Diffo useful please visit and star on [github](https://github.com/diffo-dev/diffo/). Feel free to join discussions and raise issues to discuss PR's.
15 changes: 15 additions & 0 deletions lib/diffo/provider/components/base_instance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ defmodule Diffo.Provider.BaseInstance do
- `reference: true` — no direct `PartyRef` edge; party is reachable by graph traversal
- `calculate:` — names an Ash calculation on this resource that produces the party at build time

`places do` — declares the Place roles this Instance kind relates to. Mirrors `parties do`
in structure:

places do
place :installation_site, MyApp.GeographicSite
places :coverage_areas, MyApp.GeographicLocation, constraints: [min: 1]
place :billing_address, MyApp.GeographicAddress, reference: true
end

All declarations are introspectable at runtime via `Diffo.Provider.Instance.Info` and at
compile time via `Diffo.Provider.Instance.Extension.Info`.

Expand All @@ -62,10 +71,12 @@ defmodule Diffo.Provider.BaseInstance do
- `characteristics/0` — list of `Characteristic` structs
- `features/0` — list of `Feature` structs
- `parties/0` — list of `PartyDeclaration` structs
- `places/0` — list of `PlaceDeclaration` structs
- `characteristic/1` — returns the named `Characteristic` or `nil`
- `feature/1` — returns the named `Feature` or `nil`
- `feature_characteristic/2` — returns the named characteristic within a feature, or `nil`
- `party/1` — returns the `PartyDeclaration` for the given role, or `nil`
- `place/1` — returns the `PlaceDeclaration` for the given role, or `nil`
- `build_before/1` — called automatically before every create action; upserts the
specification and creates features, characteristics, and parties, setting their ids
as action arguments
Expand Down Expand Up @@ -96,6 +107,10 @@ defmodule Diffo.Provider.BaseInstance do
party :operator, MyApp.Organization
parties :installer, MyApp.Engineer
end

places do
place :site, MyApp.GeographicSite
end
end

behaviour do
Expand Down
Loading
Loading