A projection turns persisted events into a representation suited to a particular read or integration need. It consumes a journal as a stream, selects relevant events, transforms them, and writes results elsewhere: commonly a queryable database table, but also Kafka, another database, or a gRPC service. This separates the event history from views optimized for queries or downstream consumers. section 10.0 section 10.3
A projection has a read side and a write side. The read side chooses events, normally by persistence tag; the write side supplies a handler that interprets each event and persists the new representation, often through an upsert and a database session. With Akka Persistence Query, projection sources use eventsByTag, so tags also define the available partitioning boundary. section 10.3 section 15.1.1
A practical projection pipeline usually works as follows:
- Read tagged event envelopes from the journal, starting at a recorded offset or from the beginning when no offset exists. section 10.2 section 10.4
- Handle only event types that contribute to the destination view; ignore unrelated events. section 15.1.1
- Apply an idempotent database action, such as inserting a row or upserting an aggregate value. section 10.4 section 15.1.1
- Persist progress with the output work so a restart can resume without uncertain rereads; use effectively-once semantics where configured. section 10.3 section 15.1.1
- Run one projection per tag when greater parallelism is needed, within the limits imposed by entity tags. section 10.3 section 10.4
Cargo count read model
CargoAdded events for containers are read by tag and written into cargos_per_container. An INSERT with ON CONFLICT increments each container’s cargo count, producing a compact table where container 9 has two cargos and container 11 has one. A separate offset store records each projection’s latest processed sequence. section 10.4
Betting projections
A database projection records each BetOpened with its bet, wallet, market, odds, stake, and result. A query then totals potential returns per market result and exposes them through gRPC for risk management. A separate market projection serializes market events and publishes them to Kafka, where other processes can consume them. section 15.1.1 section 15.1.2
Listing 10.5 Akka projection ingredients
import akka.projection.eventsourced.scaladsl.EventSourcedProvider
import akka.persistence.jdbc.query.scaladsl.JdbcReadJournal
import example.repository.scalike.ScalikeJdbcSession
import example.persistence.SPContainer
object CargosPerContainerProjection {
...
val sourceProvider =
EventSourcedProvider.eventsByTag[SPContainer.Event]( #1
system = system,
readJournalPluginId = JdbcReadJournal.Identifier,
tag = tag)
JdbcProjection.exactlyOnce(
projectionId =
ProjectionId("CargosPerContainerProjection", tag),
sourceProvider = sourceProvider,
handler =
() => new CPCProjectionHandler(tag, system, repository), #2
sessionFactory = () => new ScalikeJdbcSession()) #3
(system)
}
The projection definition makes the three required pieces visible together: tagged event source, processing handler, and database session factory. section 10.3
