Leanpub Header

Skip to main content

Event Sourcing in Scala

Scala 3, ZIO, PostgreSQL and the log beyond the database

CRUD erases the story your system lived through; event sourcing keeps it. Build an account aggregate in Scala 3 and ZIO: past-tense events, a pure applyEvent fold, decide for invariants and idempotency, then wire an append-only PostgreSQL journal, transactions, and a balance projection—and trace one account from HTTP command to read model. When the log must leave the database, follow the same events through transactional outbox, Kafka, and at-least-once consumers without pretending you have magic consistency. For architects and implementers who want decision-grade ES + CQRS, not a toy demo.

Minimum price

$11.99

$19.99

You pay

$19.99

Author earns

$15.99
$
You can also buy this book with 1 book credit. Get book credits with a Reader Membership or an Organization Membership for your team.
PDF
About

About

About the Book

Event Sourcing turns your domain into an append-only log of immutable facts and derives current state by replay. CQRS separates the write model (commands → events → store) from read models built by projections. This book is an advanced, decision-oriented guide to ES and CQRS in Scala 3 with ZIO, anchored in one evolving reference codebase.

The narrative follows how the companion repository grows: Parts I–II tie every design to runnable Scala; Part III walks src/main in reading order—domain, application layer, event store algebra, PostgreSQL append-only schema, JDBC integration, transactions, and projection workers—so you are not dependent on a single code dump at the end. You model events as sealed families, state as a pure fold over history, and commands as a decide function that enforces invariants and can emit multiple events. The running example is an account aggregate with explicit phases, illegal transitions, and tests at the pure core and store boundaries.

On the infrastructure side you see expected-version / optimistic concurrency, serializable-style transaction choices, idempotency keys, retries, and a production checklist. Read models are ordered folds over committed events, with idempotent UPSERT patterns and discussion of same-DB vs separate databases, read-your-writes, and failure handling. Later chapters cover snapshots, replay cost, schema evolution and upcasting, and a focused testing strategy (pure folds, store tests, Postgres integration, projection idempotency, golden streams).

A second arc explains why you might replicate the log: PostgreSQL stays the system of record, while transactional outbox and Kafka (with ZStream consumers) support integration and scale—without reimplementing the whole domain twice. Closing material addresses operations: retention and compliance, rebuilds, monitoring, DR, runbooks, and an honest view of ES cost. An appendix compares EventStoreDB and similar stores to a Postgres-first approach; others provide a code index, ADR template for adoption, bibliography, and a file-to-chapter map.

Audience: Practitioners who already know Scala, are comfortable with SQL and transactions, and can follow ZIO (effects, typed errors, ZLayer, ZStream where streaming appears). The book states clearly what it is not: a full DDD textbook, a Kafka ops manual, or EventStoreDB product training—while still connecting vocabulary (aggregate, command, invariant) to working code.

Bundle

Bundles that include this book

Author

About the Author

Piotr Pruchniewicz

I am a Scala developer by profession and a passionate advocate for Scala, Scala 3, functional programming, and concurrent programming. I am an enthusiast of ZIO, Cats Effect, and AI tools, constantly exploring how these technologies can work together to create robust and scalable solutions.

I consider myself a perpetual learner with an insatiable appetite for knowledge and passion. My curiosity drives me to continuously deepen my understanding of functional programming paradigms, effect systems, and modern software architecture. This book represents not just a compilation of knowledge, but also a journey of discovery and sharing the insights I have gained through years of working with Scala and its rich ecosystem.

Through SCALAPEDIA, I aim to share this passion and knowledge with the Scala community, helping both newcomers and experienced developers discover the full potential of functional programming in Scala.

Contents

Table of Contents

  1. Chapter 1 · Scope, audience, and how to read this book
    1. 1.1 Scope statement (frozen architecture choices)
      1. Primary stack — the spine of the book
      2. Second act — integration and scale (one coherent story, not a second full store)
      3. Commercial / specialised event databases — comparison, not parallel implementation
    2. 1.2 What you should already know
    3. 1.3 What this book is not
    4. 1.4 Repository vs manuscript (honest map)
    5. 1.5 How the codebase maps to chapters
    6. 1.6 How to read the book for maximum clarity
    7. 1.7 Progressive listings (the whole reference slice in the narrative)
    8. 1.8 Depth by part (what is fully implemented vs pattern-only)
  2. Chapter 2 · Scala 3 for event-sourced domains
    1. 2.1 Events as sealed families
    2. 2.2 Opaque types for correlation and identity
    3. 2.3 Serialization boundary
    4. 2.4 Chapter roadmap
    5. 2.5 Common modeling mistakes (avoid these)
    6. 2.6 Mini pattern: closed event enum
    7. 2.7 Packages and dependency direction (DDD-friendly)
    8. 2.8 Money and validation at the boundary
  3. Chapter 3 · ZIO: effects, errors, and layers for ES
    1. 3.1 Where ZIO sits in the architecture
    2. 3.2 Typed errors (what we actually use)
    3. 3.3 ZLayer in tests (concrete)
    4. 3.4 ZStream for projections and Kafka
    5. 3.5 Testing (aligned with Chapter 15)
    6. 3.6 Anti-pattern: ZIO inside applyEvent
  4. Chapter 4 · The problem: CRUD, lost history, and distributed truth
    1. 4.1 What CRUD optimises for
    2. 4.2 What CRUD loses
    3. 4.3 Event Sourcing in one sentence
    4. 4.4 When ES is justified
    5. 4.5 Relationship to CQRS
    6. 4.6 Same user story twice: account balance
    7. 4.7 When not to default to ES
  5. Chapter 5 · Events as immutable facts
    1. 5.1 Past tense and business meaning
    2. 5.2 Granularity: fat vs thin events
    3. 5.3 Cross-aggregate facts
    4. 5.4 Metadata
    5. 5.5 Running example: account aggregate
    6. 5.6 Why commands appear in the same chapter as events
    7. 5.7 Tests
  6. Chapter 6 · State as a fold
    1. 6.1 applyEvent must be pure
    2. 6.2 Phase model: NotExists, Active, Closed
    3. 6.3 applyEvent and rebuild
    4. 6.4 Illegal transitions
    5. 6.5 Performance note
    6. 6.6 Testing fold behaviour
  7. Chapter 7 · Commands, decisions, and invariants
    1. 7.1 Pure decision core
    2. 7.2 Business errors
    3. 7.3 decide implementation (same file as the fold)
    4. 7.4 Validation vs invariants
    5. 7.5 Idempotency
    6. 7.6 Multi-event outcomes
    7. 7.7 Tests
  8. Chapter 8 · Aggregates and consistency boundaries
    1. 8.1 One stream per aggregate instance
    2. 8.2 Small aggregates
    3. 8.3 Aggregate service (application layer)
    4. 8.4 Cross-stream rules
    5. 8.5 Read side
    6. 8.6 Reference implementation: application layer (full files)
      1. 8.6.1 AggregateError.scala
      2. 8.6.2 AccountAggregate.scala
      3. 8.6.3 PostgresAccountApplication.scala
  9. Chapter 9 · Event store algebra
    1. 9.1 Minimal operations
    2. 9.2 In-memory interpreter
    3. 9.3 Error model
    4. 9.4 ZIO environment
    5. 9.5 Reference listings: errors, port, memory interpreter, JSON
      • StoreError.scala
      • PersistedEvent.scala
      • EventStore.scala
      • InMemoryEventStore.scala
      • AccountEventJson.scala
    6. 9.6 Chapter linkage
  10. Chapter 10 · PostgreSQL: append-only schema
    1. 10.1 Core table: events
    2. 10.2 Append semantics
    3. 10.3 Indexes
    4. 10.4 Migrations
    5. 10.5 Connection and ZIO
    6. 10.6 Multi-tenancy
    7. 10.7 Reference migration: V1__events.sql
    8. 10.8 PostgresEventJournal.scala
    9. 10.9 PostgresEventStore.scala
    10. 10.10 DbMigrator.scala (Flyway)
  11. Chapter 10b · Red thread: one account from HTTP to read model
    1. 10b.0 Run the demo locally
    2. 10b.1 Actors and boundaries
    3. 10b.2 Write path: POST /accounts/{id}/commands (conceptual)
    4. 10b.3 What is in the transaction today vs tomorrow
    5. 10b.4 After commit: building the read model
    6. 10b.5 Read path: GET /accounts/{id}/balance (conceptual)
    7. 10b.6 Misconceptions (checklist)
    8. 10b.7 Where to go next
    9. 10b.9 Reference listings: HTTP adapter and entry points
      • CommandJson.scala
      • Main.scala
      • ProjectionTickMain.scala
      • AccountJdkHttpServer.scala
  12. Chapter 11 · Concurrency, transactions, and retries
    1. 11.1 Expected version
    2. 11.2 Serializable isolation
    3. 11.3 Idempotency keys
    4. 11.4 Retry policy
    5. 11.5 Locking alternatives
    6. 11.6 Observability
    7. 11.7 Sequence: append transaction (reference store)
    8. 11.8 Single transaction for read + append (reference code)
    9. 11.9 PostgresTransactions.scala
    10. 11.10 Production checklist
  13. Chapter 12 · Read models and projections
    1. 12.1 Projection = ordered fold over committed events
    2. 12.2 Pure projection in Scala (in the repo)
    3. 12.3 Idempotent UPSERT (SQL sketch)
    4. 12.4 Same database vs separate
    5. 12.5 Building ZIO workers
    6. 12.6 Read-your-writes
    7. 12.7 Multiple projections
    8. 12.8 Failure handling
    9. 12.9 Reference listings: V2 DDL, pure fold, JDBC worker
      • V2__read_model_account_balance.sql
      • ProjectionError.scala
      • AccountBalanceProjection.scala
      • AccountBalanceProjectionJob.scala
    10. 12.10 Glossary hook
  14. Chapter 13 · Snapshots and replay performance
    1. 13.1 Snapshot table (conceptual)
    2. 13.2 When to snapshot
    3. 13.3 Consistency rules
    4. 13.4 Compaction (careful)
    5. 13.5 Optional: aggregate type per stream
    6. 13.6 Serialisation format and integrity
    7. 13.7 Reconciliation and rebuild jobs
    8. 13.8 Performance model (order-of-magnitude reasoning)
    9. 13.9 DDL sketch (optional snapshots table)
  15. Chapter 14 · Event schema evolution and upcasting
    1. 14.1 Additive vs breaking changes
    2. 14.2 Upcaster
    3. 14.3 Dual-write and cutover
    4. 14.4 Compatibility matrix
    5. 14.5 JSON contracts
    6. 14.6 Testing
    7. 14.7 Where the reference code stops (and where you extend)
    8. 14.8 Upcaster placement and invariants
    9. 14.9 Metadata alignment (cross-reference Chapter 5)
    10. 14.10 Operational playbook (summary)
  16. Chapter 15 · Testing event-sourced systems
    1. 15.1 Test pyramid for this repository
    2. 15.2 Pure tests: applyEvent, decide, projection fold
    3. 15.3 Store and aggregate tests
    4. 15.4 Postgres integration test (in the repo)
    5. 15.5 Projection idempotency test pattern
    6. 15.6 Golden streams / replay regression
    7. 15.7 Performance smoke tests
  17. Chapter 16 · Why replicate the log? Boundaries and responsibilities
    1. 16.1 Postgres remains source of truth
    2. 16.2 When Kafka helps
    3. 16.3 When Kafka hurts
    4. 16.4 Contract: published envelope
    5. 16.5 Saga relationship
    6. 16.6 Boundary diagram (authority vs transport)
    7. 16.7 Repository status
  18. Chapter 17 · Transactional outbox and Kafka publishing
    1. 17.0 One transaction: events + outbox
    2. 17.1 Outbox table
    3. 17.2 Relay process
    4. 17.3 Ordering per aggregate
    5. 17.4 ZIO implementation sketch
    6. 17.5 Alternatives
    7. 17.6 Failure modes (explicit)
    8. 17.7 Relay flow diagram
  19. Chapter 18 · ZStream consumers and idempotent projections
    1. 18.1 At-least-once reality
    2. 18.2 ZStream + commit
    3. 18.3 Backpressure
    4. 18.4 Error channels
    5. 18.5 Testing streams
    6. 18.6 Observability
    7. 18.7 Idempotency keys: event identity vs broker offset
    8. 18.8 ZIO-shaped consumer skeleton (pseudocode)
    9. 18.9 Parallelism and ordering caveats
    10. 18.10 From JDBC tick to broker stream
  20. Chapter 19 · Operations, compliance, and failure modes
    1. 19.1 Retention and GDPR
    2. 19.2 Replay and rebuild jobs
    3. 19.3 Monitoring
    4. 19.4 Disaster recovery
    5. 19.5 Runbooks
    6. 19.6 Cost of ES (honest summary)
  21. Chapter 20 · CQRS and consistency: a deep dive
    1. 20.1 Write side: strong local consistency (reference and production baseline)
    2. 20.2 Read side: eventual consistency
    3. 20.3 Consistency vocabulary (for APIs and runbooks)
    4. 20.4 CQRS without ES
    5. 20.5 Sagas and ES
    6. 20.6 Antipatterns
    7. 20.7 Closing
  22. Appendix A · EventStoreDB and other specialised stores
    1. A.1 EventStoreDB — strengths
    2. A.2 When EventStoreDB wins
    3. A.3 When Postgres-first wins
    4. A.4 Other alternatives (brief)
    5. A.5 Decision checklist
  23. Appendix B · Code index
    1. B.1 Domain (event-sourced account)
    2. B.2 Event store (Part III)
    3. B.3 Application layer
    4. B.4 Projection + infrastructure + adapters
    5. B.5 Tests
    6. B.6 Planned (later parts)
  24. Appendix C · ADR template — Event Sourcing adoption
    1. C.1 Template
    2. C.2 Mandatory questions before “Accepted”
  25. Appendix E · Where the reference code appears in this book
  26. Appendix D · Bibliography and further reading
    1. D.1 Event Sourcing and distributed systems
    2. D.2 Domain-Driven Design
    3. D.3 Scala and ZIO
    4. D.4 Companion work in this repository
    5. D.5 PostgreSQL
    6. D.6 Kafka

The Leanpub 60 Day 100% Happiness Guarantee

Within 60 days of purchase you can get a 100% refund on any Leanpub purchase, in two clicks.

Now, this is technically risky for us, since you'll have the book or course files either way. But we're so confident in our products and services, and in our authors and readers, that we're happy to offer a full money back guarantee for everything we sell.

You can only find out how good something is by trying it, and because of our 100% money back guarantee there's literally no risk to do so!

So, there's no reason not to click the Add to Cart button, is there?

See full terms...

Earn $8 on a $10 Purchase, and $16 on a $20 Purchase

We pay 80% royalties on purchases of $7.99 or more, and 80% royalties minus a 50 cent flat fee on purchases between $0.99 and $7.98. You earn $8 on a $10 sale, and $16 on a $20 sale. So, if we sell 5000 non-refunded copies of your book for $20, you'll earn $80,000.

(Yes, some authors have already earned much more than that on Leanpub.)

In fact, authors have earned over $15 million writing, publishing and selling on Leanpub.

Learn more about writing on Leanpub

Free Updates. DRM Free.

If you buy a Leanpub book, you get free updates for as long as the author updates the book! Many authors use Leanpub to publish their books in-progress, while they are writing them. All readers get free updates, regardless of when they bought the book or how much they paid (including free).

Most Leanpub books are available in PDF (for computers) and EPUB (for phones, tablets and Kindle). The formats that a book includes are shown at the top right corner of this page.

Finally, Leanpub books don't have any DRM copy-protection nonsense, so you can easily read them on any supported device.

Learn more about Leanpub's ebook formats and where to read them

Write and Publish on Leanpub

You can use Leanpub to easily write, publish and sell in-progress and completed ebooks and online courses!

Leanpub is a powerful platform for serious authors, combining a simple, elegant writing and publishing workflow with a store focused on selling in-progress ebooks.

Leanpub is a magical typewriter for authors: just write in plain text, and to publish your ebook, just click a button. (Or, if you are producing your ebook your own way, you can even upload your own PDF and/or EPUB files and then publish with one click!) It really is that easy.

Learn more about writing on Leanpub