We're aiming for a modular design. This to allow us to scale the application as we see fit. Also, it's better from a testing point of view: if we make sure the contracts for the module communication are strictly defined, then we can test modules without having to set up the entire application around it.
Initially the intention was to use events for inter-module communication. But it turns out this is not always desirable. Sometimes you just want to send a plain request and get a reply. This does make it harder to set up a way for the modules to communicate with each other that is more or less generic. We want to avoid having to take into account the kind of communication when modules talk to eachother. Possible ways of communicating are: http requests, websockets, http server-sent-events, Unix domain sockets, JMS events, OCI streaming, internal async channels, etc... Some of these can be categorized under "events", others under "requests". Ideally, we want to abstract away the underlying method of communication, and only have to know whether we want to send a request to which we expect a reply, or just post an event where we don't care who listens in.
We wanted to write our own abstraction using core.async channels. But after working with them for a while, I'm no longer convinced this is the way to go. They are just too cumbersome and difficult to test. Manifold Streams are similar. The advantage is that they seem to allow more control, and they function more as a layer on top of other async platforms. But it's still only meant for async processing. In MonkeyCI we have various modules that communicate with each other in different ways depending on the situation. Sometimes this is purely in-process, or it could be over a UDS for parent-child processes. Or just over HTTP, for instance when the UI talks to the backend.
Http is still the most portable solution for the outside world. But due to the purely request/reply nature, it is somewhat harder to use it for events, especially when you're on the receiving end. I tried using SSE, but it turns out support in the existing tools is not that good. Websockets are a possible alternative, but they are more complicated.
ZeroMQ is a possible alternative. It is set up as a universal messaging library, with different "types" of sockets. These "sockets" are also an abstraction over various kinds of communication. These can be TCP sockets, or in-process channels. The advantage is that ZeroMQ already provides an abstraction over the lower layers, and it also includes a bunch of functionality to improve stability (like auto-reconnection). The different types can be used for different scenarios, like pub/sub, streaming, or request/reply. This makes it a good candidate to use in MonkeyCI to set up inter-modular communication.
A drawback is that it uses a custom protocol, so we can't use many of the existing tools for load balancing, security, etc... that are available for HTTP. So it's important that we only use it for internal communication, and not when talking to the outside world.
The various modules in the application are these:
flowchart TD
UI --> |req| API
UI --> |req| LOG[Logging]
LOG --> |push| UI
Web --> |http| UI
CLI --> |http| UI
B[Build Script]:::short --> |req| API
B --> |push| LOG
UI --> |req| BLOB[Blob]
B --> |push| BLOB
API --> |req| D[Data Store]
API <--> |push/pull| E[Events]
API -.-> |creates|B
B --> |push| E
D --> |push| E
JOB[Jobs]:::short --> |req| B
JOB --> |push| LOG
JOB --> |push| BLOB
API -.-> |creates|JOB
BLOB --> |push| E
LOG --> |push| E
D --> |http/sql| DB[(Database)]:::external
BLOB --> |http| BUCKET[(OCI Buckets)]:::external
LOG --> |http| BUCKET[(OCI Buckets)]
classDef external fill:#08f
classDef short fill:#f88
In the chart, the blue items are external apps. Red are short-lived entities. You see it becomes complicated fairly quickly. The idea is to write the code so that it is possible to put most of these things into one process, but also allow modules to be split off (and scaled) into separate processes as needed.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close