Implement EventProjection base class with declarative domain-event-to-RenderOp mapping #303

Closed
opened 2026-03-16 01:59:25 +00:00 by freemo · 1 comment
Owner

Metadata

Key Value
Branch feature/m2-event-projection
Commit Message feat: implement EventProjection base class with declarative mapping
Parent Epic #302 — Event Projection System

Background and Context

The Event Projection System needs a base class that subclasses can use to declare mappings between domain events and RenderOps. This is the foundation of the projection layer — a clean, declarative DSL that makes it easy to define how each domain event should be rendered.

Expected Behavior

EventProjection is an abstract base class. Subclasses declare mappings using an on DSL:

class RoomProjection < EventProjection
  on :entity_entered_room do |event|
    RenderOp.text_append(
      window: :main,
      text: "#{event.entity.short_desc} arrives from the #{event.direction}."
    )
  end

  on :entity_departed_room do |event|
    RenderOp.text_append(
      window: :main,
      text: "#{event.entity.short_desc} leaves to the #{event.direction}."
    )
  end
end

A ProjectionRegistry stores all projection classes and provides lookup by event type. When an event arrives, the registry finds all projections that handle that event type and invokes them.

ProjectionRegistry.register(RoomProjection)
ops = ProjectionRegistry.project(:entity_entered_room, event)
# => [RenderOp.text_append(...)]

Acceptance Criteria

  • EventProjection base class provides on DSL for declaring event-to-RenderOp mappings.
  • Subclasses can declare multiple on blocks for different event types.
  • on blocks receive the event and return one or more RenderOp instances.
  • ProjectionRegistry.register accepts projection classes.
  • ProjectionRegistry.project(event_type, event) returns all RenderOps from matching projections.
  • Multiple projections can handle the same event type (ops are concatenated).
  • Unknown event types return an empty array (no errors).

Subtasks

Code

  • Create EventProjection base class with on class-level DSL.
  • Store event mappings as class-level registry (event_type → block).
  • Implement #project(event) instance method invoking the matching block.
  • Create ProjectionRegistry singleton with #register and #project methods.
  • Support multiple projections for the same event type.
  • Handle unknown event types gracefully (return empty array).

Quality

  • Docs: Update YARD comments on affected classes and methods. Update relevant Docusaurus documentation pages if applicable.
  • Tests (Cucumber): Add tests/unit/event_projection.feature covering declaring projections with on DSL, projecting events to RenderOps, multiple projections for same event, unknown event type, registry registration and lookup, multi-op return from single projection.
  • Tests (Cucumber Integration): Add integration feature in tests/integration/ for EventProjection base class and ProjectionRegistry projecting domain events to RenderOps.
  • Tests (Profiling): Run bundle exec rake unit_profile and verify no performance regressions.
  • Quality: Verify coverage >=97% via bundle exec rake unit. If coverage is <97% then review the current unit test coverage report at build/tests/unit/coverage/ and use it to write new Cucumber based unit tests to improve coverage. Specifically, write Cucumber/Gherkin style unit tests that are descriptively named and specifically improve coverage on whichever file has the most uncovered lines by writing tests that will target the uncovered lines in the report. Once that is done rerun bundle exec rake unit to verify all tests pass and coverage is above >=97%. Only mark this as complete once coverage is >=97%, if not repeat this task as many times as is needed until coverage reaches >=97%.
  • Quality: Run bundle exec rake (default task: unit tests with coverage) and bundle exec rake integration, fix any errors if needed ensuring both pass across entire code base, do not ignore any failure even if it seems unrelated to this commit, fix it.

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation.
  • The commit is pushed to the remote on the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.
## Metadata | Key | Value | |-----|-------| | **Branch** | `feature/m2-event-projection` | | **Commit Message** | `feat: implement EventProjection base class with declarative mapping` | | **Parent Epic** | #302 — Event Projection System | ## Background and Context The Event Projection System needs a base class that subclasses can use to declare mappings between domain events and RenderOps. This is the foundation of the projection layer — a clean, declarative DSL that makes it easy to define how each domain event should be rendered. ## Expected Behavior `EventProjection` is an abstract base class. Subclasses declare mappings using an `on` DSL: ```ruby class RoomProjection < EventProjection on :entity_entered_room do |event| RenderOp.text_append( window: :main, text: "#{event.entity.short_desc} arrives from the #{event.direction}." ) end on :entity_departed_room do |event| RenderOp.text_append( window: :main, text: "#{event.entity.short_desc} leaves to the #{event.direction}." ) end end ``` A `ProjectionRegistry` stores all projection classes and provides lookup by event type. When an event arrives, the registry finds all projections that handle that event type and invokes them. ```ruby ProjectionRegistry.register(RoomProjection) ops = ProjectionRegistry.project(:entity_entered_room, event) # => [RenderOp.text_append(...)] ``` ## Acceptance Criteria - [ ] `EventProjection` base class provides `on` DSL for declaring event-to-RenderOp mappings. - [ ] Subclasses can declare multiple `on` blocks for different event types. - [ ] `on` blocks receive the event and return one or more `RenderOp` instances. - [ ] `ProjectionRegistry.register` accepts projection classes. - [ ] `ProjectionRegistry.project(event_type, event)` returns all RenderOps from matching projections. - [ ] Multiple projections can handle the same event type (ops are concatenated). - [ ] Unknown event types return an empty array (no errors). ## Subtasks ### Code - [ ] Create `EventProjection` base class with `on` class-level DSL. - [ ] Store event mappings as class-level registry (event_type → block). - [ ] Implement `#project(event)` instance method invoking the matching block. - [ ] Create `ProjectionRegistry` singleton with `#register` and `#project` methods. - [ ] Support multiple projections for the same event type. - [ ] Handle unknown event types gracefully (return empty array). ### Quality - [ ] Docs: Update YARD comments on affected classes and methods. Update relevant Docusaurus documentation pages if applicable. - [ ] Tests (Cucumber): Add `tests/unit/event_projection.feature` covering declaring projections with on DSL, projecting events to RenderOps, multiple projections for same event, unknown event type, registry registration and lookup, multi-op return from single projection. - [ ] Tests (Cucumber Integration): Add integration feature in `tests/integration/` for EventProjection base class and ProjectionRegistry projecting domain events to RenderOps. - [ ] Tests (Profiling): Run `bundle exec rake unit_profile` and verify no performance regressions. - [ ] Quality: Verify coverage >=97% via `bundle exec rake unit`. If coverage is <97% then review the current unit test coverage report at `build/tests/unit/coverage/` and use it to write new Cucumber based unit tests to improve coverage. Specifically, write Cucumber/Gherkin style unit tests that are descriptively named and specifically improve coverage on whichever file has the most uncovered lines by writing tests that will target the uncovered lines in the report. Once that is done rerun `bundle exec rake unit` to verify all tests pass and coverage is above >=97%. Only mark this as complete once coverage is >=97%, if not repeat this task as many times as is needed until coverage reaches >=97%. - [ ] Quality: Run `bundle exec rake` (default task: unit tests with coverage) and `bundle exec rake integration`, fix any errors if needed ensuring both pass across **entire** code base, do not ignore any failure even if it seems unrelated to this commit, fix it. ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation. - The commit is pushed to the remote on the branch matching the **Branch** in Metadata exactly. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done.
freemo added this to the v1.1.0 milestone 2026-03-16 01:59:25 +00:00
freemo self-assigned this 2026-03-16 01:59:25 +00:00
Author
Owner

Implementation Progress

Created the EventProjection and ProjectionRegistry classes:

Files Created

  • lib/aethyr/core/render/event_projection.rb - Base class with on DSL
  • lib/aethyr/core/render/projection_registry.rb - Singleton registry
  • tests/unit/event_projection.feature - 26 unit test scenarios
  • tests/integration/event_projection_integration.feature - 8 integration test scenarios

Test Results

  • Unit tests: 26 scenarios, 94 steps - all passing
  • Integration tests: 8 scenarios, 33 steps - all passing
  • Rubocop: No offenses detected

Key Features

  • on class-level DSL for declaring event handlers
  • Handlers receive events and return RenderOp(s)
  • Multiple projections can handle same event type
  • Unknown event types return empty array
  • ProjectionRegistry.project() collects ops from all matching projections
## Implementation Progress Created the EventProjection and ProjectionRegistry classes: ### Files Created - `lib/aethyr/core/render/event_projection.rb` - Base class with `on` DSL - `lib/aethyr/core/render/projection_registry.rb` - Singleton registry - `tests/unit/event_projection.feature` - 26 unit test scenarios - `tests/integration/event_projection_integration.feature` - 8 integration test scenarios ### Test Results - Unit tests: 26 scenarios, 94 steps - all passing - Integration tests: 8 scenarios, 33 steps - all passing - Rubocop: No offenses detected ### Key Features - `on` class-level DSL for declaring event handlers - Handlers receive events and return RenderOp(s) - Multiple projections can handle same event type - Unknown event types return empty array - ProjectionRegistry.project() collects ops from all matching projections
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference: aethyr/Aethyr#303
No description provided.