-
-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Our application runs as two separate servers handling different traffic - one for the back office area and one for all our customers. As a result we're also running two separate AnyCable servers to allow separate scaling and to keep things walled. They still use the same database and Redis but they may run different Ruby code.
In some cases we want the customer-facing application to be able to trigger GraphQL subscriptions (using anycable-graphql) to the API sitting in the other realm, e.g. if customer sends a message to the ops, the ops application should refresh the chat and the other way around.
There's currently no way to change the AnyCable channel configuration on the fly for redis and redisx adapters (and most likely others too). It would be great to be able to change it, for ex. inside the scope of the block.
Considerations
Current code is not thread-safe - the broadcast adapter is configured in broadcast_adapter= of AnyCable singleton and the channel is set during that initialization, also as an @ivar (@channel). There are two ways this could be resolved to allow dynamic replacement - either with Thread.current with fallback to AnyCable.config.redis_channel (that's how our monkey-patch works but it's likely not ideal for all cases) or a mutex on changing the @channel value.
@palkan is this a feature you'd be interested in supporting? If so I can send a PR implementing it for all broadcast adapters.
The monkey-patch we're using
module AnyCableDynamicChannel
def initialize(channel: AnyCable.config.redis_channel, **options)
super
@channel = nil
Thread.current[:channel] = channel
end
def channel
Thread.current[:channel] || AnyCable.config.redis_channel
end
def with_channel(channel_name)
return yield if channel_name == channel
begin
old_channel = channel
Thread.current[:channel] = channel_name
yield
ensure
Thread.current[:channel] = old_channel
end
end
def to_admin(&block)
with_channel('__anycable-admin__', &block)
end
def to_www(&block)
with_channel('__anycable-www__', &block)
end
endUsage in code:
def trigger_subscription(schema, event_name, args, object, scope: nil, context: nil)
raise InvalidSchemaName, "Unknown schema #{schema}, expected one of #{SCHEMAS.keys.join(', ')}" unless SCHEMAS.key?(schema)
if schema == :admin
to_admin { SCHEMAS[schema].subscriptions.trigger(event_name, args, object, scope:, context:) }
else
to_www { SCHEMAS[schema].subscriptions.trigger(event_name, args, object, scope:, context:) }
end
end
def to_admin(&block)
::AnyCable.broadcast_adapter.to_admin(&block)
end
def to_www(&block)
::AnyCable.broadcast_adapter.to_www(&block)
end