Fork me on GitHub
#fulcro
<
2017-09-12
>
tony.kay06:09:31

So, I’ve had a few people recently ask about a lein template. I went ahead and made a bare-bones template for lein. It has no real features: just a full-stack app with as little as possible (easy server, stupid UI, one query, one mutation).

lein new fulcro boo

tony.kay06:09:09

It does include devcards, tests, dev, and production setup.

tony.kay06:09:33

and i18n extract/generate from a Makefile…so, it is a decent starting point for a project, just with very limited code.

roklenarcic07:09:01

is there a way to return a field as ident in networking? Let's say I have a call that returns children of an element such as {:id 3 :name "Name" :parent 5}. Since I already have the parent, I changed my network call implementation to modify the return to {:id 3 :name "Name" :parent [:meds-by-id 5]}. Unfortunately this doesn't seem to work.

roklenarcic07:09:24

what I get is: :parent [[:meds-by-id :fulcro.client.impl.om-plumbing/not-found] [:meds-by-id :fulcro.client.impl.om-plumbing/not-found]]

claudiu08:09:28

@roklenarcic seems like quite a bit of extra complexity for speed boost 🙂 have you tried just returning :parent-id: 5 and updating the state in a post mutation ?

roklenarcic08:09:27

Of course I can do that.

tony.kay15:09:20

@roklenarcic So, I see you continually trying to pre-normalize data before merge. This is never going to work. Normalization happens for you on returns. As @claudiu said, you can use a post mutation, but I think you’re still playing with recursion. You’re making your life quite a bit harder than it has to be, I think because load-field won’t work for your case; however, load will. Here’s the basic outline:

tony.kay15:09:57

1. Recursive queries can’t use load-field because the field is just ...: E.g. (defui BulletItem static IQuery (query [this] [:id :checked :value {:subitems '...}])). A load-field would end up sending the query [{:subitems '...}], which is lacking critical information. You can’t strip out the parent’s fields, because those are also the child’s fields.

tony.kay15:09:43

(or in your case, I think you’re loading a parent…but you get the idea)

tony.kay15:09:21

2. Normalization does work for recursive queries. You just have to issue the right load to the server. The load you want is (load [:meds/by-id 5] Med {:params {:depth 1}}) or something similar. Make the server stop the recursion at the depth you desire for your initial load. 3. When you want to load the recursive elements, just use the same load, but with a deeper depth.

roklenarcic15:09:05

I think the issue is that many times I am aware of what ident I need at a certain spot in the tree, but then I need to construct and put denormalized data there, just to have om normalize it right after that

tony.kay15:09:40

but when do you “know” this? On the server???

roklenarcic15:09:09

well for that example, when loading children of an element, I know who the parent id is.

tony.kay15:09:36

but how do you know the client has it??? This is a distributed system. You’re asking for trouble if the server assumes things like that

roklenarcic15:09:06

because the children load is the result of clicking on the parent

roklenarcic15:09:09

therefore I know I have it

roklenarcic15:09:32

I know it's not completely kosher

tony.kay15:09:45

but if you just re-issue a load on that parent (and tolerate the fact that you “refresh” a few bytes of data), it works seamlessly

roklenarcic15:09:48

as you could have someone issue that load without having the parent

tony.kay15:09:09

and it becomes all one small bit of code (on client and server)

tony.kay15:09:04

penny-wise, pound-foolish to try to save bandwidth this way IMHO

tony.kay15:09:00

unless you’re loading a boat-load of data for each med, in which case perhaps you need to normalize that “heavy” chunk into another component that can be normalized (and demand-loaded) separately.

roklenarcic15:09:15

well it results in 2 extra queries for each child clicked after that load

roklenarcic15:09:46

because the load-ancestors call loads all ancestors

roklenarcic15:09:52

even though I have them

roklenarcic15:09:43

I'll need to pass app into my network, so I can look up state and see if I can skip loading some things

tony.kay15:09:13

Again, why are you worried about not re-loading some things in your graph? How big are they?

roklenarcic15:09:25

small, but they create a noticable delay on the UI

tony.kay15:09:43

so let’s talk about what you mean by a “noticeable delay”

tony.kay15:09:00

you mean they disappear and reappear, or that your networking is 2g and is super slow?

roklenarcic15:09:17

I am on the same gigabit ethernet as my server and it's about 450ms on chrome network graph to do 3 trivial rest calls

tony.kay15:09:55

Oh….you’re interfacing to REST and have to make a separate REST call for each one

roklenarcic15:09:18

and the query is select by ID

roklenarcic15:09:29

so I'm not sure, maybe server is too slow

tony.kay15:09:42

and why can’t you just interface with the database and use a real api instead of REST?

tony.kay15:09:56

you see how much time and energy that would save you, don’t you?

tony.kay15:09:06

the point of data-driven apps is to avoid this madness

roklenarcic15:09:53

I'll need to see how many queries I need to implement

roklenarcic15:09:03

not even sure why I would use for SQL

tony.kay15:09:10

You probably could have written them all by now 😜

roklenarcic15:09:21

it's just so many things to learn

roklenarcic15:09:29

I haven't even started with forms yet

tony.kay15:09:45

Yes, it is a lot to learn. Using REST with it out of the gate makes it about 10x harder.

tony.kay16:09:14

cause now you’re having to do a bunch of backflips to get things into the right shape in the middle of the entire plumbing

roklenarcic16:09:17

well I was trying to skip writing my own backend, when it's already written

tony.kay16:09:32

Not a good fit for Fulcro, IMO

tony.kay16:09:44

“already written” is never true in REST

roklenarcic16:09:51

and there's no guarantee that I won't have same problem with nested datastructures even without REST

roklenarcic16:09:25

so what do you use for SQL then

tony.kay16:09:37

So, if your SQL schema is relatively sane, then fulcro-sql will do the graph queries for you

tony.kay16:09:51

it is a young library, but it works for the 3 most common types of joins

tony.kay16:09:05

and I’d be glad to expand it if you find reasonable cases that could be supported

roklenarcic16:09:32

my fear with things that support 95% of cases is that I will have one case that is in the 5% and I'll have to change everything

tony.kay16:09:35

it will literally take the Om query and write the SQL

tony.kay16:09:54

@roklenarcic You’re covered there

tony.kay16:09:16

fulcro-sql is a query interpreter that you call. If there is a case it doesn’t supports, then you hand-write the query

tony.kay16:09:18

I would recommend you try. Give it a few days. If it sucks, go back to REST 🙂

therabidbanana16:09:04

At my former job we were getting our data from a REST endpoint, but rather than trying to get a parser working directly with REST we broke the problem into two subproblems - 1 - downloading the data into a local database in a format that was easily queryable for Fulcro (pretty easy background jobs), 2 - building a Fulcro app on the happy path. I’d recommend trying that if you absolutely *need* the data to come from the REST endpoints (though sounds like you maybe don’t).

roklenarcic16:09:57

It's a hobby/sideproject that using existing data/services that I have at work

roklenarcic16:09:23

it's not a production thing, so I can't change things that are "real" services

roklenarcic16:09:06

I'm basically learning the stack while piggybacking on existing "ecosystem" so to speak

roklenarcic16:09:19

but I can write my own backend of course

roklenarcic16:09:38

I just thought I would use test instance of the one that is running in production

roklenarcic16:09:59

which is your usual spring boot+hibernate thing

tony.kay16:09:11

(spits coffee on screen)

tony.kay16:09:26

no wonder it’s slow

roklenarcic16:09:42

you mean the hibernate+spring tech stack?

tony.kay16:09:54

your REST API…if it has all of that “load” on top of it

roklenarcic16:09:07

It's what everyone uses now in Java world

roklenarcic16:09:37

Spring + Hibernate + REST is more popular than Jesus right now

tony.kay16:09:42

I know…it’s a lot of the reason I don’t work in Java anymore. Rather wash cars.

roklenarcic16:09:03

Hey I don't like it either, but gotta earn the money somehow

roklenarcic16:09:25

all the clojure jobs are in the states or London with a few berlin ones sprinkled in

tony.kay16:09:26

I actually still like Java quite a bit…but man has that stack gotten hideously complex

roklenarcic16:09:46

People like it because it's easy (but complex)

tony.kay16:09:01

Yeah, easy like rolling a boulder up hill

roklenarcic16:09:14

You add one annotation like @EnableAuthorizationServer and it automatically spawns like 20 beans that configure a bunch of OAuth2 things with smart defaults and connect your server to OAuth server and magically insert spring security filters into your REST servlets and configure them

roklenarcic16:09:46

of course if something goes wrong you are gonna be debugging for a week

tony.kay16:09:50

Yep…then something goes wrong and you spend 20 days figuring out that you can’t fix it

tony.kay16:09:00

(e.g. 450ms REST query times)

roklenarcic16:09:12

well it's 150ms * 3

tony.kay16:09:58

but “easy”, yes

tony.kay16:09:17

The thing that came to mind when you said 150ms 3 was O(n^3) 3

roklenarcic16:09:45

But yes I hate it, but all the management (also dev managers) love easy solutions, big frameworks, things they can put on Candidate Requirements, and they are all afraid of homebrew frameworks, they want something that you can find employees that know it

tony.kay16:09:04

but I guess since n=1, it is prob O(n^3)*3 + 149ms of overhead

roklenarcic16:09:13

they deem anything not industry standard as a liability

tony.kay16:09:29

yep. Been there, done that.

tony.kay16:09:57

Big companies with deep pockets can afford the overhead, and want “cogs” they can plug into seats.

tony.kay16:09:07

it’s fine for job security

tony.kay16:09:39

but if you’re playing with Fulcro, then you’re interested in something different that has a promise of better: drink the entire cup of cool-aid and try it as designed 😉

roklenarcic16:09:45

exactly.. less ambitious employees love knowing Spring because that's what gets you hired, employers love Spring because that's what you can get people for

roklenarcic16:09:26

I'm the only one in this shop that is doing something else than Java, JS or Typescript

roklenarcic16:09:37

and i also do Rust

roklenarcic16:09:09

I'll have a look at that java backend to see how big it is

roklenarcic16:09:18

and I will probably try full stack fulcro

roklenarcic16:09:39

no reason not to, as I have unlimited time, so to speak

tony.kay16:09:47

Every time I’ve looked at the kind of Java back-end you’re talking about, it is 9 parts complexity, and 1 part functionality.

roklenarcic16:09:13

there's a lot of things that are completely orthogonal to what you're trying to do

roklenarcic16:09:11

sadly this particular backend is one of the better ones, one of the larger apps here has DB Table inheritance model

roklenarcic16:09:24

Some genius said: Patient, Doctor, Nurse, Employee, User should all "extend" the same table with basic columns like first name and last name, so now any query involving those has additional joins (since each table also has *_versions table). Even many to many connector tables are versioned. Not only is that in conflict with Liskov's substitution principle, figuring out a doctor's name and his org. unit from his username is a 15 table join.

roklenarcic16:09:24

It is acutally, you just tell hibernate what you want and hibernate generates a 3 page SQL for you

roklenarcic16:09:33

easy and slow

tony.kay16:09:33

Until you need a report

tony.kay16:09:54

I did a talk on Hibernate…about how it fails at every possible database operation you want

roklenarcic16:09:14

and "generated 3 page SQL" is not an exaggeration

roklenarcic16:09:10

funny thing is that 95% of these services never need ORM's change/dirty tracking, they just push what came in POST into DB

tony.kay16:09:04

Everything from “identity” on is a nightmare

tony.kay16:09:12

schema: spread out all over classes…is that indexed? How does that FK relate? CRUD: fail fail fail fail The only thing it is “good for” is very simple single-class form input, but the setup to even get there is crazy

roklenarcic16:09:46

5 years ago I was using MyBatis, before I was forced to switch to full ORMs

tony.kay16:09:12

Your real hard task in most databases is reporting, and it really really fails there. Everyone either hand-writes real SQL, or does the super-stupid thing and uses Hibernate to bring in a ton of objects into memory so they can hand-munge them.

roklenarcic16:09:56

I was ok with MyBatis, it had some templating and help for commas and opening and closing parenthesis and for snippets, otherwise you wrote your own queries so you knew what was executing when, and it had resultset -> object or map mapping, which was useful

tony.kay16:09:14

yeah, a thin layer like that is super-useful

roklenarcic16:09:33

I heard good things about jooq

roklenarcic16:09:53

I have to look into it someday

roklenarcic16:09:19

one thing that hibernate does address that is useful is hibernate dialects, it makes it easy to port to different databases. When you are selling a product rather than running your own production stack it becomes important to be able to run on multiple DB vendors

tony.kay16:09:28

That sounds right, until you realize how limiting that is. Now your reporting must be done strictly via Hibernate HQL and in-memory joins. I guess if you’re willing to take that cost…but really, is it that hard to have a much simpler API to provide that isolation layer?

tony.kay16:09:54

I always write my database stuff as a very thin injectable layer. There aren’t that many SQL databases that real people use. From a market-analysis standpoint in business, I’d imagine covering MySQL, PostgreSQL, MSSQL, and Oracle would get you 99%

tony.kay16:09:13

and most of your SQL would work perfectly among them with minor tweaks

tony.kay16:09:47

anyway. kind of off topic at this point 🙂

rkwofford17:09:23

The new lein template doesn't work out of the box for me. Here's what I get when running lein deps: rkwofford@rkwofford-W840SU-Series:~/code/rkw$ lein deps Could not find artifact org.clojure:clojurescript:jar:1.9.915 in central (https://repo1.maven.org/maven2/) Could not find artifact org.clojure:clojurescript:jar:1.9.915 in clojars (https://clojars.org/repo/) This could be due to a typo in :dependencies or network issues. If you are behind a proxy, try setting the 'http_proxy' environment variable.

rastandy17:09:23

Same error, same fix for me also

rkwofford17:09:42

Changing toe 1.9.908 works fine though

tony.kay18:09:37

oh shoot…my bad, that was prob a local build I made, which would work for me, but isn’t on clojars 😜

tony.kay18:09:50

I pushed an update to fix the version

tony.kay18:09:23

thanks for the ping

roklenarcic20:09:06

looking at the server side... does anyone have an example of a boot build?

roklenarcic22:09:50

Looking at your lein template tony, :cljsbuild only has one build defined, production. Is this OK?

roklenarcic22:09:23

oh right, cljsbuild is defined further down for dev

roklenarcic22:09:17

gotta say this lein template is so much more impressive than anything I've done build-wise