Automatically detect dangerous operations (like gRPC calls, external APIs) within Ecto transactions using telemetry-based monitoring.
Set it up once in your test suite, and every test will automatically fail if violations are detected.
Making external calls within database transactions can lead to:
- Deadlocks - External services can be slow or hang
- Connection pool exhaustion - Transactions hold database connections
- Long-running transactions - External latency compounds transaction time
- Distributed transaction issues - No atomic rollback across services
Add bunker
to your dependencies in mix.exs
:
def deps do
[
{:bunker, "~> 0.1.0"}
]
end
# config/test.exs
config :bunker,
enabled: true,
repos: [MyApp.Repo],
adapters: [Bunker.Adapters.GRPC],
log: true,
log_level: :error
# test/support/data_case.ex
defmodule MyApp.DataCase do
use ExUnit.CaseTemplate
import Bunker.TestHelper
setup do
# This single line enables automatic violation detection!
setup_transaction_violation_check!()
# Your other setup code...
:ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
:ok
end
end
# test/my_app/orders_test.exs
defmodule MyApp.OrdersTest do
use MyApp.DataCase
test "creating an order" do
# ❌ This test will raise an error
MyApp.Repo.transaction(fn ->
order = MyApp.Repo.insert!(%Order{total: 100})
# gRPC call detected - test raises Bunker.ViolationError
MyGrpcClient.notify_payment_service(order.id)
end)
end
test "creating an order correctly" do
# ✅ This test PASSES - RPC call is outside the transaction
{:ok, order} = MyApp.Repo.transaction(fn ->
MyApp.Repo.insert!(%Order{total: 100})
end)
# This is fine - no transaction active
MyGrpcClient.notify_payment_service(order.id)
end
end
Monitors outgoing gRPC client calls via telemetry events from the grpc
library:
config :bunker,
adapters: [Bunker.Adapters.GRPC]
The gRPC adapter automatically detects:
[:grpc, :client, :rpc, :start]
- Client-side RPC calls (outgoing)
You can create adapters for any library that emits telemetry events (or emit your own):
defmodule MyApp.CustomAdapter do
@behaviour Bunker.Adapter
@impl true
def events do
[
[:my_library, :operation, :start]
]
end
@impl true
def handle_event([:my_library, :operation, :start], _measurements, metadata, _config) do
{:ok, :custom_operation, %{
operation: metadata.operation_name,
target: metadata.target
}}
end
def handle_event(_event, _measurements, _metadata, _config) do
:ignore
end
@impl true
def format_operation(:custom_operation, metadata) do
"custom_operation: #{metadata.operation} on #{metadata.target}"
end
end
Add your custom adapter to the configuration:
config :bunker,
adapters: [
Bunker.Adapters.GRPC,
MyApp.CustomAdapter
]
Sets up automatic violation checking for all tests. Violations are automatically raised at the end of each test.
setup do
setup_transaction_violation_check!()
:ok
end
Creates a mock transaction environment for unit tests:
test "detects violations in mock transaction" do
with_mock_transaction(fn ->
:telemetry.execute([:grpc, :client, :rpc, :start], %{}, %{
service: "Users",
method: "GetUser"
})
end)
# Violation will be raised automatically if setup_transaction_violation_check!() was called
end
Sometimes you need to make external calls within a transaction:
Bunker.disabled(fn ->
Repo.transaction(fn ->
user = Repo.insert!(%User{name: "Alice"})
# External calls allowed here
MyGrpcClient.notify_user_created(user.id)
end)
end)
- Application Start: Bunker attaches telemetry handlers for all configured adapters
- Telemetry Event: When a monitored library emits an event (e.g., gRPC call starts)
- Adapter Processing: The appropriate adapter extracts operation metadata
- Transaction Check: Bunker checks if any configured repo has an active transaction
- Violation Handling: Stores violation and logs it; test helper raises it at test end
Bunker uses a telemetry-based adapter system:
- Adapters listen to telemetry events
- Core checks if we're in an Ecto transaction when an adapter detects an operation
- Handler stores violations
- Test Helper automatically raises stored violations at test completion
# Run all tests
mix test