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.

Repository

opal-movie

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 2
  • about 2 hours ago
  • September 9, 2026
License

Links
Synced at

Wed, 09 Sep 2026 17:56:14 GMT

Languages