среда, 27 декабря 2017 г.

clojure cli and nrepl

Today, I will show you how to run nrepl server and connect emacs to it.

1. Create empty folder helloproject

2. Create file deps.edn in helloproject folder and put here the following content:
{:deps {clj-time {:mvn/version "0.14.2"}}
 :aliases {:repl {:extra-deps
                  {cider/cider-nrepl       {:mvn/version "0.16.0-SNAPSHOT"}
                   org.clojure/tools.nrepl {:mvn/version "0.2.12"}
                   refactor-nrepl          {:mvn/version "2.3.1"}}}}}

Here we import library clj-time from Maven repo, and  import extra deps under alias :repl

3. Now create src folder in helloproject folder,.i.e helloproject/src

4. Create file hello.clj in helloproject/src folder and put here the following content:
(ns hello
  (:require [clj-time.core :as t]
            [clj-time.format :as f]))

(defn time-str
  "Returns a string representation of a datetime in the local time zone."
   (f/with-zone (f/formatter "hh:mm aa") (t/default-time-zone))

(defn -main []
  (println "Hello world, the time is" (time-str (t/now))))

5. Now we are ready to run repl. Change dir to helloproject and run command: clj -R:repl
The clj parameter -R:repl is important to load extra deps. If you have several aliases then you should list them comma separated, i.e. clj -R:alias1,alias2

After run command clj -R:repl you should see the prompt:
Clojure 1.9.0

6. Now we may start nrepl server. Enter the following  two commands in repl:

(require '[clojure.tools.nrepl.server :refer [start-server]] '[cider.nrepl :refer [cider-nrepl-handler]])

(let [port (or (some-> (first *command-line-args*) (java.lang.Long/parseLong)) 7888)] (start-server :port port :handler cider-nrepl-handler) (println "Started nREPL on port" port))

7. After successful nrepl server start you may open helloproject/src/hello.clj in your favourite editor and connect to remote repl.

In Emacs: M-x cider-connect
then enter: localhost and 7888

воскресенье, 3 декабря 2017 г.

http/2 client example in clojure

1. Add [com.squareup.okhttp3/okhttp "3.9.1"] as dependecy in project.clj

2. Download  Jetty's ALPN boot JAR. from here

3. Add jvm options to boot ALPN and optionally truststore to JKS

:jvm-opts ["-Xbootclasspath/p:/opt/libs/alpn-boot-8.1.9.v20160720.jar"

4.  Simple http/2 client code looks like this.

(ns test01.core
  (:import (okhttp3 OkHttpClient Request Request$Builder Response)))

(defn -main [& args]
  (let [client (OkHttpClient.)
        request (-> (Request$Builder.)
                    (.url "https://localhost:50443")
        response (-> client (.newCall request) .execute)]
    (println (-> response .body .string))
    (println (-> response .protocol .toString))))


суббота, 2 декабря 2017 г.

java 9 http client in clojure


Here is a small example, how to use HttpClient from java 9.

1) add to project.clj section  :jvm-opts ["--add-modules" "jdk.incubator.httpclient"] cause HttpClient is available as separate module.

(import '(jdk.incubator.http HttpClient HttpRequest HttpResponse HttpResponse$BodyHandler))

(def http-client (HttpClient/newHttpClient))

(def request (-> (HttpRequest/newBuilder)
                 (.uri (java.net.URI. "https://www.javabullets.com"))

(def response (.send http-client request (HttpResponse$BodyHandler/asString)))

(.statusCode response)
(.body response)

понедельник, 6 ноября 2017 г.

clojure monorepo how-to

Monorepo is the way to manage several related projects from one point. In this note I'll show you how to organize monorepo using Amperity plugin lein-monolith .

0. Lets create root project and some subprojects
                    project.clj        ;;project file for root project
                            /app01     ;; project folder for app01 project
                            /lib01       ;; project folder for lib01 project which is needed for app01
                            /lib02       ;; some other lib

1. We should put section :plugins [[lein-monolith "1.0.1"]] to root project.clj and to project.clj of all subprojects.

(defproject rootproject/all "MONOLITH"
  :description "Mono repo"

  :plugins [[lein-monolith "1.0.1"]
                 [lein-cprint "1.3.0"]]

;; some repo which is used in all subprojects
  :repositories {"readytalk" {:url "https://dl.bintray.com/readytalk/maven/"}}

 ;; this is local deps that belongs only root project until section :dependencies is included in :inherit section. But for :inherit section we will use :managed-dependencies instead.
  :dependencies [[org.clojure/clojure "1.9.0-beta4"]]

;; this managed-deps will be delivered to all subprojects
  :managed-dependencies [[org.clojure/clojure "1.9.0-beta4"]]
  :monolith {
             ;; here we tell lein-monolith to merge :repositories and :managed-dependencies
             ;; to all subprojects.
             :inherit [:repositories :managed-dependencies]

            ;; here we tell that section profiles from root projects should be merged to all subprojects.
            ;; in this example we tell that in uberjar we should omit source files.
             :inherit-leaky [:profiles]

             ;; where is our subprojects located
             :project-dirs ["apps/*" "libs/*"]}

  :profiles {:uberjar {:omit-source true}}

  :env {:foo "bar"})

2. Every project.clj of all subprojects we prepare like this:

(defproject rootproject/app01 "2.0.0-SNAPSHOT"
  :description "FIXME: write description"

;; here we include lein-monolith for subproject
  :plugins [[lein-monolith "1.0.1"]
                 [lein-cprint "1.3.0"]]

 ;; here we tell that we need to inherit all sections from root project
  :monolith/inherit true
  :deployable true

  :dependencies [[rootproject/lib01 "0.1.0-SNAPSHOT"]]
  :main ^:skip-aot lib01.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

3. Now we can run commands from root project
lein monolith each do clean, uberjar 
This command will clean and make uberjars for all subprojects. Remember, that we should run
lein install
for lib01 to install to a local repo for app01 which use it. Also we can run command only for particular subproject
lein monolith each :start rootproject/app01 do uberjar 
 This command will run uberjar target only for particular project.

Interesting fact that we can point out clojure version only in root project - project.clj, in section :managed-dependencies and this version of clojure will be delivered to all subprojects. So we don't need to include clojure version in subprojects.

Also from root project we can run different tests for different subproject using selectors. First of all wee should put metadata to all deftest like here

In example above it is integrations test with ^:integration metadata. Then we should put section
{:unit (complement :integration)
:integration :integration}

in root project.clj and put :test-selectors to :inherit section.  

воскресенье, 15 октября 2017 г.

clojure debugging 2

(defmacro safe-eval
  "This macro is used to execute any function inside try-catch block.
  returns value or nil in case of Exception and prints debug info. "
  [& forms]
     (catch Exception e#
       (println "--------------")
       (println "file :" *file* \newline)
       (println "s-exp:" (quote ~@forms))
       (println "src  :" ~(meta &form))
       (println "=>" (.getMessage e#)))))

воскресенье, 8 октября 2017 г.

clojure debugging

Add to project.clj
;;scope capture for debugging
 [vvvvalvalval/scope-capture "0.1.0"]

(require 'sc.api)

(defn abc [x]
  (let [y (* 2 x)
        z (Math/pow x 4)]
    (sc.api/spy {:abc [x y z]})))

(abc 2)
;;prints during execution: SPY [1 -2]  where 1 is id for defsc

(sc.api/defsc 1)

;;show me the state of local vars from let using given id 4
(sc.api/letsc 4 [x y z])
;; recreate the last operation result from let using given id 2
(sc.api/letsc 2 {:abc [x y z]})

;;show me the info map at the spy execution point using id 2
(sc.api/ep-info 2)

see video: https://vimeo.com/237220354

воскресенье, 1 октября 2017 г.

network async communication

Nikita made a good library [net.async/async "0.1.0"] for asynchronous network communication.

Just add this to your project.clj

      [org.clojure/clojure "1.8.0"]
      ;; async network communications
      [net.async/async "0.1.0" :exclusions [[org.clojure/clojure]

      [org.clojure/core.async "0.3.443" :exclusions [org.clojure/tools.reader]]

      [org.clojure/tools.logging "0.4.0"]

Here is an output of example below:
server:  4263
client:  ECHO/4263
server:  16640
client:  ECHO/16640
server:  75968
client:  ECHO/75968
server:  11092
client:  ECHO/11092
server:  61707
client:  ECHO/61707
server:  50773
client:  ECHO/50773
server:  16803
client:  ECHO/16803

Here is a source code of echo server and client:

(require '[clojure.core.async :refer [<! >!  <!! >!! close! go]])
(use 'net.async.tcp)

(def event-l-client (event-loop))
(def event-l-server (event-loop))

(defn echo-server [evt-loop]
  (let [acceptor (accept evt-loop {:port 8899})]
    (loop []
      (when-let [server (<!! (:accept-chan acceptor))]
          (loop []
            (when-let [msg (<! (:read-chan server))]
              (when-not (keyword? msg)
                (>! (:write-chan server) (.getBytes (str "ECHO/" (String. msg))))
                (println "server: " (String. msg)))

(defn echo-client [evt-loop]
  (let [client (connect evt-loop {:host "" :port 8899})]
    (loop []
      (go (>! (:write-chan client) (.getBytes (str (rand-int 100000)))))
      (loop []
        (let [read (<!! (:read-chan client))]
          (when (bytes? read) (println "client: " (String. read)))
          (when (and (keyword? read)
                     (not= :connected read))
            (println "disconnected from server. trying to reconnect using timeout")
      (println "-------------------------------")
      (Thread/sleep (rand-int 3000))

(future (echo-server event-l-server))

(future (echo-client event-l-client))

(shutdown! event-l-server)
(shutdown! event-l-client)