Sei sulla pagina 1di 5

Real World Clojure

01 Apr 2014 by lokori You may have heard already that Clojure is great and going to dominate the world this year. But will it !n this arti"le ! will dig deep into one o# our Clojure proje"ts so that you "an see what a real world Clojure proje"t looks like and de"ide #or yoursel#. ! will try to o##er as obje"tive a view on the matter as ! "an$ but ! will "over many things where we bene#it #rom the Clojure way o# doing things. %his is not a huge proje"t$ but not a trivial e&ample either. 'ur so#tware$ Aitu$ is a pra"ti"al and pretty straight#orward web proje"t. You "an view the #ull sour"e "ode as Aitu lives in (ithub. !n addition to a"tual web so#tware there are database "on#igurations et". so you "an set it up in a matter o# minutes with the help o# )agrant and )irtualBo& should you want to play with it.

Clojure programmers are happier


%here are a number o# things that are di##erent with Clojure that don*t dire"tly a##e"t the "ode$ but greatly improve the e##i"ien"y o# a seasoned programmer*s work#low. +rogramming #eels good be"ause there are bene#its su"h as,

-.+/ work#low has been "overed already. /einingen is doing it right while 0aven is doing it wrong. Clojars is great. Clojure is homoi"oni". You*ll learn to appre"iate this. edn 1 -.+/ 1 immutability lead to very e##i"ient handling o# test data

!n general ! "onsider the Clojure "ommunity as one o# the #inest programming language "ommunities. %he libraries are o# high 2uality and the attitude is to do things properly. /ibraries and #rameworks are #o"used and modular. %his is no small #eat. Being able to trust and understand third party libraries makes programming in Clojure #eel good.

AOP is just three letters put together


! have never 2uite understood A'+ in the #irst pla"e. ! have used it a #ew times but ! don*t think it is a valid or very use#ul idea. !t #eels like a dirty pat"h to #i& some issues with obje"t oriented programming 3or Class 'riented +rogramming i# you do 4ava5. /et*s see what we did in Clojure. 6e wanted to append "urrent user and 7%%+ re2uest id to log messages to tra"k them. As the messages #rom various threads may appear in arbitrary order in the log #ile the re2uest id is the only way to tra"k the pro"essing o# a single re2uest over the log messages. 6e "ould have written a "ustom wrapper over the standard logging #ramework to do this and then make sure

we never ever "all the real logging #ramework dire"tly. And "hange every line o# "ode already written to "all this wrapper. 6e "ame out with a solution whi"h has ideas similar to that o# an "aspect" and a "pointcut" but "ompletely di##erent implementation me"hani"s to do "weaving". 7ere*s the "ode 3translated to english5
(ns aitu.log (:require aitu.infra.print-wrapper oph.korma.korma-auth [clojure.tools.logging] [robert.hooke :refer [add-hook]])) (def ^:d namic !add-uid-and-request-id"! true) (defn add-uid-and-requestid [f logger le#el throwable message] (let [uid (if (bound" $%oph.korma.korma-auth&!current-useruid!) oph.korma.korma-auth&!current-user-uid! '-') requestid (if (bound" $%aitu.infra.print-wrapper&!requestid!) aitu.infra.print-wrapper&!requestid! '-') message-with-id (str '[(ser: ' uid ') request: ' requestid '] ' message)] (cond !add-uid-and-request-id"! (f logger le#el throwable messagewith-id) (false" !add-uid-and-request-id"!) (f logger le#el throwable message)))) (defn add-uid-and-requestid-hook [] (add-hook $%clojure.tools.logging&log! $%add-uid-and-requestid))

%hat*s it. !t*s possible to turn o## the wrapper at runtime per thread as ne"essary. %he addhook delegates the weaving tri"k to -obert 7ooke library whi"h is a tiny but "lever library that "an alter arbitrary Clojure #un"tions without being intrusive. %he 8point"ut8 is simply de#ined with a re#eren"e to our Clojure #un"tion and a re#eren"e to the standard logging #un"tion whi"h we don*t dire"tly "ontrol.

Clojure takes on PostgreSQL


Currently there is no established de #a"to library #or relational database a""ess. %here is the "lj9jdb" wrapper #or low level needs$ but higher level abstra"tions are still under "onstru"tion. !n :eptember we "hose to use :;/ <orma #or our proje"t. !t has turned out to be somewhat #rustrating. 6e are still using <orma$ but there are some issues,

0ultiple #oreign keys are not supported at the moment. 4odatime wrappers are missing. .veryone reinvents the wheel as we did. <orma*s pre#erred "onne"tion pool$ C=+0 has some bugs. /ike$ in#inite re"ursion.

<orma*s de#ault settings #or C=+0 are not suitable #or real work. !n#inite timeout on "onne"tion "he"kout is not ni"e. At the moment$ <orma uses a depre"ated version o# "lj9jdb".

:till$ <orma is better than nothing and the sour"e "ode is reasonable. 6e have read it through a "ouple o# times.

Testing with Clojure


!n prin"iple$ testing with Clojure is similar to what one would do with 4ava or any other programming language. But unlike most others$ Clojure ships with a #ull blown test #ramework out o# the bo&. And this "overs anything #rom unit testing to #i&tures and B>>. !*ll skip the obvious and "over some things ! "onsider to be parti"ularly interesting.

Still using asserts?


%here is proper support #or design by "ontra"t with pre"onditions and post"onditions. As an abstra"tion post"onditions and pre"onditions are an order o# magnitude more power#ul than old s"hool assertions. You "ertainly "an still write on o""asional assert$ but there is no state whi"h you would "he"k with assert in ''+ and imperative programming.

Disse t !our sour e ode" # dare !ou


7owever$ i# you make a small mistake in your synta& with pre"onditions$ there will not be any "ompiler warning. %his is un#ortunate and may "hange in some #uture version o# Clojure. ?or the meantime$ we wrote a separate test to ensure that our post"onditions and pre"onditions are a"tually used as the programmer has intended. ?ull sour"e@test."lj "ontains util #un"tions omitted here. %he "ode here has been translated to english.
(defn pre-post [form] (when (* %defn (nth form +)) (some $(and (map" ,) (or (contains" , :pre) (contains" , :post)) ,) form))) (defn pre-post-in-wrong-place" [form] (when-let [pp (pre-post form)] (not (or (and (s mbol" (nth form -)) (#ector" (nth form .)) (* pp (nth form /))) (and (s mbol" (nth form -)) (string" (nth form .)) (#ector" (nth form /)) (* pp (nth form 0))))))) (defn pre-post-not-#ector" [form] (when-let [pp (pre-post form)] (not (e#er " #ector" (#als pp))))) (deftest pre-post-at-right-place-test (is (empt " (matching-forms 'src&clj' pre-post-in-wrong-place"))))

(deftest pre-post-#ector-test (is (empt " (matching-forms 'src&clj' pre-post-not-#ector"))))

%his "ode takes advantage o# homoi"ono"ity and Clojure*s reader. %he sour"e "ode is parsed by the reader$ and our test e&amines it. 6e are using basi" data stru"tures and standard algorithm predi"ates like when$ some and map. 'n the other hand$ we are sear"hing #or a Clojure "ode stru"ture defn whi"h is used to de#ine a #un"tion. This sort o$ test would %e e&tremel! di$$i ult to do in almost an! other language.

Stati

he king with steroids

%his approa"h "an be applied to many other stati" "he"ks. Clojure is intentionally built with dynami" typing$ but writing your own stati" "he"ks is 2uite easy when you need them. You need "ustom stati" "he"ks in any "ase be"ause type systems in general are limited. .ven 7askell will not tell you i# you a""identally messed up the en"oding o# a properties #ile. But it*s not di##i"ult to "he"k,
(deftest properties-encoding-test '1ind characters which are not %printable characters%. 2here shouldn%t be an .' (is (empt " (matching-lines 'resources&i-3n' $'.!4.properties' [$'[^4p56rint74p58pace7]9']))))

6e rely on a generi" #un"tion whi"h re"ursively iterates over the #iles mat"hing the dire"tory and name pattern 3here$ i1An properties #iles5 and mat"hes ea"h line in ea"h #ile with some regular e&pression. !n this "ase we sear"h #or unprintable "hara"ters. 'ne "ould argue that it*s easy to write this in 4ava. 7aving done that$ ! "onsider Clojure a mu"h more e##i"ient tool in this respe"t. !n our proje"t we have not written pre9emptive "he"ks #or all imaginable s"enarios$ but rather #ollowed the /ean path o# poka9yoke. A#ter one o# us messed up the en"oding$ ! wrote that test to make sure it doesn*t happen again. %he temptation to "ut "orners is smaller when the language empowers the programmer and ena%les doing the right thing.

#solating $or ma&imum gain


>ue to it*s #un"tional nature$ Clojure is great at isolating things. :in"e there is no state$ almost anything "an be tested with a unit test. %his doesn*t mean that every single #un"tion should have a unit test$ but rather that any important #un"tion "an always be isolated #or testing. .ven #un"tions with side e##e"ts "an be tested relatively easily. Consider this auditlog test. %he a"tual test looks pretty straight#orward 3"ode translated to english5,
(deftest test-contract-update (testing 'logs properl an update to a contract' (log-#alidate $(auditlog&contract-update: -./ '-.&-.') [[:info 'uid: 2-; oper: update target: contract meta: (5:contractid -./) :diar identifier 4'-.&-.4'7)']])))

6ith a #ew lines o# "ode ! set up a mo"k to "apture the logging output in a non9intrusive manner$ mo"k the authenti"ation #ramework and test that the pie"e o# "ode responsible #or #ormatting the audit log messages works. %he test "he"ks these things, 1. %he audit log #ormatter "alls the logging #ramework with properly #ormatted messages. 2. !t properly atta"hes the 8"urrent user8 id to the log messages. Bo need to start up and "on#igure servi"es and #igure out a way to "reate a test appli"ation "onte&t. 4ust write the test #or whatever pie"e o# "ode you wish to test. 0aybe add a #ew lines #or plumbing.

Pla! it again" Sam?


%o put it bluntly$ yes. ! would still pre#er to write this appli"ation in Clojure i# ! "ould go ba"k in time with the in#ormation ! have now. ! do believe this would be in the best interest o# our "lient as well. %o summariCe$ here*s my "urrent advi"e #or "ra#ting a 8pro#essional8 Clojure appli"ation,

Dse "omponent or something like that to manage appli"ation li#e "y"le. Dse s"hema or something similar to de#ine important inter#a"es through the data stru"tures Dse <orma or something else$ but be wary when "ombining Clojure and relational databases. %ake #ull advantage o# Clojure*s spe"ial strengths. 'therwise$ what*s the point Avoid ma"ro wankery. 0a"ros o##er %uring "omplete power and should be used sparingly. Apply dynami" binding thought#ully. >ynami" variables s"attered around the "ode are antithesis o# Clojure*s prin"iples. Be "riti"al about book e&amples. %hey leave out 8details8 whi"h turn out to be important. Be prepared to add #un"tionality to libraries. 6e had to write 7%%+ logging middleware though it*s a pretty obvious #eature. 4ava interop is great$ but keep it simple. (enerating 4ava "lasses with gen9"lass was pain#ul. rei#y was easy. <eep it small and "ontained. 6e used http9kit as our embedded 7%%+ server. :mall and #o"used is the Clojure way. Clojure is dynami" and power#ul. Re$a tor or enjoy your dish o# spaghetti "ode.

The $inal 'erdi t


Clojure is still an in#ant taking it*s #irst steps in the world o# -eal Appli"ations. %his is evident as some popular libraries are la"king essential #un"tionality. But as a language it is mature and stable. %he language has deep roots and power#ul ideas ba"king it up. A #ine "ommunity is growing daily. %he world domination is "ommen"ing.

Potrebbero piacerti anche