In the Reveno, the Repository is an in-memory entity objects holder. It’s used from commands with read-only, and from transaction actions with read-write access. In addition, any repository implementation must be transactional, or, in the case of any failure, the correctness of the model will be completely broken.
There is no single approach on how to implement rollbacks, when it comes to POJO entities stored in-memory in Java. Our main task was to create the most simple API with minimal user interaction, so all rollbacks are automated. At the end we came up with the idea to divide the implementation of repositories on the basis of the objects that will be stored in them – mutable or immutable. Both have its own pros and cons – you should choose the one which works best on your particular case. We will cover everything about them in details below.
Immutable objects repository
By immutability we understand a class, with all its fields final, and any mutation operation on which leads to the new object creation. It’s generally a great way to eliminate complexity, concurrency issues and much more with the price of an additional allocation rate and hence much more garbage. Also, for large and/or complex objects, creating a new copy of it can be very costly and tedious.
Generally speaking, immutability is a great tool when you need programming simplicity and a high throughput, but tolerating more intensive GC, that sometimes might cause Tenured space collections, if your promotion rate is high.
You can find examples of it here.
Mutable objects repository
Mutable objects are ubiquitous in most projects of any scale. It’s very possibly, that you came to use the Reveno with such ones, so, we have to explain how to utilize it best here.
There is no single approach on how to change the state of the mutable object in Java. Respectively, we have two different modes in which it can work. They differ by the ease of development and performance aspect:
- Automatic – it’s the same as we do for immutable repository – no efforts from your side. But internally, it’s much more complicated, since we should preserve object snapshots during transaction execution. You should consider that choice carefully, as it might lead to sufficient performance penalties.
- Compensating Actions – you are defining a set of “rollback actions” to the set of transaction actions. Hence, you can do it as fast and GC-friendly as you like, but with additional development effort. This requires
reveno.config().mutableModelFailover(COMPENSATING_ACTIONS);config to be turned on.
An examples on using this approache can be found here.
Event Sourcing technique allows to restore the state of the system by replaying a set of events. But at some point there might be significant amount of events, and replay can take a very long time, which makes a negative impact on system startup after restart or crash. That’s why we also support snapshots, which are persisted on storage and can be used to restore system state in one pass and just throw on top the remaining events.
For example, you can make new snapshot on every 100k transactions and at system shutdown:
Reveno reveno = new Engine("/tmp/rvn"); reveno.config().snapshotting().every(100_000); reveno.config().snapshotting().atShutdown(true);
Reveno’s built-in snapshotter uses Java default serializer as a primary and Protostuff as a backup. You can also provide your own implementation of RepositorySnapshotter.
When the Reveno runs in a cluster, you can configure data sync to be with snapshots rather than transaction journal files, so new snapshot would be also created on every leadership election process (see our failover space).