Jun 14

Make business logic simple using event sourcing and a lot of RAM

Recently I’ve been playing with asynchronous HTTP processing and event sourcing. The aim was to create a fast and elegant solution for multiplayer game engine.

Asynchronous servers make it possible to handle a lot of client connections concurrently. It is achieved by handling more than one connection on one thread as opposed to thread per connection approach. The most famous example of such server is Node.js with its single thread processing. Exactly the same style of dealing with connections is possible in Java and Clojure with additional power of using extra threads for other tasks (i.e. other than HTTP I/O). For implementation, I used http-kit that is not using Netty (that’s refreshing!) as an underlying library.

Event sourcing is a nice model for asynchronous processing. It allows to separate the business logic from I/O and makes it easy to put logic in its own thread. As pointed out by guys from LMAX, it also means that mutable domain data structures are less harmful or even acceptable. This is not the case in other threaded application architectures that involve sharing data between threads.

What is event sourcing

It is an architecture in which you feed your business logic with events that may affect the state of application. Then, other events may be – but not always are – generated as a result. Events are objects or data structures that contain information about intents (client command) or results (business logic output).

Here is an example of an event extracted from game client request that is a command to move a player:

{:cmd :move :dx 10 :dy 25}

Such an event is passed to business logic function, it is then validated, and if player can be moved, the state of the game is altered and a result event is generated:

{:event :player-moved :player-id 1234 :dx 10 :dy 25}

To make everything efficient and clean no database calls are made by business logic. All the computation is done on the data that resides in memory. If a few gigs of RAM are needed then so be it, memory is cheap. To see it in a bigger picture take a look at the following diagram of a server design and follow it with a description of steps underneath.

event sourcing diagram

  1. A request is made by a client in some kind of format (JSON, XML, EDN) and handled by asynchronous handler
  2. In parallel:
    a) request is passed to the journaler that appends it to a file
    b) request is passed to the unmarshaller that creates an event data structure/object from request body
  3. Event is passed to the business logic where computation takes place (e.g. computing movements of players in MMO game, doing FOREX trading)
  4. [OPTIONAL] Resulting events generated by the business logic are passed to the marshaller where response is formed from events

Please note, that asynchronous connection handler, unmarshaller, marshaller, journaler, and business logic can all work on separate threads and not get into each other’s way. All the threads do a bit of work and pass data forward. Business logic resides in only one thread and all events are processed one by one in order! It is especially important that I/O operations are done on different threads than business logic. No database is really needed since all events that affect the state are saved by journaler to replay them in case application crashes. The design enables full use of multicore processor and simplifies dealing with domain logic.

Further considerations

With event sourcing approach there is a possibility or even a need to implement replays of events for analysis, testing purposes, and recovery from crash (state snaphots are also an option).

In order to fully understand the topic, it is definitely worth to read what other people have to say about event sourcing. Martin Fowler has nice articles [1] [2] on the topic and there’s also an interesting talk from LMAX developers worth checking out. The topic is pretty exciting because once the framework is laid out, all the programmer needs to care about is the colorful and joyful domain programming (finance engine, game engine, all those unicorns and rainbows…).

Business logic testing is exceptionally painless – there’s no I/O to mock and computation are performed on single threaded and immutable data (TDDers wet dream).

Communication with external services might seem a bit more complicated as it requires creating another worker thread that serves as a gateway to the service. Worker listens for appropriate requests from business logic (in a form of resulting events) and does the I/O calls to the service. Once the service responds, a new event needs to be produced and put in the business logic queue for further processing.

Event sourcing is a tempting pattern and especially important for applications that strive for high performance. In other cases it may be perceived as a way to simplify the development and testing process. For Clojure developers it’s another way to get rid of side effects from the codebase. For me it is a beautiful way to organise the complexity into units that are easy to work with – a step closer to the holy grail of independent encapsulated modules communicating with each other.

In the next post I present a sample implementation of event sourcing in Clojure.

One thought on “Make business logic simple using event sourcing and a lot of RAM

  1. scape

    Interesting post! I’m especially interested in doing something similar to this for a game server, I’ll need to read up more on the business logic concept. I’ve recently been working on a thread-communication library (kuroshio) to help me sort out work being handled in different threads and create dependencies through blocking and non-blocking interactions. Please give it a looksee, maybe you can provide some insight/criticism :)


Leave a Reply

Your email address will not be published.