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.
Files changed (303) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -1
  3. data/CHANGELOG.md +186 -20
  4. data/README.md +25 -15
  5. data/db/migrations/10_setup_pg_cron.rb +1 -23
  6. data/db/migrations/15_create_streams_global_index.sql +17 -0
  7. data/db/migrations/16_populate_streams_global_index.rb +150 -0
  8. data/db/migrations/17_create_events_global_index.sql +62 -0
  9. data/db/migrations/18_populate_events_global_index.rb +124 -0
  10. data/db/migrations/19_populate_events_global_index_p2.rb +25 -0
  11. data/db/migrations/20_migrate_subscriptions_current_position.sql +19 -0
  12. data/db/migrations/21_create_partitions_stats.sql +2 -0
  13. data/db/migrations/22_drop_streams_sql_view.sql +2 -0
  14. data/db/migrations/23_adjust_partitions_indexes.sql +5 -0
  15. data/db/migrations/24_drop_legacy_events_indexes.sql +3 -0
  16. data/db/migrations/25_add_parent_partitions_info_to_partitions.sql +16 -0
  17. data/db/migrations/26_change_events_stream_revision_type.rb +35 -0
  18. data/db/migrations/27_drop_events_horizon.sql +3 -0
  19. data/db/migrations/28_create_maintenance_tasks.sql +8 -0
  20. data/db/migrations/29_create_event_markers.sql +35 -0
  21. data/db/migrations/30_improve_partitions_search_in_admin_ui.rb +24 -0
  22. data/db/migrations/31_drop_events_id_default_value.sql +1 -0
  23. data/db/migrations/32_create_parittions_triggers.sql +101 -0
  24. data/docs/AGENTS.fragment.md +696 -0
  25. data/docs/admin_ui.md +3 -1
  26. data/docs/appending_events.md +110 -49
  27. data/docs/configuration.md +69 -36
  28. data/docs/event_tracing.md +55 -0
  29. data/docs/events_and_streams.md +54 -30
  30. data/docs/how_it_works.md +57 -16
  31. data/docs/linking_events.md +2 -1
  32. data/docs/maintenance.md +14 -5
  33. data/docs/multiple_commands.md +17 -8
  34. data/docs/reading_events.md +208 -43
  35. data/docs/reading_streams.md +50 -0
  36. data/docs/replication.md +99 -0
  37. data/docs/subscriptions.md +165 -35
  38. data/docs/writing_middleware.md +25 -8
  39. data/ext/pg_eventstore_ext/extconf.rb +5 -0
  40. data/ext/pg_eventstore_ext/pg_eventstore_ext.c +74 -0
  41. data/lib/pg_eventstore/async_runner.rb +63 -0
  42. data/lib/pg_eventstore/basic_config.rb +15 -0
  43. data/lib/pg_eventstore/chunks/chunk.rb +31 -0
  44. data/lib/pg_eventstore/chunks/read_api_events_index_chunk.rb +83 -0
  45. data/lib/pg_eventstore/chunks/replica_events_index_chunk.rb +35 -0
  46. data/lib/pg_eventstore/chunks/repository.rb +73 -0
  47. data/lib/pg_eventstore/chunks/subscription_checkpoint_chunk.rb +48 -0
  48. data/lib/pg_eventstore/chunks/subscription_events_index_chunk.rb +99 -0
  49. data/lib/pg_eventstore/chunks.rb +14 -0
  50. data/lib/pg_eventstore/client.rb +163 -50
  51. data/lib/pg_eventstore/commands/append.rb +62 -43
  52. data/lib/pg_eventstore/commands/delete_event.rb +7 -8
  53. data/lib/pg_eventstore/commands/delete_stream.rb +3 -3
  54. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +9 -7
  55. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +12 -7
  56. data/lib/pg_eventstore/commands/link_to.rb +5 -5
  57. data/lib/pg_eventstore/commands/read.rb +23 -5
  58. data/lib/pg_eventstore/commands/read_grouped.rb +46 -0
  59. data/lib/pg_eventstore/commands/read_streams.rb +18 -0
  60. data/lib/pg_eventstore/commands/read_streams_paginated.rb +56 -0
  61. data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +4 -4
  62. data/lib/pg_eventstore/commands/revision_check/current_revision.rb +51 -0
  63. data/lib/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rb +46 -0
  64. data/lib/pg_eventstore/commands/revision_check/expected_revision.rb +135 -0
  65. data/lib/pg_eventstore/commands/revision_check/stream_revision_check.rb +98 -0
  66. data/lib/pg_eventstore/commands/revision_check/stream_revision_comparison.rb +36 -0
  67. data/lib/pg_eventstore/commands/stream_revision.rb +14 -0
  68. data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +4 -4
  69. data/lib/pg_eventstore/commands.rb +4 -2
  70. data/lib/pg_eventstore/config.rb +30 -9
  71. data/lib/pg_eventstore/connection.rb +21 -2
  72. data/lib/pg_eventstore/errors.rb +136 -10
  73. data/lib/pg_eventstore/event.rb +34 -2
  74. data/lib/pg_eventstore/event_deserializer.rb +5 -4
  75. data/lib/pg_eventstore/event_global_index.rb +118 -0
  76. data/lib/pg_eventstore/event_marker.rb +16 -0
  77. data/lib/pg_eventstore/event_marker_index.rb +25 -0
  78. data/lib/pg_eventstore/event_serializer.rb +6 -0
  79. data/lib/pg_eventstore/extensions/acts_as_configurable.rb +88 -0
  80. data/lib/pg_eventstore/extensions/options_defaults.rb +25 -0
  81. data/lib/pg_eventstore/extensions/options_extension.rb +2 -2
  82. data/lib/pg_eventstore/feature_marker.rb +18 -0
  83. data/lib/pg_eventstore/maintenance.rb +11 -6
  84. data/lib/pg_eventstore/middleware/event_tracing.rb +53 -0
  85. data/lib/pg_eventstore/partition.rb +6 -0
  86. data/lib/pg_eventstore/pg_connection.rb +70 -14
  87. data/lib/pg_eventstore/queries/event_marker_queries.rb +74 -0
  88. data/lib/pg_eventstore/queries/event_queries.rb +7 -93
  89. data/lib/pg_eventstore/queries/events_global_index_queries.rb +131 -0
  90. data/lib/pg_eventstore/queries/index_filtering_queries.rb +179 -0
  91. data/lib/pg_eventstore/queries/links_resolver.rb +4 -4
  92. data/lib/pg_eventstore/queries/maintenance_queries.rb +159 -42
  93. data/lib/pg_eventstore/queries/partition_queries.rb +45 -103
  94. data/lib/pg_eventstore/queries/replica_queries.rb +110 -0
  95. data/lib/pg_eventstore/queries/streams_global_index_queries.rb +163 -0
  96. data/lib/pg_eventstore/queries/transaction_queries.rb +52 -10
  97. data/lib/pg_eventstore/queries.rb +29 -1
  98. data/lib/pg_eventstore/query_builders/basic_filtering.rb +38 -5
  99. data/lib/pg_eventstore/query_builders/event_markers_filtering.rb +47 -0
  100. data/lib/pg_eventstore/query_builders/event_markers_index_filtering.rb +275 -0
  101. data/lib/pg_eventstore/query_builders/event_subscription_positions_filtering.rb +37 -0
  102. data/lib/pg_eventstore/query_builders/events_filtering.rb +15 -164
  103. data/lib/pg_eventstore/query_builders/events_global_index_filtering.rb +227 -0
  104. data/lib/pg_eventstore/query_builders/filters/collection.rb +324 -0
  105. data/lib/pg_eventstore/query_builders/filters/event_type_filter.rb +37 -0
  106. data/lib/pg_eventstore/query_builders/filters/filter_row.rb +54 -0
  107. data/lib/pg_eventstore/query_builders/filters/marker_filter.rb +20 -0
  108. data/lib/pg_eventstore/query_builders/filters/marker_filter_row.rb +49 -0
  109. data/lib/pg_eventstore/query_builders/filters/stream_filter.rb +48 -0
  110. data/lib/pg_eventstore/query_builders/index_based_events_filtering.rb +260 -0
  111. data/lib/pg_eventstore/query_builders/partitions_filtering.rb +175 -51
  112. data/lib/pg_eventstore/query_builders/read_cursor/stream_cursor.rb +122 -0
  113. data/lib/pg_eventstore/query_builders/streams_global_index_filtering.rb +83 -0
  114. data/lib/pg_eventstore/query_builders/subscription_events_filtering.rb +79 -0
  115. data/lib/pg_eventstore/query_strategy/async.rb +64 -0
  116. data/lib/pg_eventstore/query_strategy/foreground.rb +25 -0
  117. data/lib/pg_eventstore/query_strategy.rb +19 -0
  118. data/lib/pg_eventstore/raw_event.rb +46 -0
  119. data/lib/pg_eventstore/rspec/test_helpers.rb +21 -8
  120. data/lib/pg_eventstore/sql_builder.rb +87 -8
  121. data/lib/pg_eventstore/stream.rb +13 -16
  122. data/lib/pg_eventstore/stream_global_index.rb +28 -0
  123. data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +5 -17
  124. data/lib/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rb +36 -0
  125. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +12 -0
  126. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +21 -7
  127. data/lib/pg_eventstore/subscriptions/events_processor.rb +20 -18
  128. data/lib/pg_eventstore/subscriptions/events_processor_consumer/multiple.rb +63 -0
  129. data/lib/pg_eventstore/subscriptions/events_processor_consumer/replica.rb +68 -0
  130. data/lib/pg_eventstore/subscriptions/events_processor_consumer/single.rb +62 -0
  131. data/lib/pg_eventstore/subscriptions/events_processor_consumer.rb +44 -0
  132. data/lib/pg_eventstore/subscriptions/events_subscription_position_worker.rb +68 -0
  133. data/lib/pg_eventstore/subscriptions/queries/event_subscription_position_queries.rb +177 -0
  134. data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +78 -54
  135. data/lib/pg_eventstore/subscriptions/replica_subscription_handler.rb +140 -0
  136. data/lib/pg_eventstore/subscriptions/replica_subscription_runner.rb +9 -0
  137. data/lib/pg_eventstore/subscriptions/subscription.rb +2 -1
  138. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/collection.rb +36 -0
  139. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/index_read_strategy.rb +82 -0
  140. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/replication_strategy.rb +75 -0
  141. data/lib/pg_eventstore/subscriptions/subscription_feed_strategy.rb +31 -0
  142. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +24 -0
  143. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +4 -2
  144. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +20 -9
  145. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +1 -1
  146. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +9 -18
  147. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +0 -12
  148. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +74 -14
  149. data/lib/pg_eventstore/{subscriptions/synchronized_array.rb → synchronized_array.rb} +15 -0
  150. data/lib/pg_eventstore/tasks/setup.rake +2 -2
  151. data/lib/pg_eventstore/utils.rb +31 -0
  152. data/lib/pg_eventstore/version.rb +1 -1
  153. data/lib/pg_eventstore/web/application.rb +100 -16
  154. data/lib/pg_eventstore/web/paginator/base_collection.rb +5 -1
  155. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +14 -4
  156. data/lib/pg_eventstore/web/paginator/events_collection.rb +29 -33
  157. data/lib/pg_eventstore/web/paginator/helpers.rb +16 -9
  158. data/lib/pg_eventstore/web/paginator/markers_collection.rb +48 -0
  159. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +13 -2
  160. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +21 -11
  161. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +18 -4
  162. data/lib/pg_eventstore/web/paginator/streams_collection.rb +95 -0
  163. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +49 -22
  164. data/lib/pg_eventstore/web/subscriptions/set_collection.rb +1 -1
  165. data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +1 -1
  166. data/lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb +1 -1
  167. data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb +1 -1
  168. data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -9
  169. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +18 -5
  170. data/lib/pg_eventstore/web/views/home/partials/events.erb +38 -1
  171. data/lib/pg_eventstore/web/views/home/partials/markers_filter.erb +18 -0
  172. data/lib/pg_eventstore/web/views/layouts/application.erb +6 -0
  173. data/lib/pg_eventstore/web/views/streams/index.erb +110 -0
  174. data/lib/pg_eventstore/web/views/streams/partials/streams.erb +11 -0
  175. data/lib/pg_eventstore/web/views/subscriptions/index.erb +4 -1
  176. data/lib/pg_eventstore/web.rb +2 -0
  177. data/lib/pg_eventstore.rb +23 -60
  178. data/pg_eventstore.gemspec +6 -4
  179. data/sig/interfaces/_raw_events_handler.rbs +3 -0
  180. data/sig/interfaces/subscription_handler.rbs +1 -1
  181. data/sig/pg/result.rbs +9 -0
  182. data/sig/pg_eventstore/async_runner.rbs +13 -0
  183. data/sig/pg_eventstore/basic_config.rbs +9 -0
  184. data/sig/pg_eventstore/chunks/chunk.rbs +13 -0
  185. data/sig/pg_eventstore/chunks/read_api_events_index_chunk.rbs +30 -0
  186. data/sig/pg_eventstore/chunks/replica_events_index_chunk.rbs +11 -0
  187. data/sig/pg_eventstore/chunks/repository.rbs +22 -0
  188. data/sig/pg_eventstore/chunks/subscription_checkpoint_chunk.rbs +18 -0
  189. data/sig/pg_eventstore/chunks/subscription_events_index_chunk.rbs +37 -0
  190. data/sig/pg_eventstore/client.rbs +40 -42
  191. data/sig/pg_eventstore/commands/append.rbs +11 -25
  192. data/sig/pg_eventstore/commands/event_modifiers/prepare_link_event.rbs +9 -12
  193. data/sig/pg_eventstore/commands/event_modifiers/prepare_regular_event.rbs +5 -4
  194. data/sig/pg_eventstore/commands/link_to.rbs +6 -11
  195. data/sig/pg_eventstore/commands/read.rbs +2 -5
  196. data/sig/pg_eventstore/commands/read_grouped.rbs +7 -0
  197. data/sig/pg_eventstore/commands/read_streams.rbs +7 -0
  198. data/sig/pg_eventstore/commands/read_streams_paginated.rbs +16 -0
  199. data/sig/pg_eventstore/commands/regular_stream_read_paginated.rbs +9 -17
  200. data/sig/pg_eventstore/commands/revision_check/current_revision.rbs +25 -0
  201. data/sig/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rbs +14 -0
  202. data/sig/pg_eventstore/commands/revision_check/expected_revision.rbs +67 -0
  203. data/sig/pg_eventstore/commands/revision_check/stream_revision_check.rbs +20 -0
  204. data/sig/pg_eventstore/commands/revision_check/stream_revision_comparison.rbs +9 -0
  205. data/sig/pg_eventstore/commands/stream_revision.rbs +7 -0
  206. data/sig/pg_eventstore/commands/system_stream_read_paginated.rbs +9 -14
  207. data/sig/pg_eventstore/config.rbs +15 -6
  208. data/sig/pg_eventstore/connection.rbs +11 -18
  209. data/sig/pg_eventstore/errors.rbs +64 -34
  210. data/sig/pg_eventstore/event.rbs +16 -2
  211. data/sig/pg_eventstore/event_deserializer.rbs +5 -10
  212. data/sig/pg_eventstore/event_global_index.rbs +60 -0
  213. data/sig/pg_eventstore/event_marker.rbs +9 -0
  214. data/sig/pg_eventstore/event_marker_index.rbs +12 -0
  215. data/sig/pg_eventstore/extensions/acts_as_configurable.rbs +29 -0
  216. data/sig/pg_eventstore/extensions/options_defaults.rbs +7 -0
  217. data/sig/pg_eventstore/feature_marker.rbs +10 -0
  218. data/sig/pg_eventstore/maintenance.rbs +12 -8
  219. data/sig/pg_eventstore/middleware/event_trace.rbs +14 -0
  220. data/sig/pg_eventstore/partition.rbs +2 -4
  221. data/sig/pg_eventstore/pg_connection.rbs +8 -0
  222. data/sig/pg_eventstore/queries/event_marker_queries.rbs +21 -0
  223. data/sig/pg_eventstore/queries/event_queries.rbs +4 -36
  224. data/sig/pg_eventstore/queries/events_global_index_queries.rbs +37 -0
  225. data/sig/pg_eventstore/queries/index_filtering_queries.rbs +50 -0
  226. data/sig/pg_eventstore/queries/links_resolver.rbs +5 -9
  227. data/sig/pg_eventstore/queries/maintenance_queries.rbs +12 -9
  228. data/sig/pg_eventstore/queries/partition_queries.rbs +32 -67
  229. data/sig/pg_eventstore/queries/replica_queries.rbs +31 -0
  230. data/sig/pg_eventstore/queries/streams_global_index_queries.rbs +39 -0
  231. data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -0
  232. data/sig/pg_eventstore/queries.rbs +11 -21
  233. data/sig/pg_eventstore/query_builders/basic_filtering.rbs +10 -3
  234. data/sig/pg_eventstore/query_builders/event_markers_filtering.rbs +17 -0
  235. data/sig/pg_eventstore/query_builders/event_markers_index_filtering.rbs +69 -0
  236. data/sig/pg_eventstore/query_builders/event_subscription_positions_filtering.rbs +15 -0
  237. data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +9 -51
  238. data/sig/pg_eventstore/query_builders/events_global_index_filtering.rbs +68 -0
  239. data/sig/pg_eventstore/query_builders/filters/collection.rbs +84 -0
  240. data/sig/pg_eventstore/query_builders/filters/event_type_filter.rbs +19 -0
  241. data/sig/pg_eventstore/query_builders/filters/filter_row.rbs +21 -0
  242. data/sig/pg_eventstore/query_builders/filters/marker_filter.rbs +13 -0
  243. data/sig/pg_eventstore/query_builders/filters/marker_filter_row.rbs +19 -0
  244. data/sig/pg_eventstore/query_builders/filters/stream_filter.rbs +24 -0
  245. data/sig/pg_eventstore/query_builders/index_based_events_filtering.rbs +60 -0
  246. data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +29 -10
  247. data/sig/pg_eventstore/query_builders/read_cursor/stream_cursor.rbs +47 -0
  248. data/sig/pg_eventstore/query_builders/streams_global_index_filtering.rbs +27 -0
  249. data/sig/pg_eventstore/query_builders/subscription_events_filtering.rbs +26 -0
  250. data/sig/pg_eventstore/query_strategy/async.rbs +17 -0
  251. data/sig/pg_eventstore/query_strategy/foreground.rbs +11 -0
  252. data/sig/pg_eventstore/query_strategy.rbs +7 -0
  253. data/sig/pg_eventstore/raw_event.rbs +20 -0
  254. data/sig/pg_eventstore/sql_builder.rbs +32 -10
  255. data/sig/pg_eventstore/stream.rbs +10 -18
  256. data/sig/pg_eventstore/stream_global_index.rbs +14 -0
  257. data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +5 -4
  258. data/sig/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rbs +9 -0
  259. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +17 -12
  260. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +17 -6
  261. data/sig/pg_eventstore/subscriptions/event_subscription_position_queries.rbs +30 -0
  262. data/sig/pg_eventstore/subscriptions/events_processor.rbs +12 -5
  263. data/sig/pg_eventstore/subscriptions/events_processor_consumer/multiple.rbs +17 -0
  264. data/sig/pg_eventstore/subscriptions/events_processor_consumer/replica.rbs +17 -0
  265. data/sig/pg_eventstore/subscriptions/events_processor_consumer/single.rbs +17 -0
  266. data/sig/pg_eventstore/subscriptions/events_processor_consumer.rbs +14 -0
  267. data/sig/pg_eventstore/subscriptions/events_subscription_position_worker.rbs +28 -0
  268. data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +7 -39
  269. data/sig/pg_eventstore/subscriptions/replica_subscription_handler.rbs +33 -0
  270. data/sig/pg_eventstore/subscriptions/replica_subscription_runner.rbs +4 -0
  271. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/collection.rbs +14 -0
  272. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/index_read_strategy.rbs +23 -0
  273. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/replication_strategy.rbs +23 -0
  274. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy.rbs +11 -0
  275. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +13 -10
  276. data/sig/pg_eventstore/subscriptions/subscription_handler_performance.rbs +1 -1
  277. data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +13 -2
  278. data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +1 -8
  279. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +4 -6
  280. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +29 -43
  281. data/sig/pg_eventstore/synchronized_array.rbs +4 -0
  282. data/sig/pg_eventstore/utils.rbs +10 -11
  283. data/sig/pg_eventstore/web/application.rbs +13 -3
  284. data/sig/pg_eventstore/web/paginator/base_collection.rbs +2 -0
  285. data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +4 -0
  286. data/sig/pg_eventstore/web/paginator/events_collection.rbs +11 -11
  287. data/sig/pg_eventstore/web/paginator/helpers.rbs +6 -15
  288. data/sig/pg_eventstore/web/paginator/markers_collection.rbs +13 -0
  289. data/sig/pg_eventstore/web/paginator/stream_contexts_collection.rbs +4 -0
  290. data/sig/pg_eventstore/web/paginator/stream_names_collection.rbs +4 -0
  291. data/sig/pg_eventstore/web/paginator/streams_collection.rbs +30 -0
  292. data/sig/pg_eventstore.rbs +8 -26
  293. metadata +175 -20
  294. data/Dockerfile +0 -3
  295. data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +0 -69
  296. data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +0 -31
  297. data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +0 -78
  298. data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +0 -15
  299. data/sig/pg_eventstore/commands/all_stream_read_grouped.rbs +0 -16
  300. data/sig/pg_eventstore/commands/regular_stream_read_grouped.rbs +0 -8
  301. data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +0 -19
  302. data/sig/pg_eventstore/subscriptions/synchronized_array.rbs +0 -4
  303. /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 is your responsibility to take care of it. Admin UI tries to look for `:admin_web_ui` config with a fallback to `:default` config. Thus, you can setup Admin UI-specific config, e.g. without some middlewares or so. Example:
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|
@@ -1,67 +1,51 @@
1
1
  # Appending Events
2
2
 
3
- ## Append your first event
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 `#append_to_stream` method.
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: SecureRandom.uuid, title: "Something happened" })
14
- stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
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:0x0 @context="MyAwesomeContext", @created_at=2023-11-30 14:47:31.296229 UTC, @data={"title"=>"Something happened", "user_id"=>"be52a81c-ad5b-4cfd-a039-0b7276974e6b"}, @global_position=7, @id="0b01137b-bdd8-4f0d-8ccf-f8c959e3a324", @link_global_position=nil, @metadata={}, @stream_id="f37b82f2-4152-424d-ab6b-0cc6f0a53aae", @stream_name="SomeStream", @stream_revision=0, @type="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 operation is atomic and it guarantees that events are added to the stream in the given order.**
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: SecureRandom.uuid, title: "Something happened 1" })
30
- event2 = SomethingHappened.new(data: { user_id: SecureRandom.uuid, title: "Something happened 2" })
31
- stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
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 `pg_eventstore` what state or version you expect the stream to be in when you append. If the stream isn't in that state then an exception will be thrown.
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 on the second:
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: SecureRandom.uuid)
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 version number, then when you save it back you can determine if somebody else has modified the record in the meantime.
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: SecureRandom.uuid)
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
- ### What to do when a PgEventstore::WrongExpectedRevisionError error is risen?
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 event's revision and increase it by one. You now have the expected revision for the event to be published.
109
- 5. You publish the event but retrieve a `WrongExpectedRevisionError`. This means another process has appended an event to the same stream, after you were loading your business object, while you were checking your business rules.
110
- 6. Now you need to repeat the process: load your business objects from the updated events stream, apply your business rules and if there is still no violation, try to append the event with the updated stream revision. You can do this procedure until the event is published or a maximum number of retries has been reached.
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 event after a `UserRemoved` event:
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: SecureRandom.uuid)
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 stream - you should use the `:middlewares` argument which allows you to override the list of middlewares you would like to use.
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 middleware keys you would like to use:
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: 'MyAwesomeContext', stream_name: 'SomeStream', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
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
 
@@ -2,22 +2,25 @@
2
2
 
3
3
  Configuration options:
4
4
 
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 | Array | `{}` | 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. It must equal the number of threads of your application. When using subscriptions it is recommended to set it to the number of subscriptions divided by two or greater. See [**Picking max connections number**](#picking-max-connections-number) chapter of this section. |
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. |
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 once
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 (or to the number of subscriptions when using subscriptions) to prevent timeout errors.
101
- Practically this is not the case, as all `pg_eventstore` queries are pretty fast. So, a good value for
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
- ### Exception scenario
106
+ ### Connections number for subscriptions
106
107
 
107
- If you are using the [`#multiple`](multiple_commands.md) method - you have to take into account the execution time of
108
- the whole block you pass in it. This is because the connection will be released only after the block's execution is
109
- finished. So, for example, if you perform several commands within the block, as well as some API request, the connection
110
- will be release only after all those steps:
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
- PgEventstore.client.multiple do
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
- Taking this into account you may want to increase `connection_pool_size` up to the number of your application's threads(
123
- or subscriptions).
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.
@@ -9,17 +9,52 @@
9
9
 
10
10
  `PgEventstore::Event` has the following attributes:
11
11
 
12
- - `id` - String(UUIDv4, optional, not `nil`). If no provided - the value will be autogenerated.
13
- - `type` - String(optional, not `nil`). Default is an event's class name. Types which start from `$` indicate system events. It is not recommended to prefix your events types with `$` sign.
14
- - `global_position` - Integer(optional, read only). Event's global position in the eventstore, aka the "all" stream position (inspired by the popular EventstoreDB). Manually assigning this attribute has no effect. It is internally set when reading events from the database.
15
- - `stream` - PgEventstore::Stream(optional, read only). A Stream an event belongs to, see description below. Manually assigning this attribute has no effect. It is internally set when appending an event to the given stream or when reading events from the database.
16
- - `stream_revision` - Integer(optional, read only). A revision of an event inside its stream.
17
- - `data` - Hash(optional). Event's payload data. For example, if you have a `DescriptionChanged` event class, then you may want to have a description value in the event payload data. Example: `DescriptionChanged.new(data: { 'description' => 'Description of something', 'post_id' => SecureRandom.uuid })`
18
- - `metadata` - Hash(optional). Event metadata. Event meta information which is not part of an events data payload. Example: `{ published_by: publishing_user.id }`
19
- - `link_global_position` - Integer(optional, read only). If an event is a link event (link events are pointers to other events), this attribute contains a `global_position` of the original event. Manually assigning this attribute has no effect. It is internally set when linking an event to the given stream or when reading events from the database.
20
- - `link_partition_id` - Integer(optional, read only). If an event is a link event - this attribute contains a partition `id` of original event. Manually assigning this attribute has no effect. It is internally set when appending an event to the given stream or when reading events from the database.
21
- - `link` - PgEventstore::Event(optional, read only). When reading from a stream using `resolve_link_tos: true`, if an event is resolved from a link - this attribute contains a `PgEventstore::Event` object which corresponds to that link. Manually assigning this attribute has no effect. It is internally set when reading events from the database.
22
- - `created_at` - Time(optional, read only). Database's timestamp when an event was appended to a stream. You may want to put your own timestamp into a `metadata` attribute - it may be useful when migrating between different databases. Manually assigning this attribute has no effect. It is internally set when appending an event to the given stream or when reading events from the database.
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 `PgEventstore::Stream` class. Here is a description of its attributes:
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). Values which start from `$` sign are reserved by `pg_eventstore`. Such contexts can't be used to append events.
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 `PgEventstore::Stream.all_stream` method. Read more about the "all" stream in the `Reading from the "all" stream` section of [Reading events](reading_events.md) chapter.
48
-
49
- ### System streams
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.