At the heart of the Reveno framework two major patterns are laid – Command Query Responsibility Segregation (CQRS) and Event Sourcing. CQRS completely decouples command and query sides, each having its own object model. This opens up the possibility to significantly improve overall system performance, tuning each part independently. With Event Sourcing, we can achieve high level of durability with fixed cost, making latency low and predictable, keeping the whole domain model in-memory.
Our main focus was not to just provide yet another CQRS+ES framework with everything generalized as building blocks and dozens of instructions. Contrary, it’s a cohesive transaction processing framework, with perfectly fitted components. With things like Zero-Copy, off-heap storages and GC-friendliness and etc we made it exetremely fast (microseconds matter latency and millions of transactions per second).
Next picture shows how the Reveno works under the hood:
In the Reveno, Domain Model is called as Transactional Model. It’s in-memory and is available from Repository instance.
The repository has two different implementations – based on mutable and immutable objects. Each has its own pros and cons, about which you can read on a dedicated page.
In order to start performing transactions, you should first define command and transaction action objects, as well as appropriate handlers for them. No restrictions have been made on their declaration – simple POJOs are fine.
Command is responsible for dispatching transaction actions, which are in turn model state mutators. Command handler in its turn should contain all major business and validation logic, and possibly return some value. Transaction action handler should perform changes to the repository and send events about it, if needed. This is what together forms the whole transaction. You can also send a batch of commands, which will be executed as an atomic transaction as well.
After successful execution, all transaction actions are journaled to the underlying storage. They can be replayed after engine restart or server failure.
Sometimes you might get in situation where your command handler is doing nothing but dispatching transaction actions or you need to define command objects and it’s handlers dynamically.
That’s where the Reveno DSL comes best. It allows you to declare complete transaction andler, which will combine command and transaction action, with optional validation. Command and transaction action objects will be assembled for you under the hood, hence there is no need for manual declarations. It has a fluent and very handy API, that’s why most of our examples use it.
Currently DSL isn’t a good choice for usage in environments with a very strong requirements for low-latency or GC allocation rate. For this cases it’s better to use standard API.
Events is another important part of the Reveno engine.
Events are dispatched asynchronously from the transaction action handler, but are executed only when the whole transaction completes successfully. If that’s the case, engine dispatches all that events to appropriate handlers, with guarantee that all subsequent changes are visible to event handlers.
The Reveno engine also guarantees an ordering in which events are dispatched to handlers. But it also supports an async events handlers, which are executed from separate thread pool. In that case, there is no guarantee on ordering.
When all handlers executes successfully, they are persisted to the underlying storage. Unless, they will be executed again when system system restart happens .
One of the key requirements to every transaction processing system is a high level of durability. Moreover, it should be observed maximum balance between system reliability and performance. Otherwise, these guarantees are losing their appeal compared with losses in the speed of the system. Understanding that there is no “silver bullet” solutions here, we had tried to give a rich set of durability configurations. See our Configuration and Journaling pages to get most details.
There are three most important components of Storage infrastructure is built from:
- Journal – append-only store of versioned transaction actions and events commits.
- Snapshot – store, with serialized whole Transactional Model at some point of a time.
- Volume – preallocated empty container, ready to be used as a journal for the best performance. Preallocated files allow achieving low and predictable latency.
Reveno comes with file system storage implementation, which in a close pair with Zero-Copy approach provides an excellent performance
For additional failover capabilities the Reveno can be started in a cluster with Master-Slave architecture. With it all commands are routed from master to slave nodes and executed If master node fails, leadership election is initiated and newly elected master node can continue operating from the point where the previous one had stopped.
There is a large amount of different options here, so you can choose the best for your case. Currently we support two different failover protocols: unicast and multicast. Unicast is the default option, which is widely supported across all platforms and networks. Multicast is based on IP multicast network capabilities, which is reliable in Reveno and scales better, but sometimes much harder to correctly configure on OS level.