Antenna (antenna v0.2.2)

View Source

Antenna is a mixture of Phoenix.PubSub and :gen_event functionality with some batteries included.

It implements back-pressure on top of GenStage, is fully conformant with OTP Design Principles. and is distributed out of the box.

Antenna supports both asynchronous and synchronous events. While the most preferrable way would be to stay fully async with Antenna.event/3, one still might propagate the event synchronously with Antenna.sync_event/3 and collect all the responses from all the handlers.

One can have as many isolated Antennas as necessary, distinguished by Antenna.t:id/0.

The workflow looks like shown below.

Sequence Diagram

sequenceDiagram
  Consumer->>+Broadcaster: sync_event(channel, event)
  Consumer->>+Broadcaster: event(channel, event) 
  Broadcaster-->>+Consumer@Node1: event
  Broadcaster-->>+Consumer@Node2: event
  Broadcaster-->>+Consumer@NodeN: event
  Consumer@Node1-->>-NoOp: mine?
  Consumer@NodeN-->>-NoOp: mine?
  Consumer@Node2-->>+Matchers: event
  Matchers-->>+Handlers: handle_match/2
  Matchers-->>+Handlers: handle_match/2
  Handlers->>-Consumer: response(to: sync_event)

ASCII representation.

Usage Example

The consumer of this library is supposed to declare one or more matchers, subscribing to one or more channels, and then call Antenna.event/2 to propagate the event.

assert {:ok, _pid, "{:tag_1, a, _} when is_nil(a)"} =
  Antenna.match(Antenna, {:tag_1, a, _} when is_nil(a), self(), channels: [:chan_1])

assert :ok = Antenna.event(Antenna, [:chan_1], {:tag_1, nil, 42})
assert_receive {:antenna_event, :chan_1, {:tag_1, nil, 42}}

Summary

Functions (Client)

Sends an event to all the associated matchers through channels.

Sends an event to all the associated matchers through channels and collects results.

Functions (Setup)

Adds a handler to the matcher process specified by pid

Declares a matcher for tagged events.

Subscribes a matcher process specified by pid to a channel(s)

Removes a handler from the matcher process specified by pid

Undeclares a matcher for tagged events previously declared with Antenna.match/4.

Unsubscribes a previously subscribed matcher process specified by pid from the channel(s)

Functions (Internals)

Returns a map of matches to matchers

Types

The identifier of the channel, messages can be sent to, preferrably atom()

The event being sent to the listeners

The actual handler to be associated with an event(s). It might be either a function or a process id, in which case the message of a following shape will be sent to it.

The identifier of the isolated Antenna

Functions

Returns a specification to start this module under a supervisor.

Functions (Client)

event(id \\ Antenna, channels, event)

@spec event(id :: id(), channels :: channel() | [channel()], event :: event()) :: :ok

Sends an event to all the associated matchers through channels.

The special :* might be specified as channels, then the event will be sent to all the registered channels.

If one wants to collect results of all the registered event handlers, they should look at sync_event/3 instead.

sync_event(id \\ Antenna, channels, event)

@spec sync_event(id :: id(), channels :: channel() | [channel()], event :: event()) ::
  [term()]

Sends an event to all the associated matchers through channels and collects results.

The special :* might be specified as channels, then the event will be sent to all the registered channels.

Unlike event/3, this call is synchronous, which means it would block until all the registered handlers respond; the results would have been collected and returned as a list of no specific order.

Functions (Setup)

handle(id \\ Antenna, handlers, pid)

@spec handle(id :: id(), handlers :: handler() | [handler()], pid()) :: :ok

Adds a handler to the matcher process specified by pid

match(id \\ Antenna, match, handlers, opts \\ [])

(macro)

Declares a matcher for tagged events.

Example

Antenna.match(Antenna, %{tag: _, success: false}, fn channel, message ->
  Logger.warning("The processing failed for [" <> 
    inspect(channel) <> "], result: " <> inspect(message))
end, channels: [:rabbit])

subscribe(id \\ Antenna, channels, pid)

@spec subscribe(id :: id(), channels :: channel() | [channel()], pid()) :: :ok

Subscribes a matcher process specified by pid to a channel(s)

unhandle(id \\ Antenna, handlers, pid)

@spec unhandle(id :: id(), handlers :: handler() | [handler()], pid()) :: :ok

Removes a handler from the matcher process specified by pid

unmatch(id \\ Antenna, match)

(macro)

Undeclares a matcher for tagged events previously declared with Antenna.match/4.

Accepts both an original match or a name returned by Antenna.match/4, which is effectively Macro.to_string(match).

Example

Antenna.unmatch(Antenna, %{tag: _, success: false})

unsubscribe(id \\ Antenna, channels, pid)

@spec unsubscribe(id :: id(), channels :: channel() | [channel()], pid()) :: :ok

Unsubscribes a previously subscribed matcher process specified by pid from the channel(s)

Functions (Internals)

registered_matchers(id)

@spec registered_matchers(id :: id()) :: %{
  required(term()) => {pid(), Supervisor.child_spec()}
}

Returns a map of matches to matchers

Types

channel()

@type channel() :: atom() | term()

The identifier of the channel, messages can be sent to, preferrably atom()

event()

@type event() :: term()

The event being sent to the listeners

handler()

@type handler() ::
  (event() -> term())
  | (channel(), event() -> term())
  | Antenna.Matcher.t()
  | pid()
  | GenServer.name()

The actual handler to be associated with an event(s). It might be either a function or a process id, in which case the message of a following shape will be sent to it.

{:antenna_event, channel, event}

id()

@type id() :: module()

The identifier of the isolated Antenna

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(init_arg)