This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-08-21
Channels
- # admin-announcements (1)
- # beginners (3)
- # boot (242)
- # clara (1)
- # cljsrn (8)
- # clojars (2)
- # clojure (68)
- # clojure-russia (23)
- # clojure-spec (28)
- # clojure-uk (11)
- # clojurescript (7)
- # datavis (3)
- # datomic (21)
- # emacs (2)
- # events (2)
- # hoplon (56)
- # jobs (3)
- # lambdaisland (1)
- # mount (20)
- # off-topic (4)
- # om (13)
- # onyx (17)
- # other-languages (2)
- # parinfer (7)
- # proto-repl (2)
- # proton (2)
- # protorepl (53)
- # re-frame (13)
- # reagent (3)
- # ring-swagger (22)
- # specter (5)
@darwin i just saw your dirac intellij integration document https://github.com/binaryage/dirac/blob/master/docs/integration.md
you are saying:
> Then I used Cursive's tools to switch REPL namespace to my current file, loaded the file, and executed the selected form in the editor.
I wondering what's your workflow. Do you use boot-reload
? I was trying to use Cmd-Shift-M
to sync all changed files and their dependencies but it was very slow.
and also just reloading the recompiled cljs into the browser is not enough, since most of the time i modified view code so i want a re-render without a page reload
I’m not sure if your question is really dirac-related, hot-reloading is not necessarily relevant to state of your (n)REPL environment
so here’s a newbie question: I’m trying to muddle through taking some existing re-frame and cljs tutorials and re-do them in Boot instead of Leiningen, and I’ve gotten to the point where I have a dev
that does all the things for the cljs side, serving up target
on port 3000, listening for changes, and so forth
but I want to also add a “server” for a CQRS-y app model and I can’t seem to figure out how to comp
together things so that dev does both
I have to (def p (future (boot (dev))))
and then later (cqrs/start-server)
- if I add the server startup fn to dev
then nothing is listening on port 3000
@chris_johnson usually youd have your server added to your ring stack
I’m sorry, I don’t understand what you mean - it is a ring server, but are you saying there’s some built-in Boot machinery I should be leveraging to start it up?
oh, I see what you mean
so in dev
I have a call to pandeiro.boot-http/serve :dir target
I’d have to modify that somehow to also serve up my clj on port 8080 or whatever
right
I was coming at this sort of backwards because I have a dev
built specifically to run a re-frame SPA in development mode, including running cljs-repl-env
so I can get my browser REPL inside CIDER
@chris_johnson do you want to serve your frontend and the backend on the same port?
and now I want to “bolt on” a server component just to demonstrate CQRS-style architecture
@onetom: no, I don't
and in fact it might not be the worst thing in the world to just have to start the server “manually”
yeah, I have ring-cors
on the server
@micha: I definitely respect that, there is no “prod setup” here yet, I’m just noodling around with an eye toward maybe publishing a blog post about going from published examples of Reagent/Leiningen to Boot/Re-Frame
approximately zero percent of the production cliff has been climbed for this project 😄
so the real answer here, I think, is for me to read up more on boot-http
and see if I can’t build something to comp in that does “serve this ring handler on port 8080, serve these assets on port 3000"
instead of the received :dir target
though to be fair, I do also have something in my dev
fn that puts things in target,
micha, shouldn't we recommend boot-jetty
instead of pandeiro-http?
or even boot-static
?
it's very little code, though it pulls in a lot of vertx
libs, yet it's supposed to be a lot leaner than jetty
now I’m trying to remember where I got my current Boot workflow from
I want to say it was a tutorial of some kind but it does many things that I am reading here are not ideal
chris_johnson there are a lot of obsolete stuff out there. things moved quite fast and there are just not enough resources to keep docs coherent
@chris_johnson one important thing which is also noted in the readme: you have to use jumblergs cors lib (https://github.com/jumblerg/ring.middleware.cors) because i had issues with https://github.com/r0man/ring-cors (at least when i was trying to use castra)
another thing I’m doing wrong, but at least I came by that one honestly
not reading the README is my own fault hehe
Well alright, this dev
task with boot-static
gets me my SPA served up on port 3000 and my http-kit ring stack on port 8080
(deftask dev
"Launch all the things"
[]
(comp
(watch)
(reload)
(cljs-repl-env) ; before cljs task
(cljs)
(serve :port 3000)
(mount.core/start)))
boot-static
does log an NPE to the REPL a little bit after starting up, but then seems to work okay?
and I get a browser REPL with (start-repl)
as expected/desired
thank you very much @micha and @onetom - I learned a lot from this discussion
frustratingly I feel like the blog post I want to write should probably go back to the woodshed and be less about “moving from lein/reagent to boot/re-frame” and more focused on building a CQRS-y re-frame app in boot
@onetom Good timing with boot-static
, I've been elbow deep in boot
and boot-http
code trying to debug why it doesn't stop the server. No such problem with boot-static
(originally got the boot-http
recommendation from https://github.com/adzerk-oss/boot-cljs/wiki/Serving-files)
@mitranim: the interface to both boot-static and boot-jetty should be about the same, either will work, but boot-static may be a bit snappier
boot-static is a step on a more ambitious refactoring path that will integrate into a build pipeline with the not-ready-for-use boot-vertx task.
one of the differentiating ideas behind the boot-vertx task is that each time the task is invoked (typically by the boot watch task) it will load in a fresh servlet with your application code from a pod pool
the only major feature that is missing from boot-vertx right now is the implementation of the input-stream expected by the ring request map
i haven’t done this because i’m not sure what the right approach is yet; vertx uses its own reactive stream standard
later on, boot-static will require the boot-vertx task once it has been completed, but for now, it is everything you need
@jumblerg: as I was just pointed to do in discussion here a bit ago, I’ve adopted your ring.middleware.cors
in my toy app but I seem to be maybe doing it wrong? This is what my handler compositon looks like:
(def handler
(-> routes
(wrap-transit-body {:keywords? true})
(wrap-cors identity)
wrap-edn-params
wrap-params))
so I have static assets being served by boot-static
on port 3000, and a ring stack on port 8080
when I dispatch a POST
from the SPA on port 3000, I see logging on the server as if the POST
went through, but in the SPA console log I get XMLHttpRequest cannot load
I had r0man/ring-cors
in place of this middleware earlier and it was working, I also earlier had #".*localhost:3000.*”
in place of identity
in this current call
I guess I’m hoping that you’ll look at my handler and say “oh, nope, you have to do X
instead of what you have there” hehe
looking at the source it certainly appears that just supplying identity
should cause the right headers to be sent
@chris_johnson: i think you may want wrap-cors on the outside
i also know that there’s a small issue with the way certain errors are handled in that library that may cause other errors on the server to be masked by access-control errors; i’ve been meaning to fix this for a while.
here’s an example from an application i’m running locally (and in production) right now:
(defn handle [req]
{:status 200
:headers {"content-type" "text/plain"}
:body "My Service"})
(def serve
(-> handle
(wrap-castra {:state-fn #(q/model *db*)} 'my.service.command 'my.service.query)
(wrap-authentication)
(wrap-castra-session secret)
(wrap-tenant #(first (db/q db/tenant-query %)))
(wrap-s3 aws-access-key aws-secret-key bucket-name)
(wrap-smtp aws-access-key aws-secret-key smtp-host smtp-from-email smtp-reply-email)
(wrap-status "/status")
(wrap-datomic datomic-uri)
(wrap-rewrite #"^/service" "")
(wrap-cors #".*localhost.*" #".*$")))
@chris_johnson here is our ring stack:
(defn make-ring-handler []
(-> (constantly {:body "Not a castra request"})
(wrap-castra 'app.users 'app.docs 'app.etc)
(wrap-castra-session (str "**********"))
(wrap-cors
#""
#"http://.*\.local:8100"
#""
#"")))
out of curiosity, do you guys not have a db-conn
parameter that you need to pass through?
or is everyone using mount now 🙂
@jumblerg, does wrap-datomic attach the db-conn to the request?
peculiar!
@micha, interesting. how does a handler get access to, say, the db conn in your setup?
so the connection is stored in a var/atom? (not saying that's a bad thing)
i dunno, i never do anything super fancy there, i don't need much infrastructure around it
here’s the full entrypoint to my service:
;;; utils ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn assert-prop [var]
(or (System/getProperty var)
(throw (Exception. (str "Required system property " var " not set")))))
;;; config options ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def secret (assert-prop "16_BYTE_SECRET"))
(def aws-access-key (assert-prop "AWS_ACCESS_KEY"))
(def aws-secret-key (assert-prop "AWS_SECRET_KEY"))
(def bucket-name (assert-prop "BUCKET_NAME"))
(def datomic-uri (assert-prop "DATOMIC_URI"))
(def smtp-host (assert-prop "SMTP_HOST"))
(def smtp-from-email (assert-prop "SMTP_FROM_EMAIL"))
(def smtp-reply-email (assert-prop "SMTP_REPLY_EMAIL"))
;;; clojure servlet api ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn handle [req]
{:status 200
:headers {"content-type" "text/plain"}
:body "My Service"})
(def serve
(-> handle
(wrap-castra {:state-fn #(q/model *db*)} 'my.service.command 'my.service.query)
(wrap-authentication)
(wrap-castra-session secret)
(wrap-tenant #(first (db/q db/sponsor-query %)))
(wrap-s3 aws-access-key aws-secret-key bucket-name)
(wrap-smtp aws-access-key aws-secret-key smtp-host smtp-from-email smtp-reply-email)
(wrap-status "/status")
(wrap-datomic datomic-uri)
(wrap-rewrite #"^/service" "")
(wrap-cors #".*localhost.*" #".*$")))
@micha: @tolitius has these example apps for his mount
library: https://github.com/tolitius/stater
would be hyper-educational if you could show an equivalent solution to his neo
or smsio
apps using your boot task approach
@micha but do you have a test suite which works against temporary dbs and mock services?
@micha, cool. I love hearing when people do things the simple way.
when i need stuff from the database i use a test account in the production db if possible, because the database is normally not owned entirely by one system
this is how I mock: (when (not= :testing env) (send-email!))
to send an email you'd put something on a queue, so you can just have sa test queue that goes nowhere
@pesterhazy so you dont test email sending? and the content of the sent email? or just test it at a unit test level or how?
ok you got me, I'm terribly sloppy with testing
I wish it wasn't so, but not enough to change my ways
@micha so when you are running your tests you should a. initialize your global email queue to something like /dev/null b. pass the queue as a parameter to your function which happens to send email too
that way when i refactor implementation details i don't need to analyze all my tests to be confident that they're still testing the right behavior
they are still slow to create and your tests depend on an external service and network connection availability
then you would need to take care of clearing the queue every time for example before re-running the tests
i had this exact same situation in the past with sqs queues and it's not obvious, neither fast to clear an sqs queue
completely deleting and re-creating them is also not possible because they throttle how quickly can you create a queue with the same name...
well it is fast to do it, but i'd imagine the test runner that is looking at things in sqs would delete messages it has handled
and the message is not deleted but they it will only re-appear in the queue after the whatever-timeout has expired
so ideally every external service (which has some state what you would like to inspect from your tests) should have a test variant, which makes it easy to 1. initialize a new instance of the external system preferably within the same process where your app and tests run 2. inspect its state 3. clean it up fast too
usually the external resources are things that require configuration so i don't make a unique instance for each test run
if you're in a situation where your canonical data is only touched by your code then it might make sense
but for example the filesystem is something which should have a mock version, but i haven't found any in the java/clojure world which would be good
because you don't know if your mock really corresponds to the current shape of the data anymore
well, that's why these mock services should be provided by the original service provider... not just client SDKs...
here is a relatively good example of a filesystem mock library (for nodejs): https://github.com/tschaub/mock-fs
im getting clearer and clearer on how to structure an clojure app in a test-friendly way, so probably in a few months i will have the chance to release some good example app. maybe for this xmas 🙂
@chris_johnson btw, i've noticed you are also using mount
. i might have some questions later regarding that.
in the mean time, i should point out that (mount/start)
is probably shouldn't be composed with those other tasks, since it probably doesn't return a boot task
re: SQS testing, ElasticMQ has a mode that SQS clients can treat as “an SQS queue"
and which can be, say, stood up inside your unit test @Before
(to use a crude Java-ism)
oh that’s a good point, I was just trying to drive the number of things to type at repl startup from 2->1 but that really should be a deftask
like start-httpkit!
or something that just does a side-effect server start and returns its argument, huh
again, not the ideal of having dev look just like production, but I’m literally just building a thing to do it so I get a better handle on modern Clojure services
laziness is a virtue
@chris_johnson (with-pass-thru [_] (mount/start))
is what you want if im correct
@chris_johnson or you can even just say (do (mount/start) identity)
, though that's not that explanatory
i tried the neovim terminal thing, but it's kind of a pain to use because of the way it deals with keybindings
well, me too, but only in server mode and connect to it from intellij, where i have proper multiline editing and paredit and syntax highlight and help and go-to-definition and peek into source, etc...
yeah the repo functions there are using alter-var-root to avoidn needing to type lots of things
Given dest and a list of srcs, updates the contents of dest such
that it is the product of srcs, overlayed on top of each other.
Files that are not in any of the srcs will be removed from dest.
This function returns an instance of boot.filesystem/FilesystemTree
reflecting the final state of dest, suitable to pass as the
:state option to the next invocation (provided of course that
files are not added to, removed from, or modified in dest in the
meantime).
The dest and srcs must be of a type that satisfies the
boot.filesystem/IToPath protocol.
The :state option can be used to provide a FileSystemTree instance
reflecting the state of dest. This eliminates the need to walk dest
to form the patch, so can be much more efficient. Of course, if the
actual contents of dest are not the same as state the result is not
well defined.
The :ignore option specifies a function of one argument, the file
path (as a string), which returns a true value if the file is to be
ignored.
The :link option can be specified with a true value to enable use
of hard links instead of copying files from srcs to dest.
so thanks once again for all your help @micha, @onetom, et. al
everything except re-frame barfing and dying when I hand it a new app-state
but that’s not boot/workflow, that’s me not understanding something about re-frame
everything else is super lovely, yes 😄
I will mention it here once I ever get my blog post refactored and posted
I’m sure there’s a glut of “how to build a simple thing with modern tools” posts out there but I’ve decided that I’m going to post something twice a month about what I’m learning, and if it’s just one voice in a chorus, so be it.