Fork me on GitHub
#beginners
<
2017-08-20
>
macrobartfast00:08:09

so, I've had success with REST services and web routing and so on, but I'm a little mystified about how to build and organize services in clojure and put them in production...

macrobartfast00:08:00

suppose you had a car rental web app that, on the backend, has to check if the user is legit and enabled, if they have active payment, if they have your special frequent renter points, etc.

noisesmith00:08:08

the thing that works for me is making an uberjar, then following all the normal steps for java

noisesmith00:08:35

or wait, are you talking about deployment or application logic?

noisesmith00:08:43

I thought you were talking about deployment

macrobartfast00:08:44

I guess application logic...

macrobartfast00:08:10

though I also kinda meant deployment... because if you have a raft of services doing all that stuff (in the java mindset)...

macrobartfast00:08:17

that also implicates deployment...

noisesmith00:08:27

but a lot of the parts are similar to a java app or whatever - you can use a jwt or a cookie combined with a db

macrobartfast00:08:37

in Java you might have message queues leading off to other jars and so on.

noisesmith00:08:50

sure, you can do that in clojure too of course

macrobartfast00:08:57

I'm probably just asking basic programming questions, lol... forgive me...

macrobartfast00:08:16

but how do I keep track of the sequence of things that need to be checked off for the prospective car rental... a series of things that have to be accomplished before, finally, something comes back to the endpoint...

macrobartfast00:08:39

in java you just new up a new PossibleCarRental or something...

macrobartfast00:08:48

in clojure... uh... a data thing?

noisesmith00:08:51

really? but what happens if the server restarts?

noisesmith00:08:57

surely it needs to be connected to a db

macrobartfast00:08:52

well, in java, the PossibleCarRental thing is making a bunch of queries to API's and databases and so on (and makes DAO's to work with what's in the databases)...

noisesmith00:08:01

(or if load balancing leads to the user talking to a different server, or whatever)

macrobartfast00:08:45

well, those are issues that java has... which is why I'm really trying to get into clojure... but I'm not understanding how it works yet in clojure...

macrobartfast01:08:07

I know there's got to be some sort of common approach...

noisesmith01:08:13

right, so instead of an object, we'd have functions paramaterized by request-id and / or user id

macrobartfast01:08:16

is it Component?

noisesmith01:08:29

component is for stateful resources that need to interact

macrobartfast01:08:38

ok, forget component for now...

noisesmith01:08:44

so component would be used to manage your database connection pool

macrobartfast01:08:01

but back to the functions you were mentioning...

macrobartfast01:08:16

so, a car rental request comes in for a user, with a cookie...

macrobartfast01:08:30

and like if 8 things has to happen, how do I keep track of the the request?

noisesmith01:08:47

so typically I find that the following steps are involved: a middleware that checks the auth and gathers data about the user, and attaches that as data to the request

noisesmith01:08:03

then a request handler that uses that auth and data, and gives you new data

noisesmith01:08:15

then another step that optionally updates the db before sending a reply to theuser

macrobartfast01:08:29

so... middleware!

noisesmith01:08:37

by middleware I mean a ring middleware

macrobartfast01:08:55

right... so middleware could be really involved, it sounds like...

noisesmith01:08:08

it takes a handler function as an argument, and returns a new function that checks auth, bails if it's invalid, and attaches user specific data to the request before calling the target

noisesmith01:08:45

and you can do this with regular functions

noisesmith01:08:04

the pattern usually looks like

(defn wrap-auth
    [handler]
      (fn rq-with-auth
         [request]
         (let [data (check-auth)]
           (if (bad? data)
             (fail request)
             (handler (assoc request :auth-data data)))))

noisesmith01:08:03

so you can wrap the regular code in a layer that makes sure you bail if perms are lacking or whatever, and provides any context about the user the handler might need

noisesmith01:08:52

so, similarly, a middleware could restore the partial state of a customer transaction (eg. a shopping cart)

macrobartfast01:08:28

oh cool this is really helpful (especially the pseudocode)

macrobartfast01:08:11

for long requests... should/can you send something back to the client that says effectively 'thanks... working on it'...

macrobartfast01:08:37

and then when the actual response comes back somehow that gets to the client?

macrobartfast01:08:54

I'm thinking http://kayak.com or something...

noisesmith01:08:00

sure, yeah- you might need a streaming api or websockets or whatever, but it's totally an option (I have requests that can last ten minutes or more, so we send continuous progress updates via a websocket to the frontend)

macrobartfast01:08:03

crazy waits while all the magic happens.

macrobartfast01:08:12

ah ok websockets, righto.

noisesmith01:08:44

there's the sente lib that gracefully falls back for clients that need to long poll ajax instead of websockets

noisesmith01:08:50

butI think it's more and more rare that people need that

macrobartfast01:08:03

why is it rare now?

noisesmith01:08:10

newer browsers

noisesmith01:08:20

fewer firewalls that prevent websocket connections

noisesmith01:08:34

to be clear, it's rare that people can't do websockets

macrobartfast01:08:58

oh duh misread what you meant...

macrobartfast01:08:05

right... websockets are more common now and more possible.

noisesmith01:08:19

another advantage with sente is that it uses transit to encode your data, so it's just plain clojure data structures on both ends of the socket

noisesmith01:08:42

you don't need to worry about a conversion step as long as you aren't sending anything you shouldn't

noisesmith01:08:10

(but still pay attention to data formats / structures across system boundaries, you still get a mess when you let that slip too much haha)

macrobartfast01:08:15

so, I couldn't figure out clojure at this level, relapsed into java for a hot minute, but can't live without in buffer repl development...

noisesmith01:08:21

but I think the new spec lib will help a lot there

macrobartfast01:08:34

I have no clue still what the whole spec thing is.

noisesmith01:08:00

yeah - a lot of the java stuff has a direct clojure versrion (or, something much simpler that is reasonable once you have immutable data...)

macrobartfast01:08:05

I was/am still too busy remembering what conj does to different things to follow the exciting stuff.

noisesmith01:08:18

well, it pays off if you stick with it

macrobartfast01:08:32

I am aware that I can mix and match java and clojure so will be doing that...

macrobartfast01:08:39

though I'm a terrible java developer too!

noisesmith01:08:53

on the other hand you can absolutely just make a spring app in java and then use clojure in all the request handlers

noisesmith01:08:08

but going all clojure is easier in the big picture

noisesmith01:08:13

sure, clojure is a java library

noisesmith01:08:21

there's a very simple api to use it from your java code

macrobartfast01:08:27

well, the routes all work for me in clojure now...

noisesmith01:08:52

cool I wasn't suggesting clojure in a spring app was a good idea - just saying "hey, even that would work"

macrobartfast01:08:53

it's more the whole getting things into the database and back out and manipulating it all and so on that is hard for me...

macrobartfast01:08:05

oh I got you on that...

noisesmith01:08:37

well, that stuff tends to work nicely in clojure since there's a very straightforward mapping from clojure data to/from sql, as opposed to objects where things get really messy

macrobartfast01:08:39

actually, in Spring, I'm a little mystified too about how to handle the complex back end stuff...

noisesmith01:08:53

it's more like, the more you get to know clojure and the more you get to know sql, the simpler that part is

macrobartfast01:08:55

so, I should just build a fake car rental app...

macrobartfast01:08:10

because that's the only way I'll understand this.

macrobartfast01:08:56

so, for a front end... uh... compojure?

noisesmith01:08:10

depends what you mean by "front end"

noisesmith01:08:34

the author of that book is one of the main authors of tools in the clojure web stack

noisesmith01:08:45

so he really knows what he's talking about there

macrobartfast01:08:53

that looks good

macrobartfast01:08:23

I'd prefer it if it were react of something on the front end...

noisesmith01:08:58

last I checked it used a react frontend

noisesmith01:08:07

reagent, right? or did that book use OM I'm forgetting now

macrobartfast01:08:07

omg ok I eat my words

macrobartfast01:08:18

that's fantastic

macrobartfast01:08:37

I think I looked at the first edition

noisesmith01:08:08

yeah, it uses reagent - it's using relatively up to date tools (cutting edge as of the publication date, and it's not like we have moved that far since then)

macrobartfast01:08:51

oh that stacks all good enough for me... in my case the stack isn't the important thing but learning it all... that said, the server side rendering I saw in most clojure tuts a year ago did wig me out a little

macrobartfast01:08:15

I shouldn't say 'most'... just the ones I looked at

macrobartfast01:08:35

anyway, I'm destroying your evening... this has been really helpful/inspiring

noisesmith01:08:51

glad to help, I'm off for dinner myself

evmin03:08:09

Good day.

evmin03:08:35

Playing with lein vs boot. Boot is twice as slow building the same uberjar - am I missing something obvious?

chang04:08:57

I tested my encode function on the repl and it works as expected. Now I am working with reagent and a bit puzzled by this from Chrome console:

(prn "testing encode:" (encode "testing" "test"))           ;;> "testing encode:" ""
(prn "testing encode:" (type encode))                       ;;> "testing encode:" #object[Function]
(prn "testing encode:" (type (encode "testing" "jordan")))  ;;> "testing encode:" #object[String]

chang04:08:16

For example I was expecting: "mikmbry" from (encode "testing" "test"). That's what I get from the repl.