opal-movie
Opal Movie
opal-movie is the optional lifecycle and dependency-injection bridge between Opal and Movie. Opal owns application ingress, configuration, policies, and read models; Movie owns actors, clustering, sharding, and event-sourced entities.
require "opal_movie"
require "opal/autoconfig/http"
record CreateOrder, order_id : String
record GetOrder, order_id : String
alias OrdersMessage = CreateOrder | GetOrder
@[LF::DI::Service]
class OrdersGuardian < Movie::AbstractBehavior(OrdersMessage)
def receive(message : OrdersMessage, context : Movie::ActorContext(OrdersMessage))
# Delegate to actors, sharding, or application-specific gateways here.
Movie::Behaviors(OrdersMessage).same
end
end
@[LF::Application]
@[LF::AutoConfig::HTTP]
@[LF::AutoConfig::Movie(message: OrdersMessage, guardian: OrdersGuardian)]
class OrdersApplication
end
Both frameworks read the same config/application.yml (or the path selected by OPAL_CONFIG):
name: orders
opal:
movie:
await_cluster_up: true
startup_timeout_ms: 10000
shutdown_timeout_ms: 10000
remoting:
enabled: true
host: 0.0.0.0
port: 2552
cluster:
enabled: true
name: orders
seed-nodes:
- movie://orders@orders-0:2552
The adapter resolves the guardian through Opal DI and registers the typed Movie::ActorSystem(OrdersMessage) bean. movie_readiness exposes a live LF::MovieIntegration::ReadinessProbe. During shutdown Opal stops HTTP first, asks Movie to leave its cluster, waits for removal, stops the actor system, and only then closes application DI.
Transactional outbox to RabbitMQ Streams
Enable Opal's existing Crabbit Streams autoconfiguration and the relay:
class OrdersIntegrationStream
include LF::Microservices::StreamTopology
super_stream "orders-integration", 6
end
module Integration
@[LF::Microservices::StreamEventContract(
namespace: "shop",
service: "orders",
contract_version: 1,
event: "order_placed",
schema_version: 1,
)]
record OrderPlaced, order_id : String do
include JSON::Serializable
include LF::Microservices::StreamEvent
end
end
@[LF::Application]
@[LF::AutoConfig::Movie(message: OrdersMessage, guardian: OrdersGuardian)]
@[LF::AutoConfig::CrabbitStreams(
topologies: [OrdersIntegrationStream],
)]
class OrdersApplication
end
opal:
movie:
outbox:
enabled: true
batch_size: 100
poll_interval_ms: 250
lease_ms: 30000
confirmation_timeout_ms: 30000
microservices:
streams:
url: rabbitmq-stream://guest:guest@localhost:5552/%2f
producer_name: orders
Attach a typed Opal event to Movie's existing persistence effect:
persist(OrderPlaced.new(command.order_id), command.operation_id)
.then_publish(
LF::MovieIntegration.stream_event(
OrdersIntegrationStream,
Integration::OrderPlaced.new(command.order_id),
routing_key: command.order_id,
)
)
The Movie write stores the domain event and outbox row atomically. The relay claims Movie's existing lease, reconstructs the original Opal envelope, publishes it through LF::Microservices::StreamPublisher, waits for broker confirmation, and only then lets Movie::Persistence::OutboxDispatcher acknowledge the row. Delivery is at-least-once, so downstream stream handlers must remain idempotent.
opal-movie
- 0
- 0
- 0
- 0
- 2
- about 2 hours ago
- September 9, 2026
Wed, 09 Sep 2026 17:56:14 GMT