Fork me on GitHub

@thheller I’ve pushed snapshots of a few of the projects, including a 2.5-SNAPSHOT of Fulcro. Not all of my tests are passing in the new aux library yet, but this demo does work with the new Fulcro (and all of the tests are passing for Fulcro). This drops pretty much all of the external deps except for cljs, clj, transit, react, garden, and async.


I like the cleanup, but it won’t be a solid release until I do a bit more work. But, this would likely get you started.


(the demo is client only…so while it pretends to be full-stack, there is no server to start). Also, the fulcro-spec library uses easy server, which means it’ll end up pulling in fulcro-auxillary…though in the test scope. I removed testing from that repo just to see the resulting deps)


So, it turns out that the tests that are failing are due to shadow-cljs code generation differences breaking with-redefs. See #shadow-cljs


@tony.kay got it to work. Looks like the culprit was cljs 1.10.217. Things started working when i downgraded to 1.10.191. Posted an issue over in #shadow-cljs about this.


not seeing any detectable performance hit from your change in 2.4.3-SNAPSHOT


@tony.kay I have this issue thats bugging me about om/fulcro query setup. the point of query language like graphql to me is that components just ask for what they want and don't have to worry where the underlying data is coming from (or its shape) yet in om/fulcro the components ALSO basically define the database schema via idents or am I just understanding idents wrong? idents just break the entire model for me yes having a normalized database is nice but the components shouldn't be the ones deciding how the normalization happens in my mind I guess I might just need to use fulcro more and let it sink in but every time this nagging feeling just keeps coming up (edited) I understand why idents are on the components but it still feels like a mistake


ok, so let's split up the issues: 1. the query lets you ask for what you want (there are two subcomponents of this: local and remote) 2. something has to normalize the data 3. components need to share that normalized data (when they are using the same instance) so that UI stays in sync


There are also these facts: the data has to live somewhere under some schema on both the client and server...and you want the two to be able to not match


i.e. the client schema is about UI, the server is about overall persistence in a larger picture


OK, so select some specific thing you want to work with in UI. The structure of that sub-graph likely matches the schema of the server. So, in the common case, that subgraph can already work against the real server; however, the whole point of a parser on the server is to allow you to map any given query to the real schema of the server.


So, the parser/interpreter model is the join-point for deciding how to map the server's schema to an arbitrary UI schema (as a tree)


This is the same problem with GraphQL or anything else: what you need at the client may not match what the server has natively. There is no getting around that.


yes I agree. however that components have to care about normalization feels wrong. you could still do the normalize in the parser not coupled to the component tree in some way


the server knows nothing of idents...that's a client concern


and how would you achieve that normalization?


"in some way"


I don't know. with some kind of schema I guess.


@thheller I like to look at idents as client ids, they are your local identity of that thing, and it can match the server thing, making things connect in a very easy and concise way


I really dislike the schema idea, what I love about Fulcro is exactly that we can archive all the power without having to define any schema at all


ok, sure, then what maps that schema to components?


you don't need to map it to components


the queries already do that


queries need to map the schema


think about refactoring your've defined a structure that statically defines things in tandem with your UI and database


for a schema like that to work, you have to have to notion of "entities", since your queries are free to ask for any data at all, this is what makes the query flexible, the ident thing is really a very light way make the connections of things, by having idents we never have to know the "type" for that query, and this is very powerful


and what @wilkerlucio said. The flexibility of no schema is really powerful


at the end of the day, idents are a very simple, easy-to-understand mechanism that gets you everything, with minimal binding to static things


I currently think of it like this: the query defines what it returns. where that comes from doesn't matter to the UI. so even you you change the underlying data it doesn't matter to the UI since the query still defined what you get


if you change the query you need to touch both yes but you do that anyways since you need to adjust the parser and UI in case of fulcro


kind of, depend on the change, you only touch the server parser if you need new data that wasn't available before, if you stay on same data space you never have to touch the server


so, let's try to stay on one topic at a time...I think the main concern is normalization, yes?


but the parser/mutations must also be aware of the idents chosen by the components


otherwise they don't know where to put things in this normalized db


coupling of normalization to the components yes. normalization itself is great and I want that.


ok, let's talk about just that for the moment.


Let's use SQL schema for examples???


ok:`CREATE TABLE person (id serial)`


how do I know a component is querying this table?


the component doesn't query this table. the component asks for person(id: 3) { name } to use graphql


the parser maps it to a table. that its even a table doesn't matter to the component.


right, so you have to say "which kind of thing" , and "which one".


could be an entity, a table, anything...but ultimately you have to care about the abstraction as something concrete...because the data has to come from somewhere and go somewhere


and you and other developers have to agree on what that is


yes to all of that but its not the components concern


its somewhere else


so, please let me agree with you that the concern can "be somewhere else", but lets talk about the details


you want it to be somewhere else


it works well for it to be where it is


if we're going to argue subjective opinion, I don't have much interest. If we are going to be objective, I'm fine 🙂


it is almost certainly the case that a schema would be better for certain use-cases, but would totally suck for others


schema is an artifact I have to maintain that talks about a lot of coupling. It has to talk about graph edge. You initial state has to match it. Lots of maintenance points.


co-located initial state, idents, and queries take care of all of that, even through refactoring. Mutations never have to be rewritten, because the data they work on simply uses stable idents. Yes, there is coupling there, but it is the loosest possible.


mapping to arbitrary server state is relatively simple. It has few cases: your tree matches, easy. your tree mis-matches, parsing/interpreting.


but mutations need to know which idents components have chosen


so mutations are directly tied to components


Nope...idents are chosen based on what concept a given set of components is rendering


the mutation and components are tied to the concept of an entity you've stored


you code that into the mutation, and you "inform" the component via ident


20 components could share an ident, and the common mutations on that table/id


a component is a rendering of a particular concept


(and all 20 different components could query for different sets of props about that concept)...but namespaced props and open data means it works fine


Stock Om let you completely define the client database, so I would posit that David agrees with something you're getting at: the components should not care about normalization or the schema.


from a stock Om Next perspective, an ident is "interpreted" just like a "query" is...on the server and client. The "default db" format (as adopted by Fulcro) gives them the meaning you're seeing.


but in Om Next, all that is pluggable, and idents are completely optional. You can define merge to be whatever you want.


(and you get to code all of that)


So, it is my take, that the default db format with idents for normalization can be made to be "pragmatically good"...that is my goal: something with minimum boilerplate, and easy maintenance. Refactoring, testing, addition of new features, etc. should cause few headaches.


And, at the end of the day, you can still write a clojure spec that says what shape your client db should have, and can be used for interesting schema-like things. When people talk about schema, this is usually the benefit they're looking for: data validation. But it adds overhead and complexity. So, having that concern as an "add-on" is good. Fulcro without schema/spec does have some headaches with respect to typos 🙂


I guess the thing I'm struggling with is that I thinking about data normalization based on my components. I can't just ask for data, I first have to create a new component and give it some ident. what if I have a component where I want to display how two different entities relate to each other


nvm .. joins


joins, or mapping via server interpretation of the query


so components that don't render anything and just serve as an ident definition are fine?


@thheller by giving away the idea of entities, we can also combine what would normally be differnet things in a single one, for example, here at our internal graph on nubank, this is a valid query:


Yes! That is not only fine, it can be quite useful.


[{[:customer/id "1234"]


because the way data relates, it's easy to write resolvers that depend on any data, and the attributes are the connection points, if we had to define a "type" for this, merging the information would not be easy (because you start having to design interface linking those separated things, and those grow wild like classes on a Java app)


That's also a good point, Wilker, of a different variety: sometimes you don't want to normalize a whole entity to just get at an attribute


If the UI doesn't need it normalized (doesn't display in different places at the same time), then this kind of back-end denormalization is quite useful.


@wilkerlucio did we release the custom read parser support on the client?


I think we did


ah, so with that, you can also customize how the query is interpreted...but I consider that an advanced use that most users should avoid. As #pathom improves I hope we'll soon have some plug-in options there that allow you to easily customize this kind of query on the client without having to write much code.


I mean, you already can...pathom is quite good already. I just expect we'll soon have some "prewritten" options.


thanks a lot for the discussion. lots to think about. I need to work with this some more.


Hi, I have an issue with linked query in root component. My query: {:query [{:all-contacts-list (prim/get-query comp/ContactsList)} [:ui/loading-data '_]]}


But loading-data is always nil. When I use [:ui/loading-data '_] in ContactsList component it works - why?


@piotrek I’d recommend using the new named load markers


I’ll take a look - thanks @tony.kay


but to answer your question: I’m not sure why that would not work…


in fact, the global load marker should be fine…so, what is your props access for it?


@piotrek actually disregard the comment about named load markers. I misread your question…this is abt the global load indicator


(defsc Root [this {:keys [all-contacts-list ui/loading-data]}]


(it’s described this way in the book and it works in my ContactsList component)


when I check the contents of the Root props loading data is never there


you’re already at root…try it not as a link query


link queries say “read this from root”, but you’re at root…I’m not sure why the internals don’t like a link from root to root, but that may be an issue


[:ui/loading-data {:all-contacts-list .......}]


as the query


Let me check


now it works - thanks @tony.kay


ok, I should probably add a note to the developer’s guide (or fix link queries from root)


For those using shadow-cljs and fulcro-spec: I just learned that shadow defaults to using static functions, which breaks provided and when-mocking. There is no fix in spec itself, because of how code is generated when static-fns is enabled. To get them to work, add this to your test builds in shadow-cljs.edn: :compiler-options {:static-fns false} ; required for mocking to work


This is the default if you’re using figwheel, so no change is needed there.


I updated the Fulcro lein template to reflect this change as well.