A simple test harness for playing with RabbitMQ using EDN and Protocol Buffers
You will need Leiningen 2.0 or above installed.
You will also need to add dev-config.edn to the main project folder, containing:
{:dev true
:port 3000
:nrepl-port 7000
:broker-host "127.0.0.1"
:broker-port 5672
:broker-username "guest"
:broker-password "guest"
:broker-vhost "/main"}
to make the system happy. (I'm not going to add this file to this repo, just make your own copy)
Prior to running the app, you will need to install (not discussed here) and run RabbitMQ. Then, using
the management console at
localhost:15672
You need to set up a vhost called /main and add the following exchanges to that vhost:
my-exchangepb-exchange
Then add the following queues and bindings:
some.queuebound tomy-exchangeperson.queuebound topb-exchangemessage.queuebound topb-exchange
Before anything else, you need to compile the *.proto files (found here) into *.java files for further processing.
First you need to install the appropriate Protocol Buffer compiler protoc from Google
for your operating system and make sure it is on your execution path.
(run protoc --version at the terminal/powershell to check)
To just compile the protocol buffers into Java, run:
lein protobuf
The lein-protobuf plugin automatically hooks into both lein compile and lein run
To compile everything and start the server for the application, in a terminal/powershell window, run:
lein run
Then, in another terminal/powershell window, run the client:
lein figwheel
This release of Chocolate does not use a database. All configuration information for both the server and the UI is contained in EDN files.
We will discuss each file separately.
The default content is:
{"Person" {:class "protobuf.examples.PersonOuterClass$Person"
:protoc "resources/proto/person.proto"
:dummy {}}
"Message" {:class "protobuf.examples.MessageOuterClass$Message"
:protoc "resources/proto/message.proto"
:dummy {}}}This file describes the mapping between the "short name" of the type (the key) and (as the value) the protocol buffer Java type (:class), the *.proto source
file (:protoc) and the "dummy" value needed by the clojusc/protobuf library for getting the
Java type back from it's binary representation (marshalling)
This file is used both internally by the marshalling and unmarshalling code as well as the user-facing "Flexible-publisher" and "Flexible-consumer" modal UI.
The default content is:
[{:id "100"
:msg_type "edn"
:exchange "my-exchange"
:queue "some.queue"
:pb_type ""}
{:id "200"
:msg_type "pb"
:exchange "pb-exchange"
:queue "person.queue"
:pb_type "Person"}
{:id "300"
:msg_type "pb"
:exchange "pb-exchange"
:queue "message.queue"
:pb_type "Message"}]This file describes a collection (vector) of configuration data needed to establish an AMQP consumer using Bunnicula.
Each hash-map defines the exchange and queue for connecting to the broker. :msg_type and :pb_type are used to resolve
how each message in Clojure format should be handled. Our goal is to work in clojure/edn and leave the conversion into and out-of
protocol buffers to lower-levels of software.
:msg_type identifies EDN message, which require no additional processing. Bunnicula automatically marshalls the data into and
out-of JSON for transmission over AMQP.
:pb_type identifies the "short name" of the actual protocol buffer Java type to be used. This must match the "short name" usedf
as the key in protobuf-types.edn'
Handlers are implemented for each consumer. By default, the handler decodes the received message and retransmits it to all connected clients over websockets (using sente). The handler for "Message" is different. In addition to sending the decoded message to the clients, it also publishes the message onto some.queue. This means that all "Message" messages will also be received by the "EDN" consumer (assuming one has been started).
The default content is:
[{:id "1"
:msg_type "edn"
:exchange "my-exchange"
:queue "some.queue"
:pb_type ""
:content {:user "Chris"}}
; ... elided
{:id "3"
:msg_type "pb"
:exchange "pb-exchange"
:queue "person.queue"
:pb_type "Person"
:content {:id 108
:name "Alice"
:email "alice@example.com"}}
;... elided
]This file describes a collection (vector) of configuration data needed to publish a message using Bunnicula.
Each hash-map defines the exchange and queue to publish the message on. :msg_type and :pb_type are used to resolve
how each message in Clojure format should be handled. Our goal is to work in clojure/edn and leave the conversion into and out-of
protocol buffers to lower-levels of software.
:content defines an EDN data structure of the actual message content to publish. In the case of :msg_type = "edn" the content
will be converted to JSON automatically by Bunnicula before publishing. In the case where :msg_type = "pb" the :pb_type value is
used to determine the specific protocol buffer Java type to use for the marshalling. The value of the :pb_type key must
match the "short name" in protobuf-types.edn.
Note: "nested" protocol buffer types are defined using nested EDN data structures
In addition to the protobuf type data stored in the "default" files listed above, each file can have a local variant. This allows the develop to work on private, possibly proprietary, message formats without polluting the global repo.
| Default types | "Optional" types (not in repo) |
|---|---|
protobuf-types.edn |
protobuf-type-local.edn |
consumer-types.edn |
consumer-types-local.edn |
publisher-types.edn |
publisher-types-local.edn |
NOTE: Do NOT push the
*-local.ednfiles to the repo!!!!!
*-local.edn files are entirely optional. If they are missing, the app still runs, just with only the base configuration.
In order to support additional "local" protocol buffer Java types, it is required for you to create a local
local_protobuf.clj file with the following content:
(ns chocolate.protobuf.local-protobuf
(:require [your.local.protobuf.support.namespaces]))Although you must add this file to your local project (and not push it to the repo), you only need to add
:requires for namespaces that help you with your "local" protobuf types. For example, if you have a protobuf type like:
import "DataPoint.proto"
message Sample
string id = 1;
repeated DataPoint = 2;
and you write a helper function to generate a large number of DataPoint instances to fill it:
(ns sample.helpers)
(defn make-data-point []
{:data "some value"})
(defn make-sample [id num-points]
{:id id :data-point (into [] (for [i (range num-points)] (make-data-point)))}) you would add the "sample.sample" namespace as a dependency to local_protobuf.clj:
(ns chocolate.protobuf.local-protobuf
(:require [sample.sample]))
NOTE: Again, do not push this file to the repo. You could be "leaking" proprietary information!
The app also supports swagger-ui at
localhost:3000/swagger-ui
specifically the /api/messages and /api/publish routes.
Remember:
/api/publishtakes a message id as it's only parameter, so understand what content
you want to send before using.
Compiling the app into an uberjar allows it to be ran standalone locally* or within a docker container
Build the uberjar with
lein uberjar
Note: the uberjar process will take around 3-5 minutes to complete
To run the uberjar locally you first must set your environment variables
If you are on Windows run;
.\dev\Invoke-CmdScript.ps1 .\dev\envVars.cmd
After the script executes, run Get-ChildItem Env: to verify environment variables are set, then run
java -jar target/uberjar/chocolate.jar
which will run the application from the uberjar. go to http://localhost:3000
If you are on Mac OS / Linux run;
./dev/run.uberjar.sh
which will set env variables and run the uberjar. go to http://locahost:3000
Recommended way to run is with docker-compose (below).
The chocolate docker container by itself is not configured to talk to a locally running RabbitMQ instance
First build the chocolate uberjar
lein uberjar
To build the chocolate container run:
docker build -t chocodoc .
To run the container:
docker run -p 3000:3000 chocodoc
The app can also be run using docker compose, which runs the chocolate container and RabbitMQ containers simultaneously.
First make sure you've built the chocolate uberjar: lein uberjar
To build the chocolate image run:
docker build -t chocodoc .
Note: the built image must be
-ttaggedchocodocfor the compose file to find it
Next build the rabbitMQ image with:
docker build -t my-rabbit .\rabbitmq\.
Note: built image must be -t tagged
my-rabbitfor compose to find it
To spin up the docker-compose containers run:
docker-compose up
Note:
ctrl-ca few times will kill the running compose, and to completely clean and remove the containers, rundocker-compose down