Skip to content

Commit 67a3695

Browse files
authored
provide fun-map integration (#18)
Fixes #17
1 parent a255ac1 commit 67a3695

File tree

5 files changed

+148
-5
lines changed

5 files changed

+148
-5
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ Because all functions have their schemas incorporated, you can get the best deve
3030

3131
Put `(malli.dev/start!)` in your `user.clj` will [enable clj-kondo](https://github.com/metosin/malli/blob/master/docs/function-schemas.md#tldr) to use the schemas when editing.
3232

33+
## Single API
34+
35+
Powered by [fun-map](https://github.com/robertluo/fun-map), you can use one single API for accessing a Kafka cluster without any further knowledge:
36+
37+
- [`kafka-cluster`](https://cljdoc.org/d/io.github.robertluo/waterfall/CURRENT/api/robertluo.waterfall#kafka-cluster)
38+
39+
You can see an example in [this easy example notebook](notebook/easy.clj).
40+
3341
## API namespaces
3442

3543
| namespace | Description |

deps.edn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
com.github.seancorfield/expectations {:mvn/version "2.0.160"}
1212
com.taoensso/nippy {:mvn/version "3.3.0-alpha2"}
1313
com.cognitect/transit-clj {:mvn/version "1.0.329"}
14-
metosin/malli {:mvn/version "0.9.2"}}}
14+
metosin/malli {:mvn/version "0.9.2"}
15+
io.github.robertluo/fun-map {:mvn/version "0.5.114"}}}
1516
:test
1617
{:extra-deps {lambdaisland/kaocha {:mvn/version "1.71.1119"}}
1718
:main-opts ["-m" "kaocha.runner"]}

notebook/easy.clj

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
;;# Easier API
2+
(ns easy
3+
(:require [robertluo.waterfall :as wf]
4+
[robertluo.waterfall.shape :as shape]))
5+
6+
;; Suppose you just want to use Kafka right away, do not want to learn
7+
;; too much, and do not want to use manifold/kafka API, do not want know
8+
;; which namespaces to require, waterfall has a single function for you.
9+
10+
;; ## One map to rule them all
11+
12+
;; Suppose all you want is to publish message to a kafka topic:
13+
(def cluster
14+
(wf/kafka-cluster
15+
{::wf/nodes "localhost:9092"
16+
::wf/shapes [(shape/topic (constantly "sentence")) (shape/edn) (shape/value-only)]}))
17+
18+
;; Declare a var (fn) allow us to put message.
19+
(def put! (::wf/put! cluster))
20+
21+
;; Put clojure data onto it when needed!
22+
(put! {:words "Hello, world!"})
23+
24+
;; When you have done using it, just close the cluster and it releases all resources.
25+
(.close cluster)
26+
27+
;;If you want to consumer messages from kafka topics, a cluster need a little more configuration:
28+
(def consuming-cluster
29+
(wf/kafka-cluster
30+
{::wf/nodes "localhost:9092"
31+
::wf/shapes [(shape/topic (constantly "sentence")) (shape/edn) (shape/value-only)]
32+
::wf/consumer-config {:position :beginning}
33+
::wf/group-id "tester1"
34+
::wf/topics ["sentence"]}))
35+
36+
;; Just refer to its `::wf/consume` key
37+
(def consume (::wf/consume consuming-cluster))
38+
39+
;; Then register you interest on it, here, we just print every message out:
40+
(consume println)
41+
42+
;;When done, close it. You might have to wait up to 10 seconds for it.
43+
(.close consuming-cluster)
44+
45+
;;## where the power come from
46+
;; This easy integration are powered by [fun-map](https://github.com/robertluo/fun-map),
47+
;; so make suer you put it in your dependencies.

src/robertluo/waterfall.clj

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
(ns robertluo.waterfall
22
"API namespace for the library"
33
(:require
4-
[robertluo.waterfall
5-
[core :as core]]
6-
[manifold.stream :as ms]))
4+
[robertluo.waterfall
5+
[core :as core]
6+
[util :as util]]
7+
[manifold.stream :as ms]
8+
[robertluo.waterfall.shape :as shape]
9+
[robertluo.waterfall.util :as util]))
710

811
(def schema
912
"Schema for waterfall"
@@ -80,7 +83,82 @@
8083
(-> (ms/transform xform strm) (ms/connect sink))
8184
strm))
8285

83-
(comment
86+
(defn kafka-cluster
87+
"returns a convient life-cycle-map of a kafka cluster. It requires:
88+
- `::nodes` kafka bootstrap servers
89+
- `::shapes` shape of data, example: [(shape/topic (constantly \"sentence\"))(shape/edn)(shape/value-only)]
90+
91+
If you just use it to publish message,
92+
- optional `::producer-config` can specify additional kafka producer configuration.
93+
94+
If you want to consume from topics:
95+
- `::topics` the topic you want to subscribe to. example: [\"sentence\"]
96+
- `::group-id` the group id for the message consumer
97+
- optional `::source-xform` is a transducer to process message before consuming
98+
- optional `::consumer-config` can specify additional kafka consumer configuration. With additions:
99+
- `:position` either `:beginning` `:end`, none for commited position (default)
100+
- `:poll-duration` for how long the consumer poll returns, is a Duration value, default 10 seconds
101+
102+
The returned map has different level of key-values let you use:
103+
- Highest level, no additional knowledge:
104+
- For consumer: `::consume` a function, a one-arity (each message) function as its arg, returns nil.
105+
- For producer:
106+
-`::put` a function with a message as its arg. e.g. ((::put return-map) {:a 3})
107+
-`::put-all` a function with message sequence as its arg
108+
- Mid level, if you need access of underlying manifold sink/source.
109+
- `::source` a manifold source.
110+
- `::sink` a manifold sink.
111+
- Lowest level, if you want to access kafka directly:
112+
- `::consumer` a Kafka consumer.
113+
- `::producer` a Kafka message producer.
114+
"
115+
{:malli/schema
116+
[:=> schema [:cat [:map {:closed true}
117+
[::nodes ::nodes]
118+
[::shapes [:vector [:fn shape/shape?]]]
119+
[::producer-config {:optional true} ::producer-config]
120+
[::group-id {:optional true} :string]
121+
[::topics {:optional true} [:vector :string]]
122+
[::source-xform {:optional true} fn?]]]
123+
:map]}
124+
[kafka-conf-map]
125+
(util/optional-require
126+
[robertluo.fun-map :as fm :refer [fw fnk]]
127+
(merge
128+
(fm/life-cycle-map
129+
{::producer (fnk [::nodes ::producer-config]
130+
(let [prod (producer nodes (or producer-config {}))]
131+
(fm/closeable prod #(ms/close! prod))))
132+
::consumer (fnk [::nodes ::group-id ::topics ::consumer-config]
133+
(assert (and group-id topics) "Has to provide group-id and topics")
134+
(let [cmer (consumer nodes group-id topics (or consumer-config {}))]
135+
(fm/closeable cmer #(ms/close! cmer))))
136+
::sink (fnk [::producer ::shapes]
137+
(-> producer ignore (xform-sink (comp (map (shape/serializer shapes))))))
138+
::source (fnk [::consumer ::shapes ::source-xform]
139+
(->> (comp (map (shape/deserializer shapes)) (or source-xform (map identity)))
140+
(xform-source consumer)))
141+
::put! (fnk [::sink] (partial ms/put! sink))
142+
::put-all (fnk [::sink] (partial ms/put-all! sink))
143+
::consume (fnk [::source] #(ms/consume % source))})
144+
kafka-conf-map)
145+
(throw (ClassNotFoundException. "Need io.github.robertluo/fun-map library in the classpath"))))
146+
147+
(comment
84148
(require '[malli.dev])
85149
(malli.dev/start!)
150+
(malli.dev/stop!)
151+
(def clu
152+
(kafka-cluster
153+
{::nodes "localhost:9092"
154+
::shapes [(shape/topic (constantly "sentence")) (shape/edn) (shape/value-only)]
155+
::consumer-config {:position :beginning}
156+
::group-id "tester1"
157+
::topics ["sentence"]
158+
::source-xform (map identity)}))
159+
(def put! (::put! clu))
160+
(put! "Hello, world")
161+
(def consume (::consume clu))
162+
(consume println)
163+
(.close clu)
86164
)

src/robertluo/waterfall/shape.clj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@
9999
[shapes]
100100
(->> (map :des shapes) (reverse) (apply comp)))
101101

102+
(defn shapes
103+
"construct shapes in order."
104+
([top-shapes]
105+
(shapes top-shapes :edn))
106+
([top-shapes bytes-shape]
107+
(shapes top-shapes bytes-shape (value-only)))
108+
([top-shapes bytes-shape kv-shape]
109+
(-> (concat top-shapes [bytes-shape kv-shape]) vector)))
110+
102111
(comment
103112
(def shapes [(edn) (key-value identity)])
104113
((serializer shapes) [:foo "bar"])

0 commit comments

Comments
 (0)