This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-05-19
Channels
- # announcements (3)
- # beginners (29)
- # biff (10)
- # calva (33)
- # cider (1)
- # clara (8)
- # clerk (10)
- # clj-kondo (6)
- # cljs-dev (5)
- # clojure (40)
- # clojure-dev (3)
- # clojure-europe (43)
- # clojure-gamedev (1)
- # clojure-nl (1)
- # clojure-norway (19)
- # clojure-uk (2)
- # clr (3)
- # cursive (12)
- # datomic (4)
- # devcards (3)
- # gratitude (3)
- # honeysql (13)
- # hoplon (25)
- # humbleui (3)
- # hyperfiddle (38)
- # malli (26)
- # pathom (38)
- # practicalli (2)
- # rdf (6)
- # reagent (8)
- # shadow-cljs (13)
- # xtdb (1)
I'm new to pathom and trying to understand how pathom handles one to many relationships. In one of the examples, I see:
(def mock-todos-db
[{::todo-message "Write demo on params"
::todo-done? true}
{::todo-message "Pathom in Rust"
::todo-done? false}])
(pco/defresolver todos-resolver [env _]
; express nested shape
{::pco/output
[{::todos
[::todo-message
::todo-done?]}]}
{::todos mock-todos-db})
The comment says that it's expressing the nested shape, but it seems like the shape would be expressed exactly the same if mock-todos-db
was a single todo item instead of a vector of todo items. Is that right? It seems weird to me that pathom doesn't have a way to express that an attribute's cardinality is "many". Is there something that explains the reasoning behind that?My understanding is that this is a limitation of EQL. There is some mention of it in the tip at the bottom of this section https://edn-query-language.org/eql/1.0.0/what-is-eql.html#_eql_for_selections It's also mentioned in the Fulcro docs, which uses Pathom: > Joins are automatically to-one if the data found in the state is a singular, and to-many if the data found is a vector

@UPWHQK562 , thanks for the links! It seems like it would be possible to extend EQL to support that. I would be curious if that has been investigated or if there’s some rationale for rejecting that approach.
Indeed, I'm curious about the rationale as well. For what it's worth, while this topic does come up occasionally, it doesn't seem like something people bump up against too often as a practical limitation and in my own experience it doesn't present much of an issue.
I thought it would be an issue before I started using it, but the 'types' - whether singular (a string, a date of some form, a boolean, a number) or a collection, are implicit and not explicit by default, and that their types are orthogonal to the graph resolution. ie you could use clojure.spec to define the type or types associated with a specific property. ie you are thinking about properties as your main currency. The reality though is that if I have a property :info.snomed.Concept/descriptions
I'll know it is a to-many just from the name and I've ended up not defining lots of specs here. Indeed, I generally have leaned towards making my specifications around functions first, so that my pathom resolvers don't do any business logic or anything too complex outside of marshalling data.
@U013CFKNP2R, thanks for the explanation. > I'll know it is a to-many just from the name For my use case, I'm looking to leverage pathom for programmatic exploration (maybe similar to data/nav) so it seems like it would be helpful if the cardinality of the property was explicit rather than implicit from the name.
@U7RJTCH6J welcome 🙂 if you need that info, I suggest you make it in some separate way (like using spec), since the attributes have this global meaning in pathom, its easy to attach any extra data (like cardinality) to its name and use where it makes sense for your solution
My use case is building a generic data browsing tool. My idea was to use pathom to be able to browse not just my data, but any data that already supports pathom or can be automatically converted to pathom's model. While I could annotate my own data, it seems like it defeats the purpose if cardinality is implicit for all other existing resolvers. What am I trying to find out is: • Am I thinking about pathom wrong? • Is cardinality for resolvers a possibility in the future? • If not, is there a rationale for intentionally excluding cardinality info? Pathom looks really cool and I've been looking for an excuse to try it out. I'm really just trying to grok its power.
A resolver might return a value for a property that is a java.time.LocalDate
. Or it might return a UUID
. Or it might return a collection of maps that are valid according to a spec defined as :info.snomed/Description
. I think the issue here is more broad than cardinality... it is type.
As such, for a generic browsing tool, you'd try to understand the return value at runtime, as Clojure, and Pathom, are dynamic. That's how I think about resolvers. I can compose them cleanly and any definition of types are orthogonal to the resolver [or indeed function]. I'd echo @UPWHQK562’s comment that it doesn't present much of an issue in general usage.
@U7RJTCH6J maybe we can try to think of it in a different way so we can work it out, from your perspective now, how does it matter for you to knowing the cardinality at pathom level? do you need to enforce any kind of consistency? or need to leverage that info somehow in a static manner? I'm trying to understand how the lack of being explicit affects your solution. and if you can give an example of where some explicit cardinality would help you would be even better
GraphQL also takes the same approach in terms of query syntax, when you look just at a query, you can't tell if some fragment is a single/many, this info must come from the schema (which is required there, but not in Pathom), the rationale is mostly about keeping things simple, but to be honest this is something inherited, but from using it for many years I currently dont see why we would need a different syntax there, it just hasn't been an issue so far
Great question! Part of my reasoning is that it's been drilled into me that modeling 1:1, 1:many, many:many relations is key idea for relational data (which may or may not be relevant in the pathom context). I'm really just trying to cheat off the reasoning of others who might have had a similar intuition and had some realization one way or the other.
My general inspiration is https://www.youtube.com/watch?v=9hLkFq3AD5U. I'm still in the planning phase where I'm trying to figure out what should be by surveying what's currently possible.
Anyway, here are some hypothetical use cases. Hopefully, they don't sound too far fetched.
1. Pagination
If you have a resolver that is multi-cardinality, it would be nice to access the results in chunks rather than realizing the full result. It seems like this isn't really a goal of pathom regardless.
2. UI Layout
The layout of the UI is determined by the shape of the data returned. For a given attribute, it can return nil, a single item, or a collection of items. Whether or not an attribute has a cardinality of many
has a strong influence on the layout. Since it's not possible to tell the difference between a 1:1 attribute that returns nil and a 1:many attribute that returns nil, the layout can shift depending on which entity is being viewed. Ideally, if you look at one entity that returns nil for an attribute and switch to another entity that returns non nil (either a collection or a single entity), it would be nice to be able to have some consistency.
3. Attribute selection
See the graphQL tool below which surfaces cardinality in the tool
4. Lazy Loading
This is sort of related to UI layout, but since a single query can require multiple fetches across resolvers, it would be nice to show results as data is available. The more that's known about the shape of the result ahead of time, the easier it is to fill in results without the UI shifting as results become available
After thinking about it a bit more, I think @U013CFKNP2R’s comment is spot on:
> I think the issue here is more broad than cardinality... it is type.
@U066U8JQJ’s point about GraphQL's schema is helpful as well.
I guess another thing to figure out is why schemas are first class in GraphQL, but not in pathom.
Pathom stays grounded at the attribute level rather than at the entity level where defining schemas makes more sense. With attribute modelling, you gain refactoring speed and agility - you can can add one more attribute that you can access from :thing/id
and don't have to concern yourself with updating the definition of the overall thing and everything it can include.
> With attribute modelling, you gain refactoring speed and agility - you can can add one more attribute that you can access from :thing/id
and don't have to concern yourself with updating the definition of the overall thing and everything it can include.
I don't think that's a differentiator between schemas and pathom's model. For example, in datascript, you can add a new attribute to any entity without doing anything besides transacting the new attribute. You would only need to update the schema if the attribute has a many
cardinality or is a reference.
That's just one example, but there's a spectrum of options you can find between pathom, datomic, datascript, asami, xtdb, etc.
sorry the delay, I wanted to watch the video before I answer back, pretty cool this Ultorg thing 🙂
now, back to the problem, I like to bring up back what the bases for EQL/Pathom are, its a language to intermediate access to information, that intends to hide lookup details from the client, providing a set of attributes that are related somehow, in the end its goal is to serve as a generic API, in this sense, altough Pathom itself doesn't have anything specifically related to pagination, EQL gives enough tools to implement one in a generic way, via query parameters, something like:
[{(:list-of-things {:limit 10 :offset 50}) [:item-attr1 :item-attr2]}]
then its up to the user to interpret the params and make them work, the implementation depends on the underlying system (limit/offset works great on SQL databases, not so much on Datomic)
I think its interesting when we get to the point 2, the UI layout. I understand the need to know what attributes are about when building generic things, so knowing the type and cardinality allows a generic UI to work on them. the way I see it its meta information about your data, and for your use case it needs to live somewhere. in GraphQL it could live in the schema, with Pathom it can live as specs, and/or as other external source, the global naming of attributes makes it trivial to map this kind of data. so, no matter what system you use, you need to know this types/cardinality ahead of time, and how you will know this its up the source of that information
if your source is a SQL, you can inspect the SQL and look it up, so the main work here is not really on the API provider side, but how you gonna read it from the source. for contrast, if you are reading data from a JSON endpoint, most likely you wont know it until you are able to read the data (unless of course they give a swagger or some sort of specification for it).
so if you do have a way to get that info, mapping it in a way that you can leverage in your app is the trivial part
for point 3 is the same I already said, if you have that info in some way, you can use it at your tool
for point 4, there are two things I see here, one is related to knowing what you will render, and with enough metadata you can make the proper space, but that is back to the same things I said already, but I like to add that with attribute modeling you get a plus here. that is, to add any extra meta data to any attribute, can be as trivial as having a map, so lets say for instance that you want to have specialized input fields depending on which attribute is showing up (like the form view on Ultorg), you could have a much such:
(def input-fields
{:user/name :crazy-editor.input-fields/string
:user/birthday :crazy-editor.input-fields/date-picker})
This way, you can just read this map to know what field to render, and if there isn't a definition, you can fallback to the type (like, if the type of the field is a date, lets use a calendar input), this creates this multiple layers of definition, where you can keep specializing and falling back when there isn't a explicit one.the second part is about streaming data, I think would be nice to be able to get parts of the response as they come, Pathom doesn't support that, but I think its not impossible to add, I've considered it for some cases where there is a lot of data and the UI might benefit for getting it in pieces, but I say this lives only in the idea realm at this point
and now to why pathom doens't enforce types, its really because it can do its work without them. this is a choice that favors dynamic things, when using a database like datomic, the types might be crucial for efficiency, so there is a decision to require them. in the case of Pathom, what Pathom really cares is how an attribute relates to other attributes, like a :user/id
might relates to many other user fields it can fetch from. another type of relationship is when we derive data from a computation starting from some other attributes (classic case of :user/first-name AND :user/last-name can compute :user/full-name). because the attributes are global, their own names is what define their semantics (its a context-free system, every attribute lives flat in the world, like datomic attributes vs table attributes, GraphQL is more like the table attributes, where you need to know in which type you are to have a reasonable meaning of what that attribute means semantically)
so in Pathom the attributes relationships can be defined just by saying their names, no types required, and when dealing with related data it dynamically will work with cardinality one or many, whatever the data gets back
👍 Thanks for the detailed response. I'm reading through it right now!
> so lets say for instance that you want to have specialized input fields depending on which attribute is showing up (like the form view on Ultorg), you could have a much such: >
(def input-fields
> {:user/name :crazy-editor.input-fields/string
> :user/birthday :crazy-editor.input-fields/date-picker})
One key idea that maybe wasn't clear is that one of the goals is to have a tool that works with existing data without any extra steps other than providing a reference to the data.
The use case is something like: A user has an existing system with pathom integration. The user opens the tool and can immediately explore the data using the existing resolvers. No extra annotation or meta data required.I get that if I have a specific data set that I want to explore, I could take some extra steps to provide more metadata and annotations to help the tool, but I'm investigating options to avoid those barriers.
so in this case you mean you want Pathom to be one of input sources (like a SQL would be) instead of the main driver?
I'm not saying pathom should or shouldn't have a role. I'm really just trying to understand the landscape of options.
makes sense, and I think in this case, Pathom wont guarantee to you that types are available from things (while GraphQL would for instance), because this is not a required part of a Pathom API
So far, it seems like pathom provides some useful metadata (how to navigate from attritube to attribute), but other useful metadata might need to come from somewhere else.
yeah, it could come from Pathom itself by making the metadata queryable, but its not a built-in thing
for instance, you can have an attribute called :attr.meta/attribute
, and have extra things about it implemented as resolvers (like :attr.meta/type
, :attr.meta/examples
, :attr.meta/cardinality
...)
> for instance, you can have an attribute called :attr.meta/attribute
, and have extra things about it implemented as resolvers (like :attr.meta/type
, :attr.meta/examples
, :attr.meta/cardinality
...)
Right, but then it doesn't work existing datasets without extra steps.
it wont work with any generic pathom graph, but if you are in control of the Pathom source, you could implement it dynamically from a source that gives such info (like SQL or Datomic)