cli4clj Version 1.2.3 Released

cli4clj is a library for easing the implementation of simple interactive command line interfaces (CLIs) for Clojure applications. In this post, I briefly announce the release of cli4clj version 1.2.3.

The most important changes in version 1.2.3 are:

  • Add support for hiding commands in help and completion.
    Hidden commands are prefixed with “_”.
  • Hide the “enable-trace” command by changing it to “_enable-trace”.
  • Add a hidden “_sleep” command.

For hiding commands, the convention of prefixing hidden commands with “_” was introduced. This means that all commands that begin with an underscore, “_”, will neither be shown in the help nor in the command completion.

The rationale for hiding certain commands is to improve the usability by removing everything a “typical” user does not need. In this process, I also changed the “enable-trace” command to “_enable-trace” in order to hide it from the user.

The “_sleep” command was added as a stopgap solution for testing multi-threaded CLI applications. The current CLI test functionality works very well. However, there is a problem with multi-threaded applications.

Clearly, when a command triggers interaction with another thread that eventually prints a result, a corresponding unit test needs to wait until the processing finished and the result was printed. The CLI “UI thread” however, cannot know how long it has to wait. Thus, I added the “_sleep” command as a simple stopgap solution.

In the following, the code of the newly added unit tests for testing the “_sleep” command is shown for illustrating the problem and the current stopgap “solution”:

(deftest async-cmd-not-finished-test
  (let [cli-opts {:cmds {:async-foo {:fn (fn []
                                           (println "Starting...")
                                           (let [tmp-out *out*]
                                             (doto
                                               (Thread.
                                                 (fn []
                                                   (binding [*out* tmp-out]
                                                     (sleep 500)
                                                     (println "Finished."))))
                                               (.start)))
                                           "Started.")}}}
        test-cmd-input ["async-foo"]
        out-string (test-cli-stdout #(start-cli cli-opts) test-cmd-input)]
    (is (= (expected-string ["Starting..." "\"Started.\""]) out-string))))

(deftest async-cmd-sleep-finished-test
  (let [cli-opts {:cmds {:async-foo {:fn (fn []
                                           (println "Starting...")
                                           (let [tmp-out *out*]
                                             (doto
                                               (Thread.
                                                 (fn []
                                                   (binding [*out* tmp-out]
                                                     (sleep 500)
                                                     (println "Finished."))))
                                               (.start)))
                                           "Started.")}}}
        test-cmd-input ["async-foo" "_sleep 1000"]
        out-string (test-cli-stdout #(start-cli cli-opts) test-cmd-input)]
    (is (= (expected-string ["Starting..." "\"Started.\"" "Finished."]) out-string))))

Actually, using sleeps for this is not really an ideal solution. However, for now, this approach serves its purpose. In future, I may come up with something better that still allows me to keep cli4clj simple.

As usual, constructive criticism, feedback, and comments are always appreciated.

Posted in cli4clj, Libs. | Tagged , , | Leave a comment

Playing a bit with Message-oriented Middleware Protocols/Transports using bowerick

bowerick combines various libraries, e.g., for Message-oriented Middleware (MoM) protocols/transports. One aim was to provide an easily usable abstraction for playing and experimenting with MoM implementations. In this post, I use bowerick for a simple toy experiment to illustrate some of the abstractions provided by bowerick and some MoM protocol/transport related aspects.

The toy example discussed in this post is: forwarding of messages to multiple MoM protocols/transports via a single common broker instance. The following protocols were used:

bowerick uses ActiveMQ as embedded broker and for OpenWire TCP and UDP clients. In addition, bowerick uses further libraries for providing easily usable STOMP, WebSocket, and MQTT clients. For a full list of libraries please refer to bowericks dependencies, which can be found, e.g., in bowericks project.clj file.

The outline of this post is as follows:

  • Toy Example and Code
    At first, the toy example is introduced and the example code is explained.
  • Raw Network Packet Data
    Subsequently, raw network packet data for the packets transporting the payload is shown.
  • TCP Streams
    Afterwards, the TCP streams that carried the data are shown.
  • Concluding Remarks
    Finally, this post is finished with some concluding remarks.

Toy Example and Code

The ActiveMQ broker already supports forwarding of messages between protocols/transports. bowerick adds a bit of convenience to this in the way that it eases the way how a broker that services multiple protocols/transports can be started. In the following listing, the first part of the toy example for this post, which can, e.g., be run in a REPL, is shown:

(def urls ["tcp://127.0.0.1:55511" "stomp://127.0.0.1:55522" "ws://127.0.0.1:55533" "mqtt://127.0.0.1:55544" "udp://127.0.0.1:55555"])
(def broker (bowerick.jms/start-broker urls))

With bowerick, multiple transports are simply defined as a vector of strings (URLs) in which each string defines one transport via the URL format that is typically used by ActiveMQ. The embedded broker is simply started via the bowerick.jms/start-broker function which takes this URLs vector as argument and returns a reference to the started embedded broker instance.

In the next step, consumers are connected via the aforementioned transports (URLs) to the same test topic. As all consumers connect to the same topic, the ActiveMQ broker will forward messages sent to this topic via these transports. Furthermore, a single producer is created that is used for sending a map to the test topic via the OpenWire TCP transport. This is shown in the following listing:

(def test-topic "/topic/testtopic.foo")
(def consumers (doall (map-indexed (fn [idx url] (bowerick.jms/create-json-consumer url test-topic (fn [rcvd] (clj-assorted-utils.util/sleep (* 50 idx)) (println (aget (.split url ":") 0) "->" rcvd)))) urls)))
(def prod (bowerick.jms/create-json-producer (first urls) test-topic))
(prod {"a" 123 "b" "xyz"})

The somewhat lengthy definition of the consumers is simply used as convenience for creating consumers for all transport URLs in the urls vector. For each successfully received message, the consumers print a prefix corresponding to the URL and the received data. The print output is artificially delayed by sleeping depending on the index of the URL string in the vector in order to enforce that the order of the printed output corresponds to the order of the URLs in the vector.

The data is transmitted as byte array of the datas JSON UTF-8 string representation. In the following listing, the output of the consumers is shown:

...
tcp -> {a 123, b xyz}
stomp -> {a 123, b xyz}
ws -> {a 123, b xyz}
mqtt -> {a 123, b xyz}
udp -> {a 123, b xyz}
...

For the sake of completeness, the following listing shows the shutdown procedure for the producer, consumers, and broker:

(bowerick.jms/close prod)
(doseq [c consumers] (bowerick.jms/close c))
(bowerick.jms/stop broker)

Raw Packet Data

During the experiment, I captured the network traffic with Wireshark. Besides the fun aspects of this, my intention was also to verify and showcase that bowerick indeed uses the advertised protocols. In the end, having some program output claim the used protocol/transport was, e.g., STOMP is not as trustworthy as actually seeing it on “the wire”.

In the following, I first print the captured packets that carried the actual payload. As the payload was serialized to the byte array representation of a UTF-8 encoded JSON string, the payload can be quite easily identified in this data. Please note that these are only the packets with the actual payload and that there further packets were exchanged throughout this experiment.

Just in case, if you are note familiar with the structure of this type of packet dump: the first column shows the offset of the packet data in bytes as hexadecimal (hex) value. The second column shows the raw packet data in hex in groups of 1 byte. The third column shows the string representation of the raw packet data. As a little bit more aid for reading the packet data, the loopback address, 127.0.0.1, in hex is “7f 00 00 01”, and the relevant port number that were used are: 55511 (“d8 d7”), 55522 (“d8 e2”), 55533 (“d8 ed”), 55544 (“d8 f8”), 55555 (“d9 03”).

Producer

At first the packet with the payload that was sent from the producer is shown:

Producer: OpenWire (TCP) (“tcp://127.0.0.1:55511”)

0000    02 00 00 00 45 00 00 7c ee 41 40 00 40 06 00 00    ....E..|.A@.@...
0010    7f 00 00 01 7f 00 00 01 a9 78 d8 d7 ea 2e 7d da    .........x....}.
0020    61 77 90 b1 80 18 04 f3 fe 70 00 00 01 01 08 0a    aw.......p......
0030    0c e4 e0 bf 50 ea e1 a6 00 00 00 44 18 05 48 04    ....P......D..H.
0040    b0 00 00 00 00 00 05 00 04 00 05 00 06 00 06 6e    ...............n
0050    00 04 00 01 00 06 00 00 00 00 04 00 00 01 56 fe    ..............V.
0060    6b 5a e0 00 00 00 13 7b 22 61 22 3a 31 32 33 2c    kZ.....{"a":123,
0070    22 62 22 3a 22 78 79 7a 22 7d 00 06 00 00 00 00    "b":"xyz"}......

Consumers

Next, the packets that contained the payload that were sent to the consumers are shown:

Consumer: OpenWire (TCP) (“tcp://127.0.0.1:55511”)

0000    02 00 00 00 45 00 00 fd ee 43 40 00 40 06 00 00    ....E....C@.@...
0010    7f 00 00 01 7f 00 00 01 d8 d7 72 58 37 d6 32 db    ..........rX7.2.
0020    c6 20 11 15 80 18 04 00 fe f1 00 00 01 01 08 0a    . ..............
0030    52 68 2f 13 0c e4 bf 97 00 00 00 c5 15 08 5e 3f    Rh/...........^?
0040    af 12 05 2c 00 1e 00 00 00 00 00 01 7a 00 20 49    ...,........z. I
0050    44 3a 63 6f 6c 69 6e 2d 33 37 39 39 37 2d 31 34    D:colin-37997-14
0060    37 33 31 34 37 32 35 32 33 36 33 2d 38 3a 31 00    73147252363-8:1.
0070    01 00 01 00 02 65 00 0d 74 65 73 74 74 6f 70 69    .....e..testtopi
0080    63 2e 66 6f 6f 18 00 00 00 05 00 03 7b 00 21 49    c.foo.......{.!I
0090    44 3a 63 6f 6c 69 6e 2d 33 37 39 39 37 2d 31 34    D:colin-37997-14
00a0    37 33 31 34 37 32 35 32 33 36 33 2d 31 36 3a 31    73147252363-16:1
00b0    00 01 00 01 00 02 00 04 00 04 6e 00 03 00 01 00    ..........n.....
00c0    15 00 04 00 00 00 00 04 00 00 01 56 fe 6b 5a e0    ...........V.kZ.
00d0    00 00 00 13 7b 22 61 22 3a 31 32 33 2c 22 62 22    ....{"a":123,"b"
00e0    3a 22 78 79 7a 22 7d 00 04 00 00 00 00 00 00 01    :"xyz"}.........
00f0    56 fe 6b 5a e3 00 00 01 56 fe 6b 5a eb 00 00 00    V.kZ....V.kZ....
0100    00 

Consumer: STOMP (TCP) (“stomp://127.0.0.1:55522”)

0000    02 00 00 00 45 00 00 f9 ee 46 40 00 40 06 00 00    ....E....F@.@...
0010    7f 00 00 01 7f 00 00 01 d8 e2 f0 29 f4 ea d6 84    ...........)....
0020    da 4f ae 7e 80 18 04 00 fe ed 00 00 01 01 08 0a    .O.~............
0030    7d e0 1b ae 0c e4 c1 7a 4d 45 53 53 41 47 45 0a    }......zMESSAGE.
0040    63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a 31    content-length:1
0050    39 0a 65 78 70 69 72 65 73 3a 30 0a 64 65 73 74    9.expires:0.dest
0060    69 6e 61 74 69 6f 6e 3a 2f 74 6f 70 69 63 2f 74    ination:/topic/t
0070    65 73 74 74 6f 70 69 63 2e 66 6f 6f 0a 73 75 62    esttopic.foo.sub
0080    73 63 72 69 70 74 69 6f 6e 3a 31 0a 70 72 69 6f    scription:1.prio
0090    72 69 74 79 3a 34 0a 6d 65 73 73 61 67 65 2d 69    rity:4.message-i
00a0    64 3a 49 44 5c 63 63 6f 6c 69 6e 2d 33 37 39 39    d:ID\ccolin-3799
00b0    37 2d 31 34 37 33 31 34 37 32 35 32 33 36 33 2d    7-1473147252363-
00c0    31 36 5c 63 31 5c 63 31 5c 63 31 5c 63 31 0a 74    16\c1\c1\c1\c1.t
00d0    69 6d 65 73 74 61 6d 70 3a 31 34 37 33 31 34 37    imestamp:1473147
00e0    32 36 33 37 31 32 0a 0a 7b 22 61 22 3a 31 32 33    263712..{"a":123
00f0    2c 22 62 22 3a 22 78 79 7a 22 7d 00 0a             ,"b":"xyz"}..

Consumer: WebSocket (TCP) (“ws://127.0.0.1:55533”)

0000    02 00 00 00 45 00 00 f7 ee 47 40 00 40 06 00 00    ....E....G@.@...
0010    7f 00 00 01 7f 00 00 01 d8 ed 2b 82 b2 30 0b 74    ..........+..0.t
0020    9a c7 80 6c 80 18 04 fb fe eb 00 00 01 01 08 0a    ...l............
0030    6d 7b db 92 0c e4 c1 d2 81 7e 00 bf 4d 45 53 53    m{.......~..MESS
0040    41 47 45 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67    AGE.content-leng
0050    74 68 3a 31 39 0a 65 78 70 69 72 65 73 3a 30 0a    th:19.expires:0.
0060    64 65 73 74 69 6e 61 74 69 6f 6e 3a 2f 74 6f 70    destination:/top
0070    69 63 2f 74 65 73 74 74 6f 70 69 63 2e 66 6f 6f    ic/testtopic.foo
0080    0a 73 75 62 73 63 72 69 70 74 69 6f 6e 3a 30 0a    .subscription:0.
0090    70 72 69 6f 72 69 74 79 3a 34 0a 6d 65 73 73 61    priority:4.messa
00a0    67 65 2d 69 64 3a 49 44 3a 63 6f 6c 69 6e 2d 33    ge-id:ID:colin-3
00b0    37 39 39 37 2d 31 34 37 33 31 34 37 32 35 32 33    7997-14731472523
00c0    36 33 2d 31 36 3a 31 3a 31 3a 31 3a 31 0a 74 69    63-16:1:1:1:1.ti
00d0    6d 65 73 74 61 6d 70 3a 31 34 37 33 31 34 37 32    mestamp:14731472
00e0    36 33 37 31 32 0a 0a 7b 22 61 22 3a 31 32 33 2c    63712..{"a":123,
00f0    22 62 22 3a 22 78 79 7a 22 7d 00                   "b":"xyz"}.

Note that the packets for STOMP and WebSocket show quite strong similarities. The reason for this is that the WebSocket transport used in bowerick uses STOMP as MoM protocol. However, there are also differences due to the differences between the two transports.

Consumer: MQTT (TCP) (“mqtt://127.0.0.1:55544”)

0000    02 00 00 00 45 00 00 5a ee 45 40 00 40 06 00 00    ....E..Z.E@.@...
0010    7f 00 00 01 7f 00 00 01 d8 f8 5f bd c8 97 d2 49    .........._....I
0020    2b 9d 6d 9c 80 18 04 00 fe 4e 00 00 01 01 08 0a    +.m......N......
0030    63 7e 77 da 0c e4 c2 85 32 24 00 0d 74 65 73 74    c~w.....2$..test
0040    74 6f 70 69 63 2f 66 6f 6f 00 01 7b 22 61 22 3a    topic/foo..{"a":
0050    31 32 33 2c 22 62 22 3a 22 78 79 7a 22 7d          123,"b":"xyz"}

Consumer: OpenWire (UDP) (“udp://127.0.0.1:55555”)

0000    02 00 00 00 45 00 01 65 ee 44 00 00 40 11 00 00    ....E..e.D..@...
0010    7f 00 00 01 7f 00 00 01 ac 91 3c 67 01 51 ff 64    ..........<g.Q.d
0020    00 00 01 45 15 00 00 00 06 00 01 7a 01 00 21 49    ...E.......z..!I
0030    44 3a 63 6f 6c 69 6e 2d 33 37 39 39 37 2d 31 34    D:colin-37997-14
0040    37 33 31 34 37 32 35 32 33 36 33 2d 31 34 3a 31    73147252363-14:1
0050    00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01    ................
0060    01 65 01 00 0d 74 65 73 74 74 6f 70 69 63 2e 66    .e...testtopic.f
0070    6f 6f 01 18 00 00 00 05 00 01 7b 01 00 21 49 44    oo........{..!ID
0080    3a 63 6f 6c 69 6e 2d 33 37 39 39 37 2d 31 34 37    :colin-37997-147
0090    33 31 34 37 32 35 32 33 36 33 2d 31 36 3a 31 00    3147252363-16:1.
00a0    00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 01    ................
00b0    65 01 00 0d 74 65 73 74 74 6f 70 69 63 2e 66 6f    e...testtopic.fo
00c0    6f 00 00 01 6e 00 01 7b 01 00 21 49 44 3a 63 6f    o...n..{..!ID:co
00d0    6c 69 6e 2d 33 37 39 39 37 2d 31 34 37 33 31 34    lin-37997-147314
00e0    37 32 35 32 33 36 33 2d 31 36 3a 31 00 00 00 00    7252363-16:1....
00f0    00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00    ................
0100    00 00 00 01 00 00 00 00 00 00 00 15 00 00 00 00    ................
0110    00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00    ................
0120    01 56 fe 6b 5a e0 00 01 00 00 00 13 7b 22 61 22    .V.kZ.......{"a"
0130    3a 31 32 33 2c 22 62 22 3a 22 78 79 7a 22 7d 00    :123,"b":"xyz"}.
0140    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
0150    00 00 00 00 00 00 01 56 fe 6b 5a e3 00 00 01 56    .......V.kZ....V
0160    fe 6b 5a f3 00 00 00 00 00                         .kZ......

TCP Streams for Consumers

In addition to the raw packet data, I also print the TCP streams that contained the payload data for the consumers. Similarly to the packet data, please note that these are only the streams that contained the actual payload and I did not check if additional streams were involved during the communication. However, you can download the pcap file from which this data was taken from the bowerick repository.

Consumer: OpenWire (TCP) (“tcp://127.0.0.1:55511”)

For the OpenWire TCP stream, I manually wrapped the data either at hopefully meaningful offsets or alternatively at column 80 in order to avoid the lines getting too long.

.....ActiveMQ............
..StackTraceEnabled....PlatformDetails..NJVM: 1.8.0_92, 25.92-b14, Oracle Corpo
ration, OS: FreeBSD, 10.3-RELEASE, amd64..CacheEnabled....Host...127.0.0.1..Tcp
NoDelayEnabled....SizePrefixDisabled....CacheSize.......ProviderName...ActiveMQ
..TightEncodingEnabled....MaxFrameSize...........MaxInactivityDuration.......u0
. MaxInactivityDurationInitalDelay.......'...ProviderVersion...5.14.0
.....ActiveMQ........s......TcpNoDelayEnabled....SizePrefixDisabled....CacheSiz
e.......ProviderName...ActiveMQ..StackTraceEnabled....PlatformDetails..NJVM: 1.
8.0_92, 25.92-b14, Oracle Corporation, OS: FreeBSD, 10.3-RELEASE, amd64..CacheE
nabled....TightEncodingEnabled....MaxFrameSize...........MaxInactivityDuration.
......u0. MaxInactivityDurationInitalDelay.......'...ProviderVersion...5.14.0..
.P..~........|. ID:colin-37997-1473147252363-0:1..tcp://localhost:55511..localh
ost
...O..........x. ID:colin-37997-1473147252363-8:1. ID:colin-37997-1473147252363
-7:1..................................~...}........z. ID:colin-37997-1473147252
363-8:1............e.7ActiveMQ.Advisory.TempQueue,ActiveMQ.Advisory.TempTopic..
............................^......y. ID:colin-37997-1473147252363-8:1.....p.._
}........z. ID:colin-37997-1473147252363-8:1......e.
testtopic.foo......... ID:colin-37997-1473147252363-7:1.
.....................^?...,........z. ID:colin-37997-1473147252363-8:1......e.
testtopic.foo.......{.!ID:colin-37997-1473147252363-16:1..........n............
....V.kZ.....{"a":123,"b":"xyz"}.........V.kZ....V.kZ........K...W.............
n..{.!ID:colin-37997-1473147252363-16:1........n..........

Consumer: STOMP (TCP) (“stomp://127.0.0.1:55522”)

CONNECT
accept-version:1.1
host:127.0.0.1

.
CONNECTED
server:ActiveMQ/5.14.0
heart-beat:0,0
session:ID:colin-37997-1473147252363-9:1
version:1.1

.
SUBSCRIBE
receipt:2
ack:client
destination:/topic/testtopic.foo
id:1

.
RECEIPT
receipt-id:2

.
MESSAGE
content-length:19
expires:0
destination:/topic/testtopic.foo
subscription:1
priority:4
message-id:ID\ccolin-37997-1473147252363-16\c1\c1\c1\c1
timestamp:1473147263712

{"a":123,"b":"xyz"}.
ACK
receipt:3
subscription:1
message-id:ID\ccolin-37997-1473147252363-16\c1\c1\c1\c1

.
RECEIPT
receipt-id:3

.

Consumer: WebSocket (TCP) (“ws://127.0.0.1:55533”)

GET / HTTP/1.1
Host: 127.0.0.1:55533
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: QzJg5xeq9ERBZCTZLR7Ugw==
Sec-WebSocket-Version: 13
Pragma: no-cache
Cache-Control: no-cache

HTTP/1.1 101 Switching Protocols
Date: Tue, 06 Sep 2016 07:34:15 GMT
Connection: Upgrade
Sec-WebSocket-Accept: xjAqlX7Q/bGXOz0vro4ef+UXle4=
Sec-WebSocket-Protocol: stomp
Upgrade: WebSocket

...3...|...p...V.......G.......P.......@...........9...gCONNECTED
server:ActiveMQ/5.14.0
heart-beat:0,0
session:ID:colin-37997-1473147252363-9:2
version:1.2

.....n.}.,Um.'Dk.
c]..hO..i@.ArA..e...uZ..vG.@`A.doJ.^.$..~..MESSAGE
content-length:19
expires:0
destination:/topic/testtopic.foo
subscription:0
priority:4
message-id:ID:colin-37997-1473147252363-16:1:1:1:1
timestamp:1473147263712

{"a":123,"b":"xyz"}.

Consumer: MQTT (TCP) (“mqtt://127.0.0.1:55544”)

....MQTT...<..paho216318442465645 ........
testtopic/foo......2$.
testtopic/foo..{"a":123,"b":"xyz"}@...

Concluding Remarks

With respect to MoM protocols/transports, the ActiveMQ broker is capable of forwarding data to various MoM protocols/transports. bowerick adds further convenience functionality on top of this.

A simple toy example was used to illustrate the use of multiple MoM protocols/transports with bowerick. In addition, raw network packet data as well as TCP stream data of some parts of the corresponding communication were shown.

With respect to the serialization, I used my current favorite serialization approach from an inter-operation perspective: byte arrays of UTF-8 encoded JSON strings. This way of serializing data provides, in my opinion, excellent inter-operation capabilities with respect to, e.g., MoM protocols/transports as well as programming language combinations.

With respect to the MoM protocol/transport, I quite like the simplicity of STOMP. The captured STOMP TCP stream nicely shows the clarity of this protocol. On the other hand, I also liked MQTT because of its minimalism.

I hope that this post is useful. As usual, constructive criticism, comments, and suggestions are highly appreciated.

Posted in bowerick, Libs. | Tagged , , , , , , , , | Leave a comment

bowerick: Serialization Convenience, Command Line Helpers, and To-dos

In my previous post, I introduced bowerick for easing simple Message-oriented Middleware (MoM) tasks with Clojure (and Java). In this post, I will introduce some more features of bowerick in more detail. In addition, I will discuss some of the things that, in my opinion, need further improvement.

The outline of this post is as follows:

  • Motivation (Skip this if you are only interested in the technical parts.)
  • Serialization Convenience
  • Command Line Helpers
    • Broker
    • Client
  • To-dos
    • Modularity
    • Logging
  • Concluding Remarks

Motivation

In this part, I discuss my motivation for implementing the features that I will cover in this post. You can safely skip this part if you are more interested in the technical parts. However, I want to provide a bit of context, just in case it could be helpful or interesting.

As I mentioned in my previous post, I started with bowerick (or more precisely its predecessor) in scope of some research I did. Some of my needs at that time were that I wanted to ease and speed-up the prototype development and to keep my prototypes, experiments, etc. as self-contained as reasonably possible. In this scope, the intent of bowerick was to bundle the MoM related functionality and to provide easily usable abstractions on an API-level.

During my research, I experimented a bit with different ways of serializing the data that was sent via the MoM. In this context, I also considered not only the actual serialization but also compression/decompression.

Thanks to many available nice libraries, various serialization and compression methods can be easily used. However, while the implementation overhead of using a single library is quite small, the effort accumulates with the amount of libraries and serialization/compression methods that are used. Furthermore, in order to reduce the probability of errors in my experiments, I used unit tests for assessing the functionality.

Besides providing easily usable abstractions on an API-level, e.g., for serialization as mentioned above, I also considered the use of the MoM broker as another part that could be improved for easing usage and development. For this, I developed a simple command line helper runnable for easily running a simple MoM broker. After finishing my dissertation etc., I also found some time to add experimental “client” support as well.

Serialization Convenience

The default way for creating producers and consumers with bowerick is via the create-producer respectively create-consumer functions in the bowerick.jms namespace. By default the created producers/consumers use the serialization/de-serialization mechanisms of the underlying JMS transport implementation, such as ActiveMQ OpenWire.

However, depending on the used transport, the default serialization mechanisms on the producer side may not be suited for all data types. For the producer, I implemented a fallback serialization that currently defaults to serialization to UTF-8 encoded JSON via Cheshire. On the consumer side, however, it is not easily possible to identify the type of serialization that was used just based on the byte array payload (I refrained from implementing a smarter consumer as it is not possible to reliably distinguish between meta-data that identifies a serialization method and potential valid payload data that should be forwarded as is.).

In order to allow the easy use of other serialization and also compression mechanisms, I added various convenience functions for creating more specialized producers/consumers. Currently, these functions are (in the bowerick.jms namespace):

For create-nippy-producer, below, I give some examples of possible Nippy options for compressing the data, which are set via the “normal” Nippy options map. For now, do not worry about the “1” argument. I will write about that in another post.

  • (def lz4-prod (create-nippy-producer “tcp://broker:61616” “/topic/foo” 1 {:compressor taoensso.nippy/lz4-compressor}))
  • (def snappy-prod (create-nippy-producer “tcp://broker:61616” “/topic/foo” 1 {:compressor taoensso.nippy/snappy-compressor}))
  • (def lzma2-prod (create-nippy-producer “tcp://broker:61616” “/topic/foo” 1 {:compressor taoensso.nippy/lzma2-compressor}))

For create-nippy-consumer, no options need to be set as Nippy can identify the compression mechanisms for decompression.

From the point of interoperability and as I am primarily working with “basic” data structures, such as maps or sequences, my current favorite serialization mechanism is create-json-producer / create-json-consumer. This offers excellent interoperability across different messaging transports/protocols, such as OpenWire, STOMP, or MQTT, and also across programming languages. I plan to write about using multiple transports in a later post.

Command Line Helpers

In the following, I will describe simple broker and client related command line helper functionality provided by bowerick.

Broker

The main idea for the broker command line helper was to have an easy way for starting a simple broker instance. In the simplest form, a broker can be started with a bowerick stand-alone jar file as follows:

java -jar target/bowerick-1.99.6-standalone.jar

By default, bowerick listens sets up an OpenWire transport connector that listens on 127.0.0.1:61616. In the following listing, an example for specifying the transport/protocol is shown. Please note the additional escaped quotation marks.

java -jar target/bowerick-1.99.6-standalone.jar --url "\"stomp://127.0.0.1:61617\""

bowerick also offers convenience functionality for leveraging ActiveMQs support for multiple transports/protocols. I plan to write more about multiple transports/protocols in another blog post. For now, with respect to the broker command line helper, in the following listing, the syntax for starting a broker with multiple transports/protocols is shown.

java -jar target/bowerick-1.99.6-standalone.jar --url "[\"tcp://127.0.0.1:61616\" \"stomp://127.0.0.1:61617\"]"

Note that the list of transports is just a Clojure vector of strings in which each string defines one transport/protocol. Again, note the escaped quotation marks.

Client

While the broker command line helper essentially only provides a way for conveniently starting a MoM broker instance, the client command line helper is intended to allow more dynamic interaction. Thus, the main part of the client-side command line helper is an interactive command line interface (CLI), which uses my cli4clj library for creating the interactive CLI.

Currently, the client command line helper is in an early stage and I consider it as an experiment in progress. Use cases of the client command line helper can be, e.g.:

  • simple debugging
  • or serving as a playground tool for easily playing with a MoM.

In the following, I will provide a brief usage example for the client command line helper. For this example, I assume that a JMS broker is running that listens for incoming OpenWire connections on tcp://127.0.0.1:61616. Such a broker instance can be, e.g., started with the bowerick broker command line helper as follows:

java -jar target/bowerick-1.99.6-standalone.jar

The following listening shows the start of the bowerick client command line helper and the prompt of the interactive CLI:

java -jar target/bowerick-1.99.6-standalone.jar --client
...
bowerick#

In order to receive data from a destination, the “receive” command can be used as follows:

bowerick# receive tcp://localhost:61616:/topic/foo.bar
Creating JSON consumer: tcp://localhost:61616 /topic/foo.bar 1
Creating consumer: tcp://localhost:61616 /topic/foo.bar
Creating connection.
Creating destination. Type: topic Name: foo.bar
Set up consumer for: tcp://localhost:61616:/topic/foo.bar
bowerick#

The “receive” command creates a consumer that is connected to the specified broker and destination. I will discuss the “URL format” a bit more later. The consumer that is set up with the “receive” command will print the received data to stdout.

In order to send data to a destination, the “send” command can be used as follows:

bowerick# send tcp://localhost:61616:/topic/foo.bar baz
Creating JSON producer: tcp://localhost:61616 /topic/foo.bar 1
Creating producer: tcp://localhost:61616 /topic/foo.bar
Creating connection.
Creating destination. Type: topic Name: foo.bar
Sending: tcp://localhost:61616:/topic/foo.bar <- baz bowerick# Received: tcp://localhost:61616:/topic/foo.bar ->
"baz"

bowerick#

Please note that the listing also shows the reception output of the consumer that was previously set up with the “receive” command. Due to the current way of how the output is printed, the output of the consumer is simply printed on top of the prompt, which looks not so nice, in my opinion.

Also note that the producer instances created with the “send” command are cached. For subsequent invocations of “send” with an earlier used URL the producer initialization is omitted:

bowerick# send tcp://localhost:61616:/topic/foo.bar baz
Sending: tcp://localhost:61616:/topic/foo.bar <- baz bowerick# Received: tcp://localhost:61616:/topic/foo.bar ->
"baz"

bowerick#

Furthermore, the interactive CLI supports Clojure data types like maps or vectors. The default serialization mechanism uses JSON serialization via Cheshire mentioned above:

bowerick# send tcp://localhost:61616:/topic/foo.bar {"a" "string", :b [1 7 0 1]}
Sending: tcp://localhost:61616:/topic/foo.bar <- {"a" "string", :b [1 7 0 1]} bowerick# Received: tcp://localhost:61616:/topic/foo.bar ->
{"a" "string", "b" [1 7 0 1]}

bowerick#

Note that one effect of the JSON default serialization is that a keyword “:b” is changed to the string “b”.

The destination-url format is: ://

::/[topic,queue]/. can be, e.g.: tcp, udp, stomp, ssl, or stomp+ssl.
is the IP address or name of the broker. is the port number on which the broker listens. is the name of the topic/queue to which the data will be sent.

I borrowed “:/” for separating the broker part from the destination part from the way SSH SCP URLs are expressed. However, I am not yet fully convinced that this is the right way to do it. Anyway, as this is just a detail of the experimental client CLI, I do not see any major problems for changing this in the future.

To-dos

As I mentioned in this and in my previous post, bowerick and its predecessor project are a result of the needs I had during some of my experiments. Hence, I more or less “selfishly” focused primarily on the needs I had. However, with the rework I started since the fork from the original project, I also tried to get bowerick in a shape that makes it more useful for others.

Still, as this is currently a single person spare time project, there are quite a number of things that can be improved. In this section, I will briefly discuss some of these aspects.

Modularity

bowerick bundles a lot of different functionality, such as different MoM transports, serialization mechanisms, and compression methods. Thus, it has a lot of dependencies and the resulting uberjar files are pretty large.

If only a subset of the functionality offered by bowerick is used, many dependencies and space occupied in the uberjar file will be redundant. In order to counteract these issues, one potential improvement of bowerick could be to modularize bowerick into smaller better defined sub-modules.

I realize that splitting up bowerick into sub-modules is the inverse of the original intent to combine functionality and provide easily usable abstractions. However, maybe there are possibilities to keep the ease of use that is currently offered and still split bowerick into better defined sub-modules.

Logging

The logging in bowerick is currently just done via printing messages to stdout and stderr.
It is not possible to specify logging targets, such as files, or log levels to adjust the verbosity of the output. This is clearly not what one would expect in a more productive environment.

Furthermore, bowericks dependencies further complicate the situation as there may be different potentially incompatible logging mechanisms pulled in. Admittedly, I didn’t even have the time to get a complete overview of all logging mechanisms that are pulled in by the dependencies.

Consequently, providing more sophisticated logging in bowerick and to harmonizing the logging of dependencies could be another potential improvement.

Concluding Remarks

I hope that bowerick is useful for some of you. As you can see in the history, I tried to keep the dependencies as up to date as reasonably possible, up to now. Furthermore, I hope that the test-driven development approach with a high test coverage and the continuous testing and integration help to keep bowerick updated and to improve its quality. Nonetheless, there is the caveat that bowerick is a single person spare time project, for now. Constructive feedback, suggestions, etc. are always appreciated.

Posted in bowerick, Libs. | Tagged , , , , | Leave a comment

bowerick: Easing Simple Message-oriented Middleware Tasks with Clojure (and Java)

TL;DR

The aim of bowerick is to ease simple Message-oriented Middleware (MoM) tasks with Clojure and, to some extent, with Java. The general functionality that is offered by bowerick is:

  • Creating message producers and sending messages.
  • Creating message consumers and receiving messages.
  • Programmatically running embedded MoM brokers.

bowerick also offers more advanced features. However, I want to keep this post concise. Hence, I focus on the most fundamental features.

bowerick supports the following protocols for clients and servers: OpenWire (UDP & TCP), STOMP, STOMP via WebSocket, and MQTT. All protocols except OpenWire UDP can also be used via encrypted connections (SSL/TLS).

History

In this section, I want to briefly summarize the history of bowerick. I hope that having a bit of historical context will help you to determine whether bowerick fits your needs or not. If you are more interested in the technical aspects, just skip this section and continue reading with the next section.

I initially started the development of the project (clj-jms-activemq-toolkit) that gave rise to bowerick during my PhD thesis and employment as researcher at the Frankfurt University of Applied Sciences. Hence, you will find the copyright of the university in various places.

Back then, my main need was to have something that helped me with some experiments that I did as part of my research work and for my PhD thesis. Essentially, I wanted to have abstractions to ease simple MoM tasks and I also implemented prototypes for evaluating some ideas which partially continue in bowerick.

bowerick started as a fork of clj-jms-activemq-toolkit. From my perspective, bowerick is the direct continuation of clj-jms-activemq-toolkit, for which I was the only contributor anyway. The intention of the fork is to signal the changed scope of bowerick, from a loose collection of JMS tooling and prototypes to something, hopefully, more evolved.

However, please be aware that bowerick is still a toy project of mine. Future development is primarily driven by “fun aspects”. Nonetheless, I hope to provide a solid project that is helpful and easy and fun to use as you can see, e.g., in the continuous integration, API documentation, and test coverage efforts. Since the fork of bowerick, I also performed extensive re-factoring and further development in order to improve code and capabilities for which I did not have the time while I was writing my dissertation.

Brief Introduction

The main aim of this post is to provide a brief introduction of bowerick. Hence, I will focus on the fundamental features.

I think the most illustrative way to show the fundamental functionality (producer, consumer, embedded broker) is to show an example code snippet. In the following listing, a corresponding example is shown:

; Can also be run in: lein repl
(require '[bowerick.jms :as jms])
(def url "tcp://127.0.0.1:61616")
(def destination "/topic/my.test.topic")
; Create an embedded broker.
(def brkr (jms/start-broker url))
; Create a consumer that prints the received data.
(def consumer (jms/create-json-consumer url destination (fn [data] (println "Received:" data))))
; Create a producer ...
(def producer (jms/create-json-producer url destination))
; ... and send some data.
(producer "foo")
; nilReceived: foo
(producer '(1 7 0 1))
; nilReceived: (1 7 0 1)
(jms/close producer)
(jms/close consumer)
(jms/stop brkr)
; (quit)

As embedded broker and for the OpenWire protocol, bowerick uses Apache ActiveMQ. For the other supported protocols, bowerick uses different other libraries.

In the following listing, examples for URLs of supported protocols are shown:

;
; Encrypted
;
; OpenWire via TCP
(def url "ssl://127.0.0.1:61616")
; STOMP via TCP
(def url "stomp+ssl://127.0.0.1:61616")
; STOMP via WebSocket
(def url "wss://127.0.0.1:61616")
; MQTT via TCP
(def url "mqtt+ssl://127.0.0.1:61616")
;
; Unencrypted
;
; OpenWire via TCP
(def url "tcp://127.0.0.1:61616")
; OpenWire via UDP
(def url "udp://127.0.0.1:61616")
; STOMP via TCP
(def url "stomp://127.0.0.1:61616")
; STOMP via WebSocket
(def url "ws://127.0.0.1:61616")
; MQTT via TCP
(def url "mqtt://127.0.0.1:61616")

As bowerick is the continuation of clj-jms-activemq-toolkit, the versioning continues where the original project stopped. Currently, I think that the re-worked API etc. is pretty much stable. Still, the current version is 1.99.x, which indicates its state as a sort of preliminary version before the 2.x release. However, I think that the, at time of writing, latest version 1.99.5 is a pretty good candidate for the final 2.0.0 release.

I hope that you consider bowerick useful. I intend to post more details about bowerick and more of its features in future posts. Feedback and comments are always appreciated.

Posted in Announcements, bowerick, Libs. | Tagged , , , , , | 1 Comment

Panoramic Photos, Heidelberg & Dielheim

I used a trip this year to Dielheim and Heidelberg, as opportunity to try to make some more panoramic photos. As usual, I took the individual photos with my smartphone and used Huggin as stitcher.

The first two panoramas were taken from the castle in Heidelberg. The first panorama was taken in the direction of the old town of Heidelberg. The second panorama shows a view more in the direction of the Neckar river.

20160707_031 - 20160707_047_scaled

20160707_088 - 20160707_098_scaled

The last panorama shows a view of a landscape in Dielheim.

20160709_079 - 20160709_090_scaled

Posted in Misc. | Tagged , | Leave a comment

cli4clj 1.2.0 Released

This is a brief post to announce the release of version 1.2.0 of cli4clj, a small library for easing the creation of interactive command line interfaces (CLIs). The most noteworthy changes in this version are:

  • Addition of a run-time setting to print full traces of exceptions instead of just printing the exception message.
  • Fallback to String representation for elements that would otherwise cause a RuntimeException due to invalid tokens.
  • Addition of a “print” command for the example application.

The “enable-trace true” run-time setting, which defaults to “false”, enables printing of the full traces of exceptions instead of just printing the exception message. In the following listing a somewhat shortened example is shown:

cli# enable-trace false
print-exception-trace is set to: false
cli# d 1 0
Divide by zero
cli# enable-trace true
print-exception-trace is set to: true
cli# d 1 0
java.lang.ArithmeticException: Divide by zero
 at clojure.lang.Numbers.divide (Numbers.java:158)
 cli4clj.example$divide.invokeStatic (example.clj:21)
 cli4clj.example$divide.invoke (example.clj:20)
 ...

Some input values, e.g., “/foo/bar” would cause an exception due to an invalid token. In version 1.2.0, I added a fallback, that returns the String representation in such cases instead of failing with an exception. The fallback can be disabled by setting the cli4clj configuration setting “:invalid-token-to-string” in the options map to false.

Last but not least, a “print” function was added to the example application. The print function serves two purposes. It pretty prints its arguments including the type of the first argument. This is, e.g., intended for getting an understanding of what data types cli4clj uses for what input. In addition, the print command shows the use of optional arguments.

Some known issues and things that I may improve in the future, as my time permits it, are, e.g.: the full stack trace is printed to stdout instead of stderr because of some strange behavior I had here on my development machine. The setting for switching between full stack trace vs. short exception message output uses a global atom var, for now. I plan to refactor that in future.

I hope that you consider these changes useful. As usual, feedback and comments are always appreciated.

Posted in cli4clj, Libs. | Tagged , , , | Leave a comment

cli4clj 1.1.0 Released

This is a brief post to announce the release of cli4clj, a small library for easing the creation of interactive command line interfaces (CLIs), version 1.1.0. The most noteworthy changes in version 1.1.0 are:

  • Updated dependencies
  • Moved the testing helper functions from cli4clj.cli to cli4clj.cli-tests.

I did the latter change in order to improve the organization of the code and to make it easier for users to identify which functions/macros are useful for which purpose. However, if you are already using the testing helper functionality, you need to change the imported namespace from cli4clj.cli to cli4clj.cli-tests.

As usual, constructive criticism, comments, and feedback are highly appreciated.

PS: Shortly after writing this, I realized that I did not update to the latest stable jline2 version, which is 2.14.2. cli4clj version 1.1.1 fixes this.

 

Posted in cli4clj, Libs. | Tagged , , , | Leave a comment