easyop 0.1.1 → 0.1.3
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 +159 -3
- data/README.md +287 -0
- data/lib/easyop/configuration.rb +8 -0
- data/lib/easyop/events/bus/active_support_notifications.rb +82 -0
- data/lib/easyop/events/bus/adapter.rb +119 -0
- data/lib/easyop/events/bus/custom.rb +51 -0
- data/lib/easyop/events/bus/memory.rb +64 -0
- data/lib/easyop/events/bus.rb +66 -0
- data/lib/easyop/events/event.rb +44 -0
- data/lib/easyop/events/registry.rb +128 -0
- data/lib/easyop/plugins/async.rb +12 -0
- data/lib/easyop/plugins/event_handlers.rb +76 -0
- data/lib/easyop/plugins/events.rb +162 -0
- data/lib/easyop/version.rb +1 -1
- data/lib/easyop.rb +11 -0
- metadata +10 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Easyop
|
|
4
|
+
module Plugins
|
|
5
|
+
# Emits domain events after an operation completes.
|
|
6
|
+
#
|
|
7
|
+
# Install on a base operation class to inherit into all subclasses:
|
|
8
|
+
#
|
|
9
|
+
# class ApplicationOperation
|
|
10
|
+
# include Easyop::Operation
|
|
11
|
+
# plugin Easyop::Plugins::Events
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# Then declare events on individual operations:
|
|
15
|
+
#
|
|
16
|
+
# class PlaceOrder < ApplicationOperation
|
|
17
|
+
# emits "order.placed", on: :success, payload: [:order_id, :total]
|
|
18
|
+
# emits "order.failed", on: :failure, payload: ->(ctx) { { error: ctx.error } }
|
|
19
|
+
# emits "order.attempted", on: :always
|
|
20
|
+
#
|
|
21
|
+
# def call
|
|
22
|
+
# ctx.order_id = Order.create!(ctx.to_h).id
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# Plugin options:
|
|
27
|
+
# bus: [Bus::Base, nil] per-class bus override (default: global registry bus)
|
|
28
|
+
# metadata: [Hash, Proc, nil] extra metadata merged into every event from this class
|
|
29
|
+
#
|
|
30
|
+
# `emits` DSL options:
|
|
31
|
+
# on: [:success (default), :failure, :always]
|
|
32
|
+
# payload: [Proc, Array, nil] Proc receives ctx; Array slices ctx keys; nil = full ctx.to_h
|
|
33
|
+
# guard: [Proc, nil] extra condition — event only fires if truthy
|
|
34
|
+
module Events
|
|
35
|
+
def self.install(base, bus: nil, metadata: nil, **_options)
|
|
36
|
+
base.extend(ClassMethods)
|
|
37
|
+
base.prepend(RunWrapper)
|
|
38
|
+
base.instance_variable_set(:@_events_bus, bus)
|
|
39
|
+
base.instance_variable_set(:@_events_metadata, metadata)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module ClassMethods
|
|
43
|
+
# Declare an event this operation emits after execution.
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# emits "order.placed", on: :success, payload: [:order_id]
|
|
47
|
+
# emits "order.failed", on: :failure, payload: ->(ctx) { { error: ctx.error } }
|
|
48
|
+
#
|
|
49
|
+
# @param name [String]
|
|
50
|
+
# @param on [Symbol] :success (default), :failure, or :always
|
|
51
|
+
# @param payload [Proc, Array, nil]
|
|
52
|
+
# @param guard [Proc, nil] optional condition — fires only when truthy
|
|
53
|
+
def emits(name, on: :success, payload: nil, guard: nil)
|
|
54
|
+
_emitted_events << { name: name.to_s, on: on, payload: payload, guard: guard }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @api private — inheritable list of emits declarations
|
|
58
|
+
def _emitted_events
|
|
59
|
+
@_emitted_events ||= _inherited_emitted_events
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @api private — bus for this class (falls back to superclass, then global registry)
|
|
63
|
+
def _events_bus
|
|
64
|
+
if instance_variable_defined?(:@_events_bus)
|
|
65
|
+
@_events_bus
|
|
66
|
+
elsif superclass.respond_to?(:_events_bus)
|
|
67
|
+
superclass._events_bus
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @api private — metadata for this class (falls back to superclass)
|
|
72
|
+
def _events_metadata
|
|
73
|
+
if instance_variable_defined?(:@_events_metadata)
|
|
74
|
+
@_events_metadata
|
|
75
|
+
elsif superclass.respond_to?(:_events_metadata)
|
|
76
|
+
superclass._events_metadata
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def _inherited_emitted_events
|
|
83
|
+
superclass.respond_to?(:_emitted_events) ? superclass._emitted_events.dup : []
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module RunWrapper
|
|
88
|
+
# Wraps _easyop_run to publish declared events in an ensure block.
|
|
89
|
+
# Events fire AFTER the operation completes — success, failure, or exception.
|
|
90
|
+
def _easyop_run(ctx, raise_on_failure:)
|
|
91
|
+
super
|
|
92
|
+
ensure
|
|
93
|
+
_events_publish_all(ctx)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def _events_publish_all(ctx)
|
|
99
|
+
declarations = self.class._emitted_events
|
|
100
|
+
return if declarations.empty?
|
|
101
|
+
|
|
102
|
+
bus = self.class._events_bus || Easyop::Events::Registry.bus
|
|
103
|
+
|
|
104
|
+
declarations.each do |decl|
|
|
105
|
+
next unless _events_should_fire?(decl, ctx)
|
|
106
|
+
|
|
107
|
+
event = _events_build_event(decl, ctx)
|
|
108
|
+
bus.publish(event)
|
|
109
|
+
rescue StandardError
|
|
110
|
+
# Individual publish failures must never crash the operation.
|
|
111
|
+
# Log if Rails is available.
|
|
112
|
+
_events_log_warning($ERROR_INFO)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def _events_should_fire?(decl, ctx)
|
|
117
|
+
case decl[:on]
|
|
118
|
+
when :success then return false unless ctx.success?
|
|
119
|
+
when :failure then return false unless ctx.failure?
|
|
120
|
+
when :always then nil # always fire
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
guard = decl[:guard]
|
|
124
|
+
guard ? guard.call(ctx) : true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def _events_build_event(decl, ctx)
|
|
128
|
+
Easyop::Events::Event.new(
|
|
129
|
+
name: decl[:name],
|
|
130
|
+
payload: _events_extract_payload(decl[:payload], ctx),
|
|
131
|
+
metadata: _events_build_metadata(ctx),
|
|
132
|
+
source: self.class.name
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def _events_extract_payload(payload_spec, ctx)
|
|
137
|
+
case payload_spec
|
|
138
|
+
when Proc then payload_spec.call(ctx)
|
|
139
|
+
when Array then ctx.slice(*payload_spec)
|
|
140
|
+
when nil then ctx.to_h
|
|
141
|
+
else payload_spec
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def _events_build_metadata(ctx)
|
|
146
|
+
meta = self.class._events_metadata
|
|
147
|
+
case meta
|
|
148
|
+
when Proc then meta.call(ctx)
|
|
149
|
+
when Hash then meta.dup
|
|
150
|
+
else {}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def _events_log_warning(err)
|
|
155
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
156
|
+
|
|
157
|
+
Rails.logger.warn "[EasyOp::Events] Failed to publish event: #{err.message}"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/lib/easyop/version.rb
CHANGED
data/lib/easyop.rb
CHANGED
|
@@ -18,6 +18,17 @@ require_relative "easyop/flow"
|
|
|
18
18
|
# require "easyop/plugins/recording"
|
|
19
19
|
# require "easyop/plugins/async"
|
|
20
20
|
|
|
21
|
+
# Domain event plugins — require together or individually:
|
|
22
|
+
# require "easyop/events/event"
|
|
23
|
+
# require "easyop/events/bus"
|
|
24
|
+
# require "easyop/events/bus/memory"
|
|
25
|
+
# require "easyop/events/bus/active_support_notifications"
|
|
26
|
+
# require "easyop/events/bus/custom"
|
|
27
|
+
# require "easyop/events/bus/adapter" # inherit this to build a custom bus
|
|
28
|
+
# require "easyop/events/registry"
|
|
29
|
+
# require "easyop/plugins/events"
|
|
30
|
+
# require "easyop/plugins/event_handlers"
|
|
31
|
+
|
|
21
32
|
module Easyop
|
|
22
33
|
# Convenience: inherit from this instead of including Easyop::Operation
|
|
23
34
|
# when you want a common base class for all your operations.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: easyop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pawel Niemczyk
|
|
@@ -53,12 +53,21 @@ files:
|
|
|
53
53
|
- lib/easyop.rb
|
|
54
54
|
- lib/easyop/configuration.rb
|
|
55
55
|
- lib/easyop/ctx.rb
|
|
56
|
+
- lib/easyop/events/bus.rb
|
|
57
|
+
- lib/easyop/events/bus/active_support_notifications.rb
|
|
58
|
+
- lib/easyop/events/bus/adapter.rb
|
|
59
|
+
- lib/easyop/events/bus/custom.rb
|
|
60
|
+
- lib/easyop/events/bus/memory.rb
|
|
61
|
+
- lib/easyop/events/event.rb
|
|
62
|
+
- lib/easyop/events/registry.rb
|
|
56
63
|
- lib/easyop/flow.rb
|
|
57
64
|
- lib/easyop/flow_builder.rb
|
|
58
65
|
- lib/easyop/hooks.rb
|
|
59
66
|
- lib/easyop/operation.rb
|
|
60
67
|
- lib/easyop/plugins/async.rb
|
|
61
68
|
- lib/easyop/plugins/base.rb
|
|
69
|
+
- lib/easyop/plugins/event_handlers.rb
|
|
70
|
+
- lib/easyop/plugins/events.rb
|
|
62
71
|
- lib/easyop/plugins/instrumentation.rb
|
|
63
72
|
- lib/easyop/plugins/recording.rb
|
|
64
73
|
- lib/easyop/plugins/transactional.rb
|