event_engine 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/README.md +210 -399
- data/Rakefile +0 -1
- data/lib/event_engine/catalog_entry.rb +84 -0
- data/lib/event_engine/configuration.rb +9 -9
- data/lib/event_engine/definition_publisher.rb +22 -0
- data/lib/event_engine/event.rb +0 -8
- data/lib/event_engine/event_builder.rb +0 -8
- data/lib/event_engine/event_schema.rb +32 -57
- data/lib/event_engine/event_schema_json_loader.rb +19 -0
- data/lib/event_engine/handler_registry.rb +4 -4
- data/lib/event_engine/invalid_rules_error.rb +8 -0
- data/lib/event_engine/processing_rules.rb +39 -0
- data/lib/event_engine/processor_registry.rb +19 -0
- data/lib/event_engine/processor_resolver.rb +16 -0
- data/lib/event_engine/railtie.rb +54 -4
- data/lib/event_engine/rules_file.rb +14 -0
- data/lib/event_engine/schema_catalog_builder.rb +18 -0
- data/lib/event_engine/schema_registry.rb +7 -58
- data/lib/event_engine/unregistered_processor_error.rb +8 -0
- data/lib/event_engine/unroutable_event_error.rb +9 -0
- data/lib/event_engine/unrouted_events_error.rb +9 -0
- data/lib/event_engine/version.rb +1 -1
- data/lib/event_engine.rb +117 -123
- data/lib/tasks/event_engine_catalog.rake +15 -6
- data/lib/tasks/event_engine_rules.rake +10 -0
- data/the_local/agents/event_engine-develop.md +154 -0
- data/the_local/agents/event_engine-info.md +82 -0
- data/the_local/agents/event_engine-install.md +99 -0
- data/the_local/interface.yml +27 -0
- metadata +45 -48
- data/app/assets/config/event_engine_manifest.js +0 -1
- data/app/assets/stylesheets/event_engine/application.css +0 -15
- data/app/controllers/event_engine/application_controller.rb +0 -4
- data/app/helpers/event_engine/application_helper.rb +0 -4
- data/app/jobs/event_engine/application_job.rb +0 -4
- data/app/mailers/event_engine/application_mailer.rb +0 -6
- data/app/models/event_engine/application_record.rb +0 -5
- data/app/views/layouts/event_engine/application.html.erb +0 -15
- data/config/routes.rb +0 -2
- data/lib/event_engine/definition_loader.rb +0 -26
- data/lib/event_engine/dsl_compiler.rb +0 -50
- data/lib/event_engine/engine.rb +0 -56
- data/lib/event_engine/event_definition/inputs.rb +0 -43
- data/lib/event_engine/event_definition/payloads.rb +0 -47
- data/lib/event_engine/event_definition/schemas.rb +0 -158
- data/lib/event_engine/event_definition/validation.rb +0 -18
- data/lib/event_engine/event_definition.rb +0 -76
- data/lib/event_engine/event_schema_dumper.rb +0 -13
- data/lib/event_engine/event_schema_loader.rb +0 -37
- data/lib/event_engine/event_schema_merger.rb +0 -62
- data/lib/event_engine/event_schema_writer.rb +0 -47
- data/lib/event_engine/lifecycle_definition.rb +0 -86
- data/lib/event_engine/process_type.rb +0 -26
- data/lib/event_engine/reference/guide.md +0 -129
- data/lib/event_engine/reference.rb +0 -16
- data/lib/event_engine/schema_catalog.rb +0 -50
- data/lib/event_engine/schema_compatibility.rb +0 -50
- data/lib/event_engine/schema_diff.rb +0 -35
- data/lib/event_engine/schema_drift_guard.rb +0 -38
- data/lib/event_engine/subject_registry.rb +0 -40
- data/lib/event_engine/the_local/agents/event_engine-develop.md +0 -142
- data/lib/event_engine/the_local/agents/event_engine-info.md +0 -140
- data/lib/event_engine/the_local/agents/event_engine-install.md +0 -140
- data/lib/event_engine/the_local.rb +0 -55
- data/lib/generators/event_engine/install_generator.rb +0 -31
- data/lib/generators/event_engine/templates/event_schema.rb +0 -10
- data/lib/generators/event_engine/templates/initializer.rb +0 -4
- data/lib/tasks/event_engine_schema.rake +0 -82
- data/lib/tasks/event_engine_schema_check.rake +0 -20
- data/lib/tasks/event_engine_tasks.rake +0 -4
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
module EventEngine
|
|
2
|
-
class SchemaCatalog
|
|
3
|
-
def initialize(schema_registry:, subject_registry:)
|
|
4
|
-
@schema_registry = schema_registry
|
|
5
|
-
@subject_registry = subject_registry
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def to_markdown
|
|
9
|
-
(["# Event Catalog"] + event_sections).join("\n\n") + "\n"
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
def event_sections
|
|
15
|
-
@schema_registry.events.map do |event|
|
|
16
|
-
section(@schema_registry.latest_for(event))
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def section(schema)
|
|
21
|
-
([
|
|
22
|
-
"## #{schema.event_name} (v#{schema.event_version})",
|
|
23
|
-
"- Type: #{schema.event_type}",
|
|
24
|
-
subject_line(schema)
|
|
25
|
-
] + payload_lines(schema)).compact.join("\n")
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def payload_lines(schema)
|
|
29
|
-
return [] if schema.payload_fields.empty?
|
|
30
|
-
|
|
31
|
-
["- Payload:"] + schema.payload_fields.map do |field|
|
|
32
|
-
" - #{field[:name]} (#{field[:required] ? "required" : "optional"})"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def subject_line(schema)
|
|
37
|
-
return nil unless schema.subject
|
|
38
|
-
|
|
39
|
-
details = subject_details(schema.subject)
|
|
40
|
-
details.empty? ? "- Subject: #{schema.subject}" : "- Subject: #{schema.subject} (#{details})"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def subject_details(name)
|
|
44
|
-
registered = @subject_registry[name]
|
|
45
|
-
return "" unless registered
|
|
46
|
-
|
|
47
|
-
registered.metadata.map { |key, value| "#{key}: #{value}" }.join(", ")
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
module EventEngine
|
|
2
|
-
class SchemaCompatibility
|
|
3
|
-
def self.violations(old_registry:, new_registry:)
|
|
4
|
-
new_registry.events.flat_map do |event|
|
|
5
|
-
previous = old_registry.latest_for(event)
|
|
6
|
-
next [] unless previous
|
|
7
|
-
|
|
8
|
-
current = new_registry.latest_for(event)
|
|
9
|
-
new(old: previous, new: current).breaking_changes.map do |change|
|
|
10
|
-
"#{event}: #{change}"
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def initialize(old:, new:)
|
|
16
|
-
@old = old
|
|
17
|
-
@new = new
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def breaking_changes
|
|
21
|
-
removed_required_fields + newly_required_fields
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def removed_required_fields
|
|
27
|
-
(required_names(@old) - field_names(@new)).map do |name|
|
|
28
|
-
"required payload field removed: #{name}"
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def newly_required_fields
|
|
33
|
-
(optional_names(@old) & required_names(@new)).map do |name|
|
|
34
|
-
"payload field became required: #{name}"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def field_names(schema)
|
|
39
|
-
schema.payload_fields.map { |field| field[:name] }
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def required_names(schema)
|
|
43
|
-
schema.payload_fields.select { |field| field[:required] }.map { |field| field[:name] }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def optional_names(schema)
|
|
47
|
-
schema.payload_fields.reject { |field| field[:required] }.map { |field| field[:name] }
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
module EventEngine
|
|
2
|
-
class SchemaDiff
|
|
3
|
-
def initialize(expected:, actual:)
|
|
4
|
-
@expected = expected
|
|
5
|
-
@actual = actual
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def changed?
|
|
9
|
-
@expected != @actual
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def to_s
|
|
13
|
-
expected_lines = @expected.lines
|
|
14
|
-
actual_lines = @actual.lines
|
|
15
|
-
|
|
16
|
-
Array.new([ expected_lines.size, actual_lines.size ].max) do |index|
|
|
17
|
-
line_diff(expected_lines[index], actual_lines[index])
|
|
18
|
-
end.compact.join
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def line_diff(expected_line, actual_line)
|
|
24
|
-
return " #{expected_line}" if expected_line == actual_line
|
|
25
|
-
|
|
26
|
-
[ marked("-", expected_line), marked("+", actual_line) ].compact.join
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def marked(sign, line)
|
|
30
|
-
return nil if line.nil?
|
|
31
|
-
|
|
32
|
-
"#{sign}#{line}"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
require "event_engine/schema_diff"
|
|
2
|
-
|
|
3
|
-
module EventEngine
|
|
4
|
-
class SchemaDriftGuard
|
|
5
|
-
class DriftError < StandardError; end
|
|
6
|
-
|
|
7
|
-
def self.check!(schema_path:, definitions:)
|
|
8
|
-
raise DriftError, "Schema file does not exist: #{schema_path}" unless File.exist?(schema_path)
|
|
9
|
-
|
|
10
|
-
committed = File.read(schema_path)
|
|
11
|
-
regenerated = dump_to_string(definitions)
|
|
12
|
-
|
|
13
|
-
return true if committed == regenerated
|
|
14
|
-
|
|
15
|
-
raise DriftError, <<~MSG
|
|
16
|
-
EventEngine schema drift detected.
|
|
17
|
-
|
|
18
|
-
The DSL definitions do not match #{schema_path}.
|
|
19
|
-
|
|
20
|
-
#{SchemaDiff.new(expected: committed, actual: regenerated)}
|
|
21
|
-
Run:
|
|
22
|
-
bin/rails event_engine:schema:dump
|
|
23
|
-
|
|
24
|
-
And commit the updated schema file.
|
|
25
|
-
MSG
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def self.dump_to_string(definitions)
|
|
29
|
-
Tempfile.create("event_schema") do |file|
|
|
30
|
-
EventEngine::EventSchemaDumper.dump!(
|
|
31
|
-
definitions: definitions,
|
|
32
|
-
path: file.path
|
|
33
|
-
)
|
|
34
|
-
File.read(file.path)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
module EventEngine
|
|
2
|
-
class SubjectRegistry
|
|
3
|
-
class UnknownSubjectError < StandardError; end
|
|
4
|
-
|
|
5
|
-
class Subject
|
|
6
|
-
attr_reader :name, :metadata
|
|
7
|
-
|
|
8
|
-
def initialize(name, **metadata)
|
|
9
|
-
@name = name
|
|
10
|
-
@metadata = metadata
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.define(&block)
|
|
15
|
-
registry = new
|
|
16
|
-
registry.instance_eval(&block) if block
|
|
17
|
-
registry
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
@subjects = {}
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def subject(name, **metadata)
|
|
25
|
-
@subjects[name] = Subject.new(name, **metadata)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def [](name)
|
|
29
|
-
@subjects[name]
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def registered?(name)
|
|
33
|
-
@subjects.key?(name)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def names
|
|
37
|
-
@subjects.keys
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: event_engine-develop
|
|
3
|
-
description: Use PROACTIVELY for any EventEngine work — defining events, choosing process_type, emitting, and keeping the committed schema in sync. MUST BE USED instead of hand-writing event plumbing.
|
|
4
|
-
tools: Read, Write, Edit, Grep
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
You build EventEngine events following the reference's conventions: one
|
|
8
|
-
EventDefinition class per event in app/event_definitions/, payloads composed from
|
|
9
|
-
inputs, process_type set explicitly, emitted through the generated
|
|
10
|
-
EventEngine.<event_name> helpers. After any definition change you run
|
|
11
|
-
`bin/rails event_engine:schema:dump` and commit db/event_schema.rb, keeping
|
|
12
|
-
event_engine:schema_check green. You keep handlers idempotent.
|
|
13
|
-
|
|
14
|
-
## EventEngine
|
|
15
|
-
|
|
16
|
-
> **DO NOT** explore the event_engine gem source code. This reference is the
|
|
17
|
-
> complete user-facing API, embedded verbatim into every event_engine local so
|
|
18
|
-
> their guidance never drifts. Keep it the single source of truth.
|
|
19
|
-
|
|
20
|
-
EventEngine is a Rails engine for defining domain events as declarative classes,
|
|
21
|
-
compiling them to a committed schema, emitting them through generated helpers, and
|
|
22
|
-
dispatching them to registered handlers. Core builds and routes events; it ships no
|
|
23
|
-
handlers of its own. Durable delivery, an event store, and ready-made subscriber
|
|
24
|
-
classes are separate companion gems (`event_engine-delivery`, `event_engine-store`,
|
|
25
|
-
`event_engine-subscribers`) — this reference covers core only.
|
|
26
|
-
|
|
27
|
-
### What it offers
|
|
28
|
-
|
|
29
|
-
**Define events** — subclass `EventEngine::EventDefinition` in `app/event_definitions/`:
|
|
30
|
-
|
|
31
|
-
```ruby
|
|
32
|
-
class CowFed < EventEngine::EventDefinition
|
|
33
|
-
event_name :cow_fed # the event's identity (required)
|
|
34
|
-
event_type :domain # classification, e.g. :domain (required)
|
|
35
|
-
process_type :durable # routing type (optional; set it explicitly)
|
|
36
|
-
|
|
37
|
-
input :cow # a required input
|
|
38
|
-
optional_input :farmer # an optional input
|
|
39
|
-
|
|
40
|
-
required_payload :weight, from: :cow, attr: :weight
|
|
41
|
-
optional_payload :farmer_name, from: :farmer, attr: :name
|
|
42
|
-
end
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
| DSL method | Purpose |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `event_name(:symbol)` | The event's identity; becomes `EventEngine.<name>`. Required. |
|
|
48
|
-
| `event_type(:symbol)` | Classification, e.g. `:domain`. Required. |
|
|
49
|
-
| `process_type(:symbol)` | Routing type (optional). One of the six values below. |
|
|
50
|
-
| `input(:name)` / `optional_input(:name)` | Inputs the emit helper must / may receive. |
|
|
51
|
-
| `required_payload(name, from:, attr: nil)` | Payload field; `from:` names an input, `attr:` is the method read on it (`nil` passes the input through). |
|
|
52
|
-
| `optional_payload(name, from:, attr: nil)` | Same, but omitted when the source input is nil. |
|
|
53
|
-
|
|
54
|
-
Duplicate input names raise `ArgumentError`; payload `from:` must reference a
|
|
55
|
-
declared input.
|
|
56
|
-
|
|
57
|
-
**process_type** — core stamps this symbol onto every emitted event but does not act
|
|
58
|
-
on it. Which handlers receive an event is decided by each handler's `levels:`. The
|
|
59
|
-
values:
|
|
60
|
-
|
|
61
|
-
| value | intent |
|
|
62
|
-
|---|---|
|
|
63
|
-
| `:inline` | handled in-process, synchronously |
|
|
64
|
-
| `:background` | handled in-process, via a background job |
|
|
65
|
-
| `:durable` | handled when a durable outbox drains |
|
|
66
|
-
| `:broker` | published to an external transport |
|
|
67
|
-
| `:telemetry` | metrics / observability handlers |
|
|
68
|
-
| `:sourced` | an append-only event store |
|
|
69
|
-
|
|
70
|
-
The companion gems register the handlers that give `:durable`, `:broker`, `:sourced`,
|
|
71
|
-
etc. their behavior; core just routes to whatever is registered. If `process_type`
|
|
72
|
-
is omitted it is `nil` — set it explicitly so routing intent is clear.
|
|
73
|
-
|
|
74
|
-
**Emit events** — booting installs an `EventEngine.<event_name>` helper per event:
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
EventEngine.cow_fed(
|
|
78
|
-
cow: cow, farmer: farmer, # declared inputs, by name
|
|
79
|
-
occurred_at: Time.current, # optional, defaults to now
|
|
80
|
-
metadata: { request_id: "abc" }, # optional
|
|
81
|
-
idempotency_key: "…", # optional, defaults to a UUID
|
|
82
|
-
aggregate_type: "Cow", aggregate_id: cow.id, aggregate_version: 1,
|
|
83
|
-
event_version: 1 # optional, defaults to the latest schema version
|
|
84
|
-
)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Missing a required input, or passing an unknown one, raises `ArgumentError`. The
|
|
88
|
-
event's `payload` is symbol-keyed.
|
|
89
|
-
|
|
90
|
-
**Register handlers** — a handler is any object responding to `call(event)`:
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
EventEngine.register_handler(handler, levels: [:inline, :durable]) # or levels: :all
|
|
94
|
-
EventEngine.dispatch(event) # fan an event out (emit helpers call this)
|
|
95
|
-
EventEngine.reset_handlers! # clear all handlers
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Handlers run synchronously in registration order; if one raises, the rest don't run.
|
|
99
|
-
Keep handlers idempotent.
|
|
100
|
-
|
|
101
|
-
**Configure** — `config/initializers/event_engine.rb`, logger only:
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
EventEngine.configure { |config| config.logger = Rails.logger }
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
**Schema workflow** — definitions compile to a committed `db/event_schema.rb`, which
|
|
108
|
-
is authoritative at boot:
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
bin/rails event_engine:schema:dump # compile definitions → db/event_schema.rb
|
|
112
|
-
bin/rails event_engine:schema_check # CI: fail if definitions drift from the file
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
A new event is version 1; changing an event's identity or payload bumps its version.
|
|
116
|
-
Changing only `process_type` does not bump the version.
|
|
117
|
-
|
|
118
|
-
### Install
|
|
119
|
-
|
|
120
|
-
1. Add the gem and install: `gem "event_engine"`, then `bundle install`.
|
|
121
|
-
2. Run `bin/rails g event_engine:install` — creates `db/event_schema.rb` and
|
|
122
|
-
`config/initializers/event_engine.rb`.
|
|
123
|
-
3. Define events as classes in `app/event_definitions/`.
|
|
124
|
-
4. Run `bin/rails event_engine:schema:dump` and commit `db/event_schema.rb`.
|
|
125
|
-
5. Set `config.logger` in the initializer if you want something other than the default.
|
|
126
|
-
|
|
127
|
-
Durable delivery, an event store, and prebuilt subscriber classes are separate gems
|
|
128
|
-
(`event_engine-delivery`, `event_engine-store`, `event_engine-subscribers`); add them
|
|
129
|
-
when you need them and follow their own setup.
|
|
130
|
-
|
|
131
|
-
### EventEngine conventions
|
|
132
|
-
|
|
133
|
-
- Define one `EventDefinition` class per event in `app/event_definitions/`; never
|
|
134
|
-
hand-build event hashes.
|
|
135
|
-
- Build payloads from inputs with `required_payload`/`optional_payload`; don't pass
|
|
136
|
-
raw payload hashes to the emit helper.
|
|
137
|
-
- Always set `process_type` explicitly so routing intent is clear.
|
|
138
|
-
- Emit only through the generated `EventEngine.<event_name>` helpers, passing the
|
|
139
|
-
declared inputs.
|
|
140
|
-
- Re-run `event_engine:schema:dump` and commit `db/event_schema.rb` after any
|
|
141
|
-
definition change; keep `event_engine:schema_check` green in CI.
|
|
142
|
-
- Keep handlers and subscribers idempotent.
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: event_engine-info
|
|
3
|
-
description: Use to learn what EventEngine offers — the event-definition DSL, process_type, emitting, handlers, and the schema workflow.
|
|
4
|
-
tools: Read
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
You explain how EventEngine works and how to use it, answering only from the
|
|
8
|
-
reference: defining events, process_type routing, emitting through the generated
|
|
9
|
-
helpers, registering handlers, and the schema dump/check workflow. You make no
|
|
10
|
-
changes.
|
|
11
|
-
|
|
12
|
-
## EventEngine
|
|
13
|
-
|
|
14
|
-
> **DO NOT** explore the event_engine gem source code. This reference is the
|
|
15
|
-
> complete user-facing API, embedded verbatim into every event_engine local so
|
|
16
|
-
> their guidance never drifts. Keep it the single source of truth.
|
|
17
|
-
|
|
18
|
-
EventEngine is a Rails engine for defining domain events as declarative classes,
|
|
19
|
-
compiling them to a committed schema, emitting them through generated helpers, and
|
|
20
|
-
dispatching them to registered handlers. Core builds and routes events; it ships no
|
|
21
|
-
handlers of its own. Durable delivery, an event store, and ready-made subscriber
|
|
22
|
-
classes are separate companion gems (`event_engine-delivery`, `event_engine-store`,
|
|
23
|
-
`event_engine-subscribers`) — this reference covers core only.
|
|
24
|
-
|
|
25
|
-
### What it offers
|
|
26
|
-
|
|
27
|
-
**Define events** — subclass `EventEngine::EventDefinition` in `app/event_definitions/`:
|
|
28
|
-
|
|
29
|
-
```ruby
|
|
30
|
-
class CowFed < EventEngine::EventDefinition
|
|
31
|
-
event_name :cow_fed # the event's identity (required)
|
|
32
|
-
event_type :domain # classification, e.g. :domain (required)
|
|
33
|
-
process_type :durable # routing type (optional; set it explicitly)
|
|
34
|
-
|
|
35
|
-
input :cow # a required input
|
|
36
|
-
optional_input :farmer # an optional input
|
|
37
|
-
|
|
38
|
-
required_payload :weight, from: :cow, attr: :weight
|
|
39
|
-
optional_payload :farmer_name, from: :farmer, attr: :name
|
|
40
|
-
end
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
| DSL method | Purpose |
|
|
44
|
-
|---|---|
|
|
45
|
-
| `event_name(:symbol)` | The event's identity; becomes `EventEngine.<name>`. Required. |
|
|
46
|
-
| `event_type(:symbol)` | Classification, e.g. `:domain`. Required. |
|
|
47
|
-
| `process_type(:symbol)` | Routing type (optional). One of the six values below. |
|
|
48
|
-
| `input(:name)` / `optional_input(:name)` | Inputs the emit helper must / may receive. |
|
|
49
|
-
| `required_payload(name, from:, attr: nil)` | Payload field; `from:` names an input, `attr:` is the method read on it (`nil` passes the input through). |
|
|
50
|
-
| `optional_payload(name, from:, attr: nil)` | Same, but omitted when the source input is nil. |
|
|
51
|
-
|
|
52
|
-
Duplicate input names raise `ArgumentError`; payload `from:` must reference a
|
|
53
|
-
declared input.
|
|
54
|
-
|
|
55
|
-
**process_type** — core stamps this symbol onto every emitted event but does not act
|
|
56
|
-
on it. Which handlers receive an event is decided by each handler's `levels:`. The
|
|
57
|
-
values:
|
|
58
|
-
|
|
59
|
-
| value | intent |
|
|
60
|
-
|---|---|
|
|
61
|
-
| `:inline` | handled in-process, synchronously |
|
|
62
|
-
| `:background` | handled in-process, via a background job |
|
|
63
|
-
| `:durable` | handled when a durable outbox drains |
|
|
64
|
-
| `:broker` | published to an external transport |
|
|
65
|
-
| `:telemetry` | metrics / observability handlers |
|
|
66
|
-
| `:sourced` | an append-only event store |
|
|
67
|
-
|
|
68
|
-
The companion gems register the handlers that give `:durable`, `:broker`, `:sourced`,
|
|
69
|
-
etc. their behavior; core just routes to whatever is registered. If `process_type`
|
|
70
|
-
is omitted it is `nil` — set it explicitly so routing intent is clear.
|
|
71
|
-
|
|
72
|
-
**Emit events** — booting installs an `EventEngine.<event_name>` helper per event:
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
EventEngine.cow_fed(
|
|
76
|
-
cow: cow, farmer: farmer, # declared inputs, by name
|
|
77
|
-
occurred_at: Time.current, # optional, defaults to now
|
|
78
|
-
metadata: { request_id: "abc" }, # optional
|
|
79
|
-
idempotency_key: "…", # optional, defaults to a UUID
|
|
80
|
-
aggregate_type: "Cow", aggregate_id: cow.id, aggregate_version: 1,
|
|
81
|
-
event_version: 1 # optional, defaults to the latest schema version
|
|
82
|
-
)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Missing a required input, or passing an unknown one, raises `ArgumentError`. The
|
|
86
|
-
event's `payload` is symbol-keyed.
|
|
87
|
-
|
|
88
|
-
**Register handlers** — a handler is any object responding to `call(event)`:
|
|
89
|
-
|
|
90
|
-
```ruby
|
|
91
|
-
EventEngine.register_handler(handler, levels: [:inline, :durable]) # or levels: :all
|
|
92
|
-
EventEngine.dispatch(event) # fan an event out (emit helpers call this)
|
|
93
|
-
EventEngine.reset_handlers! # clear all handlers
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Handlers run synchronously in registration order; if one raises, the rest don't run.
|
|
97
|
-
Keep handlers idempotent.
|
|
98
|
-
|
|
99
|
-
**Configure** — `config/initializers/event_engine.rb`, logger only:
|
|
100
|
-
|
|
101
|
-
```ruby
|
|
102
|
-
EventEngine.configure { |config| config.logger = Rails.logger }
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**Schema workflow** — definitions compile to a committed `db/event_schema.rb`, which
|
|
106
|
-
is authoritative at boot:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
bin/rails event_engine:schema:dump # compile definitions → db/event_schema.rb
|
|
110
|
-
bin/rails event_engine:schema_check # CI: fail if definitions drift from the file
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
A new event is version 1; changing an event's identity or payload bumps its version.
|
|
114
|
-
Changing only `process_type` does not bump the version.
|
|
115
|
-
|
|
116
|
-
### Install
|
|
117
|
-
|
|
118
|
-
1. Add the gem and install: `gem "event_engine"`, then `bundle install`.
|
|
119
|
-
2. Run `bin/rails g event_engine:install` — creates `db/event_schema.rb` and
|
|
120
|
-
`config/initializers/event_engine.rb`.
|
|
121
|
-
3. Define events as classes in `app/event_definitions/`.
|
|
122
|
-
4. Run `bin/rails event_engine:schema:dump` and commit `db/event_schema.rb`.
|
|
123
|
-
5. Set `config.logger` in the initializer if you want something other than the default.
|
|
124
|
-
|
|
125
|
-
Durable delivery, an event store, and prebuilt subscriber classes are separate gems
|
|
126
|
-
(`event_engine-delivery`, `event_engine-store`, `event_engine-subscribers`); add them
|
|
127
|
-
when you need them and follow their own setup.
|
|
128
|
-
|
|
129
|
-
### EventEngine conventions
|
|
130
|
-
|
|
131
|
-
- Define one `EventDefinition` class per event in `app/event_definitions/`; never
|
|
132
|
-
hand-build event hashes.
|
|
133
|
-
- Build payloads from inputs with `required_payload`/`optional_payload`; don't pass
|
|
134
|
-
raw payload hashes to the emit helper.
|
|
135
|
-
- Always set `process_type` explicitly so routing intent is clear.
|
|
136
|
-
- Emit only through the generated `EventEngine.<event_name>` helpers, passing the
|
|
137
|
-
declared inputs.
|
|
138
|
-
- Re-run `event_engine:schema:dump` and commit `db/event_schema.rb` after any
|
|
139
|
-
definition change; keep `event_engine:schema_check` green in CI.
|
|
140
|
-
- Keep handlers and subscribers idempotent.
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: event_engine-install
|
|
3
|
-
description: Use to add EventEngine to a Rails app and set it up correctly.
|
|
4
|
-
tools: Bash, Read, Edit
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
You install EventEngine following the reference's install section exactly: add the
|
|
8
|
-
gem, bundle, run `bin/rails g event_engine:install`, set the logger in the
|
|
9
|
-
initializer, then dump and commit db/event_schema.rb. You do not invent steps, and
|
|
10
|
-
you do not set up the separate delivery/store/subscribers gems unless asked.
|
|
11
|
-
|
|
12
|
-
## EventEngine
|
|
13
|
-
|
|
14
|
-
> **DO NOT** explore the event_engine gem source code. This reference is the
|
|
15
|
-
> complete user-facing API, embedded verbatim into every event_engine local so
|
|
16
|
-
> their guidance never drifts. Keep it the single source of truth.
|
|
17
|
-
|
|
18
|
-
EventEngine is a Rails engine for defining domain events as declarative classes,
|
|
19
|
-
compiling them to a committed schema, emitting them through generated helpers, and
|
|
20
|
-
dispatching them to registered handlers. Core builds and routes events; it ships no
|
|
21
|
-
handlers of its own. Durable delivery, an event store, and ready-made subscriber
|
|
22
|
-
classes are separate companion gems (`event_engine-delivery`, `event_engine-store`,
|
|
23
|
-
`event_engine-subscribers`) — this reference covers core only.
|
|
24
|
-
|
|
25
|
-
### What it offers
|
|
26
|
-
|
|
27
|
-
**Define events** — subclass `EventEngine::EventDefinition` in `app/event_definitions/`:
|
|
28
|
-
|
|
29
|
-
```ruby
|
|
30
|
-
class CowFed < EventEngine::EventDefinition
|
|
31
|
-
event_name :cow_fed # the event's identity (required)
|
|
32
|
-
event_type :domain # classification, e.g. :domain (required)
|
|
33
|
-
process_type :durable # routing type (optional; set it explicitly)
|
|
34
|
-
|
|
35
|
-
input :cow # a required input
|
|
36
|
-
optional_input :farmer # an optional input
|
|
37
|
-
|
|
38
|
-
required_payload :weight, from: :cow, attr: :weight
|
|
39
|
-
optional_payload :farmer_name, from: :farmer, attr: :name
|
|
40
|
-
end
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
| DSL method | Purpose |
|
|
44
|
-
|---|---|
|
|
45
|
-
| `event_name(:symbol)` | The event's identity; becomes `EventEngine.<name>`. Required. |
|
|
46
|
-
| `event_type(:symbol)` | Classification, e.g. `:domain`. Required. |
|
|
47
|
-
| `process_type(:symbol)` | Routing type (optional). One of the six values below. |
|
|
48
|
-
| `input(:name)` / `optional_input(:name)` | Inputs the emit helper must / may receive. |
|
|
49
|
-
| `required_payload(name, from:, attr: nil)` | Payload field; `from:` names an input, `attr:` is the method read on it (`nil` passes the input through). |
|
|
50
|
-
| `optional_payload(name, from:, attr: nil)` | Same, but omitted when the source input is nil. |
|
|
51
|
-
|
|
52
|
-
Duplicate input names raise `ArgumentError`; payload `from:` must reference a
|
|
53
|
-
declared input.
|
|
54
|
-
|
|
55
|
-
**process_type** — core stamps this symbol onto every emitted event but does not act
|
|
56
|
-
on it. Which handlers receive an event is decided by each handler's `levels:`. The
|
|
57
|
-
values:
|
|
58
|
-
|
|
59
|
-
| value | intent |
|
|
60
|
-
|---|---|
|
|
61
|
-
| `:inline` | handled in-process, synchronously |
|
|
62
|
-
| `:background` | handled in-process, via a background job |
|
|
63
|
-
| `:durable` | handled when a durable outbox drains |
|
|
64
|
-
| `:broker` | published to an external transport |
|
|
65
|
-
| `:telemetry` | metrics / observability handlers |
|
|
66
|
-
| `:sourced` | an append-only event store |
|
|
67
|
-
|
|
68
|
-
The companion gems register the handlers that give `:durable`, `:broker`, `:sourced`,
|
|
69
|
-
etc. their behavior; core just routes to whatever is registered. If `process_type`
|
|
70
|
-
is omitted it is `nil` — set it explicitly so routing intent is clear.
|
|
71
|
-
|
|
72
|
-
**Emit events** — booting installs an `EventEngine.<event_name>` helper per event:
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
EventEngine.cow_fed(
|
|
76
|
-
cow: cow, farmer: farmer, # declared inputs, by name
|
|
77
|
-
occurred_at: Time.current, # optional, defaults to now
|
|
78
|
-
metadata: { request_id: "abc" }, # optional
|
|
79
|
-
idempotency_key: "…", # optional, defaults to a UUID
|
|
80
|
-
aggregate_type: "Cow", aggregate_id: cow.id, aggregate_version: 1,
|
|
81
|
-
event_version: 1 # optional, defaults to the latest schema version
|
|
82
|
-
)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Missing a required input, or passing an unknown one, raises `ArgumentError`. The
|
|
86
|
-
event's `payload` is symbol-keyed.
|
|
87
|
-
|
|
88
|
-
**Register handlers** — a handler is any object responding to `call(event)`:
|
|
89
|
-
|
|
90
|
-
```ruby
|
|
91
|
-
EventEngine.register_handler(handler, levels: [:inline, :durable]) # or levels: :all
|
|
92
|
-
EventEngine.dispatch(event) # fan an event out (emit helpers call this)
|
|
93
|
-
EventEngine.reset_handlers! # clear all handlers
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Handlers run synchronously in registration order; if one raises, the rest don't run.
|
|
97
|
-
Keep handlers idempotent.
|
|
98
|
-
|
|
99
|
-
**Configure** — `config/initializers/event_engine.rb`, logger only:
|
|
100
|
-
|
|
101
|
-
```ruby
|
|
102
|
-
EventEngine.configure { |config| config.logger = Rails.logger }
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**Schema workflow** — definitions compile to a committed `db/event_schema.rb`, which
|
|
106
|
-
is authoritative at boot:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
bin/rails event_engine:schema:dump # compile definitions → db/event_schema.rb
|
|
110
|
-
bin/rails event_engine:schema_check # CI: fail if definitions drift from the file
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
A new event is version 1; changing an event's identity or payload bumps its version.
|
|
114
|
-
Changing only `process_type` does not bump the version.
|
|
115
|
-
|
|
116
|
-
### Install
|
|
117
|
-
|
|
118
|
-
1. Add the gem and install: `gem "event_engine"`, then `bundle install`.
|
|
119
|
-
2. Run `bin/rails g event_engine:install` — creates `db/event_schema.rb` and
|
|
120
|
-
`config/initializers/event_engine.rb`.
|
|
121
|
-
3. Define events as classes in `app/event_definitions/`.
|
|
122
|
-
4. Run `bin/rails event_engine:schema:dump` and commit `db/event_schema.rb`.
|
|
123
|
-
5. Set `config.logger` in the initializer if you want something other than the default.
|
|
124
|
-
|
|
125
|
-
Durable delivery, an event store, and prebuilt subscriber classes are separate gems
|
|
126
|
-
(`event_engine-delivery`, `event_engine-store`, `event_engine-subscribers`); add them
|
|
127
|
-
when you need them and follow their own setup.
|
|
128
|
-
|
|
129
|
-
### EventEngine conventions
|
|
130
|
-
|
|
131
|
-
- Define one `EventDefinition` class per event in `app/event_definitions/`; never
|
|
132
|
-
hand-build event hashes.
|
|
133
|
-
- Build payloads from inputs with `required_payload`/`optional_payload`; don't pass
|
|
134
|
-
raw payload hashes to the emit helper.
|
|
135
|
-
- Always set `process_type` explicitly so routing intent is clear.
|
|
136
|
-
- Emit only through the generated `EventEngine.<event_name>` helpers, passing the
|
|
137
|
-
declared inputs.
|
|
138
|
-
- Re-run `event_engine:schema:dump` and commit `db/event_schema.rb` after any
|
|
139
|
-
definition change; keep `event_engine:schema_check` green in CI.
|
|
140
|
-
- Keep handlers and subscribers idempotent.
|