Basic Axon Framework sample using vert.x and angular.js
Some people on the Axon Framework mailing list were asking for more basic samples, preferably using something like vert.x. Since I am familiar with Axon as well as vert.x I thought I could help out. Therefore I have created a very basic application based on vert.x using the Axon Framework. The application is all about maintaining a list of TODO items. These items are shared between all connected web browsers. You have to be able to create a new TODO item and you have to be able to mark them as completed.
During my visit of Devoxx I learned about a front-end framework called AngularJS. This is a very nice addition to the vert.x technology stack. Since I always use Bootstrap for my front-end work, the front-end should look familiar to a lot of you. The following images shows the end result of the application. If you want to know how I created the sample, read on.
Overview of the sample
We are creating a ToDo item sample. We want to be able to create ToDo items from a website. The ToDo items have to be stored and other people that connect to the web application also see the same ToDo items. Updates to the ToDO items, mark them as complete, are also pushed to other connected browsers. The following image gives an overview of the design of the sample. If you want to follow along, checkout the vertx-sample project at my github: vertx-samples:axon-todo-list. Check the read me file if you want to run the sample.
The image contains a lot of information. The client, in this case a browser, communicates with the backend using the vertx event bus. Creating a new ToDo item is a message on the bus send to an endpoint. The message is picked up by the handler that listens to that endpoint. The handlers use the Axon gateway to send the command to create a new todo or to mark a todo item as complete. The command is handled by the command handler, that uses the ToDo aggregate to actually create the item and to mark it as completed. Axon uses events to make the state changes. These events are also used by the ToDo event listener. This component sends messages to the vertx event bus to persist the todo item and sends a message to the topic that all clients listen to. Within the AngularJS controller we register a listener for these vert.x events and update the data model. These changes are automatically picked up by the view to change the view.
Sounds like a lot doesn’t it? The next sections breaks up these pieces into smaller parts.
Short introduction in Vert.x
Vert.x is all about creating scalable applications using non-blocking IO. For me Vert.x is interesting due to its modular nature. You can create modules that communicate through messages over the event bus. You could use multiple languages, I stick to groovy and JavaScript. A very interesting part is the extension of the event bus to the client. Vert.x makes use of the Socksjs library. More information is available in another blog post of mine: First steps with Vert.x. In this blog post I stick to a bit of code important for this sample.
The sample consists of 2 custom modules and the mongo persister module. The custom modules are the web module and the todo (axon) module. The following code shows the main Verticle of the application.
def log = container.logger def persisterConfig = ["db_name": "axon_todo_list", "address": "vertx.mongo.persist"] container.with { deployModule('vertx.mongo-persistor-v1.2', persisterConfig, 1) { log.info "App: The mongo-persister module is deployed." } deployModule('web') { log.info "App: The web module is deployed." } deployModule('todo') { log.info "App: The Todo module is deployed." } }
Let’s first check the web mod. The web mod needs to deliver the index.html file, the scripts, css files and of course the javascript libraries. We configure the http server together with a router for routing the url’s and a web sockets server.
def log = container.logger RouteMatcher routeMatcher = new RouteMatcher() routeMatcher.get("/") { req -> req.response.sendFile "static/index.html" } routeMatcher.get("/favicon.ico") { req -> req.response.sendFile "static/img/favicon.ico" } routeMatcher.getWithRegEx("^\\/static\\/.*") { req -> req.response.sendFile(req.path.substring(1)) } DefaultHttpServer server = vertx.createHttpServer() server.requestHandler(routeMatcher.asClosure()) vertx.createSockJSServer(server).bridge(prefix: '/eventbus', [[:]], [[:]]) server.listen(8080) log.info "The http server is started"
The next step is looking at the html code with the AngularJS JavaScript library.
Using AngularJS to show data and send changes
AngularJS is a JavaScript library providing an MVC structure in the browser. I cannot go into to much depth here, but this is what you need to know to understand the sample.
With angularjs you bind data to html elements. Special attributes are added to the html elements that are picked up by the angularjs code. We add the ng-app attribute to the html element and the ng-controller attribute to the body tag.
<!doctype html> <html ng-app="eventBusServices"> <head>…</head> <body ng-controller="TodoListCtrl"> … </body> </html>
Now we have the controller wired and the html is the view. The last part is the model. The model is maintained by the controller and bound to the view using special tags. The following piece of code shows the part for creating the list with TODO’s.
<div ng-repeat="todoItem in todoItems"> <div class="alert" ng-click="markCompleted(todoItem)"> <label class="checkbox inline"> <input type="checkbox" ng-model="todoItem.completed"> <span class="done-{{todoItem.completed}}">{{todoItem.todoText}}</span> </label> </div> </div>
Pay special attention to the ng-repeat, the ng-model and the ng-click attributes. The other important parts are between the {{…}} brackets. This is binding the model to the specific html elements. Now let us have a look at the controller, remember the ng-click with the function markCompleted(todoItem), we will later on refer to this.
Another thing you need to know is that angularjs does dependency injection. So we declare services as modules, angularjs can inject these services into constructors. This is shown in the following piece of code.
angular.module('eventBusServices', []).factory('EventBus', function () { return new vertx.EventBus("http://localhost:8080/eventbus"); }); function TodoListCtrl($scope, EventBus) { $scope.todoItems = []; $scope.connectionStatus = "Waiting"; $scope.connectionStatusClass = "alert-error"; EventBus.onopen = function () { // More on this later on } EventBus.onclose = function () { $scope.connectionStatus = "Not connected"; $scope.connectionStatusClass = "alert-error"; $scope.$digest(); } $scope.addTodo = function () { send(EventBus, "command.todo.create", {todoText: $scope.todoText}); $scope.todoText = ''; }; $scope.markCompleted = function (todoItem) { send(EventBus, "command.todo.markcompleted", {identifier: todoItem.identifier}); } }
In order to limit the amount of code I did not present the onopen function yet. Together with the EventBus.onclose these form the bridge between vert.x and angularjs. Important to notice is that we change the text for the connection status and the class for the div. The other two methods are on the $scope, this is interesting. Did you remember the name of the function is ng-click? You can call methods on $scope immediately. Again you are missing the implementation for the send method. So the following code block shows this function.
function send(eventbus, address, message) { eventbus.send(address, message, function (reply) { var replyMessageDiv = $('#replymessage'); replyMessageDiv.html("<div class=\"alert alert-info\">" + reply.message + "</div>"); replyMessageDiv.fadeIn('fast'); replyMessageDiv.fadeOut(5000); }); }
In this code block you can see the send-reply mechanism of vertx. Not that hard to figure out. The final piece of the puzzle is the EventBus.onopen function. In this function we register the handler that receives all the messages through the vert.x event bus. Finally we send a message to mongo to obtain all the current todo items to show through the browser.
EventBus.onopen = function () { $scope.connectionStatus = "Connected"; $scope.connectionStatusClass = "alert-success"; EventBus.registerHandler("message.all.clients", function (msg, replyTo) { if (msg.name == "TodoCreated") { $scope.todoItems.push({"todoText": msg.todoText, "completed": false, "identifier": msg.identifier}); } else if (msg.name == "TodoCompleted") { var result = $.grep($scope.todoItems, function (e) { return e.identifier == msg.identifier; }); if (result.length == 1) { result[0].completed = true; } } $scope.$digest(); }); var mongoAction = {"action": "find", "collection": "todos", "matcher": {}}; EventBus.send("vertx.mongo.persist", mongoAction, function (reply) { var msg = reply.results; for (var i = 0; i &lt; msg.length; i++) { var todoItem = msg[i]; $scope.todoItems.push({"todoText": todoItem.todoText, "completed": todoItem.completed, "identifier": todoItem.identifier}); } $scope.$digest(); }); $scope.$digest(); };
When we receive a TodoItemCreated event, we use push an item to the array called todoItems in $scope. When a TodoCompleted event is received we try to find the todo item to mark completed. The final bit is creating a query to send to the persistent module. Again we push all the todoItems that we find to the array using $code.todoItems.push.
Wow, that was a lot ground to cover. But by now you should have an idea what AngularJS together with Vert.x can do for you. I forgot to mention one thing, that is the look and feel of the sample. We use Bootstrap from twitter.
Doing Axon
I hope you already know what the Axon Framework is, if not check the homepage and start reading the documentation. Axon promotes a scalable architecture to create high scalable and performant applications. It implements the CQRS (Command Query Responsibility Segragation) principle and provides a number of implementations for an Event Store. The idea is to send commands to change state and receive events when state is changed. If you want to find data, you need to query the query database.
Now let us have a look at the todo module that contains our Axon code. Initializing Axon takes place in the Interface.groovy script. But we need to have Axon available first. Axon comes in a few libraries and it needs a few additional libraries. With vert.x you create a lib folder within the module. In our case we need the following libraries:
- axon-core-2.0-m3
- axon-mongo-2.0-m3
- cglib-nodep-2.2.2
- dom4j-1.6.1
- mongo-java-driver-2.9.1
- slf4j-api-1.6.6
- slf4j-log4j-1.6.6
- xmlpull-1.1.3.1
- xpp3_min-1.1.4c
- xstream-1.4.3
This is the main groovy script that is called by vert.x to start the module.
CommandBus commandBus = new SimpleCommandBus() CommandGateway gateway = new DefaultCommandGateway(commandBus) org.axonframework.eventhandling.EventBus axonEventBus = new SimpleEventBus() def xstream = new XStream() xstream.classLoader = getClass().classLoader def serializer = new DBObjectXStreamSerializer(xstream) MongoTemplate mongoTemplate = new DefaultMongoTemplate(new Mongo(), "axon-todo", "domain", "saga", null, null) MongoEventStore eventStore = new MongoEventStore(serializer, mongoTemplate) def todoRepository = new EventSourcingRepository<Todo>(Todo.class) {} todoRepository.setEventStore(eventStore) todoRepository.setEventBus(axonEventBus) /* Register the command handlers with the command bus */ def handler = new TodoCommandHandler(todoRepository, container) commandBus.subscribe("commands.CreateTodoCommand", handler) commandBus.subscribe("commands.MarkTodoAsCompleteCommand", handler)
First we create the Event bus, the command bus and the command gateway. The next step is to create the event store and the repository. Notice that we create our own XStream instance. This is required, we need to set the class loader to make it work. The final bit here is registering the command handlers with the command bus. The only Axon piece we are missing right now is the listener to create the query database. This is missing because this listener also requires the vert.x event bus to send messages to the clients. The next code block shows the final piece of the configuration.
EventBus eventBus = vertx.eventBus axonEventBus.subscribe(new TodoEventListener(eventBus, container)) eventBus.registerHandler("command.todo.create") { message -> message.reply(["message": "We have received a new ToDo"]) def identifier = new TodoIdentifier() gateway.send(new CreateTodoCommand(identifier, message.body.todoText)) } eventBus.registerHandler("command.todo.markcompleted") { message -> message.reply(["message": "We have received a ToDo that is completed"]) def identifier = new TodoIdentifier(message.body.identifier) gateway.send(new MarkTodoAsCompleteCommand(identifier)) }
The two blocks at the end register handlers with the vert.x event bus. As you can see we send a reply back to the client that did send the original todo item. the next step is to send the command to Axon through the gateway. To follow what happens with the command we need to have a look at the command handler. The command handler implements the CommandHandler interface. The following code block shows the handle method.
Object handle(CommandMessage commandMessage, UnitOfWork unitOfWork) { switch(commandMessage.payloadType) { case CreateTodoCommand.class: CreateTodoCommand command = commandMessage.payload as CreateTodoCommand log.info "The received command is ${command.todoText}" def todo = new Todo(command.identifier, command.todoText) repository.add(todo) return todo case MarkTodoAsCompleteCommand.class: MarkTodoAsCompleteCommand command = commandMessage.payload as MarkTodoAsCompleteCommand def load = repository.load(command.identifier) load.markAsComplete() return load default: log.info "Received a command of type we cannot handle: ${commandMessage.payloadType}" return null } }
This code should explain itself. The Todo aggregate is to easy to show. The last piece of code I want to show is part of the Axon event listener. This code shows the update query for the mongo database when a mark completed event is received.
void handle(EventMessage eventMessage) { def identifier = eventMessage.getIdentifier() def eventType = eventMessage.payloadType logger.debug "Received an event with id ${identifier} and type ${eventType}" switch (eventType) { case TodoCreatedEvent.class: handleTodoCreatedEvent(eventMessage) break case TodoMarkedAsCompleteEvent.class: handleTodoMarkedAsCompletedEvent(eventMessage) break default: logger.info "Cannot handle this event" } } private void handleTodoMarkedAsCompletedEvent(EventMessage eventMessage) { logger.info "Received a TodoMarkedAsCompleteEvent" def event = eventMessage.payload as TodoMarkedAsCompleteEvent def mongoAction = [ "action": "update", "collection": "todos", "criteria": ["identifier": event.identifier.asString()], "objNew": ["\$set": ["completed": true]], "upsert": true, "multi": false] eventBus.send("vertx.mongo.persist", mongoAction) { eventBus.publish("message.all.clients", ["name": "TodoCompleted", "identifier": event.identifier.asString()]) } }
Notice that after persisting the the todo item that is now completed we send a message to all clients with the todo item that is now completed.
Concluding
Wow that was a long one. I hope you managed to follow what I was doing. Of course the best thing is to get the sample from github and run the sample, look at the code and maybe change the code. If you need help, drop me a line. The sample also contains a read me file that explains how to run the sample. The steps are basic, install vert.x, install MongoDB, run the vert.x app.