hindsight-postgresql-store
Copyright(c) 2024
LicenseBSD3
Maintainermaintainer@example.com
Stabilityexperimental
Safe HaskellNone
LanguageGHC2021

Hindsight.Store.PostgreSQL

Description

Overview

PostgreSQL-backed event store providing full ACID guarantees, scalability, and production-ready durability. Supports both synchronous and asynchronous projections.

Recommended for production systems, distributed deployments, and applications requiring strong consistency guarantees.

Quick Start

import Hindsight.Store.PostgreSQL

main :: IO ()
main = do
  -- Create store
  let connStr = "postgresql:/localhostevents"
  store <- newSQLStore connStr

  -- Initialize schema (run once)
  Pool.use (getPool store) createSQLSchema

  -- Insert events (see Hindsight.Store for details)
  streamId <- StreamId <$> UUID.nextRandom
  let event = mkEvent MyEvent myData
  result <- insertEvents store Nothing $ singleEvent streamId NoStream event

  -- Subscribe to events
  handle <- subscribe store matcher (EventSelector AllStreams FromBeginning)
  -- ... process events ...

  -- Cleanup when done
  shutdownSQLStore store

Configuration

Connection via PostgreSQL connection string. Pool size defaults to 300 connections.

For custom configuration, use newSQLStoreWithProjections to register synchronous projections that execute within event insertion transactions.

Use Cases

When to use PostgreSQL store:

  • Production systems requiring durability and ACID guarantees
  • Distributed multi-node deployments
  • High event throughput scenarios
  • Large event volumes (millions+ events)
  • Applications needing advanced SQL querying
  • Systems requiring synchronous projections (strong consistency)

When NOT to use PostgreSQL store:

  • Simple testing (use Memory store)
  • Single-node apps without database (use Filesystem store)
  • Environments where PostgreSQL can't be deployed
  • Prototypes and development (unless testing PostgreSQL-specific features)

Trade-offs

Advantages:

  • Full ACID guarantees (PostgreSQL transactions)
  • Scales to millions of events
  • Distributed multi-node support
  • Advanced querying via SQL
  • Synchronous projections (consistency within single transaction)
  • LISTEN/NOTIFY for efficient subscriptions
  • Battle-tested PostgreSQL reliability

Limitations:

  • Requires PostgreSQL database
  • More complex deployment than Memory/Filesystem
  • Higher resource requirements
  • Network latency for remote databases

Sync vs Async Projections

Synchronous Projections: Execute within event insertion transaction. Changes to events and projection state are atomic. Use registerSyncProjection and newSQLStoreWithProjections. Guarantees strong consistency but adds latency to writes.

Asynchronous Projections: Run separately using runProjection from hindsight-postgresql-projections. Process events after they're committed. Better write performance but eventual consistency.

Implementation

Events stored in events table with compound key (transaction_no, seq_no) for total ordering. Stream metadata in stream_heads table. Projection state in projections table. LISTEN/NOTIFY used for efficient subscription updates.

See Schema for complete schema DDL.

Synopsis

Documentation

newSQLStore :: ByteString -> IO SQLStoreHandle Source #

Create a PostgreSQL event store with default configuration.

Uses a connection pool size of 10 and an empty sync projection registry.

newSQLStoreWithProjections :: ByteString -> SyncProjectionRegistry -> IO SQLStoreHandle Source #

Create a PostgreSQL event store with custom sync projections.

Sync projections run within the same transaction as event insertion, providing guaranteed consistency between events and their projections.

On startup, this function will: 1. Register all sync projections in the database 2. Catch up any projections that are behind 3. Fail with an exception if catch-up fails

shutdownSQLStore :: SQLStoreHandle -> IO () Source #

Shutdown the SQL store gracefully

This function should be called before releasing the connection pool. It shuts down the event dispatcher used by all subscriptions.

data SQLStoreHandle Source #

Handle for interacting with PostgreSQL event store.

getPool :: SQLStoreHandle -> Pool Source #

Get the connection pool from a store handle.

Useful for running custom queries or projections that need direct database access.

getConnectionString :: SQLStoreHandle -> ByteString Source #

Get the connection string from a store handle.

Useful for creating additional store instances with the same configuration.

data SQLStore Source #

Phantom type representing the PostgreSQL storage backend.

Instances

Instances details
EventStore SQLStore Source #

PostgreSQL EventStore instance.

This instance automatically uses the sync projections configured in the handle. All insertEvents calls will execute the sync projections that were registered when the store was created.

Instance details

Defined in Hindsight.Store.PostgreSQL

Associated Types

type StoreConstraints SQLStore m 
Instance details

Defined in Hindsight.Store.PostgreSQL

type BackendHandle SQLStore Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

type Cursor SQLStore Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

type StoreConstraints SQLStore m Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL

data SQLCursor Source #

Position cursor for PostgreSQL event store.

Uses a compound key of (transactionNo, sequenceNo) to provide total ordering across all events. The Ord instance uses lexicographical ordering, so events are ordered first by transaction number, then by sequence within transaction.

Constructors

SQLCursor 

Fields

Instances

Instances details
FromJSON SQLCursor Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

ToJSON SQLCursor Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

Generic SQLCursor Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

Associated Types

type Rep SQLCursor 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

type Rep SQLCursor = D1 ('MetaData "SQLCursor" "Hindsight.Store.PostgreSQL.Core.Types" "hindsight-postgresql-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "SQLCursor" 'PrefixI 'True) (S1 ('MetaSel ('Just "transactionNo") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Int64) :*: S1 ('MetaSel ('Just "sequenceNo") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Int32)))
Show SQLCursor Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

Eq SQLCursor Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

Ord SQLCursor Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

type Rep SQLCursor Source # 
Instance details

Defined in Hindsight.Store.PostgreSQL.Core.Types

type Rep SQLCursor = D1 ('MetaData "SQLCursor" "Hindsight.Store.PostgreSQL.Core.Types" "hindsight-postgresql-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "SQLCursor" 'PrefixI 'True) (S1 ('MetaSel ('Just "transactionNo") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Int64) :*: S1 ('MetaSel ('Just "sequenceNo") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Int32)))

data SyncProjectionRegistry Source #

Registry of synchronous projections

insertEventsWithSyncProjections Source #

Arguments

:: forall (t :: Type -> Type) m. (Traversable t, MonadUnliftIO m) 
=> SQLStoreHandle

Store handle with connection pool

-> SyncProjectionRegistry

Registry of sync projections to execute

-> Maybe CorrelationId

Optional correlation ID for event tracking

-> Transaction t SQLStore

Transaction containing events to insert

-> m (InsertionResult SQLStore)

Result indicating success or failure

Insert events with synchronous projections using a custom registry.

Events and projections are committed atomically in a single PostgreSQL transaction. If any projection fails, the entire transaction is rolled back, ensuring consistency.

This function allows you to override the sync projection registry from the handle. In most cases, you should use the generic insertEvents function instead, which automatically uses the registry from the handle.

This function is useful for testing or scenarios where you need to use a different registry than the one configured in the handle.

createSQLSchema :: Session () Source #

Re-export createSchema with a more specific name to avoid conflicts

Orphan instances

EventStore SQLStore Source #

PostgreSQL EventStore instance.

This instance automatically uses the sync projections configured in the handle. All insertEvents calls will execute the sync projections that were registered when the store was created.

Instance details

Associated Types

type StoreConstraints SQLStore m 
Instance details

Defined in Hindsight.Store.PostgreSQL