Skip to content

Commit 115fbfd

Browse files
committed
places do in instance and party extensions
1 parent 30f3d09 commit 115fbfd

15 files changed

Lines changed: 463 additions & 27 deletions

File tree

documentation/dsls/DSL-Diffo.Provider.Instance.Extension.md

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ module at compile time via persisters and are introspectable at runtime via
2222
payload and an enabled/disabled default.
2323
- `parties do` — the party roles that instances of this kind relate to, with multiplicity,
2424
reference, and calculation options.
25+
- `places do` — the place roles that instances of this kind relate to, mirroring `parties do`
26+
in structure and options.
2527

2628
## behaviour
2729

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

4042

4143
## structure
42-
Defines the structural shape of the Instance — its specification, characteristics, features, and parties
44+
Defines the structural shape of the Instance — its specification, characteristics, features, parties, and places
4345

4446
### Nested DSLs
4547
* [specification](#structure-specification)
@@ -51,6 +53,9 @@ Defines the structural shape of the Instance — its specification, characterist
5153
* [parties](#structure-parties)
5254
* party
5355
* parties
56+
* [places](#structure-places)
57+
* place
58+
* places
5459

5560

5661
### Examples
@@ -69,6 +74,10 @@ structure do
6974
parties do
7075
party :provider, MyApp.Provider
7176
end
77+
78+
places do
79+
place :installation_site, MyApp.GeographicSite
80+
end
7281
end
7382
7483
```
@@ -326,6 +335,95 @@ Declares a plural party role on this Instance
326335
Target: `Diffo.Provider.Instance.Extension.PartyDeclaration`
327336

328337

338+
### structure.places
339+
List of Instance Place roles
340+
341+
### Nested DSLs
342+
* [place](#structure-places-place)
343+
* [places](#structure-places-places)
344+
345+
346+
### Examples
347+
```
348+
places do
349+
place :installation_site, MyApp.GeographicSite
350+
places :coverage_areas, MyApp.GeographicLocation, constraints: [min: 1]
351+
place :billing_address, MyApp.GeographicAddress, reference: true
352+
end
353+
354+
```
355+
356+
357+
358+
359+
### structure.places.place
360+
```elixir
361+
place role, place_type
362+
```
363+
364+
365+
Declares a singular place role on this Instance
366+
367+
368+
369+
370+
371+
### Arguments
372+
373+
| Name | Type | Default | Docs |
374+
|------|------|---------|------|
375+
| [`role`](#structure-places-place-role){: #structure-places-place-role .spark-required} | `atom` | | The role name, an atom |
376+
| [`place_type`](#structure-places-place-place_type){: #structure-places-place-place_type } | `any` | | The module of the Place kind. A BasePlace-derived resource. |
377+
### Options
378+
379+
| Name | Type | Default | Docs |
380+
|------|------|---------|------|
381+
| [`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. |
382+
| [`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. |
383+
384+
385+
386+
387+
388+
### Introspection
389+
390+
Target: `Diffo.Provider.Instance.Extension.PlaceDeclaration`
391+
392+
### structure.places.places
393+
```elixir
394+
places role, place_type
395+
```
396+
397+
398+
Declares a plural place role on this Instance
399+
400+
401+
402+
403+
404+
### Arguments
405+
406+
| Name | Type | Default | Docs |
407+
|------|------|---------|------|
408+
| [`role`](#structure-places-places-role){: #structure-places-places-role .spark-required} | `atom` | | The role name, an atom |
409+
| [`place_type`](#structure-places-places-place_type){: #structure-places-places-place_type } | `any` | | The module of the Place kind. A BasePlace-derived resource. |
410+
### Options
411+
412+
| Name | Type | Default | Docs |
413+
|------|------|---------|------|
414+
| [`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. |
415+
| [`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. |
416+
| [`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] |
417+
418+
419+
420+
421+
422+
### Introspection
423+
424+
Target: `Diffo.Provider.Instance.Extension.PlaceDeclaration`
425+
426+
329427

330428

331429

documentation/dsls/DSL-Diffo.Provider.Party.Extension.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,54 @@ Target: `Diffo.Provider.Party.Extension.PartyRole`
110110

111111

112112

113+
## places
114+
Declares the roles this Party kind plays with respect to Places
115+
116+
### Nested DSLs
117+
* [role](#places-role)
118+
119+
120+
### Examples
121+
```
122+
places do
123+
role :headquartered_at, MyApp.GeographicSite
124+
end
125+
126+
```
127+
128+
129+
130+
131+
### places.role
132+
```elixir
133+
role role, place_type
134+
```
135+
136+
137+
Declares a role this Party kind plays with respect to Places
138+
139+
140+
141+
142+
143+
### Arguments
144+
145+
| Name | Type | Default | Docs |
146+
|------|------|---------|------|
147+
| [`role`](#places-role-role){: #places-role-role .spark-required} | `atom` | | The role name, an atom |
148+
| [`place_type`](#places-role-place_type){: #places-role-place_type } | `any` | | The module of the related Place resource |
149+
150+
151+
152+
153+
154+
155+
### Introspection
156+
157+
Target: `Diffo.Provider.Party.Extension.PlaceRole`
158+
159+
160+
161+
113162

114163
<style type="text/css">.spark-required::after { content: "*"; color: red !important; }</style>

documentation/how_to/use_diffo_provider_extension.livemd

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ In this 'Diffo Provider Instance Extension' livebook you will learn about:
3535
* Using the Assigner
3636
* Composing a Resource from partially assigned Resources
3737
* Declaring domain Parties using the Party Extension
38+
* Declaring domain Places using the Place Extension
3839

3940
### Installing Neo4j and Configuring Bolty
4041

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

147148
The extension has two top-level sections:
148149

149-
**`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`.
150+
**`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`.
150151

151152
**`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.
152153

@@ -208,6 +209,10 @@ defmodule Diffo.Compute.Cluster do
208209
party :operator, Tenant
209210
party :manager, Engineer
210211
end
212+
213+
places do
214+
place :data_centre, Diffo.Compute.DataCentre
215+
end
211216
end
212217

213218
behaviour do
@@ -507,6 +512,60 @@ defmodule Diffo.Compute.Engineer do
507512
end
508513
```
509514

515+
## Place Extension
516+
517+
`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.
518+
519+
`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.
520+
521+
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).
522+
523+
### Defining Place kinds
524+
525+
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.
526+
527+
```elixir
528+
defmodule Diffo.Compute.DataCentre do
529+
@moduledoc """
530+
DataCentre in the Compute domain
531+
"""
532+
533+
alias Diffo.Provider.BasePlace
534+
alias Diffo.Compute
535+
536+
use Ash.Resource,
537+
fragments: [BasePlace],
538+
domain: Compute
539+
540+
resource do
541+
description "A Compute Data Centre"
542+
plural_name :data_centres
543+
end
544+
545+
jason do
546+
pick [:id, :href, :name, :type]
547+
compact true
548+
rename type: "@type"
549+
end
550+
551+
outstanding do
552+
expect [:id, :name, :type]
553+
end
554+
555+
actions do
556+
create :build do
557+
accept [:id, :href, :name]
558+
change set_attribute(:type, :GeographicSite)
559+
end
560+
end
561+
562+
instances do
563+
role :data_centre, Diffo.Compute.Cluster
564+
role :data_centre, Diffo.Compute.GPU
565+
end
566+
end
567+
```
568+
510569
### Compute Domain
511570

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

529589
resources do
530590
resource GPU do
@@ -561,6 +621,11 @@ defmodule Diffo.Compute do
561621
define :get_engineer_by_id, action: :read, get_by: :id
562622
define :list_engineers, action: :read
563623
end
624+
625+
resource DataCentre do
626+
define :create_data_centre, action: :build
627+
define :get_data_centre_by_id, action: :read, get_by: :id
628+
end
564629
end
565630
end
566631
```
@@ -591,27 +656,18 @@ alias Diffo.Provider.Instance.Party
591656

592657
### Creating a Cluster
593658

594-
We'll use a helper module to set up the data centre place:
659+
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:
595660

596661
```elixir
597-
defmodule Diffo.Compute.Test do
598-
alias Diffo.Provider
599-
alias Diffo.Provider.Instance.Place
600-
601-
def create_data_centre_place do
602-
dc =
603-
Provider.create_place!(%{
604-
id: "NXTM2",
605-
name: :dataCentreId,
606-
href: "place/compute/NXTM2",
607-
referred_type: :GeographicSite
608-
})
609-
610-
%Place{id: dc.id, role: :dataCentre}
611-
end
612-
end
662+
alias Diffo.Provider.Instance.Place
663+
664+
{:ok, dc} = Compute.create_data_centre(%{id: "NXTM2", name: "NextDC M2"})
665+
```
613666

614-
places = [Diffo.Compute.Test.create_data_centre_place()]
667+
Now build the cluster, passing the data centre as a place and our party members by id and role:
668+
669+
```elixir
670+
places = [%Place{id: dc.id, role: :data_centre}]
615671
parties = [
616672
%Party{id: tenant.id, role: :operator},
617673
%Party{id: engineer.id, role: :manager}
@@ -682,10 +738,10 @@ What happens when I request a specific assignment from an instance to which the
682738

683739
### What Next?
684740

685-
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.
741+
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.
686742

687-
`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.
743+
`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.
688744

689-
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.
745+
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`.
690746

691747
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.

lib/diffo/provider/components/base_instance.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ defmodule Diffo.Provider.BaseInstance do
4141
- `reference: true` — no direct `PartyRef` edge; party is reachable by graph traversal
4242
- `calculate:` — names an Ash calculation on this resource that produces the party at build time
4343
44+
`places do` — declares the Place roles this Instance kind relates to. Mirrors `parties do`
45+
in structure:
46+
47+
places do
48+
place :installation_site, MyApp.GeographicSite
49+
places :coverage_areas, MyApp.GeographicLocation, constraints: [min: 1]
50+
place :billing_address, MyApp.GeographicAddress, reference: true
51+
end
52+
4453
All declarations are introspectable at runtime via `Diffo.Provider.Instance.Info` and at
4554
compile time via `Diffo.Provider.Instance.Extension.Info`.
4655
@@ -62,10 +71,12 @@ defmodule Diffo.Provider.BaseInstance do
6271
- `characteristics/0` — list of `Characteristic` structs
6372
- `features/0` — list of `Feature` structs
6473
- `parties/0` — list of `PartyDeclaration` structs
74+
- `places/0` — list of `PlaceDeclaration` structs
6575
- `characteristic/1` — returns the named `Characteristic` or `nil`
6676
- `feature/1` — returns the named `Feature` or `nil`
6777
- `feature_characteristic/2` — returns the named characteristic within a feature, or `nil`
6878
- `party/1` — returns the `PartyDeclaration` for the given role, or `nil`
79+
- `place/1` — returns the `PlaceDeclaration` for the given role, or `nil`
6980
- `build_before/1` — called automatically before every create action; upserts the
7081
specification and creates features, characteristics, and parties, setting their ids
7182
as action arguments
@@ -96,6 +107,10 @@ defmodule Diffo.Provider.BaseInstance do
96107
party :operator, MyApp.Organization
97108
parties :installer, MyApp.Engineer
98109
end
110+
111+
places do
112+
place :site, MyApp.GeographicSite
113+
end
99114
end
100115
101116
behaviour do

lib/diffo/provider/components/base_place.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ defmodule Diffo.Provider.BasePlace do
7474
relate [
7575
{:place_refs, :RELATES, :incoming, :PlaceRef}
7676
]
77+
78+
label :Place
7779
end
7880

7981
attributes do

0 commit comments

Comments
 (0)