Projection Declaration Language
The Projection Declaration Language (PDL) is a modern, indentation-based language for defining Chronicle projections without writing code. It provides an elegant, compact syntax that reads as rules applied to events. Declarations written in PDL compile into projection definitions with zero loss of functionality.
Overview
Section titled “Overview”The PDL allows you to:
- Define projections declaratively using a simple, readable syntax
- Test and iterate on projections quickly in the Workbench
- Create projections without recompiling your application
- Visualize projection results immediately
- Query event data ad-hoc without defining a read model type
- Support all Chronicle projection capabilities
Mental Model
Section titled “Mental Model”“When event X happens, apply these effects.”
projectiondefines the read model targetfrom Eventdefines a rule for an event- Assignments and operations are effects applied to the read model
childrenandjoincreate scoped mutation contexts- Defaults remove noise; overrides are explicit
Basic Structure
Section titled “Basic Structure”Every projection definition starts with a projection declaration and contains one or more directives or blocks.
With an explicit read model target
Section titled “With an explicit read model target”projection {Name} => {ReadModelType} {directives and blocks}Example:
projection User => UserReadModel from UserRegistered Name = name Email = email IsActive = trueWithout an explicit read model (inferred)
Section titled “Without an explicit read model (inferred)”The => ReadModelType part is optional. When omitted, the read model schema is inferred from the events used in the projection — Chronicle automatically derives the target type from the event properties.
projection {Name} {directives and blocks}Example:
projection User from UserRegisteredWhen to use inferred projections
Inferred projections are designed for ad-hoc querying only. They enable you to query the event log without first defining a read model type. An inferred projection can never be registered as a permanent projection — you must always specify an explicit
=> ReadModelTypewhen saving a projection.
Type compatibility
Section titled “Type compatibility”When multiple from blocks contribute the same property to the inferred schema, their property types must be compatible. If two events define a property with the same name but different incompatible types, the compiler reports an error:
projection BadProjection from EventA // EventA.value is string from EventB // EventB.value is int → error: incompatible typesKey Features
Section titled “Key Features”- Indentation-based: Structure defined by indentation (spaces only, no tabs)
- Event-driven: Rules trigger when events occur
- AutoMap: Automatically map matching property names
- Inferred schema: Read model can be omitted for ad-hoc queries (see
IProjections.Query()) - Expressions: Support for property paths, event context, literals, and templates
- Operations: Counters, arithmetic, assignments
- Relationships: Joins and nested children
- Removal: Remove instances based on events or joined events
- Composite Keys: Multi-property keys for complex scenarios
Topics
Section titled “Topics”- From Event - Define rules that trigger when events occur
- Property Mapping - Map event data to read model properties
- Auto-Map - Automatically map matching properties
- Keys - Explicit and composite keys for projection instances
- Event Context - Access event metadata like timestamps and correlation IDs
- From Every - Apply rules to all events
- From All - Subscribe to all event types without filtering
- Counters - Increment, decrement, and count operations
- Arithmetic - Add and subtract operations
- Joins - Combine data from related events
- Children - Define nested collections
- Removal - Remove projection instances based on events
- Expressions - Understanding expression syntax
- Ad-hoc Querying - Query the event log without registering a projection
- Grammar (EBNF) - Complete formal grammar specification
Example Projection
Section titled “Example Projection”Here’s a comprehensive example demonstrating multiple features:
projection Group => GroupReadModel from GroupCreated Name = name Description = description CreatedAt = $eventContext.occurred
from GroupRenamed Name = name UpdatedAt = $eventContext.occurred
children members identified by userId from UserAddedToGroup key userId parent groupId Name = userName Role = role AddedAt = $eventContext.occurred
from UserRoleChanged key userId parent groupId Role = role
remove with UserRemovedFromGroup key userId parent groupId
remove with GroupDeletedThis projection:
- Creates a group from
GroupCreatedevents - Updates the name when
GroupRenamedoccurs - Manages a collection of members
- Tracks when users are added and their roles changed
- Removes members when they leave
- Removes the entire group when deleted
Getting Started
Section titled “Getting Started”Start with simple projections and gradually add complexity as needed. The Projection Declaration Language is designed to be intuitive and readable, making it easy to understand what a projection does at a glance.