Fork me on GitHub
#hoplon
<
2016-09-11
>
thedavidmeister12:09:08

(deftask build-heroku
  "Builds a standalone jar."
  []
  (comp (aot :namespace #{'app.main})
        (hoplon)
        (cljs :optimizations :advanced
              :compiler-options { :parallel-build true }
        (prerender)
        (uber)
        (jar :main 'app.main)
        (sift :include #{#"\.jar$"})
        (target :dir #{"target_prod"})))

thedavidmeister12:09:48

(ns app.main
  (:require [app.handler]
            [clojure.string :as string]
            [clojure.tools.cli :refer [parse-opts]])
  (:gen-class))

(def cli-options
  [["-u" "--db-update"]])

(defn error-msg [errors]
  (str "The following errors occurred while parsing your command:\n\n"
       (string/join \newline errors)))

(defn exit [status msg]
  (println msg)
  (System/exit status))

(defn -main [& args]
  (let [{:keys [options arguments errors summary]} (parse-opts args cli-options)]
    (if errors (exit 1 (error-msg errors)))
    (apply (resolve 'app.handler/-main) arguments))))

thedavidmeister12:09:21

(ns app.handler
  (:use org.httpkit.server)
(defn -main [& [port]]
  (let [port (Integer. (or port (environ.core/env :port)))]
    (sente.wire/start-router!)
    (run-server app {:port port})))

thedavidmeister12:09:29

@danvingo not a totally clean example, but it’s the guts of what works for me on heroku

thedavidmeister12:09:38

ultimately i bailed on the war and just made a jar

thedavidmeister12:09:07

heroku deploy:jar --jar target_prod/project.jar --app my-app;

keithsparkjoy15:09:19

Hello! Newb to hoplon here, just learning. I ran into something odd experimenting with for-tpl the other day. Here’s a simplification that demonstrates what I’m seeing...

keithsparkjoy15:09:22

In the snippet above, if I change for to for-tpl, I don’t see any output on my test page

micha15:09:56

interesting, let me try

keithsparkjoy15:09:07

What I end up with in <body> is a bunch of <script> references.

micha15:09:08

ah i see the problem

micha15:09:22

so for-tpl is a reactive thing

micha15:09:54

it's a structure that allows you to manage dynamic allocation of dom components

micha15:09:56

over time

micha15:09:04

so item there is a Cell

micha15:09:11

cells themselves are not seqable

micha15:09:28

so you get an error when you try to apply a function to a cell

micha15:09:37

here is what i recommend instead

micha15:09:41

if the collection is immutable you can just use for, there is no benefit to for-tpl

micha15:09:57

you only need the tpl ones when the collection will change over time and you want to update the dom accordingly

keithsparkjoy15:09:33

Yep @micha in my more complicated case there was a cell

keithsparkjoy15:09:45

I tried to simplify it down to the simplest possible example

micha15:09:05

the problem you saw there was that cells are not seqable

micha15:09:09

neither are atoms, btw

micha15:09:14

they're simply containers

keithsparkjoy15:09:17

Sure - makes sense

micha15:09:21

so you can't use them with apply

keithsparkjoy15:09:23

but I didn’t have a cell in my example

keithsparkjoy15:09:33

unless for-tpl somehow turns it into a cell

micha15:09:35

for-tpl always binds cells

micha15:09:46

so item in your for-tpl will always be a cell

micha15:09:01

likewise if you did (for-tpl [[a b] items] ...)

micha15:09:08

a and b will be bound to cells

keithsparkjoy15:09:20

Ahh okay that’s the bit.

micha15:09:23

regardless of what items is

micha15:09:49

items doesn't have to be a cell, it must be either a cell containing something seqable, or a seqable thing itself

keithsparkjoy15:09:13

Okay that helps me understand better - thanks @micha!

micha15:09:33

are you enjoying it so far?

keithsparkjoy15:09:51

Very much so. Nifty development experience with the sounds and the auto-refresh.

keithsparkjoy15:09:04

The reactive framework behind is neat - watched the video about how it works. Just trying to get my head around all of it now 🙂

micha15:09:09

i guess the main way it diverges from most other approaches is by leveraging static allocation wherever possible

micha15:09:30

like the for-tpl mechanism manages allocation

micha15:09:48

rather than the react model where you conceptually allocate and deallocate all the time

micha15:09:06

hoplon tries to avoid deallocating whenever possible

micha15:09:21

it will hold unused elements in its cache to reuse them

micha15:09:29

thereby keeping their state intact

micha15:09:57

that's the reason for the *-tpl forms

micha15:09:13

it's similar to a "fill vector"

micha15:09:32

it's interesting to look in the dom inspector while it's changing

jamieorc15:09:31

@leontalbot Reading through the hoplon/ui faq I found some dead links. I was going to update, but editing is not public. The second one on window attrs looks like it should be https://github.com/hoplon/ui/blob/master/src/hoplon/ui.cljs#L524

jamieorc16:09:51

@micha thanks for the castra error/exception tip yesterday. I added a little info to the wiki on this: https://github.com/hoplon/hoplon/wiki/Castra

micha16:09:02

awesome, thanks!

jamieorc16:09:04

@micha it’s been fun exploring. I made some primitive pagination. Then I noticed you have defp in the sample you shared with me yesterday. Is that public code or internal?

micha16:09:58

i could make it public 😉

micha16:09:28

however it's pretty tightly coupled to the scheme we use on the backend to make efficient optimized paginated queries on ms sql server

micha16:09:48

basically in our application the user can use our api to make a million of anything

micha16:09:57

so we need to paginate every query we make

jamieorc16:09:04

Not surprise. My initial exploration makes me thing that’s pretty inevitable

micha16:09:31

yeah the browser simply doesn't provide enough resources to do anything better

micha16:09:58

if we had a decent sized local cache we could make better abstractions work

jamieorc16:09:00

I’m building this exploration over a large database that often has thousands of rows

micha16:09:14

but liimited to 4M it's just not feasible

micha16:09:28

yeah this is the problem we face too

micha16:09:40

customers with hundreds of thousands of entities of various types

micha16:09:46

that are related to other entities

micha16:09:59

so we need to have fully indexed queries and so on

micha16:09:14

a more general approach can't really provide the indexing we need

jamieorc16:09:20

sure. What’s you db?

micha16:09:36

a really fantastic database in my limited experience

micha16:09:12

this thing has been pretty much the berzerker of databases considering what we throw at it all day 24/7

micha16:09:48

this db only contains our "organizational state"

jamieorc16:09:49

this is MS SQL Server?

micha16:09:27

organizational state is like customer configuration info and stuff like that, like our customers setting up ad campaigns and so on

micha16:09:43

we also do "big data" stuff but that's a whole separate thing

jamieorc16:09:04

It’s been a decade since I’ve been on it. I recall now you or Alan telling me you were building over an old MS app.

micha16:09:23

yeah the old app is C# asp .net mvc blah blah

micha16:09:44

with a terrible ORM layer that relentlessly hammers SQL all day long mercilessly

micha16:09:15

which db are you using?

jamieorc16:09:40

sounds familiar. I haven’t done ASP since before C# and http://asp.net. Did a bunch back in the late 90s to 2000.

micha16:09:59

it wouldn't be so bad if it wasn't for the asp part, and the orm

micha16:09:15

i guess that's the whole thing 🙂

jamieorc16:09:31

Mongodb. It’s been great, mostly. In the Rails world it’s great to have dynamic schemas. It was a great fit for our original system, but as I want some more relational data, it’s limitations show

micha16:09:45

the way i do the pagination with sql is to compile the query with the pagination added, sort of like a view, but by compiling the queries individually we can optimize them better

micha16:09:54

and we don't have overhead of maintaining views, which is a pain

micha16:09:35

the main idea in this scheme is to use "common table expressions" which are sort of analogous to what you'd use temporary tables for in mysql

micha16:09:01

not sure how that relates to mongo tho

micha16:09:09

i can paste it if you want

jamieorc16:09:04

I’d be glad to see it. Might give me ideas.

leontalbot16:09:31

@jamieorc: thanks ! I am away from my computer right now. Maybe @jumblerg can assist you!

micha16:09:06

we try to abstract away concerns like pagination in a separate state machine

micha16:09:14

that's what this paste will be

leontalbot16:09:25

I was wondering if hoplon web app can be ported to android and iOS easily

leontalbot16:09:44

(other subject)

micha16:09:10

@jamieorc i added the clj macro file to the gist

micha16:09:59

so the way to look at that source file is paginate is the entry point

micha16:09:06

that is a function that returns the state machine instance

micha16:09:17

it takes all kinds of configuration options and whatnot

micha16:09:39

the rest of the functions in that namespace are methods on the state machine

micha16:09:53

they act as "commands" in the CQRS parlance

micha16:09:12

the state machine instance contains fields whose values are javelin cells

micha16:09:27

these cells comprise the current state of the machine

micha16:09:04

so for example, the :data field of the state machine returned by paginate is a javelin cell whose contents are the paginated data

micha16:09:28

that will change potentially as you apply methods to the state machine object, like next-page! etc

micha16:09:52

also notice that paginate takes a remote argument

micha16:09:24

that's where our API pagination protocol comes in

micha16:09:51

the remote is assumed to accept various pagination related options itself

micha16:09:18

so it must accept sorting, filtering, page size, current page, etc

jamieorc16:09:43

cool. It’ll take me a while to digest this, but this is great. Already gives me some ideas.

micha16:09:09

i have been struggling to express these machines in a more rigorous state machine model

micha16:09:28

but i can't find anything that doesn't increase the complexity

micha16:09:00

i still have not fully wrapped my head around uml statecharts yet though

jamieorc16:09:43

Has anyone? 😄

micha16:09:26

haha right

micha16:09:52

so a cool thing aabout factoring out the state machine is that different components in the UI can be hooked up to it

micha16:09:55

there are at least 5 completely independent UI components there that are hooked up to the same pagination machine

micha16:09:34

in the upper right in the orange bar there is the refresh button, the page navigation and page size widget, and the search box for filtering

micha16:09:49

then there is the data table, with sortable columns, etc

micha16:09:04

and on the left is the full-featured search and filtering widget

micha16:09:16

none of these components know about the others in any way

micha16:09:27

they simply manipulate the state machine instance

micha16:09:50

and are reactively connected to the cells that comprise the state machine's state

micha17:09:28

an interesting aspect of this project was how to build a new UI for a legacy system

micha17:09:56

there is a lot of business logic in the c# application that we can't just duplicate (for cost reasons)

micha17:09:26

but the c# UI just wasn't cutting the mustard anymore, too many performance problems mostly caused by the ORM

micha17:09:48

but of course the ORM becomes the core of the whole thing so it's not really feasible to fix that in place either

micha17:09:50

so what we did was make a castra backend to give us the CQRS dataflow that separates the commands that might change something int he database from the queries that are just reading from the database

micha17:09:22

we could then make optimized queries in the castra :query functions that talk directly to the database

micha17:09:28

but the "commands", eg. modifying something, those are proxied to our public management API

micha17:09:40

which runs in the legacy system

micha17:09:22

this guarantees that the new code can't introduce corrupted data into the database, and all other side effects that happen in the ORM on update or create are still done by the existing legacy system

micha17:09:07

this plan lets us concentrate on just replacing the UI first

micha17:09:44

later we can migrate the backend to something more sane without running two possibly incompatible versions of our business logic

keithsparkjoy18:09:46

@micha thanks for the additional info - sorry I’d stepped away from the computer

jamieorc18:09:20

@micha cheers… was away

jamieorc18:09:27

That strategy of replacing the UI first is what I’m hoping to eventually do with my rails app

jamieorc18:09:53

I’ve had it in mind for a while, but not the resources yet

micha18:09:28

yeah we needed to triage the UI first, it was just taking more and more developer hours to do simple things

micha18:09:42

and regressions whenever we change anything

micha18:09:01

so the first step is to stop digging the hole, put down the shovel

micha18:09:17

we tried to avoid redesigning the UI for this stage, too, as much as possible

micha18:09:39

we just want to get the implementation into a sane platform that we can iterate on later

flyboarder20:09:23

^ hoplon.firebase now also manages user authentication for you, cells are provided in hoplon.firebase.auth for consumption in your application.