gitlab-org/gitlab
EventStore
A pub/sub layer built on Sidekiq for decoupling domain events from their consumers.
Purpose
Producers publish small, schema-validated events. Multiple subscribers react asynchronously without the producer knowing about them. The pattern keeps services from accumulating after-effect chains.
Source
| Concern | Location |
|---|---|
| Core API | lib/gitlab/event_store.rb |
| Subscriber concern | lib/gitlab/event_store/subscriber.rb |
| Event base class | lib/gitlab/event_store/event.rb |
| Initializer (subscription wiring) | config/initializers/event_store.rb |
| Event definitions | app/events/ and ee/app/events/ (~15 directories) |
Defining an event
module Projects
class ProjectCreatedEvent < Gitlab::EventStore::Event
def schema
{
type: 'object',
required: %w[project_id],
properties: {
project_id: { type: 'integer' }
}
}
end
end
endGenerated via bin/rails generate event Projects/ProjectCreated project_id:integer.
Publishing
Gitlab::EventStore.publish(
Projects::ProjectCreatedEvent.new(data: { project_id: project.id })
)Publish happens synchronously in the request transaction. The schema is validated before enqueueing.
Subscribing
class Onboarding::ProjectCreatedWorker
include ApplicationWorker
include Gitlab::EventStore::Subscriber
feature_category :onboarding
idempotent!
def handle_event(event)
# event.data is a hash matching the schema
end
endWire it in the initializer:
# config/initializers/event_store.rb
Gitlab::EventStore.subscribe(
Onboarding::ProjectCreatedWorker,
to: Projects::ProjectCreatedEvent
)A worker can subscribe with a if: block for filtering:
Gitlab::EventStore.subscribe(
FooWorker,
to: Issues::IssueClosedEvent,
if: ->(event) { event.data[:weight].present? }
)Delivery semantics
- Each subscriber receives its own Sidekiq job (one event → N enqueued jobs).
- Idempotent and deduplicated by default (subscribers must be idempotent).
- Schema mismatch raises in development/test, logs in production.
- The event is enqueued only after the database transaction commits (via
after_commit); failed transactions don't fire events.
When to use
- Cross-domain notifications (project created → onboarding wakes up; issue closed → analytics reads it).
- Avoid orchestrators with
MyService.new(...).execute then OtherWorker.perform_async then ....
When not to use
- Synchronous business logic — call the service directly.
- Tight feedback loops where the consumer must finish before the producer responds.
- Things that need ordering guarantees across events — Sidekiq doesn't preserve order.
Related
- Sidekiq jobs — subscribers are workers.
- Patterns and conventions.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.