event_engine-event_definition 0.2.0 → 0.4.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/.claude/agents/the_local-develop.md +48 -0
- data/.claude/agents/the_local-info.md +40 -0
- data/.claude/agents/the_local-install.md +49 -0
- data/CHANGELOG.md +43 -0
- data/CLAUDE.md +11 -0
- data/README.md +89 -7
- data/Rakefile +2 -0
- data/docs/examples.md +1 -1
- data/lib/event_engine/definition/event_schema.rb +82 -0
- data/lib/event_engine/definition/schema_registry.rb +73 -0
- data/lib/event_engine/definition/version.rb +1 -1
- data/lib/event_engine/definition.rb +19 -2
- data/lib/event_engine/domain_pack_build.rb +2 -0
- data/lib/event_engine/dsl_compiler.rb +1 -1
- data/lib/event_engine/event_engine_helpers_writer.rb +10 -2
- data/lib/tasks/event_definition.rake +20 -0
- data/the_local/agents/event_engine-event_definition-develop.md +253 -0
- data/the_local/agents/event_engine-event_definition-info.md +62 -0
- data/the_local/agents/event_engine-event_definition-install.md +92 -0
- data/the_local/interface.yml +29 -0
- metadata +14 -9
- data/lib/event_engine/event_schema.rb +0 -80
- data/lib/event_engine/schema_registry.rb +0 -71
- data/lib/tasks/event_engine_definition.rake +0 -19
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event_engine-event_definition-develop
|
|
3
|
+
description: Use PROACTIVELY for declaring a domain event, declaring a lifecycle family of events (created / updated / failed and the like) for one subject, registering the subjects events are about, reading an event's compiled schema or fingerprint, setting where raised events are published, and listing the loaded packs and their schema.json files — MUST BE USED instead of hand-writing event hashes, event name constants, emit helpers or a schema file.
|
|
4
|
+
tools: Read, Write, Edit, Grep
|
|
5
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You write and change a pack's event definitions, subjects and publisher wiring by
|
|
9
|
+
following the steps below. Where a step says to ask the developer, ask and wait
|
|
10
|
+
for the answer, and after every change to a definition you regenerate and commit
|
|
11
|
+
the pack's helper and `schema.json`.
|
|
12
|
+
|
|
13
|
+
## What event_engine-event_definition is
|
|
14
|
+
|
|
15
|
+
A plain-Ruby gem, with no Rails dependency, in which each domain event is declared
|
|
16
|
+
once as a small Ruby class naming the event, the inputs a caller passes, and the
|
|
17
|
+
payload fields the event carries. Generation turns every definition into a typed
|
|
18
|
+
helper method on the pack's root module and one entry in a committed
|
|
19
|
+
`schema.json`.
|
|
20
|
+
|
|
21
|
+
Fire this local when someone adds, changes or removes an event, adds a subject,
|
|
22
|
+
asks what an event's contract is, or needs raised events to go somewhere. Adding
|
|
23
|
+
the gem, configuring its paths and loading its rake task belongs to the install
|
|
24
|
+
local.
|
|
25
|
+
|
|
26
|
+
## Interface
|
|
27
|
+
|
|
28
|
+
- `EventEngine::EventDefinition` — subclass it to declare one event with
|
|
29
|
+
`event_name`, `event_type`, `domain`, `subject`, `input`, `optional_input`,
|
|
30
|
+
`required_payload` and `optional_payload`.
|
|
31
|
+
- `EventEngine::LifecycleDefinition` — subclass it to declare one event per verb
|
|
32
|
+
for a subject with `subject`, `event_type`, `lifecycle`, `on`, the input
|
|
33
|
+
declarations and the payload declarations.
|
|
34
|
+
- `EventEngine::SubjectRegistry.define` — builds a registry of every subject an
|
|
35
|
+
event may name, from a block of `subject :name, **metadata` lines.
|
|
36
|
+
- `EventEngine::EventDefinition.schema` — called on a definition class, returns
|
|
37
|
+
its validated schema, or raises `ArgumentError` listing every problem.
|
|
38
|
+
- `EventEngine::Definition.publisher=` — sets the object every generated helper
|
|
39
|
+
hands its event to.
|
|
40
|
+
- `EventEngine::Definition.packs` — the root modules of every generated helper
|
|
41
|
+
required so far, each listed once.
|
|
42
|
+
- `EventEngine::Definition.pack_schema_paths` — the absolute `schema.json` path
|
|
43
|
+
of each of those packs.
|
|
44
|
+
|
|
45
|
+
## How to use it
|
|
46
|
+
|
|
47
|
+
### Declare one event
|
|
48
|
+
|
|
49
|
+
1. Put the definition in a `.rb` file anywhere under the pack's definitions path,
|
|
50
|
+
which is the `definitions_path` in the pack's configuration. Every
|
|
51
|
+
`.rb` file under it, at any depth, is loaded, so one class per file is enough.
|
|
52
|
+
2. Subclass `EventEngine::EventDefinition` and declare the identity:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class LeadCreated < EventEngine::EventDefinition
|
|
56
|
+
event_name :lead_created
|
|
57
|
+
event_type :domain
|
|
58
|
+
domain :marketing
|
|
59
|
+
subject :lead
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `event_name` is required and must be a snake_case symbol. It becomes the
|
|
63
|
+
helper method's name.
|
|
64
|
+
- `event_type` is required. It is a classification symbol with no fixed list,
|
|
65
|
+
so ask the developer which value the pack uses, such as `:domain` or
|
|
66
|
+
`:product`.
|
|
67
|
+
- `domain` is optional. Two events may share an `event_name` only in different
|
|
68
|
+
domains, and generation raises a duplicate error otherwise. Ask the
|
|
69
|
+
developer which domain the event belongs to.
|
|
70
|
+
- `subject` is optional. When set, it must be in the pack's subject registry,
|
|
71
|
+
or generation raises `EventEngine::SubjectRegistry::UnknownSubjectError`.
|
|
72
|
+
3. Declare the inputs. Each input is one keyword argument on the generated
|
|
73
|
+
helper, and it holds the whole object the caller already has:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
input :lead
|
|
77
|
+
optional_input :campaign
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `input` makes the keyword required. `optional_input` defaults it to `nil`.
|
|
81
|
+
- Declaring the same name twice raises `ArgumentError` at once.
|
|
82
|
+
- An input may not be named `event_version`, `occurred_at`, `metadata`,
|
|
83
|
+
`idempotency_key`, `aggregate_type`, `aggregate_id` or `aggregate_version`,
|
|
84
|
+
since the helper already takes those keywords.
|
|
85
|
+
4. Declare the payload fields, the flat values the event carries, each read off
|
|
86
|
+
one input:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
required_payload :lead_id, from: :lead, attr: :id
|
|
90
|
+
required_payload :email, from: :lead, attr: :email
|
|
91
|
+
optional_payload :source, from: :campaign, attr: :channel
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- `from:` is required and must name an input declared on the same class.
|
|
96
|
+
- `attr:` names the attribute read off that input.
|
|
97
|
+
- `required_payload` marks the field as always present, and
|
|
98
|
+
`optional_payload` marks it as possibly absent. Ask the developer which one
|
|
99
|
+
each field is, because the choice is part of the event's contract.
|
|
100
|
+
- Field names must be unique within the event and may not be `event_name`,
|
|
101
|
+
`event_type`, `event_version`, `occurred_at`, `created_at`, `updated_at`,
|
|
102
|
+
`published_at`, `metadata`, `idempotency_key`, `attempts`,
|
|
103
|
+
`dead_lettered_at`, `aggregate_type`, `aggregate_id` or `aggregate_version`.
|
|
104
|
+
- The definition records the mapping only. Reading `lead.id` and the rest is
|
|
105
|
+
done by whichever publisher receives the event.
|
|
106
|
+
5. Check the definition by calling `LeadCreated.schema`. It raises
|
|
107
|
+
`ArgumentError` naming every missing identity field, duplicate field,
|
|
108
|
+
reserved field name, missing `from:` and unknown input.
|
|
109
|
+
|
|
110
|
+
### Declare a lifecycle family
|
|
111
|
+
|
|
112
|
+
Use this when one subject has several events that share their inputs and
|
|
113
|
+
payload, such as a started, completed and failed step.
|
|
114
|
+
|
|
115
|
+
1. Put it under the definitions path like any other definition.
|
|
116
|
+
2. Subclass `EventEngine::LifecycleDefinition`:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class ImportLifecycle < EventEngine::LifecycleDefinition
|
|
120
|
+
subject :import
|
|
121
|
+
event_type :domain
|
|
122
|
+
lifecycle :started, :completed, :failed
|
|
123
|
+
|
|
124
|
+
input :import
|
|
125
|
+
required_payload :import_id, from: :import, attr: :id
|
|
126
|
+
|
|
127
|
+
on :failed do
|
|
128
|
+
domain :data
|
|
129
|
+
optional_input :error
|
|
130
|
+
optional_payload :error_message, from: :error, attr: :message
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- Each verb becomes one event named `<subject>_<verb>`, such as
|
|
136
|
+
`import_started`, so the subject and every verb must be snake_case.
|
|
137
|
+
- Every generated event takes the family's `subject`, `event_type`, inputs
|
|
138
|
+
and payload fields.
|
|
139
|
+
- The subject must be in the pack's subject registry.
|
|
140
|
+
- A `lifecycle` family has no `domain` of its own. Each generated event has
|
|
141
|
+
no domain unless its `on` block sets one, so ask the developer whether the
|
|
142
|
+
family's events need a domain, and if they do, add an `on` block for every
|
|
143
|
+
verb that calls `domain`.
|
|
144
|
+
3. Use `on :<verb> do ... end` to add to one verb's event. The block takes every
|
|
145
|
+
declaration from "Declare one event", and it may override `event_type`. An
|
|
146
|
+
`on` block for a verb missing from `lifecycle` has no effect.
|
|
147
|
+
4. Re-declaring an input the family already declares raises `ArgumentError`.
|
|
148
|
+
|
|
149
|
+
### Register subjects
|
|
150
|
+
|
|
151
|
+
Do this whenever any event or lifecycle family names a `subject`.
|
|
152
|
+
|
|
153
|
+
1. Build the registry with every subject used across the pack:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
SUBJECTS = EventEngine::SubjectRegistry.define do
|
|
157
|
+
subject :lead
|
|
158
|
+
subject :import, owner: "data team"
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Metadata after the name is optional and free-form. Ask the developer whether
|
|
163
|
+
any subject needs it.
|
|
164
|
+
2. Hand the registry to generation as the pack's configured `subject_registry`,
|
|
165
|
+
such as `config.subject_registry = SUBJECTS`. If the pack has no
|
|
166
|
+
configuration yet, the install local sets it up.
|
|
167
|
+
|
|
168
|
+
### Regenerate after every change
|
|
169
|
+
|
|
170
|
+
1. Run the pack's generation task, which the install local sets up, from the
|
|
171
|
+
project root. It raises instead of writing if any definition is invalid.
|
|
172
|
+
2. Commit the regenerated helper and `schema.json` together with the definition
|
|
173
|
+
change.
|
|
174
|
+
3. Raise the event from consuming code through the generated helper on the
|
|
175
|
+
pack's root module, passing the inputs as keywords:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
MarketingEvents.lead_created(lead: lead, campaign: campaign)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each helper also takes the optional keywords `event_version`, `occurred_at`,
|
|
182
|
+
`metadata`, `idempotency_key`, `aggregate_type`, `aggregate_id` and
|
|
183
|
+
`aggregate_version`, which are passed to the publisher unchanged.
|
|
184
|
+
|
|
185
|
+
### Read an event's contract
|
|
186
|
+
|
|
187
|
+
Call `.schema` on the definition class and read from the result:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
schema = LeadCreated.schema
|
|
191
|
+
schema.event_name # => :lead_created
|
|
192
|
+
schema.required_inputs # => [:lead]
|
|
193
|
+
schema.optional_inputs # => [:campaign]
|
|
194
|
+
schema.payload_fields # => [{ name: :lead_id, required: true, from: :lead, attr: :id }, ...]
|
|
195
|
+
schema.fingerprint # => a SHA-256 hex string
|
|
196
|
+
schema.to_h # => the same hash written into schema.json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The fingerprint covers the event name, event type, inputs and payload fields,
|
|
200
|
+
including whether each field is required. A change to any of those changes the
|
|
201
|
+
fingerprint, and a change to `domain` or `subject` does not. Compare fingerprints
|
|
202
|
+
to tell whether an event's contract changed.
|
|
203
|
+
|
|
204
|
+
### Set the publisher
|
|
205
|
+
|
|
206
|
+
1. Ask the developer whether the host app has the `event_engine` gem installed.
|
|
207
|
+
If it does, it sets the publisher at boot and nothing is written here.
|
|
208
|
+
2. Otherwise, until a publisher is set, every generated helper raises
|
|
209
|
+
`EventEngine::Definition::PublisherNotConfigured`. Ask the developer where
|
|
210
|
+
raised events should go, then write a publisher that responds to:
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
def publish(event_name, domain:, inputs:, event_version:, occurred_at:,
|
|
214
|
+
metadata:, idempotency_key:, aggregate_type:, aggregate_id:,
|
|
215
|
+
aggregate_version:)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`event_name` and `domain` are symbols, and `inputs` is a hash of the input
|
|
219
|
+
objects exactly as the caller passed them, keyed by input name. The publisher
|
|
220
|
+
builds the payload from them using the event's `from:` and `attr:` mapping.
|
|
221
|
+
3. Assign it once at boot, after the gem is required and before any helper is
|
|
222
|
+
called:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
EventEngine::Definition.publisher = MyPublisher.new
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
4. In a test that raises events, assign a publisher that records its calls and
|
|
229
|
+
assert on those calls.
|
|
230
|
+
|
|
231
|
+
### List the loaded packs
|
|
232
|
+
|
|
233
|
+
1. Require every pack's generated helper first. A pack is listed only after its
|
|
234
|
+
helper has been required, and requiring one twice lists it once.
|
|
235
|
+
2. Read `EventEngine::Definition.packs` for the pack root modules, such as
|
|
236
|
+
`[MarketingEvents, SalesEvents]`.
|
|
237
|
+
3. Read `EventEngine::Definition.pack_schema_paths` for the absolute path of each
|
|
238
|
+
pack's `schema.json`, in the same order.
|
|
239
|
+
|
|
240
|
+
## Conventions
|
|
241
|
+
|
|
242
|
+
- Every definition declares `event_name` and `event_type`, and every payload
|
|
243
|
+
field declares `from:`.
|
|
244
|
+
- Every `subject` used anywhere in the pack is in its subject registry.
|
|
245
|
+
- Never edit the generated helper or `schema.json` by hand. Regenerate and commit
|
|
246
|
+
both after every definition change.
|
|
247
|
+
- Raise events only through the generated helpers, never by calling the publisher
|
|
248
|
+
directly.
|
|
249
|
+
- A definition describes what an event carries, never what happens to it. How a
|
|
250
|
+
raised event is processed belongs to the publisher.
|
|
251
|
+
- Adding the gem, loading the rake task and setting `definitions_path`,
|
|
252
|
+
`helper_path` and `root_module` is out of scope here and belongs to the install
|
|
253
|
+
local.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event_engine-event_definition-info
|
|
3
|
+
description: Use to learn what event_engine-event_definition offers — declaring domain events, lifecycle event families, subjects, packs, and the generated helper and schema.json.
|
|
4
|
+
tools: Read
|
|
5
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You explain what event_engine-event_definition does, answering only from this
|
|
9
|
+
reference. You make no changes, and you never read the gem's source.
|
|
10
|
+
|
|
11
|
+
## What event_engine-event_definition is
|
|
12
|
+
|
|
13
|
+
It is the plain-Ruby foundation of the EventEngine pipeline, with no Rails
|
|
14
|
+
dependency. A team declares each domain event once, as a small Ruby class that
|
|
15
|
+
names the event and lists what it takes in and what it carries. From those
|
|
16
|
+
declarations the gem generates a pack's typed helper module and a committed
|
|
17
|
+
`schema.json` that describes every event in the pack.
|
|
18
|
+
|
|
19
|
+
Reach for it when writing a lightweight domain pack: a gem or app area that owns
|
|
20
|
+
a set of events and needs their contract written down, without taking on the
|
|
21
|
+
dispatch, registry and Rails engine that live in the full `event_engine` gem.
|
|
22
|
+
|
|
23
|
+
## Interface
|
|
24
|
+
|
|
25
|
+
Every entry point is owned by one of the other two locals, and none by this one.
|
|
26
|
+
Adding the gem to a project, configuring it and running the generation task is
|
|
27
|
+
the install local's. Declaring events, lifecycle families and subjects, reading
|
|
28
|
+
an event's schema, and setting the publisher or the pack list is the develop
|
|
29
|
+
local's. Route to those rather than answering here.
|
|
30
|
+
|
|
31
|
+
## How to use it
|
|
32
|
+
|
|
33
|
+
Decide what you are doing. Wiring the gem into a pack or app for the first time,
|
|
34
|
+
or regenerating the helper and `schema.json`, needs the install local. Writing
|
|
35
|
+
or changing the events themselves needs the develop local.
|
|
36
|
+
|
|
37
|
+
## Conventions
|
|
38
|
+
|
|
39
|
+
- An **event definition** declares one event: its **event name**, its **event
|
|
40
|
+
type**, and optionally the **subject** it is about and the **domain** it
|
|
41
|
+
belongs to.
|
|
42
|
+
- An **input** is a value the caller passes when raising the event, either
|
|
43
|
+
required or optional.
|
|
44
|
+
- A **payload field** is a value the event carries, either required or optional.
|
|
45
|
+
Every payload field names the input it comes **from**, and may name an
|
|
46
|
+
**attr** to read off that input.
|
|
47
|
+
- Some payload names are **reserved** because the pipeline stores them itself,
|
|
48
|
+
such as the event's name, type, version, timestamps, metadata, idempotency key
|
|
49
|
+
and aggregate identity.
|
|
50
|
+
- A **schema** is the validated form of one event definition. Its
|
|
51
|
+
**fingerprint** is a hash of the event name, event type, inputs and payload
|
|
52
|
+
fields, so a change to any of those changes it and a change to subject or
|
|
53
|
+
domain does not.
|
|
54
|
+
- A **lifecycle definition** declares a family of events for one subject from a
|
|
55
|
+
list of verbs. Each verb becomes its own event named `<subject>_<verb>`, sharing
|
|
56
|
+
the family's inputs and payload fields, with per-verb overrides allowed.
|
|
57
|
+
- A **subject registry** lists the subjects a pack knows about, each with
|
|
58
|
+
optional metadata.
|
|
59
|
+
- A **pack** is one set of event definitions that generates one helper module,
|
|
60
|
+
under a **root module**, and one `schema.json`.
|
|
61
|
+
- The **publisher** is what receives raised events. Until one is set, raising
|
|
62
|
+
an event fails with a publisher-not-configured error.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event_engine-event_definition-install
|
|
3
|
+
description: Use to hook event_engine-event_definition into a project — adding the gem, requiring it, loading its rake task, configuring the definitions path, helper path and root module, and running the generation task.
|
|
4
|
+
tools: Bash, Read, Edit
|
|
5
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You follow these steps exactly and invent none. Where a step says to ask the
|
|
9
|
+
developer, ask and wait for the answer.
|
|
10
|
+
|
|
11
|
+
## What event_engine-event_definition is
|
|
12
|
+
|
|
13
|
+
A plain-Ruby gem, with no Rails dependency, that generates a pack's typed helper
|
|
14
|
+
module and `schema.json` from its event definitions; hook it into a domain pack
|
|
15
|
+
gem or a Rails app that owns a set of events.
|
|
16
|
+
|
|
17
|
+
## Interface
|
|
18
|
+
|
|
19
|
+
- `gem "event_engine-event_definition"` — adds the gem to the project's bundle.
|
|
20
|
+
- `require "event_engine/definition"` — loads the gem.
|
|
21
|
+
- `load "tasks/event_definition.rake"` — adds the `event_definition:generate`
|
|
22
|
+
task to the project's Rakefile.
|
|
23
|
+
- `EventEngine::Definition.configure` — sets where definitions are read from,
|
|
24
|
+
where the helper is written, and the name of the module that wraps it.
|
|
25
|
+
- `rake event_definition:generate` — writes the helper module and `schema.json`
|
|
26
|
+
from every definition under the definitions path.
|
|
27
|
+
|
|
28
|
+
## How to use it
|
|
29
|
+
|
|
30
|
+
1. Ask the developer whether this is a domain pack gem or a Rails app.
|
|
31
|
+
2. Add the gem.
|
|
32
|
+
- Rails app: add `gem "event_engine-event_definition"` to the `Gemfile`.
|
|
33
|
+
- Pack gem: add `spec.add_dependency "event_engine-event_definition"` to the
|
|
34
|
+
pack's `*.gemspec`, since the generated helper requires the gem at runtime.
|
|
35
|
+
- Run `bundle install`.
|
|
36
|
+
3. Ask the developer for three values. None has a default, and the task fails if
|
|
37
|
+
any is missing.
|
|
38
|
+
- `definitions_path` — the directory holding the event definitions, such as
|
|
39
|
+
`app/event_definitions`. Every `.rb` file under it, at any depth, is loaded.
|
|
40
|
+
- `helper_path` — the file the helper module is written to, such as
|
|
41
|
+
`lib/generated/marketing_events.rb`. `schema.json` is written to the same
|
|
42
|
+
directory.
|
|
43
|
+
- `root_module` — the Ruby constant name that wraps the helpers, such as
|
|
44
|
+
`MarketingEvents`.
|
|
45
|
+
4. Add these lines to the project's `Rakefile`, with the three values from step 3:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
require "event_engine/definition"
|
|
49
|
+
load "tasks/event_definition.rake"
|
|
50
|
+
|
|
51
|
+
EventEngine::Definition.configure do |config|
|
|
52
|
+
config.definitions_path = "app/event_definitions"
|
|
53
|
+
config.helper_path = "lib/generated/marketing_events.rb"
|
|
54
|
+
config.root_module = "MarketingEvents"
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The paths are relative to the directory rake is run from, so run it from the
|
|
59
|
+
project root.
|
|
60
|
+
5. Ask the developer whether any event declares a subject. If one does, add
|
|
61
|
+
`config.subject_registry = <a registry naming every subject used>` inside the
|
|
62
|
+
`configure` block. Writing that registry is the develop local's job. Without
|
|
63
|
+
it, generation raises an unknown subject error.
|
|
64
|
+
6. Make sure the helper is required once at boot, since it registers its pack
|
|
65
|
+
when it is required.
|
|
66
|
+
- Rails app: in `config/application.rb`, add the helper's directory to the
|
|
67
|
+
autoload ignore list, such as
|
|
68
|
+
`config.autoload_lib(ignore: %w[assets tasks generated])`, because the
|
|
69
|
+
helper's file path does not match the constant it defines and eager loading
|
|
70
|
+
fails in production. Then in `config/initializers/event_engine.rb`, require
|
|
71
|
+
the helper, such as
|
|
72
|
+
`require Rails.root.join("lib/generated/marketing_events")`.
|
|
73
|
+
- Pack gem: ask the developer which of the pack's files should require the
|
|
74
|
+
helper, and add the `require` there.
|
|
75
|
+
7. Run `bundle exec rake event_definition:generate`. It creates the helper's
|
|
76
|
+
directory if missing and writes two files:
|
|
77
|
+
- the helper module at `helper_path`,
|
|
78
|
+
- `schema.json` next to it.
|
|
79
|
+
8. Commit both generated files.
|
|
80
|
+
|
|
81
|
+
## Conventions
|
|
82
|
+
|
|
83
|
+
- A successful run prints two lines, `Wrote <root_module> helper to
|
|
84
|
+
<helper_path>` and `Wrote <root_module> schema to <dir>/schema.json`. Confirm
|
|
85
|
+
both files exist.
|
|
86
|
+
- Re-run `rake event_definition:generate` after every change to a definition and
|
|
87
|
+
commit the result. Never edit the generated files by hand; each run overwrites
|
|
88
|
+
them.
|
|
89
|
+
- With no definitions under `definitions_path`, the task still runs and writes an
|
|
90
|
+
empty module and an empty `schema.json`.
|
|
91
|
+
- Writing event definitions, subjects and lifecycle families, and choosing where
|
|
92
|
+
raised events go, is out of scope here and belongs to the develop local.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
scope: declaring domain events with a plain-Ruby DSL and generating a pack's typed helper module and committed schema.json from them
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
- gem "event_engine-event_definition"
|
|
5
|
+
- require "event_engine/definition"
|
|
6
|
+
- load "tasks/event_definition.rake"
|
|
7
|
+
- EventEngine::Definition.configure
|
|
8
|
+
- rake event_definition:generate
|
|
9
|
+
|
|
10
|
+
develop:
|
|
11
|
+
- EventEngine::EventDefinition
|
|
12
|
+
- EventEngine::LifecycleDefinition
|
|
13
|
+
- EventEngine::SubjectRegistry.define
|
|
14
|
+
- EventEngine::EventDefinition.schema
|
|
15
|
+
- EventEngine::Definition.publisher=
|
|
16
|
+
- EventEngine::Definition.packs
|
|
17
|
+
- EventEngine::Definition.pack_schema_paths
|
|
18
|
+
|
|
19
|
+
sources:
|
|
20
|
+
- lib/event_engine/definition.rb
|
|
21
|
+
- lib/event_engine/definition/configuration.rb
|
|
22
|
+
- lib/tasks/event_definition.rake
|
|
23
|
+
- lib/event_engine/event_definition.rb
|
|
24
|
+
- lib/event_engine/event_definition/inputs.rb
|
|
25
|
+
- lib/event_engine/event_definition/payloads.rb
|
|
26
|
+
- lib/event_engine/event_definition/schemas.rb
|
|
27
|
+
- lib/event_engine/lifecycle_definition.rb
|
|
28
|
+
- lib/event_engine/subject_registry.rb
|
|
29
|
+
- event_engine-event_definition.gemspec
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: event_engine-event_definition
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tylercschneider
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: 'The plain-Ruby foundation of the EventEngine pipeline: the EventDefinition
|
|
14
13
|
DSL and the shared schema-contract value objects, with no Rails dependency. Lightweight
|
|
@@ -20,14 +19,20 @@ executables: []
|
|
|
20
19
|
extensions: []
|
|
21
20
|
extra_rdoc_files: []
|
|
22
21
|
files:
|
|
22
|
+
- ".claude/agents/the_local-develop.md"
|
|
23
|
+
- ".claude/agents/the_local-info.md"
|
|
24
|
+
- ".claude/agents/the_local-install.md"
|
|
23
25
|
- CHANGELOG.md
|
|
26
|
+
- CLAUDE.md
|
|
24
27
|
- LICENSE.txt
|
|
25
28
|
- README.md
|
|
26
29
|
- Rakefile
|
|
27
30
|
- docs/examples.md
|
|
28
31
|
- lib/event_engine/definition.rb
|
|
29
32
|
- lib/event_engine/definition/configuration.rb
|
|
33
|
+
- lib/event_engine/definition/event_schema.rb
|
|
30
34
|
- lib/event_engine/definition/null_publisher.rb
|
|
35
|
+
- lib/event_engine/definition/schema_registry.rb
|
|
31
36
|
- lib/event_engine/definition/version.rb
|
|
32
37
|
- lib/event_engine/definition_loader.rb
|
|
33
38
|
- lib/event_engine/domain_pack_build.rb
|
|
@@ -38,12 +43,14 @@ files:
|
|
|
38
43
|
- lib/event_engine/event_definition/schemas.rb
|
|
39
44
|
- lib/event_engine/event_definition/validation.rb
|
|
40
45
|
- lib/event_engine/event_engine_helpers_writer.rb
|
|
41
|
-
- lib/event_engine/event_schema.rb
|
|
42
46
|
- lib/event_engine/lifecycle_definition.rb
|
|
43
|
-
- lib/event_engine/schema_registry.rb
|
|
44
47
|
- lib/event_engine/subject_registry.rb
|
|
45
|
-
- lib/tasks/
|
|
48
|
+
- lib/tasks/event_definition.rake
|
|
46
49
|
- sig/event_engine/definition.rbs
|
|
50
|
+
- the_local/agents/event_engine-event_definition-develop.md
|
|
51
|
+
- the_local/agents/event_engine-event_definition-info.md
|
|
52
|
+
- the_local/agents/event_engine-event_definition-install.md
|
|
53
|
+
- the_local/interface.yml
|
|
47
54
|
homepage: https://eventengine.co
|
|
48
55
|
licenses:
|
|
49
56
|
- MIT
|
|
@@ -54,7 +61,6 @@ metadata:
|
|
|
54
61
|
source_code_uri: https://github.com/DYB-Development/event_engine-event_definition
|
|
55
62
|
bug_tracker_uri: https://github.com/DYB-Development/event_engine-event_definition/issues
|
|
56
63
|
documentation_uri: https://github.com/DYB-Development/event_engine-event_definition#readme
|
|
57
|
-
post_install_message:
|
|
58
64
|
rdoc_options: []
|
|
59
65
|
require_paths:
|
|
60
66
|
- lib
|
|
@@ -69,8 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
69
75
|
- !ruby/object:Gem::Version
|
|
70
76
|
version: '0'
|
|
71
77
|
requirements: []
|
|
72
|
-
rubygems_version:
|
|
73
|
-
signing_key:
|
|
78
|
+
rubygems_version: 4.0.20
|
|
74
79
|
specification_version: 4
|
|
75
80
|
summary: Plain-Ruby event-definition contract for the EventEngine pipeline
|
|
76
81
|
test_files: []
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
module EventEngine
|
|
2
|
-
class EventSchema
|
|
3
|
-
class DuplicateEventNameError < StandardError; end
|
|
4
|
-
|
|
5
|
-
def self.define(&block)
|
|
6
|
-
schema = new
|
|
7
|
-
block.call(schema)
|
|
8
|
-
schema
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def initialize
|
|
12
|
-
@schemas_by_event = {}
|
|
13
|
-
@finalized = false
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def register(schema)
|
|
17
|
-
raise FrozenError, "EventSchema is finalized" if @finalized
|
|
18
|
-
key = key_for(schema.domain, schema.event_name)
|
|
19
|
-
version = schema.event_version
|
|
20
|
-
|
|
21
|
-
@schemas_by_event[key] ||= {}
|
|
22
|
-
guard_duplicate_event_name!(@schemas_by_event[key][version], schema)
|
|
23
|
-
@schemas_by_event[key][version] = schema
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def guard_duplicate_event_name!(existing, incoming)
|
|
27
|
-
return unless existing
|
|
28
|
-
|
|
29
|
-
raise DuplicateEventNameError,
|
|
30
|
-
"duplicate (domain, event_name) " \
|
|
31
|
-
"(#{incoming.domain.inspect}, #{incoming.event_name.inspect}): " \
|
|
32
|
-
"already registered at version #{existing.event_version.inspect}"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def events(domain: nil)
|
|
36
|
-
@schemas_by_event.keys
|
|
37
|
-
.select { |(schema_domain, _name)| domain.nil? || schema_domain == domain }
|
|
38
|
-
.map { |(_domain, event_name)| event_name }
|
|
39
|
-
.uniq
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def versions_for(event_name, domain: nil)
|
|
43
|
-
version_sets_for(event_name, domain).flat_map(&:keys).uniq.sort
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def schema_for(event_name, version, domain: nil)
|
|
47
|
-
set = version_sets_for(event_name, domain).find { |versions| versions.key?(version) }
|
|
48
|
-
set && set[version]
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def latest_for(event_name, domain: nil)
|
|
52
|
-
merged = version_sets_for(event_name, domain).reduce({}, :merge)
|
|
53
|
-
return nil if merged.empty?
|
|
54
|
-
merged[merged.keys.max]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def finalize!
|
|
58
|
-
@finalized = true
|
|
59
|
-
@schemas_by_event.each_value(&:freeze)
|
|
60
|
-
@schemas_by_event.freeze
|
|
61
|
-
freeze
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def schemas_by_event
|
|
65
|
-
@schemas_by_event
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
private
|
|
69
|
-
|
|
70
|
-
def key_for(domain, event_name)
|
|
71
|
-
[domain, event_name]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def version_sets_for(event_name, domain = nil)
|
|
75
|
-
@schemas_by_event.select do |(schema_domain, name), _versions|
|
|
76
|
-
name == event_name && (domain.nil? || schema_domain == domain)
|
|
77
|
-
end.values
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
module EventEngine
|
|
2
|
-
class SchemaRegistry
|
|
3
|
-
class UnknownEventError < StandardError; end
|
|
4
|
-
|
|
5
|
-
class RegistryFrozenError < StandardError; end
|
|
6
|
-
|
|
7
|
-
def initialize(event_schema = EventSchema.new)
|
|
8
|
-
@event_schema = event_schema
|
|
9
|
-
@loaded = false
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def register(schema)
|
|
13
|
-
@event_schema.register(schema)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def events
|
|
17
|
-
@event_schema.events
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def versions_for(event_name, domain: nil)
|
|
21
|
-
@event_schema.versions_for(event_name, domain: domain)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def load_from_schema!(schema)
|
|
25
|
-
raise RegistryFrozenError, "EventRegistry already loaded" if loaded?
|
|
26
|
-
@event_schema = schema
|
|
27
|
-
|
|
28
|
-
@loaded = true
|
|
29
|
-
self
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def reset!
|
|
33
|
-
@event_schema = {}
|
|
34
|
-
@loaded = false
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def schema(event_name, version: nil, domain: nil)
|
|
38
|
-
raise RegistryFrozenError, "EventRegistry not loaded" unless loaded?
|
|
39
|
-
|
|
40
|
-
schema =
|
|
41
|
-
if version
|
|
42
|
-
@event_schema.schema_for(event_name, version, domain: domain)
|
|
43
|
-
else
|
|
44
|
-
@event_schema.latest_for(event_name, domain: domain)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
unless schema
|
|
48
|
-
raise UnknownEventError,
|
|
49
|
-
"Unknown #{version ? "version #{version} for " : ""}event: #{event_name}"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
schema
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def latest_for(event_name, domain: nil)
|
|
56
|
-
@event_schema.latest_for(event_name, domain: domain)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def event_schema
|
|
60
|
-
@event_schema
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def finalize!
|
|
64
|
-
@event_schema.finalize!
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def loaded?
|
|
68
|
-
@loaded == true
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
require "event_engine/definition"
|
|
2
|
-
|
|
3
|
-
namespace :event_engine do
|
|
4
|
-
namespace :definition do
|
|
5
|
-
desc "Generate the pack's helper and schema.json from its EventDefinitions"
|
|
6
|
-
task :dump do
|
|
7
|
-
config = EventEngine::Definition.configuration
|
|
8
|
-
|
|
9
|
-
definitions = EventEngine::DefinitionLoader.load!(config.definitions_path)
|
|
10
|
-
|
|
11
|
-
EventEngine::DomainPackBuild.run(
|
|
12
|
-
definitions,
|
|
13
|
-
helper_path: config.helper_path,
|
|
14
|
-
root_module: config.root_module,
|
|
15
|
-
subject_registry: config.subject_registry
|
|
16
|
-
)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|