Moo is a Java based chat service written as test bed for various concepts. It features a modular design, which separates the concepts of a UI, client and a server. As a result, a UI can be developed based on a common chat API, without compile time dependency on neither client or a server. Depending on which server is running, a UI only needs a compatible runtime client dependency.
My goal in writing moo
was to achieve practical, working example of a
modular chat service with at least two client/server implementations that
can be simply swapped without affecting UI. Similarly, I wanted to come up with a
practical and intuitive API that makes building any user interface straightforward.
As a result of my initial goals, moo
is not feature rich nor is it optimized for
performance. It only provides the very basic chat functionality.
If you're looking for a working example of a chat application, web sockets or JMS, complete test suite powered by Mockito, high code coverage, then you may find moo a happy place to poke around.
- Java
- Web Sockets
- (J)ava (M)essaging (S)ervice (ActiveMQ)
- Spring Boot
- Mockito
- Distributed Architecture
- Server JMX reporting
- Console UI
- Server generated nick names
- Single public chat hall
By default moo
is bound to a web socket client. It expects a running web
socket server.
Moo running on web sockets client/server:
Moo running on JMS client/server with ActiveMQ broker (not shown):
In both screenshots we have a 4 way tmux session. In the left upper corner we're running moo server. In the right upper we have a moo reader which displays chat activity. In both bottom corners, we're running two instances of a writer, simulating user chat experience. In a typical use case, end user would run one instance of a reader and writer only.
There are some subtle but interesting UI behavioral differences depending on
which client/server implementation is used. For example, with web sockets when
server is aborted messages generated by the writer are lost. This is not the
case with a JMS server, as messages are retained by the broker and delivered
when server is started again. Another difference is that websocket server
tracks activity of connected clients while JMS server does not. As a result,
JMS clients can be connected to the broker indefinitely, while websocket clients
will be aborted by the server is evictionTimeout
defined in websocket
server application.properties
is exceeded.
Make a local build:
cd moo/
mvn install
Starting server:
cd moo/moo-server-IMPL/
mvn spring-boot:run
IMPL = one of the available implementations: moo-server-socket
or
moo-server-jms
. Details about each server are available in the server
project readme.
Depending on which server is running, a compatible client runtime dependency
of a UI app is required. For example, for text based shell UI, a socket compatible
client would be enabled by commented out the following in moo-ui-shell/moo-ui-shell-reader/pom.xml
and moo-ui-shell/moo-ui-shell-writer/pom.xml
:
<!--
only one moo-client implementation can be enabled otherwise spring
IOC will not resolve injection points due to multiple implementations
-->
<dependency>
<groupId>pl.zimowski</groupId>
<artifactId>moo-client-socket</artifactId>
<scope>runtime</scope>
</dependency>
<!--
<dependency>
<groupId>pl.zimowski</groupId>
<artifactId>moo-client-jms</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<scope>runtime</scope>
</dependency>
-->
Starting console UI requires two terminal sessions. It's probably easiest to
split terminal session into two using something like screen
or tmux
.
First, start UI reader which will allow to view public chats:
cd moo/moo-ui-shell/moo-ui-shell-reader
mvn spring-boot:run
Reader running on websocket client will attempt to connect to server at
localhost
on port 8000
. This can be re-configured via
application.properties
, or more conveniently, by overriding these spring
managed props as command line args. Client aborts immediately if server
connection cannot be established.
To be able to actually send chat messages, it is necessary to have a running instance of a writer:
cd moo/moo-ui-shell/moo-ui-shell-writer
mvn spring-boot:run
Because writer uses the same client as reader, same customization strategy applies. If server is based on websockets, then websocket client should be used. If server is based on JMS, then JMS client should be used, etc.
As a convenience, the socket server and a compatible reader are dockerized
and can be run with a single command from the root (moo/
):
docker-compose up
Docker compose will bring up two images and each can be interacted with:
docker ps
to obtain a container id, then:
docker exec -it <container_id> bash
To produce chat messages we simply start a writer with a standard maven command:
cd moo/moo-ui-shell/moo-ui-shell-writer/
mvn clean spring-boot:run
By default the output from the server and reader will appear where docker-compose up
was invoked, so output from server and reader is mixed up and may be difficult to
parse to a naked eye. It can be separated by container id using docker logs, though:
docker logs <container_id> -f
Playing around with Coveralls Github plugin, found it interesting but imperfect. The readme badge is really nice, but it is not reliably updated when coverage changes and there are a lot of complaints about it posted on the web. For me, sometimes it updates instantly, other times takes 24 hours. Don't trust the badge. The sure way to verify code coverage is to invoke suite locally:
cd moo/
mvn clean verify
Then check jacoco
reports:
cd moo-reports/target/site/jacoco-aggregate/
and open index.html
in your favorite browser.
As moo is a modular maven project, its version is referenced in several places. The easiest way to modify release version is to use the maven plugin. For example, to bump up the version execute the following from project root:
mvn versions:set -DnewVersion=2.2.0-SNAPSHOT
Maven will use version declaration to determine version being replaced and replace it (and all references to it) with the specified new version. It will create temporary backup files to give ability to reverse the change. If upon reviewing changed files everything looks good we tell maven to permanently write our changes with:
mvn versions:commit
In case we do not like version changes performed by maven, we can reverse these with:
mvn versions:revert