Messaging in Elixir with Conduit
Allen Madsen
@blatyo
github.com/blatyo
Cinch Financial
Your totally unbiased, comprehensive, personal CFO.
Conduit is a Framework for Working with Message Queues
What is a message queue?
enqueue
dequeue
name: user.created
async
Delivery Guarantees
- At most once delivery
- Immediately acknowledge messages
- Work may or may not get done
- At least once delivery
- Acknowledge messages after work is done
- Work is guaranteed to happen once or more
- Work needs to be idempotent
- Exactly once delivery
- Complex enough that I'm not going to cover it
Buffer
Backup
Push vs Pull
- Push - Queue sends messages based on quality of service configuration.
- Backpressure is handled by the message queue.
- More efficient. Only does something when there is a message.
- Pull - Consumer polls for messages.
- Backpressure is your job.
- Less efficient. Must check with message queue even where there are no messages.
Decoupling
- Simplified deploys
- Only needs to know how to talk to queue. Not many other apps.
- Can be stopped/started arbitrarily as long as setup for at least once guarantee.
- Choice of language
- Independent scaling
- Resiliancy
- Failure in one system doesn't cascade into others
- System can recover
Why Build Conduit?
Lots of libraries, not much in regards to a scalable architecture.
Why Build Conduit?
Consider a library that allows you to make a connection versus one that gives you a scalable OTP architecture.
Reusable messaging patterns packaged as plugs.
Why Build Conduit?
What is Conduit?
Conduit is a framework for...
Connecting to a Message Queue Through an Adapter
config :my_app, MyApp.Broker,
adapter: ConduitAMQP,
url: "amqp://my_app:secret@my-rabbit-host.com"
config :my_app, MyApp.Broker,
adapter: ConduitSQS,
access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role],
secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role]
And eventually others
config :my_app, MyApp.Stomp,
adapter: ConduitStomp,
host: "localhost",
port: 61613,
login: "guest",
passcode: "guest"
Configuring Queues and Exchanges
defmodule MyApp.Broker do
use Conduit.Broker, otp_app: :my_app
configure do
exchange "my.topic", type: "topic", durable: true
queue "my.queue", from: ["#.created.user"], exchange: "amq.topic", durable: true
end
end
Send Messages with Pipelines
defmodule MyApp.Broker do
use Conduit.Broker, otp_app: :my_app
pipeline :out_tracking do
plug Conduit.Plug.CorrelationId
plug Conduit.Plug.CreatedBy, app: "my_app"
plug Conduit.Plug.CreatedAt
plug Conduit.Plug.LogOutgoing
end
pipeline :serialize do
plug Conduit.Plug.Format, content_type: "application/json"
plug Conduit.Plug.Encode
end
outgoing do
pipe_through [:out_tracking, :serialize]
publish :created_user, exchange: "amq.topic", to: "my_app.created.user"
end
end
import Conduit.Message
message = put_body(%Conduit.Message{}, %{"email" => "bob@gmail.com"})
MyApp.Broker.publish(message, :created_user)
Receive Messages with Pipelines
defmodule MyApp.Broker do
use Conduit.Broker, otp_app: :my_app
pipeline :in_tracking do
plug Conduit.Plug.CorrelationId
plug Conduit.Plug.LogIncoming
end
pipeline :error_handling do
plug Conduit.Plug.AckException
plug Conduit.Plug.DeadLetter, broker: MyApp.Broker, publish_to: :error
plug Conduit.Plug.Retry, attempts: 5
end
pipeline :deserialize do
plug Conduit.Plug.Decode
plug Conduit.Plug.Parse, content_type: "application/json"
end
incoming MyApp do
pipe_through [:in_tracking, :error_handling, :deserialize]
subscribe :user_created, SendWelcomeEmailSubscriber,
from: "my_app.created.user"
end
end
Receive Messages with Pipelines (cont)
defmodule MyApp.SendWelcomeEmailSubscriber do
use Conduit.Subscriber
def process(message, _) do
%{"email" => email} = message.body
# send email
message
end
end
How do I test with Conduit?
Use the Test Adapter
config :my_app, MyApp.Broker,
adapter: Conduit.TestAdapter
Calling a Subscriber
import Conduit.Message
message =
%Conduit.Message{}
|> put_body(%{"email" => "bob@gmail.com"})
|> put_correlation_id("123")
message = MyApp.Broker.receives(:user_created, message)
# assert properties about the message
# assert side effects
Through it's pipelines
import Conduit.Message
message =
%Conduit.Message{}
|> put_body(%{"email" => "bob@gmail.com"})
|> put_correlation_id("123")
message = MyApp.UserCreatedSubscriber.run(message)
# assert properties about the message
# assert side effects
Or directly
Sending a Message
import Conduit.Message
message =
%Conduit.Message{}
|> put_body(%{"email" => "bob@gmail.com"})
|> put_correlation_id("123")
MyApp.Broker.publish(:user_created, message)
# or something that calls publish
assert_message_published message
# assert properties about the message
Whats the Future for Conduit?
Future?
- v1.0.0
- More adapters
- More how to docs
- v2.0.0
- Batch message processing
- More GenStage
Questions?
deck
By blatyo
deck
- 3,320