Kiwi.Prevalence is a .NET System Prevalence Layer. As such, it maintains an application model in memory, providing gated access to ensure consistency and journalling to provide persistence and durability.
Quite simple, use this without restrictions, but don't blame us. For a formal specification, checkout the MIT licence.
- requires .NET 4.
- best downloaded from nuget
The application model is mainatined by a Repository<TModel> which owns an instance of your preferred model, TModel.
To maintain model consistency over time, its not allowed to interact directly with the model. Instead, the methods Repository<TModel>::Query(λ) and Repository<TModel>::Execute(command) must be used.
Repository<TModel>::Qyery(λ) ensures that your λ (on the form Func<TModel,TResult>) can access the model in assumed read-only mode (a read lock is taken).
Modyfying operations are taken out by Repository<TModel>::Query(command) where the command must implement ICommand<TModel,TResult> and is executed in exclusive-write mode (a write lock is taken).
To further protect the model, results from either Query or Execute is marshalled, which in this contect means that a deep copy of the result is taken. This guarantess that the results are detached from the model, preventing nasty concurrency bugs.
A snapshot is the model serialized to disk. A snapshot in essence captures the model state at a given point in time.
All commands must be Json-serializable and are captured in a journal file. Whenever a repository is restored (typically during application startup), the journal is replayed on the current snapshot to catch up with latest changes in the model.
The model and all commands must be Json-serializable and Json-deserializable.
- The JSON format somewhat limits the expressiveness of your model. In particular, it may not contain cycles.
- The JSON format is chosen since its so forgiving and most of all, human and machine readable.
Actual serialization is carried out by Kiwi.Json.
In few words - JSON serialized to disk files. Expect to find some or all of these files
- <repo name>.journal - contains JSON-serialized commands
- <repo name>.snapshot - contains a JSON-serialized model
- <repo name>.journal.<revision> - backup of journal taken at a specific journal revision
- <repo name>.snapshot.<revision> - backup of snapshot taken at a specific journal revision
The Repository class takes most strategic decisions from a IRepositoryConfiguration. A custom implementation (or instantiation of the default) allows customization of
- Command serialization (the format used in the journal)
- Marshalling
- Synchronization
Each of the above strategies are quite small abstractions and should be fairly easy to implement if a non-default behaviour is wanted.
For applications where the model isn't at risk of being compromised by pusblishing results from Query or Execute, an additional parameter QueryOptions.NoMarshall can be supplied that effectively skips the marshalling step all together.
Since marshalling doesn't affect persistence in the journal or snapshot in any way, it's possible to setup a repository with a custom marshaller (say one based on BinaryFormatter, allowing cycles in the data).
The default synchronization uses System.Threading.ReaderWriterLock, but other schemes can be specified by passing a custom ISynchronize instance to the repository.
- Fork and patch, fork and patch...
- or, use another library. Personally, I can recommend #livedb.
- or, if you rather roll your own, use our code as inspiration or
- or, start from this minimal implementation provided by by one of the early driving forces, Klaus Wuestefeld.