pg_eventstore 2.0.0 → 3.0.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/.rubocop.yml +10 -1
- data/CHANGELOG.md +186 -20
- data/README.md +25 -15
- data/db/migrations/10_setup_pg_cron.rb +1 -23
- data/db/migrations/15_create_streams_global_index.sql +17 -0
- data/db/migrations/16_populate_streams_global_index.rb +150 -0
- data/db/migrations/17_create_events_global_index.sql +62 -0
- data/db/migrations/18_populate_events_global_index.rb +124 -0
- data/db/migrations/19_populate_events_global_index_p2.rb +25 -0
- data/db/migrations/20_migrate_subscriptions_current_position.sql +19 -0
- data/db/migrations/21_create_partitions_stats.sql +2 -0
- data/db/migrations/22_drop_streams_sql_view.sql +2 -0
- data/db/migrations/23_adjust_partitions_indexes.sql +5 -0
- data/db/migrations/24_drop_legacy_events_indexes.sql +3 -0
- data/db/migrations/25_add_parent_partitions_info_to_partitions.sql +16 -0
- data/db/migrations/26_change_events_stream_revision_type.rb +35 -0
- data/db/migrations/27_drop_events_horizon.sql +3 -0
- data/db/migrations/28_create_maintenance_tasks.sql +8 -0
- data/db/migrations/29_create_event_markers.sql +35 -0
- data/db/migrations/30_improve_partitions_search_in_admin_ui.rb +24 -0
- data/db/migrations/31_drop_events_id_default_value.sql +1 -0
- data/db/migrations/32_create_parittions_triggers.sql +101 -0
- data/docs/AGENTS.fragment.md +696 -0
- data/docs/admin_ui.md +3 -1
- data/docs/appending_events.md +110 -49
- data/docs/configuration.md +69 -36
- data/docs/event_tracing.md +55 -0
- data/docs/events_and_streams.md +54 -30
- data/docs/how_it_works.md +57 -16
- data/docs/linking_events.md +2 -1
- data/docs/maintenance.md +14 -5
- data/docs/multiple_commands.md +17 -8
- data/docs/reading_events.md +208 -43
- data/docs/reading_streams.md +50 -0
- data/docs/replication.md +99 -0
- data/docs/subscriptions.md +165 -35
- data/docs/writing_middleware.md +25 -8
- data/ext/pg_eventstore_ext/extconf.rb +5 -0
- data/ext/pg_eventstore_ext/pg_eventstore_ext.c +74 -0
- data/lib/pg_eventstore/async_runner.rb +63 -0
- data/lib/pg_eventstore/basic_config.rb +15 -0
- data/lib/pg_eventstore/chunks/chunk.rb +31 -0
- data/lib/pg_eventstore/chunks/read_api_events_index_chunk.rb +83 -0
- data/lib/pg_eventstore/chunks/replica_events_index_chunk.rb +35 -0
- data/lib/pg_eventstore/chunks/repository.rb +73 -0
- data/lib/pg_eventstore/chunks/subscription_checkpoint_chunk.rb +48 -0
- data/lib/pg_eventstore/chunks/subscription_events_index_chunk.rb +99 -0
- data/lib/pg_eventstore/chunks.rb +14 -0
- data/lib/pg_eventstore/client.rb +163 -50
- data/lib/pg_eventstore/commands/append.rb +62 -43
- data/lib/pg_eventstore/commands/delete_event.rb +7 -8
- data/lib/pg_eventstore/commands/delete_stream.rb +3 -3
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +9 -7
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +12 -7
- data/lib/pg_eventstore/commands/link_to.rb +5 -5
- data/lib/pg_eventstore/commands/read.rb +23 -5
- data/lib/pg_eventstore/commands/read_grouped.rb +46 -0
- data/lib/pg_eventstore/commands/read_streams.rb +18 -0
- data/lib/pg_eventstore/commands/read_streams_paginated.rb +56 -0
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +4 -4
- data/lib/pg_eventstore/commands/revision_check/current_revision.rb +51 -0
- data/lib/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rb +46 -0
- data/lib/pg_eventstore/commands/revision_check/expected_revision.rb +135 -0
- data/lib/pg_eventstore/commands/revision_check/stream_revision_check.rb +98 -0
- data/lib/pg_eventstore/commands/revision_check/stream_revision_comparison.rb +36 -0
- data/lib/pg_eventstore/commands/stream_revision.rb +14 -0
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +4 -4
- data/lib/pg_eventstore/commands.rb +4 -2
- data/lib/pg_eventstore/config.rb +30 -9
- data/lib/pg_eventstore/connection.rb +21 -2
- data/lib/pg_eventstore/errors.rb +136 -10
- data/lib/pg_eventstore/event.rb +34 -2
- data/lib/pg_eventstore/event_deserializer.rb +5 -4
- data/lib/pg_eventstore/event_global_index.rb +118 -0
- data/lib/pg_eventstore/event_marker.rb +16 -0
- data/lib/pg_eventstore/event_marker_index.rb +25 -0
- data/lib/pg_eventstore/event_serializer.rb +6 -0
- data/lib/pg_eventstore/extensions/acts_as_configurable.rb +88 -0
- data/lib/pg_eventstore/extensions/options_defaults.rb +25 -0
- data/lib/pg_eventstore/extensions/options_extension.rb +2 -2
- data/lib/pg_eventstore/feature_marker.rb +18 -0
- data/lib/pg_eventstore/maintenance.rb +11 -6
- data/lib/pg_eventstore/middleware/event_tracing.rb +53 -0
- data/lib/pg_eventstore/partition.rb +6 -0
- data/lib/pg_eventstore/pg_connection.rb +70 -14
- data/lib/pg_eventstore/queries/event_marker_queries.rb +74 -0
- data/lib/pg_eventstore/queries/event_queries.rb +7 -93
- data/lib/pg_eventstore/queries/events_global_index_queries.rb +131 -0
- data/lib/pg_eventstore/queries/index_filtering_queries.rb +179 -0
- data/lib/pg_eventstore/queries/links_resolver.rb +4 -4
- data/lib/pg_eventstore/queries/maintenance_queries.rb +159 -42
- data/lib/pg_eventstore/queries/partition_queries.rb +45 -103
- data/lib/pg_eventstore/queries/replica_queries.rb +110 -0
- data/lib/pg_eventstore/queries/streams_global_index_queries.rb +163 -0
- data/lib/pg_eventstore/queries/transaction_queries.rb +52 -10
- data/lib/pg_eventstore/queries.rb +29 -1
- data/lib/pg_eventstore/query_builders/basic_filtering.rb +38 -5
- data/lib/pg_eventstore/query_builders/event_markers_filtering.rb +47 -0
- data/lib/pg_eventstore/query_builders/event_markers_index_filtering.rb +275 -0
- data/lib/pg_eventstore/query_builders/event_subscription_positions_filtering.rb +37 -0
- data/lib/pg_eventstore/query_builders/events_filtering.rb +15 -164
- data/lib/pg_eventstore/query_builders/events_global_index_filtering.rb +227 -0
- data/lib/pg_eventstore/query_builders/filters/collection.rb +324 -0
- data/lib/pg_eventstore/query_builders/filters/event_type_filter.rb +37 -0
- data/lib/pg_eventstore/query_builders/filters/filter_row.rb +54 -0
- data/lib/pg_eventstore/query_builders/filters/marker_filter.rb +20 -0
- data/lib/pg_eventstore/query_builders/filters/marker_filter_row.rb +49 -0
- data/lib/pg_eventstore/query_builders/filters/stream_filter.rb +48 -0
- data/lib/pg_eventstore/query_builders/index_based_events_filtering.rb +260 -0
- data/lib/pg_eventstore/query_builders/partitions_filtering.rb +175 -51
- data/lib/pg_eventstore/query_builders/read_cursor/stream_cursor.rb +122 -0
- data/lib/pg_eventstore/query_builders/streams_global_index_filtering.rb +83 -0
- data/lib/pg_eventstore/query_builders/subscription_events_filtering.rb +79 -0
- data/lib/pg_eventstore/query_strategy/async.rb +64 -0
- data/lib/pg_eventstore/query_strategy/foreground.rb +25 -0
- data/lib/pg_eventstore/query_strategy.rb +19 -0
- data/lib/pg_eventstore/raw_event.rb +46 -0
- data/lib/pg_eventstore/rspec/test_helpers.rb +21 -8
- data/lib/pg_eventstore/sql_builder.rb +87 -8
- data/lib/pg_eventstore/stream.rb +13 -16
- data/lib/pg_eventstore/stream_global_index.rb +28 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +5 -17
- data/lib/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rb +36 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +12 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +21 -7
- data/lib/pg_eventstore/subscriptions/events_processor.rb +20 -18
- data/lib/pg_eventstore/subscriptions/events_processor_consumer/multiple.rb +63 -0
- data/lib/pg_eventstore/subscriptions/events_processor_consumer/replica.rb +68 -0
- data/lib/pg_eventstore/subscriptions/events_processor_consumer/single.rb +62 -0
- data/lib/pg_eventstore/subscriptions/events_processor_consumer.rb +44 -0
- data/lib/pg_eventstore/subscriptions/events_subscription_position_worker.rb +68 -0
- data/lib/pg_eventstore/subscriptions/queries/event_subscription_position_queries.rb +177 -0
- data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +78 -54
- data/lib/pg_eventstore/subscriptions/replica_subscription_handler.rb +140 -0
- data/lib/pg_eventstore/subscriptions/replica_subscription_runner.rb +9 -0
- data/lib/pg_eventstore/subscriptions/subscription.rb +2 -1
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/collection.rb +36 -0
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/index_read_strategy.rb +82 -0
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/replication_strategy.rb +75 -0
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategy.rb +31 -0
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +24 -0
- data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +4 -2
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +20 -9
- data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +1 -1
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +9 -18
- data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +0 -12
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +74 -14
- data/lib/pg_eventstore/{subscriptions/synchronized_array.rb → synchronized_array.rb} +15 -0
- data/lib/pg_eventstore/tasks/setup.rake +2 -2
- data/lib/pg_eventstore/utils.rb +31 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +100 -16
- data/lib/pg_eventstore/web/paginator/base_collection.rb +5 -1
- data/lib/pg_eventstore/web/paginator/event_types_collection.rb +14 -4
- data/lib/pg_eventstore/web/paginator/events_collection.rb +29 -33
- data/lib/pg_eventstore/web/paginator/helpers.rb +16 -9
- data/lib/pg_eventstore/web/paginator/markers_collection.rb +48 -0
- data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +13 -2
- data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +21 -11
- data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +18 -4
- data/lib/pg_eventstore/web/paginator/streams_collection.rb +95 -0
- data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +49 -22
- data/lib/pg_eventstore/web/subscriptions/set_collection.rb +1 -1
- data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +1 -1
- data/lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb +1 -1
- data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb +1 -1
- data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -9
- data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +18 -5
- data/lib/pg_eventstore/web/views/home/partials/events.erb +38 -1
- data/lib/pg_eventstore/web/views/home/partials/markers_filter.erb +18 -0
- data/lib/pg_eventstore/web/views/layouts/application.erb +6 -0
- data/lib/pg_eventstore/web/views/streams/index.erb +110 -0
- data/lib/pg_eventstore/web/views/streams/partials/streams.erb +11 -0
- data/lib/pg_eventstore/web/views/subscriptions/index.erb +4 -1
- data/lib/pg_eventstore/web.rb +2 -0
- data/lib/pg_eventstore.rb +23 -60
- data/pg_eventstore.gemspec +6 -4
- data/sig/interfaces/_raw_events_handler.rbs +3 -0
- data/sig/interfaces/subscription_handler.rbs +1 -1
- data/sig/pg/result.rbs +9 -0
- data/sig/pg_eventstore/async_runner.rbs +13 -0
- data/sig/pg_eventstore/basic_config.rbs +9 -0
- data/sig/pg_eventstore/chunks/chunk.rbs +13 -0
- data/sig/pg_eventstore/chunks/read_api_events_index_chunk.rbs +30 -0
- data/sig/pg_eventstore/chunks/replica_events_index_chunk.rbs +11 -0
- data/sig/pg_eventstore/chunks/repository.rbs +22 -0
- data/sig/pg_eventstore/chunks/subscription_checkpoint_chunk.rbs +18 -0
- data/sig/pg_eventstore/chunks/subscription_events_index_chunk.rbs +37 -0
- data/sig/pg_eventstore/client.rbs +40 -42
- data/sig/pg_eventstore/commands/append.rbs +11 -25
- data/sig/pg_eventstore/commands/event_modifiers/prepare_link_event.rbs +9 -12
- data/sig/pg_eventstore/commands/event_modifiers/prepare_regular_event.rbs +5 -4
- data/sig/pg_eventstore/commands/link_to.rbs +6 -11
- data/sig/pg_eventstore/commands/read.rbs +2 -5
- data/sig/pg_eventstore/commands/read_grouped.rbs +7 -0
- data/sig/pg_eventstore/commands/read_streams.rbs +7 -0
- data/sig/pg_eventstore/commands/read_streams_paginated.rbs +16 -0
- data/sig/pg_eventstore/commands/regular_stream_read_paginated.rbs +9 -17
- data/sig/pg_eventstore/commands/revision_check/current_revision.rbs +25 -0
- data/sig/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rbs +14 -0
- data/sig/pg_eventstore/commands/revision_check/expected_revision.rbs +67 -0
- data/sig/pg_eventstore/commands/revision_check/stream_revision_check.rbs +20 -0
- data/sig/pg_eventstore/commands/revision_check/stream_revision_comparison.rbs +9 -0
- data/sig/pg_eventstore/commands/stream_revision.rbs +7 -0
- data/sig/pg_eventstore/commands/system_stream_read_paginated.rbs +9 -14
- data/sig/pg_eventstore/config.rbs +15 -6
- data/sig/pg_eventstore/connection.rbs +11 -18
- data/sig/pg_eventstore/errors.rbs +64 -34
- data/sig/pg_eventstore/event.rbs +16 -2
- data/sig/pg_eventstore/event_deserializer.rbs +5 -10
- data/sig/pg_eventstore/event_global_index.rbs +60 -0
- data/sig/pg_eventstore/event_marker.rbs +9 -0
- data/sig/pg_eventstore/event_marker_index.rbs +12 -0
- data/sig/pg_eventstore/extensions/acts_as_configurable.rbs +29 -0
- data/sig/pg_eventstore/extensions/options_defaults.rbs +7 -0
- data/sig/pg_eventstore/feature_marker.rbs +10 -0
- data/sig/pg_eventstore/maintenance.rbs +12 -8
- data/sig/pg_eventstore/middleware/event_trace.rbs +14 -0
- data/sig/pg_eventstore/partition.rbs +2 -4
- data/sig/pg_eventstore/pg_connection.rbs +8 -0
- data/sig/pg_eventstore/queries/event_marker_queries.rbs +21 -0
- data/sig/pg_eventstore/queries/event_queries.rbs +4 -36
- data/sig/pg_eventstore/queries/events_global_index_queries.rbs +37 -0
- data/sig/pg_eventstore/queries/index_filtering_queries.rbs +50 -0
- data/sig/pg_eventstore/queries/links_resolver.rbs +5 -9
- data/sig/pg_eventstore/queries/maintenance_queries.rbs +12 -9
- data/sig/pg_eventstore/queries/partition_queries.rbs +32 -67
- data/sig/pg_eventstore/queries/replica_queries.rbs +31 -0
- data/sig/pg_eventstore/queries/streams_global_index_queries.rbs +39 -0
- data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -0
- data/sig/pg_eventstore/queries.rbs +11 -21
- data/sig/pg_eventstore/query_builders/basic_filtering.rbs +10 -3
- data/sig/pg_eventstore/query_builders/event_markers_filtering.rbs +17 -0
- data/sig/pg_eventstore/query_builders/event_markers_index_filtering.rbs +69 -0
- data/sig/pg_eventstore/query_builders/event_subscription_positions_filtering.rbs +15 -0
- data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +9 -51
- data/sig/pg_eventstore/query_builders/events_global_index_filtering.rbs +68 -0
- data/sig/pg_eventstore/query_builders/filters/collection.rbs +84 -0
- data/sig/pg_eventstore/query_builders/filters/event_type_filter.rbs +19 -0
- data/sig/pg_eventstore/query_builders/filters/filter_row.rbs +21 -0
- data/sig/pg_eventstore/query_builders/filters/marker_filter.rbs +13 -0
- data/sig/pg_eventstore/query_builders/filters/marker_filter_row.rbs +19 -0
- data/sig/pg_eventstore/query_builders/filters/stream_filter.rbs +24 -0
- data/sig/pg_eventstore/query_builders/index_based_events_filtering.rbs +60 -0
- data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +29 -10
- data/sig/pg_eventstore/query_builders/read_cursor/stream_cursor.rbs +47 -0
- data/sig/pg_eventstore/query_builders/streams_global_index_filtering.rbs +27 -0
- data/sig/pg_eventstore/query_builders/subscription_events_filtering.rbs +26 -0
- data/sig/pg_eventstore/query_strategy/async.rbs +17 -0
- data/sig/pg_eventstore/query_strategy/foreground.rbs +11 -0
- data/sig/pg_eventstore/query_strategy.rbs +7 -0
- data/sig/pg_eventstore/raw_event.rbs +20 -0
- data/sig/pg_eventstore/sql_builder.rbs +32 -10
- data/sig/pg_eventstore/stream.rbs +10 -18
- data/sig/pg_eventstore/stream_global_index.rbs +14 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +5 -4
- data/sig/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rbs +9 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +17 -12
- data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +17 -6
- data/sig/pg_eventstore/subscriptions/event_subscription_position_queries.rbs +30 -0
- data/sig/pg_eventstore/subscriptions/events_processor.rbs +12 -5
- data/sig/pg_eventstore/subscriptions/events_processor_consumer/multiple.rbs +17 -0
- data/sig/pg_eventstore/subscriptions/events_processor_consumer/replica.rbs +17 -0
- data/sig/pg_eventstore/subscriptions/events_processor_consumer/single.rbs +17 -0
- data/sig/pg_eventstore/subscriptions/events_processor_consumer.rbs +14 -0
- data/sig/pg_eventstore/subscriptions/events_subscription_position_worker.rbs +28 -0
- data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +7 -39
- data/sig/pg_eventstore/subscriptions/replica_subscription_handler.rbs +33 -0
- data/sig/pg_eventstore/subscriptions/replica_subscription_runner.rbs +4 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/collection.rbs +14 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/index_read_strategy.rbs +23 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/replication_strategy.rbs +23 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy.rbs +11 -0
- data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +13 -10
- data/sig/pg_eventstore/subscriptions/subscription_handler_performance.rbs +1 -1
- data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +13 -2
- data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +1 -8
- data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +4 -6
- data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +29 -43
- data/sig/pg_eventstore/synchronized_array.rbs +4 -0
- data/sig/pg_eventstore/utils.rbs +10 -11
- data/sig/pg_eventstore/web/application.rbs +13 -3
- data/sig/pg_eventstore/web/paginator/base_collection.rbs +2 -0
- data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +4 -0
- data/sig/pg_eventstore/web/paginator/events_collection.rbs +11 -11
- data/sig/pg_eventstore/web/paginator/helpers.rbs +6 -15
- data/sig/pg_eventstore/web/paginator/markers_collection.rbs +13 -0
- data/sig/pg_eventstore/web/paginator/stream_contexts_collection.rbs +4 -0
- data/sig/pg_eventstore/web/paginator/stream_names_collection.rbs +4 -0
- data/sig/pg_eventstore/web/paginator/streams_collection.rbs +30 -0
- data/sig/pg_eventstore.rbs +8 -26
- metadata +175 -20
- data/Dockerfile +0 -3
- data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +0 -69
- data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +0 -31
- data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +0 -78
- data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +0 -15
- data/sig/pg_eventstore/commands/all_stream_read_grouped.rbs +0 -16
- data/sig/pg_eventstore/commands/regular_stream_read_grouped.rbs +0 -8
- data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +0 -19
- data/sig/pg_eventstore/subscriptions/synchronized_array.rbs +0 -4
- /data/sig/interfaces/{_raw_event_handler.rbs → raw_event_handler.rbs} +0 -0
data/docs/admin_ui.md
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
## Authorization
|
|
12
12
|
|
|
13
|
-
Admin UI is implemented as a rack application. It doesn't have any built-in authentication/authorization mechanism - it
|
|
13
|
+
Admin UI is implemented as a rack application. It doesn't have any built-in authentication/authorization mechanism - it
|
|
14
|
+
is your responsibility to take care of it. Admin UI tries to look for `:admin_web_ui` config with a fallback to
|
|
15
|
+
`:default` config. Thus, you can setup Admin UI-specific config, e.g. without some middlewares or so. Example:
|
|
14
16
|
|
|
15
17
|
```ruby
|
|
16
18
|
PgEventstore.configure(name: :admin_web_ui) do |config|
|
data/docs/appending_events.md
CHANGED
|
@@ -1,67 +1,51 @@
|
|
|
1
1
|
# Appending Events
|
|
2
2
|
|
|
3
|
-
## Append
|
|
3
|
+
## Append event
|
|
4
4
|
|
|
5
|
-
The easiest way to append an event is to create an event object and a stream object and call the client's
|
|
5
|
+
The easiest way to append an event is to create an event object and a stream object and call the client's
|
|
6
|
+
`#append_to_stream` method.
|
|
6
7
|
|
|
7
8
|
```ruby
|
|
8
|
-
require 'securerandom'
|
|
9
|
-
|
|
10
9
|
class SomethingHappened < PgEventstore::Event
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
event = SomethingHappened.new(data: { user_id:
|
|
14
|
-
stream = PgEventstore::Stream.new(context: '
|
|
12
|
+
event = SomethingHappened.new(data: { user_id: '1', title: "Something happened" })
|
|
13
|
+
stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
|
|
15
14
|
PgEventstore.client.append_to_stream(stream, event)
|
|
16
|
-
# => #<SomethingHappened:
|
|
15
|
+
# => #<SomethingHappened:0x000077c654b8f8c0 @readonly=Set[], @id="ecab1317-f2ae-4fc0-ad16-b820cd6fe053", @type="SomethingHappened", @global_position=133486960, @stream=#<PgEventstore::Stream:0x000077c6549e3990 @context="FooCtx", @stream_name="Foo", @stream_id="1", @stream_revision=nil, @starting_position=nil>, @stream_revision=16024, @data={"title" => "Something happened", "user_id" => "1"}, @markers=[], @metadata={}, @link_global_position=nil, @link_partition_id=nil, @link=nil, @created_at=2026-07-08 10:25:12.721906 UTC>
|
|
17
16
|
```
|
|
18
17
|
|
|
19
18
|
## Appending multiple events
|
|
20
19
|
|
|
21
|
-
You can pass an array of events to the `#append_to_stream` method. This way events will be appended one-by-one. **This
|
|
20
|
+
You can pass an array of events to the `#append_to_stream` method. This way events will be appended one-by-one. **This
|
|
21
|
+
operation is atomic and it guarantees that events are added to the stream in the given order.**
|
|
22
22
|
|
|
23
23
|
```ruby
|
|
24
|
-
require 'securerandom'
|
|
25
|
-
|
|
26
24
|
class SomethingHappened < PgEventstore::Event
|
|
27
25
|
end
|
|
28
26
|
|
|
29
|
-
event1 = SomethingHappened.new(data: { user_id:
|
|
30
|
-
event2 = SomethingHappened.new(data: { user_id:
|
|
31
|
-
stream = PgEventstore::Stream.new(context: '
|
|
27
|
+
event1 = SomethingHappened.new(data: { user_id: '1', title: "Something happened 1" })
|
|
28
|
+
event2 = SomethingHappened.new(data: { user_id: '1', title: "Something happened 2" })
|
|
29
|
+
stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
|
|
32
30
|
PgEventstore.client.append_to_stream(stream, [event1, event2])
|
|
33
31
|
```
|
|
34
32
|
|
|
35
|
-
### Duplicated event id
|
|
36
|
-
|
|
37
|
-
If two events with the same id are appended to any stream - `pg_eventstore` will only append one event, and the second command will raise an error.
|
|
38
|
-
|
|
39
|
-
```ruby
|
|
40
|
-
class SomethingHappened < PgEventstore::Event
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
event = SomethingHappened.new(id: SecureRandom.uuid)
|
|
44
|
-
stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
|
|
45
|
-
PgEventstore.client.append_to_stream(stream, event)
|
|
46
|
-
# Raises PG::UniqueViolation error
|
|
47
|
-
PgEventstore.client.append_to_stream(stream, event)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
33
|
## Handling concurrency
|
|
51
34
|
|
|
52
|
-
When appending events to a stream you can supply a stream state or stream revision. You can use this to tell
|
|
35
|
+
When appending events to a stream you can supply a stream state or stream revision. You can use this to tell
|
|
36
|
+
`pg_eventstore` what state or version you expect the stream to be in when you append. If the stream isn't in that state
|
|
37
|
+
then an exception will be thrown.
|
|
53
38
|
|
|
54
|
-
For example if we try to append two records expecting both times that the stream doesn't exist we will get an exception
|
|
39
|
+
For example if we try to append two records expecting both times that the stream doesn't exist we will get an exception
|
|
40
|
+
on the second:
|
|
55
41
|
|
|
56
42
|
```ruby
|
|
57
|
-
require 'securerandom'
|
|
58
|
-
|
|
59
43
|
class SomethingHappened < PgEventstore::Event
|
|
60
44
|
end
|
|
61
45
|
|
|
62
46
|
event1 = SomethingHappened.new(data: { foo: :bar })
|
|
63
47
|
event2 = SomethingHappened.new(data: { bar: :baz })
|
|
64
|
-
stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id:
|
|
48
|
+
stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id: '1')
|
|
65
49
|
|
|
66
50
|
# Successfully appends an event
|
|
67
51
|
PgEventstore.client.append_to_stream(stream, event1, options: { expected_revision: :no_stream })
|
|
@@ -76,15 +60,14 @@ Here are possible values of `:expected_revision` option:
|
|
|
76
60
|
- `:stream_exists`. Expects a stream to be present when appending an event
|
|
77
61
|
- a revision number(Integer). Expects a stream to be in the given revision.
|
|
78
62
|
|
|
79
|
-
This check can be used to implement optimistic concurrency. When you retrieve a stream, you take note of the current
|
|
63
|
+
This check can be used to implement optimistic concurrency. When you retrieve a stream, you take note of the current
|
|
64
|
+
version number, then when you save it back you can determine if somebody else has modified the record in the meantime.
|
|
80
65
|
|
|
81
66
|
```ruby
|
|
82
|
-
require 'securerandom'
|
|
83
|
-
|
|
84
67
|
class SomethingHappened < PgEventstore::Event
|
|
85
68
|
end
|
|
86
69
|
|
|
87
|
-
stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id:
|
|
70
|
+
stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id: '1')
|
|
88
71
|
event1 = SomethingHappened.new(data: { foo: :bar })
|
|
89
72
|
event2 = SomethingHappened.new(data: { bar: :baz })
|
|
90
73
|
|
|
@@ -99,20 +82,95 @@ PgEventstore.client.append_to_stream(stream, event2, options: { expected_revisio
|
|
|
99
82
|
PgEventstore.client.append_to_stream(stream, event2, options: { expected_revision: revision })
|
|
100
83
|
```
|
|
101
84
|
|
|
102
|
-
###
|
|
85
|
+
### Dynamic Consistency Boundaries support
|
|
86
|
+
|
|
87
|
+
You can validate a revision of separate event type(s) when publishing an event. This allows you to enforce consistency
|
|
88
|
+
of a specific event type(s) instead the consistency of the whole stream. To do so, you have to supply a
|
|
89
|
+
<event type>-to-<event revision> map as a value of `:expected_revion` option. Available per-type expected revisions:
|
|
90
|
+
|
|
91
|
+
- `:any`. Doesn't perform any checks. This is the default.
|
|
92
|
+
- `:no_event`. Expects an event to be absent when appending an event
|
|
93
|
+
- `:event_exists`. Expects an event to be present when appending an event
|
|
94
|
+
- a revision number(Integer). Expects an event to be in the given revision.
|
|
95
|
+
|
|
96
|
+
For example, next append command succeeds only if there is no `Foo` event, `Bar` event has revision `1` and `Baz` event
|
|
97
|
+
exists with any revision:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
|
|
101
|
+
event = PgEventstore::Event.new(data: { foo: :bar })
|
|
102
|
+
PgEventstore.client.append_to_stream(
|
|
103
|
+
stream, event, options: { expected_revision: { 'Foo' => :no_event, 'Bar' => 1, 'Baz' => :event_exists } }
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If event type(s) revision validation fails - `PgEventstore::WrongExpectedTypesRevisionError` exception is risen. Please
|
|
108
|
+
note, that this is different exception class comparing to the one which is present when stream revision validation
|
|
109
|
+
fails(`PgEventstore::WrongExpectedRevisionError`).
|
|
110
|
+
|
|
111
|
+
## Event markers
|
|
112
|
+
|
|
113
|
+
You can assign multiple markers(strings) to an event. This is multipurpose feature which may have application-specific
|
|
114
|
+
meaning, like observability or can be used as an additional constraint to validate event type revision as a part of
|
|
115
|
+
Dynamic Consistency Boundaries support.
|
|
116
|
+
|
|
117
|
+
### Publishing event with markers
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
|
|
121
|
+
event = PgEventstore::Event.new(markers: %w[foo bar])
|
|
122
|
+
PgEventstore.client.append_to_stream(stream, event)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Refer to [Read API docs](reading_events.md#filtering-events-by-markers) to find out how to filter events using markers.
|
|
126
|
+
|
|
127
|
+
### Dynamic Consistency Boundaries and markers
|
|
128
|
+
|
|
129
|
+
In addition to [per-event type validation](appending_events.md#dynamic-consistency-boundaries-support) - you can specify
|
|
130
|
+
a set of markers the specific event type may contain in order to pass revision validation. The available per-type
|
|
131
|
+
revisions are the same(`:any`, `:no_event`, `:event_exists` or number), but the syntax is a bit different. The example
|
|
132
|
+
bellow succeeds if there is no `Foo` event yet with either `'foo'` **or** `'bar'` marker:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
|
|
136
|
+
event = PgEventstore::Event.new(type: 'Foo', markers: ['bar'])
|
|
137
|
+
PgEventstore.client.append_to_stream(
|
|
138
|
+
stream, event, options: { expected_revision: { 'Foo' => { expected_revision: :no_event, markers: %w[foo bar] } } }
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Please note, there is no possibility to ensure the event has both - `'foo'` and `'bar'` markers. If you want to build
|
|
143
|
+
the logic that requires to differentiate between events with `'foo'`, `'bar'` and both markers - you have to
|
|
144
|
+
additionally mark such events with third marker. For example, you can have `'foobar'` marker which is used for events
|
|
145
|
+
with both `'foo'` and `'bar'` markers:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
|
|
149
|
+
event = PgEventstore::Event.new(type: 'Foo', markers: %w[foo bar foobar])
|
|
150
|
+
PgEventstore.client.append_to_stream(
|
|
151
|
+
stream, event, options: { expected_revision: { 'Foo' => { expected_revision: :no_event, markers: %w[foobar] } } }
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## What to do when a WrongExpectedRevisionError or WrongExpectedTypesRevisionError error is risen?
|
|
103
156
|
|
|
104
157
|
Imagine the following scenario:
|
|
158
|
+
|
|
105
159
|
1. You load events of a stream to build the state of your business object represented by the stream.
|
|
106
160
|
2. You check your business rules to see if you can change that object's state the way you want to change it.
|
|
107
161
|
3. If no business rules have been violated, you have the go to publish the event representing the state change.
|
|
108
|
-
4. To make sure the new event will follow the last event you used to build your object state, you retrieve that last
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
4. To make sure the new event will follow the last event you used to build your object state, you retrieve that last
|
|
163
|
+
event's revision and increase it by one. You now have the expected revision for the event to be published.
|
|
164
|
+
5. You publish the event but retrieve a `WrongExpectedRevisionError`. This means another process has appended an event
|
|
165
|
+
to the same stream, after you were loading your business object, while you were checking your business rules.
|
|
166
|
+
6. Now you need to repeat the process: load your business objects from the updated events stream, apply your business
|
|
167
|
+
rules and if there is still no violation, try to append the event with the updated stream revision. You can do this
|
|
168
|
+
procedure until the event is published or a maximum number of retries has been reached.
|
|
111
169
|
|
|
112
|
-
The following example shows the described retry procedure, with a simple business rule that does not allow adding an
|
|
170
|
+
The following example shows the described retry procedure, with a simple business rule that does not allow adding an
|
|
171
|
+
event after a `UserRemoved` event:
|
|
113
172
|
|
|
114
173
|
```ruby
|
|
115
|
-
require 'securerandom'
|
|
116
174
|
class UserAboutMeChanged < PgEventstore::Event
|
|
117
175
|
end
|
|
118
176
|
|
|
@@ -121,7 +179,7 @@ end
|
|
|
121
179
|
|
|
122
180
|
def latest_event(stream)
|
|
123
181
|
PgEventstore.client.read(stream, options: { max_count: 1, direction: 'Backwards' }).first
|
|
124
|
-
rescue PgEventstore::StreamNotFoundError
|
|
182
|
+
rescue PgEventstore::StreamNotFoundError
|
|
125
183
|
end
|
|
126
184
|
|
|
127
185
|
def publish_event(stream, event)
|
|
@@ -138,10 +196,10 @@ def publish_event(stream, event)
|
|
|
138
196
|
retries_count += 1
|
|
139
197
|
raise if retries_count > 3
|
|
140
198
|
retry
|
|
141
|
-
end
|
|
199
|
+
end
|
|
142
200
|
end
|
|
143
201
|
|
|
144
|
-
stream = PgEventstore::Stream.new(context: 'UserProfile', stream_name: 'User', stream_id:
|
|
202
|
+
stream = PgEventstore::Stream.new(context: 'UserProfile', stream_name: 'User', stream_id: '1')
|
|
145
203
|
event = UserAboutMeChanged.new(data: { user_id: '123', about_me: 'hi there!' })
|
|
146
204
|
|
|
147
205
|
publish_event(stream, event)
|
|
@@ -149,7 +207,9 @@ publish_event(stream, event)
|
|
|
149
207
|
|
|
150
208
|
## Middlewares
|
|
151
209
|
|
|
152
|
-
If you would like to skip some of your registered middlewares from processing events before they get appended to a
|
|
210
|
+
If you would like to skip some of your registered middlewares from processing events before they get appended to a
|
|
211
|
+
stream - you should use the `:middlewares` argument which allows you to override the list of middlewares you would like
|
|
212
|
+
to use.
|
|
153
213
|
|
|
154
214
|
Let's say you have these registered middlewares:
|
|
155
215
|
|
|
@@ -159,11 +219,12 @@ PgEventstore.configure do |config|
|
|
|
159
219
|
end
|
|
160
220
|
```
|
|
161
221
|
|
|
162
|
-
And you want to skip `FooMiddleware` and `BazMiddleware`. You simply have to provide an array of corresponding
|
|
222
|
+
And you want to skip `FooMiddleware` and `BazMiddleware`. You simply have to provide an array of corresponding
|
|
223
|
+
middleware keys you would like to use:
|
|
163
224
|
|
|
164
225
|
```ruby
|
|
165
226
|
event = PgEventstore::Event.new
|
|
166
|
-
stream = PgEventstore::Stream.new(context: '
|
|
227
|
+
stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
|
|
167
228
|
PgEventstore.client.append_to_stream(stream, event, middlewares: %i[bar])
|
|
168
229
|
```
|
|
169
230
|
|
data/docs/configuration.md
CHANGED
|
@@ -2,22 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Configuration options:
|
|
4
4
|
|
|
5
|
-
| name
|
|
6
|
-
|
|
7
|
-
| pg_uri
|
|
8
|
-
| max_count
|
|
9
|
-
| middlewares
|
|
10
|
-
| event_class_resolver
|
|
11
|
-
| connection_pool_size
|
|
12
|
-
| connection_pool_timeout
|
|
13
|
-
| subscription_pull_interval
|
|
14
|
-
| subscription_max_retries
|
|
15
|
-
| subscription_retries_interval
|
|
16
|
-
| subscriptions_set_max_retries
|
|
17
|
-
| subscriptions_set_retries_interval
|
|
18
|
-
| subscription_restart_terminator
|
|
19
|
-
| failed_subscription_notifier
|
|
20
|
-
| subscription_graceful_shutdown_timeout
|
|
5
|
+
| name | value | default value | description |
|
|
6
|
+
|----------------------------------------------|----------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
7
|
+
| pg_uri | String | `'postgresql://postgres:postgres@localhost:5432/eventstore'` | PostgreSQL connection string. See PostgreSQL [docs](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS) for more information. |
|
|
8
|
+
| max_count | Integer | `1000` | Number of events to return in one response when reading from a stream. |
|
|
9
|
+
| middlewares | Hash | `{}` | A hash where a key is a name of your middleware and value is an object that respond to `#serialize` and `#deserialize` methods. See [**Writing middleware**](writing_middleware.md) chapter. |
|
|
10
|
+
| event_class_resolver | `#call` | `PgEventstore::EventClassResolver.new` | A `#call`-able object that accepts a string and returns an event's class. See **Resolving events classes** chapter bellow for more info. |
|
|
11
|
+
| connection_pool_size | Integer | `5` | Max number of connections per ruby process. See [**Picking max connections number**](#picking-max-connections-number) chapter of this section for more details. |
|
|
12
|
+
| connection_pool_timeout | Integer | `5` | Time in seconds to wait for a connection in the pool to be released. If no connections are available during this time - `ConnectionPool::TimeoutError` will be raised. See `connection_pool` gem [docs](https://github.com/mperham/connection_pool#usage) for more info. |
|
|
13
|
+
| subscription_pull_interval | Float | `1.0` | How often to pull new subscription events in seconds. The minimum meaningful value is `0.2`. Values less than `0.2` will act as it is `0.2`. |
|
|
14
|
+
| subscription_max_retries | Integer | `5` | Max number of retries of failed subscription. |
|
|
15
|
+
| subscription_retries_interval | Integer | `1` | Interval in seconds between retries of failed subscriptions. |
|
|
16
|
+
| subscriptions_set_max_retries | Integer | `10` | Max number of retries for failed subscription sets. |
|
|
17
|
+
| subscriptions_set_retries_interval | Integer | `1` | Interval in seconds between retries of failed subscription sets. |
|
|
18
|
+
| subscription_restart_terminator | `#call` | `nil` | A callable object that accepts `PgEventstore::Subscription` object to determine whether restarts should be stopped(true - stops restarts, false - continues restarts). |
|
|
19
|
+
| failed_subscription_notifier | `#call` | `nil` | A callable object which is invoked with `PgEventstore::Subscription` instance and error instance after the related subscription died due to error and no longer can be automatically restarted due to max retries number reached. You can use this hook to send a notification about failed subscription. |
|
|
20
|
+
| subscription_graceful_shutdown_timeout | Integer, Float | `15` | The number of seconds to wait until force-shutdown the subscription during the stop process. If your subscription handler does not finish current event processing during this time(for example because of heavy-lifting task) - it will be force-shutdown. |
|
|
21
|
+
| events_subscription_position_update_interval | Integer, Float | `0.2` | Events subscription position update interval. See related docs [**Subscription position**](subscriptions.md#subscription-position) |
|
|
22
|
+
| eventstore_role | Symbol | `:standalone` | Sets the role of the current configuration. Read more about this config option [bellow](#eventstore-role) |
|
|
23
|
+
| max_events_to_replicate | Integer | `10_000` | Max number of events to replicate at a time. Read more about this config option [bellow](#max-events-to-replicate) |
|
|
21
24
|
|
|
22
25
|
## Multiple configurations
|
|
23
26
|
|
|
@@ -93,31 +96,61 @@ end
|
|
|
93
96
|
|
|
94
97
|
## Picking max connections number
|
|
95
98
|
|
|
96
|
-
A connection is hold from the connection pool to perform the request and it is released back to the connection pool
|
|
97
|
-
the request is finished. If you run into the (theoretical) edge case, when all your application's threads (or
|
|
99
|
+
A connection is hold from the connection pool to perform the request, and it is released back to the connection pool
|
|
100
|
+
once the request is finished. If you run into the (theoretical) edge case, when all your application's threads (or
|
|
98
101
|
subscriptions) are performing `pg_eventstore` queries at the same time and all those queries take more
|
|
99
102
|
than `connection_pool_timeout` seconds to complete, you have to have `connection_pool_size` set to the exact amount of
|
|
100
|
-
your application's threads
|
|
101
|
-
|
|
102
|
-
the `connection_pool_size` option is **half the number ** of your application's threads(or half the number of
|
|
103
|
-
Subscriptions).
|
|
103
|
+
your application's threads to prevent timeout errors. Practically this is not the case, as all `pg_eventstore` queries
|
|
104
|
+
are pretty fast. So, a good value for the `connection_pool_size` option is the number of your application's threads.
|
|
104
105
|
|
|
105
|
-
###
|
|
106
|
+
### Connections number for subscriptions
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
Subscriptions implementation is more demanding on connections number. This is because it internally implements
|
|
109
|
+
different services that support various aspects of the feature. Here is a breakdown of connections distribution:
|
|
110
|
+
|
|
111
|
+
- 1 connection per subscription to manage each subscription itself(e.g. update its stats, positions, etc)
|
|
112
|
+
- 1 connection per 10 subscriptions to pull new events
|
|
113
|
+
- 1 connection to process remote commands, such as starting/stopping/restart subscriptions from admin web UI
|
|
114
|
+
- 1 connection to assign subscription positions of newly created events
|
|
115
|
+
- 1 connection to manage [SubscriptionsSet](subscriptions.md#pgeventstoresubscriptionsset)
|
|
116
|
+
|
|
117
|
+
So, the formula to calculate the number of connection your subscriptions process should have is next:
|
|
111
118
|
|
|
112
119
|
```ruby
|
|
113
|
-
|
|
114
|
-
# Connection is hold from the connection pool
|
|
115
|
-
PgEventstore.client.read(some_stream)
|
|
116
|
-
Stripe::Payment.create(some_attrs)
|
|
117
|
-
PgEventstore.client.append_to_stream(some_stream, some_event)
|
|
118
|
-
# Connection is released
|
|
119
|
-
end
|
|
120
|
+
number_of_subscriptions + (number_of_subscriptions / 10.0).ceil + 3
|
|
120
121
|
```
|
|
121
122
|
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
So, for example, for 10 subscriptions - the implementation may consume up to 14 connections at a time. On practice this
|
|
124
|
+
number will be lower than the threshold most amount of time, because queries behind the implementation are near instant.
|
|
125
|
+
Nevertheless, it is wise to always have the required number ready to be utilized.
|
|
126
|
+
|
|
127
|
+
### Tips
|
|
128
|
+
|
|
129
|
+
To be able to always have enough amount of available connections - it is recommended to use some connection pooler,
|
|
130
|
+
such as [pgbouncer](https://www.pgbouncer.org/) in transaction mode.
|
|
131
|
+
|
|
132
|
+
## Eventstore role
|
|
133
|
+
|
|
134
|
+
Defines the role of your event store. Available values are:
|
|
135
|
+
|
|
136
|
+
- `:standalone` Means you are running the only copy of your database. This is the default. No functional restrictions
|
|
137
|
+
are applied
|
|
138
|
+
- `:primary` Means this is your primary node where events are get published
|
|
139
|
+
- `:replica` Means this is a replica of your primary node. No events can be published
|
|
140
|
+
|
|
141
|
+
Functional restrictions that apply to `:primary` and `:replica` roles:
|
|
142
|
+
|
|
143
|
+
- `:primary` and `:replica` roles are not allowed to perform any maintenance operations, such as delete events or
|
|
144
|
+
delete streams
|
|
145
|
+
- `:replica` role is not allowed to publish any events
|
|
146
|
+
|
|
147
|
+
## Max events to replicate
|
|
148
|
+
|
|
149
|
+
This option determines how many events you want to copy from your primary node into your replica node at a time. The
|
|
150
|
+
larger this value - the more resources are needed. It is both - memory and CPU intensive as a replica subscription first
|
|
151
|
+
loads events into memory, prepares them and then persists into the destination replica node. Thus, it may be wise to
|
|
152
|
+
split your replica subscriptions from your application subscriptions and adjust the environment to handle higher loads.
|
|
153
|
+
|
|
154
|
+
Please note, that this is an **upper limit** of events to replicate at a time. Subscriptions have their own measurement
|
|
155
|
+
of how many events a subscription handler processes per second. Thus, the number of events actually processed by a
|
|
156
|
+
subscription at a time may differ.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Event tracing
|
|
2
|
+
|
|
3
|
+
pg_eventstore implements event tracing feature out of the box via `PgEventstore::Middleware::EventTracing` middleware
|
|
4
|
+
class. It allows you to connect events between each other with markers and build causation dependency between them. The
|
|
5
|
+
middleware is not configured by default. You can configure it as follows:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
PgEventstore.configure do |config|
|
|
9
|
+
config.middlewares = { event_tracing: PgEventstore::Middleware::EventTracing.new }
|
|
10
|
+
end
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You can now supply new event with an event, based on which current event is going to be published. Example:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
stream = PgEventstore::Stream.new(context: 'User', stream_name: 'RegistrationRequest', stream_id: '1')
|
|
17
|
+
user_registration_confirmed = PgEventstore.client.append_to_stream(
|
|
18
|
+
stream, PgEventstore::Event.new(type: 'UserRegistrationConfirmed')
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
stream = PgEventstore::Stream.new(context: 'User', stream_name: 'User', stream_id: '1')
|
|
22
|
+
user_created = PgEventstore.client.append_to_stream(
|
|
23
|
+
stream, PgEventstore::Event.new(caused_by: user_registration_confirmed, type: 'UserCreated')
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`UserCreated` event now has `#causation_id` value equal to `user_registration_confirmed.id`. Both of them have the same
|
|
28
|
+
`#correlation_id`. You can use those values to find those events.
|
|
29
|
+
|
|
30
|
+
Use `#correlation_id` to find all connected events:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
PgEventstore.client.read(
|
|
34
|
+
PgEventstore::Stream.all_stream,
|
|
35
|
+
options: { filter: { event_types: [{ markers: [user_created.correlation_id] }] } }
|
|
36
|
+
) # => returns both events
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use `#causation_id` to find all events, which were persisted, based on the given causation id:
|
|
40
|
+
```ruby
|
|
41
|
+
PgEventstore.client.read(
|
|
42
|
+
PgEventstore::Stream.all_stream,
|
|
43
|
+
options: { filter: { event_types: [{ markers: [user_registration_confirmed.id] }] } }
|
|
44
|
+
) # => returns both events
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`#causation_id` and `#correlation_id` values are persisted into special keys of `#metadata` and are loaded back into
|
|
48
|
+
`#causation_id` and `#correlation_id` attributes during the deserialization process. Related constants are:
|
|
49
|
+
- `PgEventstore::Middleware::EventTracing::CAUSATION_ID_KEY` for `#causation_id`
|
|
50
|
+
- `PgEventstore::Middleware::EventTracing::CORRELATION_ID_KEY` for `#correlation_id`
|
|
51
|
+
|
|
52
|
+
You can also find `#correlation_id` and `#causation_id` values in `Event#feature_markers` attribute, `Markers` section
|
|
53
|
+
of extended event view(it is available by clicking on `JSON` link with eye icon near it on the admin dashboard page)
|
|
54
|
+
under `Feature markers` chapter. It is only available when `PgEventstore::Middleware::EventTracing` is configured
|
|
55
|
+
though, but filtering by markers is always there despite on configuration options.
|
data/docs/events_and_streams.md
CHANGED
|
@@ -9,17 +9,52 @@
|
|
|
9
9
|
|
|
10
10
|
`PgEventstore::Event` has the following attributes:
|
|
11
11
|
|
|
12
|
-
- `id` - String(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `
|
|
16
|
-
|
|
17
|
-
- `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- `
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
- `id` - `String`(UUIDv7, optional, not `nil`, default is `SecureRandom.uuid_v7`). Application-specific identifier, may
|
|
13
|
+
be used by some middlewares. Uniqueness is not guaranteed on the database level, but you can rely on
|
|
14
|
+
`SecureRandom.uuid_v7` as a source of unique values.
|
|
15
|
+
- `type` - `String`(optional, not `nil`). Default is an event's class name. Types which start from `$` indicate system
|
|
16
|
+
events. It is not recommended to prefix your events types with `$` sign.
|
|
17
|
+
- `global_position` - Integer(optional, read only). Event's global position in the eventstore, aka the "all" stream
|
|
18
|
+
position (inspired by the popular EventstoreDB). Manually assigning this attribute has no effect. It is internally set
|
|
19
|
+
when writing events into the database.
|
|
20
|
+
- `stream` - `PgEventstore::Stream`(optional, read only). A Stream an event belongs to, see description below. Manually
|
|
21
|
+
assigning this attribute has no effect. It is internally set when appending an event to the given stream or when
|
|
22
|
+
reading events from the database.
|
|
23
|
+
- `stream_revision` - `Integer`(optional, read only). Stream revision at the given event
|
|
24
|
+
- `data` - Hash(optional). Event's payload data. For example, if you have a `DescriptionChanged` event class, then you
|
|
25
|
+
may want to have a description value in the event payload data. Example:
|
|
26
|
+
`DescriptionChanged.new(data: { 'description' => 'Description of something', 'post_id' => SecureRandom.uuid_v7 })`
|
|
27
|
+
- `markers` - Array of strings(optional). Event markers. You can supply new events with markers list. Persisted events
|
|
28
|
+
contain assigned markers in this attribute. Example: `PgEventstore::Event.new(markers: %w[foo bar])`
|
|
29
|
+
- `feature_markers` - `Array<PgEventstore::FeatureMarker>`(optional). Event markers, assigned by a middleware(or any
|
|
30
|
+
other external implementation), but is not going to be persisted along with the rest markers into the default markers
|
|
31
|
+
metadata key and won't be automatically available under `#markers` when reading events. The main reason for that is to
|
|
32
|
+
split event type-related markers and feature-specific markers. Your implementation is responsible for serializing and
|
|
33
|
+
deserializing those markers. You can see the example of such implementation by inspecting
|
|
34
|
+
`PgEventstore::Middleware::EventTracing` middleware.
|
|
35
|
+
- `metadata` - `Hash`(optional). Event metadata. Event meta information which is not part of an events data payload.
|
|
36
|
+
Example: `{ published_by: publishing_user.id }`
|
|
37
|
+
- `link_global_position` - `Integer`(optional, read only). If an event is a link event (link events are pointers to
|
|
38
|
+
other events), this attribute contains a `global_position` of the original event. Manually assigning this attribute
|
|
39
|
+
has no effect. It is internally set when linking an event to the given stream or when reading events from the
|
|
40
|
+
database.
|
|
41
|
+
- `link_partition_id` - `Integer`(optional, read only). If an event is a link event - this attribute contains a
|
|
42
|
+
partition `id` of original event. Manually assigning this attribute has no effect. It is internally set when appending
|
|
43
|
+
an event to the given stream or when reading events from the database.
|
|
44
|
+
- `link` - `PgEventstore::Event`(optional, read only). When reading from a stream using `resolve_link_tos: true`, if an
|
|
45
|
+
event is resolved from a link - this attribute contains a `PgEventstore::Event` object which corresponds to that link.
|
|
46
|
+
Manually assigning this attribute has no effect. It is internally set when reading events from the database.
|
|
47
|
+
- `created_at` - `Time`(optional, read only). Database's timestamp when an event was appended to a stream. You may want
|
|
48
|
+
to put your own timestamp into a `metadata` attribute - it may be useful when migrating between different databases.
|
|
49
|
+
Manually assigning this attribute has no effect. It is internally set when appending an event to the given stream or
|
|
50
|
+
when reading events from the database.
|
|
51
|
+
- `caused_by` - `PgEventstore::Event`(optional). This is a part of event tracing feature(not configured by default).
|
|
52
|
+
Pass your persisted event to make your unpersisted event be marked as an event, caused by the given persisted event,
|
|
53
|
+
thus, creating causation dependency between them
|
|
54
|
+
- `correlation_id` - `String`(UUIDv7, optional). This is a part of event tracing feature(not configured by default). All
|
|
55
|
+
connected events are marked with the same correlation id. Thus, you can fetch all connected events at once.
|
|
56
|
+
- `causation_id` - `String`(UUIDv7, optional). This is a part of event tracing feature(not configured by default). The
|
|
57
|
+
id of an event caused current event be persisted.
|
|
23
58
|
|
|
24
59
|
Example:
|
|
25
60
|
|
|
@@ -29,11 +64,14 @@ PgEventstore::Event.new(data: { 'foo' => 'bar' }, type: 'FooChanged')
|
|
|
29
64
|
|
|
30
65
|
## Stream object
|
|
31
66
|
|
|
32
|
-
To be able to manipulate a stream, you have to compute a stream's object first. It can be achieved by using the
|
|
67
|
+
To be able to manipulate a stream, you have to compute a stream's object first. It can be achieved by using the
|
|
68
|
+
`PgEventstore::Stream` class. Here is a description of its attributes:
|
|
33
69
|
|
|
34
|
-
- `context` - String(required). A Bounded Context, read more [here](https://martinfowler.com/bliki/BoundedContext.html).
|
|
70
|
+
- `context` - String(required). A Bounded Context, read more [here](https://martinfowler.com/bliki/BoundedContext.html).
|
|
35
71
|
- `stream_name` - String(required). A stream name.
|
|
36
72
|
- `stream_id` - String(required). A stream id.
|
|
73
|
+
- `stream_revision` - Integer(optional, read only). Current stream revision
|
|
74
|
+
- `starting_position` - Integer(optional, read only). Global position of the first event in the stream
|
|
37
75
|
|
|
38
76
|
Example:
|
|
39
77
|
|
|
@@ -44,20 +82,6 @@ PgEventstore::Stream.new(context: 'Sales', stream_name: 'Customer', stream_id: '
|
|
|
44
82
|
|
|
45
83
|
### "all" stream
|
|
46
84
|
|
|
47
|
-
There is a special stream, called the "all" stream. You can get this object by calling the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
System stream is a special stream, the representation of which is pre-defined by the gem. System stream object can be created in next way:
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
PgEventstore::Stream.system_stream(stream_name)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Current list of system streams is:
|
|
58
|
-
|
|
59
|
-
- `"$streams"`. Reading from this stream will return 0 revision events. This allows effectively loop through a list of streams. Read more in [Reading events](reading_events.md#streams-stream-filtering) chapter.
|
|
60
|
-
|
|
61
|
-
## Important note
|
|
62
|
-
|
|
63
|
-
Because the database is designed for Eventsourcing, some limitations should be met - a combination of `Event#type`, `Stream#context` and `Stream#stream_name` must have low cardinality(low unique values number). This means you should pre-defined values there. Otherwise it may lead to the performance degradation. See [How it works](how_it_works.md) chapter for the details.
|
|
85
|
+
There is a special stream, called the "all" stream. You can get this object by calling the
|
|
86
|
+
`PgEventstore::Stream.all_stream` method. Read more about the "all" stream in the `Reading from the "all" stream`
|
|
87
|
+
section of [Reading events](reading_events.md) chapter.
|