Raw actions
CFE:
Would there be room for something like this in core? One of the big strengths of Ash is being able to package up business logic once and expose it over several channels, but sometimes the plumbing that makes things that fit the model so easy makes other things that don't quite fit tough.
I'm not married to the specific syntax, rather food for thought regarding the functionality. It would be great to have a layer in the middle between the JSON:API/GraphQL/etc. frontend transformers and the underlying CRUD,
Query
and Changeset
wiring.
Is there already a way folks are accomplishing this besides manually adding plug routers/absinthe resolvers? Has there been any discussion of something like this?44 Replies
Yes, there is absolutely a place for this in core, and I intend to add it soon 🙂
For sure this would be slightly more interesting if one could provide a cross-transformer way to describe the input and output schemas for the transformers to consume.
The syntax will be:
and it will have an action lifecycle just like any other action
Fantastic, this will be really nice.
Will likely operate on a struct called something like
%Ash.Action.Input{}
or perhaps %Ash.Action.Execution{}
some names to be decided on still there 😆
Once we have action :name, <return_type>
we will also get flow :name, FlowModule
action types for dispatching directly to an Ash.Flow
I'd imagine that input in this case will also carry along a little bit more of the upper level channel stuff than currently do the CRUD actions.
🤔 probably not? Anything you want to capture from the parent scope can be added to the context in a plug
So you can thread through whatever you want and have it at
changeset.context.whatever
and query.context.whatever
What did you have in mind for what additionally we'd supply?It's not the end of the world to have plugs to inject things like headers etc. but given this action type is quite specifically targeting cases where you want to break the mold a bit, maybe it would make sense to pass that stuff through.
Yeah, I see what you're saying...I think what we would do to support that is add an option to all actions for a way to capture transport specific stuff
Like, why not pass along a struct that is keyed on the incoming channel type (
:graphql
, :json_api
) and dump a bunch of stuff in there that users can access if they need it.
Yeah
I honestly wouldn't hate that happening for the existing action types either, but I don't find the need quite as often.
Similarly, if I really want that I can obviously just inject the whole
Conn
into my context now (;The explicit mapper is more verbose I guess, but what I like is that you can look at an action and understand how it needs to be adapted to another transport layer. Just passing everything in means some random thing deep down could be accessing something off of the
conn
and now Ash ceases to be the unifying layer in between
but I like the spirit of this, and I think there is a way to get the best of both worldsThat's what I was thinking with the keying, though. That makes it pretty legible in the action itself where you're pulling stuff off of, and easy to catch by
CaseClauseError
when there's an action that wants stuff off the transport but hasn't implemented it.
I definitely get that the actions are intended to be mostly transport-agnostic, and I get the appeal from that standpoint to extract just what needs to be there from whatever upstream looks like.Yeah, the main problem is that action invocations and changes will often be in other files where their changes live, that kind of thing
So its not so easy to see what they are using
and usually that isn't a big deal because you know it can only be transformations of the resource itself
So I think something that leaves the transport layer at the front door will be important
Even if its just
use_transport_layer_context [:ash_graphql, :ash_json_api]
(but I think I like the function better :D)
I'll have to play with it though to see how it feels. If you were interested that could be an interesting project to tackleSounded like this stuff was pretty early-stage at this point. Is there a branch yet, or still on the drawing board?
oh, no I mean the transport layer stuff
no work in progress on either thing though, but open to contributions for both 🙂
Something like this looks good to me, I think that would cover 90% of my use-cases already. Did you have any plan in mind for specifying the schema a bit more flexibly than
argument
? Or augmenting argument
to take structured types?argument can take arbitrarily structured types just fine
argument :argument_name, SomeEmbeddedresource
would generally be the way to do thatNice
but you could also define a custom type that implements the
graphql_type
and json_schema
callbacks for ash_graphql and ash_json_api respectively
(or only one of those if you're only using one of them)Definitely. Is there a way to define this (embedded resource style types) inline? For top-level types I for sure don't mind having a seperate resource module, but you can imagine in the case where you have deeply-nested types for I/O it could get a bit messy.
🤔 well, kind of
You could make a custom map type
And then do
or we could potentially support
json_schema
in the core map type
but there are compiler issues w/ actually defining types in-line
(maybe side-steppable)Great! This looks like a thing that could make good use of a DSL as well, I must say.
Potentially 😄 That could be its own package. Like an
ash_type_builder
for building complex types that will automatically define graphql object types and json schemas.You can get validation as well by being able to use
Ash.Type.NewType
, embedded resources, etc.I think there are some dragons waiting to rear their head with that kind of design really, which is why Ash sticks to arguments/embeds, and if you look at complex data validation/data shape tools, it gets really hairy at some point. We try to push people down a more easy to model path
For sure, it could be its own thing, basically just constructing
Ash.Type
s. Depends whether you want to force new transformers to support it. But maybe those types could internally be transformed into embedded resources if the support for generating GQL and JSON types off of those is already there.
(and would also be mandatory for new transformers)Yeah, embedded resources can be automatically turned into json schemas/graphql types
I see no harm in making a more terse way of declaring deeply nested embeds
and we could potentially support it in core, similar to the way that ecto supports in-line embeds
I guess it's a reasonably straightforward transform if you make use of nested modules.
We do actually also eventually want a json schema/open api -> ash resource transformer
But even still, the hard part of all of that is that embeds support actions & calculations and all of that, stuff that make your deeply nested data much richer
This would be for exposing other existing APIs as Ash resources, no?
Yeah
Basically to generate a rich client library that can be plugged into your other resources with relationships & calculations and the like
The raw action type also goes a long way in that regard.
i.e
Right on, that makes sense. This seems something close to writing your own data layer, but partially so.
Given a lot of those APIs won't expose a full CRUD, will have various structures, etc.
Yeah, we need a lot of different action types at the end of the day
For example:
all of which will be wrappers over the generic
action
I mean, I like the idea of being able to try to map what you can onto the Ash functionality. If the backend lets you paginate, you tell it you can paginate and write a bit of code to transform the Ash pagination input onto the backend structure. If not, you say you can't paginate.
Then you can write other actions on it as usual and mix it into Queries + Changesets.
Yeah, the idea is that if you can map your domain stuff onto our kinds of things (
read
, create
, update
, destroy
) then you are going to have a lot of work done for you
The main thing is just that there are other shapes of actions that we want to support
so that we cover the case
if you think of actions as type signatures it makes more sense
So we're missing
aggregate
is just a convenience really, an alternative to action()
when you want to do stuff we know how to aggregate for you alreadyAnd I believe bulk versions of CUD are on the way as well.
yes, I've been unfortunately delayed on those projects but I'm planning on picking them back up soon
atomic updates will come with bulk updates
but those won't be separate action types
you'll be able to use any (with maybe some exceptions) CUD action type as a bulk action, with tools to optimize for that case
No, but I guess they'll change the type signature?
Or just
[record]
as the case may be.ah, yes. With an
| :ok
at the end since most bulk actions probably won't want to return everything
but yes that is the general shape we're achieving
I think I'll be back onto bulk actions this coming week or next week, if all goes well I'll have bulk creates merged by end of next week.Right, awesome.
I dropped the new OpenAPI stuff into one of my projects earlier.
I'm gonna open a new thread for that, 1m.