Description
Every Diffo Instance consumer ends up writing the same after-action body inside its :define, :relate, and :assign_* update actions. The inputs are entirely Diffo-known — characteristics/0, pools/0, Characteristic.update_all/3, Pool.update_pools/3, Relationship.relate_instance/2, Assigner.assign/3 are all framework — yet each consumer re-writes the wiring on every action of every resource.
The three recurring shapes:
:define (~11 copies per typical consumer)
change after_action(fn changeset, result, _context ->
with {:ok, result} <- Ash.load(result, [:characteristics]),
{:ok, result} <- Characteristic.update_all(result, changeset, characteristics()),
{:ok, result} <- Pool.update_pools(result, changeset, pools()),
{:ok, result} <- MyDomain.get_<resource>_by_id(result.id),
do: {:ok, result}
end)
:relate (~11 copies)
change after_action(fn changeset, result, _context ->
with {:ok, result} <- Relationship.relate_instance(result, changeset),
{:ok, result} <- MyDomain.get_<resource>_by_id(result.id),
do: {:ok, result}
end)
:assign_ (~6 copies, parameterised by pool name)*
change after_action(fn changeset, result, _context ->
with {:ok, result} <- Assigner.assign(result, changeset, :pairs),
{:ok, result} <- MyDomain.get_<resource>_by_id(result.id),
do: {:ok, result}
end)
In diffo_example we collapsed all three into local change modules:
update :define do
argument :characteristic_value_updates, {:array, :term}
change set_attribute(:resource_state, :operating)
change DiffoExample.Changes.Define
end
update :relate do
argument :relationships, {:array, :struct}
change DiffoExample.Changes.Relate
end
update :assign_pair do
argument :assignment, :struct, constraints: [instance_of: Assignment]
change {DiffoExample.Changes.Assign, pool: :pairs}
end
That collapsed roughly 28 hand-written after-action bodies across 11 resources into three change modules — and read much closer to the intent expressed by the action name.
What we'd find useful
The three change modules (or whatever shape diffo prefers) shipped with diffo, so consumers can drop them in:
change Diffo.Provider.Changes.Define
change Diffo.Provider.Changes.Relate
change {Diffo.Provider.Changes.Assign, pool: :pairs}
The reload step can use the resource's primary :read action via Ash.Query.for_read(:read) (which is how our local versions ended up working) — so no consumer-specific reader is needed.
Why it matters
These three patterns are the connective tissue between Diffo's DSL declarations and what consumers actually do with them at runtime. Asking every consumer to thread characteristics(), pools(), Characteristic.update_all, Pool.update_pools, Relationship.relate_instance, Assigner.assign together in identical after-actions is making the consumer re-derive a contract Diffo already holds. It also drifts — the eleven copies across our domain were close to identical but not perfectly, and we caught a missing Ash.load(:characteristics) step that needed to be added everywhere when we hit a NotLoaded error.
A possible direction
Diffo.Provider.Changes.Define / Relate / Assign next to Diffo.Provider.Extension.Characteristic / Pool / Instance.Relationship feel like the natural home. The three local change modules we wrote are around 40-50 lines each and entirely Diffo-internal logic; we'd happily move them upstream if the maintainer agrees with the shape.
Related: #169 — the same observation pattern (Diffo holds the declarations, consumer re-derives them) shows up at JSON-encoding time too.
Description
Every Diffo Instance consumer ends up writing the same after-action body inside its
:define,:relate, and:assign_*update actions. The inputs are entirely Diffo-known —characteristics/0,pools/0,Characteristic.update_all/3,Pool.update_pools/3,Relationship.relate_instance/2,Assigner.assign/3are all framework — yet each consumer re-writes the wiring on every action of every resource.The three recurring shapes:
:define (~11 copies per typical consumer)
:relate (~11 copies)
:assign_ (~6 copies, parameterised by pool name)*
In diffo_example we collapsed all three into local change modules:
That collapsed roughly 28 hand-written after-action bodies across 11 resources into three change modules — and read much closer to the intent expressed by the action name.
What we'd find useful
The three change modules (or whatever shape diffo prefers) shipped with diffo, so consumers can drop them in:
The reload step can use the resource's primary
:readaction viaAsh.Query.for_read(:read)(which is how our local versions ended up working) — so no consumer-specific reader is needed.Why it matters
These three patterns are the connective tissue between Diffo's DSL declarations and what consumers actually do with them at runtime. Asking every consumer to thread
characteristics(),pools(),Characteristic.update_all,Pool.update_pools,Relationship.relate_instance,Assigner.assigntogether in identical after-actions is making the consumer re-derive a contract Diffo already holds. It also drifts — the eleven copies across our domain were close to identical but not perfectly, and we caught a missingAsh.load(:characteristics)step that needed to be added everywhere when we hit a NotLoaded error.A possible direction
Diffo.Provider.Changes.Define/Relate/Assignnext toDiffo.Provider.Extension.Characteristic/Pool/Instance.Relationshipfeel like the natural home. The three local change modules we wrote are around 40-50 lines each and entirely Diffo-internal logic; we'd happily move them upstream if the maintainer agrees with the shape.Related: #169 — the same observation pattern (Diffo holds the declarations, consumer re-derives them) shows up at JSON-encoding time too.