opal_movie
Version, currently main branch1 version
- main branchlatestSep 9, 2026
github.com/mikeoz32/opal-movie
Optional Movie actor runtime integration for Opal applications
Installation
# Add this to your shard.yml
dependencies:
opal_movie:
github: mikeoz32/opal-movie
branch: mainmain is a branch, not a release, so this tracks it rather than pinning a version.
Then run:
shards installshard.yml
- Crystal
>= 1.21.0, < 1.22.0- License
- MIT
- Author
- Mike Oz
Dependencies
Runtime Dependencies
README
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.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This branch
- Branch
main- Seen
- Sep 9, 2026
- Crystal
>= 1.21.0, < 1.22.0- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/mikeoz32/opal-movie
Metadata
- Created
- Sep 10, 2026
- Updated
- Oct 20, 2026
- Synced
- Oct 20, 2026
- Versions
- 1