-
Notifications
You must be signed in to change notification settings - Fork 1
Closed
Description
Problem
-
Entity reducers have no way to signal that an event should be ignored/skipped. When receiving an unexpected event (e.g., an update event for an entity that doesn't exist), reducers must either return an invalid/placeholder entity or throw an exception (which goes against the principle that reducers should be pure).
-
The existing
ReadModelActionenum uses non-semantic names (Nothinginstead ofSkip). -
Naming is inconsistent between concepts - projections have
ReadModelActionbut reducers have no equivalent.
Proposed Solution
Create a consistent, semantic naming pattern across both reducers and projections:
// For Projections (replace ReadModelAction)
export enum ProjectionAction {
Skip, // was ReadModelAction.Nothing
Delete, // was ReadModelAction.Delete
}
export type ProjectionResult<TReadModel> = TReadModel | ProjectionAction
// For Reducers (new)
export enum ReducerAction {
Skip,
}
export type ReducerResult<TEntity> = TEntity | ReducerActionExample Usage
Entity Reducer
import { ReducerAction, ReducerResult } from '@magek/common'
@Entity
export class Product {
constructor(readonly id: UUID, readonly name: string) {}
@Reduces(ProductUpdated)
public static reduceProductUpdated(
event: ProductUpdated,
current?: Product
): ReducerResult<Product> {
if (!current) {
// Can't update a non-existent product - skip this event
return ReducerAction.Skip
}
return new Product(current.id, event.newName)
}
}Read Model Projection
import { ProjectionAction, ProjectionResult } from '@magek/common'
@ReadModel
export class UserReadModel {
@Projects(User, 'id')
public static projectUser(
entity: User,
current?: UserReadModel
): ProjectionResult<UserReadModel> {
if (entity.deleted) {
return ProjectionAction.Delete
}
if (!hasRelevantChanges(entity, current)) {
return ProjectionAction.Skip
}
return new UserReadModel(entity.id, entity.name)
}
}Files to Modify
| File | Changes |
|---|---|
packages/common/src/concepts/reducer-metadata.ts |
Add ReducerAction, ReducerResult |
packages/common/src/concepts/projection-metadata.ts |
Replace ReadModelAction with ProjectionAction |
packages/common/src/index.ts |
Update exports |
packages/core/src/services/event-store.ts |
Handle ReducerAction.Skip |
packages/core/src/services/read-model-store.ts |
Use ProjectionAction |
docs/content/architecture/entity.md |
Document ReducerAction.Skip |
docs/content/architecture/read-model.md |
Use ProjectionAction |
packages/core/test/services/event-store.test.ts |
Add reducer skip tests |
packages/core/test/services/read-model-store.test.ts |
Use ProjectionAction |
Summary
| Concept | Skip | Delete |
|---|---|---|
| Entity Reducer | ReducerAction.Skip |
N/A (events are immutable) |
| Read Model Projection | ProjectionAction.Skip |
ProjectionAction.Delete |
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels