Sourced.EventStore.Query (sourced v0.2.0)

Copy Markdown View Source

Describes which events a store should match.

A query is a plain list of items, as described by the DCB specification. Each item is a map with an optional :types and an optional :tags key. An event matches the query when it matches any of the items (OR). Within an item, the event's type must be one of the item's types (OR) and the event's tags must contain all of the item's tags (AND). An omitted or empty list places no constraint on that dimension, so an item with only tags matches them across every event type, and one with only types matches those types whatever they are tagged with.

An empty query ([]) matches every event.

Tags are what makes this work: rather than querying into the payload, events carry Sourced.StoredEvent.tag/0s naming the domain concepts they concern, and items match on those. See Sourced.StoredEvent for the tag conventions and Sourced.Middleware.Domain.Event for deriving them from domain structs.

A query is a matcher and nothing else. Bounding a result to a range of sequences, or to a number of events, is a property of a particular read rather than of the set of events being described, so :from, :to and :limit are options of Sourced.EventStore.Behaviour.query/2 instead. The same matcher can then be reused for the read, for the append that follows it, and for a subscription, without one use silently inheriting another's bounds.

Examples

# Every OrderPlaced or OrderShipped tagged for order 1, plus every event
# of any type tagged for customer 42.
[
  %{types: ["OrderPlaced", "OrderShipped"], tags: ["order:1"]},
  %{tags: ["customer:42"]}
]

Note that the performance of any given query will be dependent on the event store and its implementation details.

Summary

Types

A single item of a query, constraining event types, tags, or both.

t()

An event type criterion: a stored type string ("order_placed"), an atom shorthand for one (:order_placed), or a domain event module (OrderPlaced). Criteria are kept as given; atoms are resolved to their stored type by the Sourced.Middleware.Domain middleware.

Functions

Filters events down to the ones matching query.

Returns true if query has no items, i.e. it matches every event.

Returns true if event matches the query.

Types

item()

@type item() :: %{
  optional(:types) => [type()],
  optional(:tags) => [Sourced.StoredEvent.tag()]
}

A single item of a query, constraining event types, tags, or both.

t()

@type t() :: [item()]

type()

@type type() :: String.t() | atom() | module()

An event type criterion: a stored type string ("order_placed"), an atom shorthand for one (:order_placed), or a domain event module (OrderPlaced). Criteria are kept as given; atoms are resolved to their stored type by the Sourced.Middleware.Domain middleware.

Functions

apply(events, query)

@spec apply([Sourced.StoredEvent.t()], t() | nil) :: [Sourced.StoredEvent.t()]

Filters events down to the ones matching query.

Intended for event store implementations that load events into memory before matching them. Bounding the result is separate; see Sourced.EventStore.Behaviour.query/2.

empty?(query)

@spec empty?(t() | nil) :: boolean()

Returns true if query has no items, i.e. it matches every event.

Examples

iex> Query.empty?([])
true

iex> Query.empty?(nil)
true

iex> Query.empty?([%{tags: ["order:1"]}])
false

matches?(query, event)

@spec matches?(t() | nil, Sourced.StoredEvent.t()) :: boolean()

Returns true if event matches the query.

An empty query matches every event; otherwise the event must match at least one item.

Matching is exact: criteria are compared against event.type as-is, so an item holding event modules matches events whose type is a module — which is what Sourced.Middleware.Domain restores it to on the way out — while one holding stored type strings matches raw events.