Packages

build microservices with Kafka + Avro schemas

Current section

Files

Jump to
kaufmann_ex lib publisher.ex
Raw

lib/publisher.ex

defmodule KaufmannEx.Publisher do
@moduledoc """
Publishes Avro encoded messages to the default topic (`KaufmannEx.Config.default_topic/0`).
"""
require Logger
alias KaufmannEx.Publisher.PartitionSelector
alias KaufmannEx.Publisher.TopicSelector
alias KafkaEx.Protocol.Produce.Message
alias KafkaEx.Protocol.Produce.Request
@doc """
Publishes encoded message
Encodes messages into Avro Schema with ` KaufmannEx.Schemas.encode_message/2`
Defaults to partition 0 for publication. This is less than ideal.
"""
@spec produce(String.t(), String.t(), term(), term()) :: :ok | {:error, any}
def produce(topic, message_name, data, context \\ %{})
def produce(topic, message_name, data, context) when is_atom(message_name),
do: produce(topic, message_name |> Atom.to_string(), data, context)
def produce(topic, message_name, data, context) do
with {:ok, payload} <- KaufmannEx.Schemas.encode_message(message_name, data),
{:ok, partition} <- choose_partition(topic, context) do
Logger.debug(fn -> "Publishing Event #{message_name} on #{topic}@#{partition}" end)
message = %Message{value: payload, key: message_name}
produce_request = %Request{
partition: partition,
topic: topic,
messages: [message]
}
KafkaEx.produce(produce_request)
else
{:error, error} ->
{:error, error}
{:error, error, _} ->
{:error, error}
{:error, error, _payload, _schema} ->
{:error, error}
end
end
@doc """
Produces message to configured producer
Chooses publication topic from Topic Strategy
Events with are produced to the Producer set in config `:kaufmann_ex, :producer_mod`. This defaults to `KaufmannEx.Publisher`
"""
@spec publish(atom, map, map) :: :ok
def publish(event_name, message_body, context \\ %{}) do
log_time_took(context[:timestamp], event_name)
{:ok, topic} = choose_topic(event_name, context)
producer = Application.fetch_env!(:kaufmann_ex, :producer_mod)
producer.produce(topic, event_name, message_body, context)
end
defp choose_partition(topic, metadata) do
partitions_count = get_partitions_count(topic)
strategy = KaufmannEx.Config.partition_strategy()
PartitionSelector.choose_partition(
partitions_count,
metadata,
strategy
)
end
@spec choose_topic(atom, map) :: {atom, String.t()}
def choose_topic(event_name, context) do
strategy = KaufmannEx.Config.topic_strategy()
TopicSelector.choose_topic(event_name, context, strategy)
end
defp get_partitions_count(topic) do
%KafkaEx.Protocol.Metadata.Response{
topic_metadatas: [
%KafkaEx.Protocol.Metadata.TopicMetadata{
partition_metadatas: partition_metadatas
}
]
} = KafkaEx.metadata(topic: topic)
length(partition_metadatas)
end
defp log_time_took(nil, _), do: nil
defp log_time_took(timestamp, event_name) do
Logger.info(fn ->
{:ok, published_at, _} = DateTime.from_iso8601(timestamp)
took = DateTime.diff(DateTime.utc_now(), published_at, :millisecond)
"Responded with #{event_name} in #{took}ms"
end)
end
end