defmodule EventBus do
@moduledoc """
Traceable, extendable and minimalist event bus implementation for Elixir with
built-in event store and event observation manager based on ETS.
"""
alias EventBus.Manager.{
Notification,
Observation,
Store,
Subscription,
Topic
}
alias EventBus.Model.Event
@typedoc "EventBus.Model.Event struct"
@type event :: Event.t()
@typedoc "Event id"
@type event_id :: String.t() | integer()
@typedoc "Tuple of topic name and event id"
@type event_shadow :: {topic(), event_id()}
@typedoc "Event subscriber"
@type subscriber :: subscriber_without_config() | subscriber_with_config()
@typedoc "Subscriber configuration"
@type subscriber_config :: any()
@typedoc "List of event subscribers"
@type subscribers :: list(subscriber())
@typedoc "Event subscriber with config"
@type subscriber_with_config :: {module(), subscriber_config()}
@typedoc "Tuple of subscriber and event reference"
@type subscriber_with_event_ref ::
subscriber_with_event_shadow() | subscriber_with_topic_and_event_id()
@typedoc "Tuple of subscriber and event shadow"
@type subscriber_with_event_shadow :: {subscriber(), event_shadow()}
@typedoc "Tuple of subscriber, topic and event id"
@type subscriber_with_topic_and_event_id ::
{subscriber(), topic(), event_id()}
@typedoc "Tuple of subscriber and list of topic patterns"
@type subscriber_with_topic_patterns :: {subscriber(), topic_patterns()}
@typedoc "Event subscriber without config"
@type subscriber_without_config :: module()
@typedoc "Topic name"
@type topic :: atom()
@typedoc "List of topic names"
@type topics :: list(topic())
@typedoc "Regex pattern to match topic name"
@type topic_pattern :: String.t()
@typedoc "List of topic patterns"
@type topic_patterns :: list(topic_pattern())
@doc """
Send an event to all subscribers.
## Examples
event = %Event{id: 1, topic: :webhook_received,
data: %{"message" => "Hi all!"}}
EventBus.notify(event)
:ok
"""
@spec notify(event()) :: :ok
defdelegate notify(event),
to: Notification,
as: :notify
@doc """
Check if a topic registered.
## Examples
EventBus.topic_exist?(:demo_topic)
true
"""
@spec topic_exist?(topic()) :: boolean()
defdelegate topic_exist?(topic),
to: Topic,
as: :exist?
@doc """
List all the registered topics.
## Examples
EventBus.topics()
[:metrics_summed]
"""
@spec topics() :: topics()
defdelegate topics,
to: Topic,
as: :all
@doc """
Register a topic.
## Examples
EventBus.register_topic(:demo_topic)
:ok
"""
@spec register_topic(topic()) :: :ok
defdelegate register_topic(topic),
to: Topic,
as: :register
@doc """
Unregister a topic.
## Examples
EventBus.unregister_topic(:demo_topic)
:ok
"""
@spec unregister_topic(topic()) :: :ok
defdelegate unregister_topic(topic),
to: Topic,
as: :unregister
@doc """
Subscribe a subscriber to the event bus.
## Examples
EventBus.subscribe({MyEventSubscriber, [".*"]})
:ok
# For configurable subscribers you can pass tuple of subscriber and config
my_config = %{}
EventBus.subscribe({{OtherSubscriber, my_config}, [".*"]})
:ok
"""
@spec subscribe(subscriber_with_topic_patterns()) :: :ok
defdelegate subscribe(subscriber_with_topic_patterns),
to: Subscription,
as: :subscribe
@doc """
Unsubscribe a subscriber from the event bus.
## Examples
EventBus.unsubscribe(MyEventSubscriber)
:ok
# For configurable subscribers you must pass tuple of subscriber and config
my_config = %{}
EventBus.unsubscribe({OtherSubscriber, my_config})
:ok
"""
@spec unsubscribe(subscriber()) :: :ok
defdelegate unsubscribe(subscriber),
to: Subscription,
as: :unsubscribe
@doc """
Check if the given subscriber subscribed to the event bus for the given topic
patterns.
## Examples
EventBus.subscribe({MyEventSubscriber, [".*"]})
:ok
EventBus.subscribed?({MyEventSubscriber, [".*"]})
true
EventBus.subscribed?({MyEventSubscriber, ["some_initialized"]})
false
EventBus.subscribed?({AnothEventSubscriber, [".*"]})
false
"""
@spec subscribed?(subscriber_with_topic_patterns()) :: boolean()
defdelegate subscribed?(subscriber_with_topic_patterns),
to: Subscription,
as: :subscribed?
@doc """
List the subscribers.
## Examples
EventBus.subscribers()
[MyEventSubscriber]
# One usual and one configured subscriber with its config
EventBus.subscribers()
[MyEventSubscriber, {OtherSubscriber, %{}}]
"""
@spec subscribers() :: subscribers()
defdelegate subscribers,
to: Subscription,
as: :subscribers
@doc """
List the subscribers for the given topic.
## Examples
EventBus.subscribers(:metrics_received)
[MyEventSubscriber]
# One usual and one configured subscriber with its config
EventBus.subscribers(:metrics_received)
[MyEventSubscriber, {OtherSubscriber, %{}}]
"""
@spec subscribers(topic()) :: subscribers()
defdelegate subscribers(topic),
to: Subscription,
as: :subscribers
@doc """
Fetch an event.
## Examples
EventBus.fetch_event({:hello_received, "123"})
%EventBus.Model.Model{}
"""
@spec fetch_event(event_shadow()) :: event() | nil
defdelegate fetch_event(event_shadow),
to: Store,
as: :fetch
@doc """
Fetch an event's data.
## Examples
EventBus.fetch_event_data({:hello_received, "123"})
"""
@spec fetch_event_data(event_shadow()) :: any()
defdelegate fetch_event_data(event_shadow),
to: Store,
as: :fetch_data
@doc """
Mark the event as completed for the subscriber.
## Examples
topic = :hello_received
event_id = "124"
event_shadow = {topic, event_id}
# For regular subscribers
EventBus.mark_as_completed({MyEventSubscriber, event_shadow})
# For configurable subscribers you must pass tuple of subscriber and config
my_config = %{}
subscriber = {OtherSubscriber, my_config}
EventBus.mark_as_completed({subscriber, event_shadow})
:ok
"""
@spec mark_as_completed(subscriber_with_event_ref()) :: :ok
defdelegate mark_as_completed(subscriber_with_event_ref),
to: Observation,
as: :mark_as_completed
@doc """
Mark the event as skipped for the subscriber.
## Examples
EventBus.mark_as_skipped({MyEventSubscriber, {:unmatched_occurred, "124"}})
# For configurable subscribers you must pass tuple of subscriber and config
my_config = %{}
subscriber = {OtherSubscriber, my_config}
EventBus.mark_as_skipped({subscriber, {:unmatched_occurred, "124"}})
:ok
"""
@spec mark_as_skipped(subscriber_with_event_ref()) :: :ok
defdelegate mark_as_skipped(subscriber_with_event_ref),
to: Observation,
as: :mark_as_skipped
end