# persistent-stm - STM transactions involving persistent storage
[](https://github.com/cdsmith/persistent-stm/actions/workflows/ci.yml)
[](https://hackage.haskell.org/package/persistent-stm)
Haskell's `STM` monad implements composable transactions on in-memory state,
offering atomicity, isolation, and consistency. However, they lack persistence,
so changes are not durable at all. This package adds a limited form of
persistence to the existing STM monad, allowing for transactions that include
writing values durably to external storage.
The persistence is limited in the following senses:
* First, it is only suitable for use by a single process at a time. It is not
possible to access the storage from multiple processes at the same time,
*even* *if* some of those processes are merely readers.
* While the view of data in memory is always consistent, the consistency of data
on disk depends on the `Persistence` implementation, which you choose. The
included implementation, `filePersistence`, does *not* guarantee that data
will be in a consistent or readable state if the process is suddenly
terminated with a power outage, system crash, etc. This can be fixed by using
a `Persistence` implementation built on a transactional storage layer such as
a database.
The persistence essentially works as a key-value store. The key is a `String`,
and the value can be of any type that implements `DBStorable`. The `DBStorable`
class works like `Binary` or other serialization classes, except that it's
designed to have access to the `DB` so that it can contain other `DBRef`s. This
way, at runtime, you can maintain complex data structures that point directly to
each other, but are persisted via their keys.
# Quick Start
A simple example of using `persistent-stm` follows:
```haskell
import PersistentSTM.DB
main :: IO ()
main = do
persistence <- filePersistence "./my-data"
withDB persistence $ \db -> do
n <- atomically $ do
ref <- getDBRef db "my-key"
readDBRef ref >>= \case
Nothing -> do
writeDBRef ref 1
return 1
Just n -> do
writeDBRef ref (n + 1)
return (n + 1)
putStrLn $ "Number of times program was run: " ++ show n
```
Here, `filePersistence` creates a `Persistence` implementation that stores data
in a directory called `./my-data` on disk. The `withDB` function brackets the
portion of code that uses the directory for storage. During the execution of
`withDB`, one can use `db` to read and write persistent values inside of STM
transactions. That is done using `getDBRef`, `readDBRef`, `writeDBRef`, and
`deleteDBRef`.
# FAQ
## Does this work reliably?
I'm publishing this now to get more feedback, but I am confident that within the
limitations described above, this is a correct implementation. Until there is a
broader community consensus, I'll still label this experimental.
## How does this compare with the TCache package?
The implementation here was inspired by TCache, and involves some similar ideas.
I was motivated to implement this package because of several details in which
use of TCache was hard to justify. These include:
* Playing too fast and loose with unsafe operations for my taste.
* Far too much use of global state and overlapping instances.
* Many more possible states making it hard to reason about the correctness of
the implementation.
* Failure to build with newer GHC versions.
All things put together, I reached the conclusion that I could trust a new
implementation more than TCache.
Note that TCache has more features that this package. I don't intend to
implement features like triggers, indexes, and so on, all of which can be
implemented on top of the basic functionality in this package if desired.