-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
deliver broadcast messages asynchronously #41
base: master
Are you sure you want to change the base?
Conversation
d686343
to
f08db8e
Compare
graphql-anycable.gemspec
Outdated
@@ -30,11 +30,13 @@ Gem::Specification.new do |spec| | |||
spec.add_dependency "anyway_config", ">= 1.3", "< 3" | |||
spec.add_dependency "graphql", ">= 1.11", "< 3" | |||
spec.add_dependency "redis", ">= 4.2.0" | |||
spec.add_dependency "activejob", ">= 5.2.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't be a dependency; the gem is Rails-free, we only provide ActiveJob integration if ActiveJob is available in the runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it to spec.add_development_dependency "activejob", ">= 6.0.0"
because otherwise, I got an error during running specs
lib/graphql-anycable.rb
Outdated
@@ -6,6 +6,7 @@ | |||
require_relative "graphql/anycable/cleaner" | |||
require_relative "graphql/anycable/config" | |||
require_relative "graphql/anycable/railtie" if defined?(Rails) | |||
require_relative "graphql/adapters/base_job" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move into graphql/anycable/railtie
and load only if ActiveJob is present (using ActiveSupport.on_load(:active_job) { ... }
hook).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I done it, but I had an error in this case
initializer 'graphql_anycable.load_trigger_job' do
ActiveSupport.on_load(:active_job) do
require "graphql/adapters/trigger_job"
end
end
in that case, my application (rails 6, with configurated active_job) does not load it, because my application does not call any ActiveJob
jobs in the code
I checked and saw, that ActiveSupport.on_load(:active_job)
loads only when my rails application calls any jobs
but, maybe I misunderstood something.
Currently, I just commented ActiveSupport.on_load(:active_job)
and I think, if we are in Railtie
, we can require
TriggerJob
in initialize
block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.
I'm looking at how ActionMailer or ActiveStorage does this, and it seems that we can just add require "active_job/railtie"
and load our job class after that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for that!
The trouble was with my test application
lib/graphql/adapters/base_job.rb
Outdated
class BaseJob < ActiveJob::Base | ||
DEFAULT_QUEUE_NAME = :default | ||
|
||
queue_as { GraphQL::AnyCable.config.async_broadcasting["queue"] || DEFAULT_QUEUE_NAME } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default name must be define in the configuration, no need to use a fallback here:
queue_as { GraphQL::AnyCable.config.async_broadcasting["queue"] || DEFAULT_QUEUE_NAME } | |
queue_as do | |
AnyCable.config.active_job_queue_name | |
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, we go with adapter instances, we can specify the queue right when calling #perform_later
:
TriggerJob.set(queue: queue).perform_later(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Done
lib/graphql/adapters/base_job.rb
Outdated
private | ||
|
||
def schema_parse(serialized_payload) | ||
payload = JSON.parse(serialized_payload) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, we can use symbolize_names here and avoid #transform_keys
?
payload = JSON.parse(serialized_payload) | |
payload = JSON.parse(serialized_payload, symbolize_names: true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, yeah. We can just put symbolized hash to ActiveJob
arguments, and it will work without JSON.parse
lib/graphql/anycable/config.rb
Outdated
attr_config use_async_broadcasting: true | ||
attr_config async_broadcasting: { queue: "broadcasting", class: "GraphQL::Adapters::BaseJob" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not how adapter pattern is implemented. The API must be as follows:
GraphQL::AnyCable.delivery_method = :inline
GraphQL::AnyCable.delivery_method = :active_job
# or with options
GraphQL::AnyCable.delivery_method = :active_job, queue: "custom_queue", job_class: "MyJob"
And then, in the #trigger
method we simply delegate the execution to the configured adapter instance. Smth like:
def trigger(...)
AnyCable.delivery_adapter.trigger(...)
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it, but without "="
It works like that
GraphQL::AnyCable.delivery_method("active_job", queue: "custom_queue", job_class: "MyJob")
But, if we need like setter
, we will able to call it only like that (adds {
and }
for options)
GraphQL::AnyCable.delivery_method = :active_job, {queue: "custom_queue", job_class: "MyJob"}
Because the setter, as I know, cannot accept keyword arguments
GraphQL::AnyCable.delivery_method = "inline", queue: "queue", job_class: "Job"
SyntaxError ((irb):7: syntax error, unexpected tLABEL)
...ble.delivery_method = 1, queue: 123, job_class: "Abc"
... ^~~~~~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we need like setter, we will able to call it only like that
Yes, and that's how it should be done. That's a common pattern used by Rails and many libraries, We shouldn't reinvent it.
So, please, implement it using the original proposed interface.
P.S. See, for example: https://github.com/palkan/active_delivery/blob/master/lib/abstract_notifier/async_adapters.rb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mm, yea, I earlier said, why I did not do that
I fixed it, but it will work only with
GraphQL::AnyCable.delivery_method = :active_job, {queue: "custom_queue", job_class: "MyJob"}
so, we need to wrap by {
and }
last arguments
end | ||
|
||
executor_class_job.perform_later( | ||
serialized_arguments, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not serialize arguments; it's the responsibility of Active Job; we may assume that arguments are GlobalID identifiable, so we can pass them as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea, currently I do not serialize them, but I still collected them (arguments)
|
||
return Adapters::BaseJob unless custom_class | ||
|
||
Object.const_get(config.async_broadcasting["class"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The job class is inferred in Rails context where we can use #safe_constantize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
But for job_class
, I put #constantize
instead of #safe_constantize
, because I think that #constantize
will be useful for cases when the user puts anything wrong to param job_class
.
And constantize
will raise an error and we will see it immediately
But, if I am wrong, I will fix it
Object.const_get(config.async_broadcasting["class"]) | ||
end | ||
|
||
def perform(event, object) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do we use this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was pre-alfa version of the code, sorry :)
spec/adapters/base_job_spec.rb
Outdated
require "active_job" | ||
|
||
RSpec.describe GraphQL::Adapters::BaseJob, type: :job do | ||
ActiveJob::Base.queue_adapter = :inline |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never define configuration outside of before
and after
hooks, and always restore the value after each test. Otherwise it's easier to introduce unpredictable global state and flakiness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the advice!
79c4a14
to
1dee021
Compare
@executor_method = executor_object.class::EXECUTOR_METHOD_NAME | ||
end | ||
|
||
def trigger(...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use here "...", but It requires "ruby-2.7".
I checked ci-cd, and I saw minimal ruby versions - 2.7, but if we look at the documentation (in anycable-rails, because it is a dependency), I see the requirement for ruby-version 2.6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@palkan please tell me, do we need to consider ruby 2.6 and avoid using "..." ?
If yes, I will fix it
lib/graphql-anycable.rb
Outdated
@@ -6,6 +6,7 @@ | |||
require_relative "graphql/anycable/cleaner" | |||
require_relative "graphql/anycable/config" | |||
require_relative "graphql/anycable/railtie" if defined?(Rails) | |||
require_relative "graphql/adapters/base_job" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.
I'm looking at how ActionMailer or ActiveStorage does this, and it seems that we can just add require "active_job/railtie"
and load our job class after that.
@@ -3,31 +3,19 @@ | |||
module GraphQL | |||
module Adapters | |||
class DeliveryAdapter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of adapter patter is to provide extensibility. Moving all different implementations into a single file and calling it an "adapter" doesn't make it an adapter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially, I didn`t understand what was needed, but I now got it.
Now we can easily add any other adapter with his logic and add it in config.delivery_method
Thanks for the lesson
lib/graphql/anycable/config.rb
Outdated
attr_config use_async_broadcasting: true | ||
attr_config async_broadcasting: { queue: "broadcasting", class: "GraphQL::Adapters::BaseJob" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we need like setter, we will able to call it only like that
Yes, and that's how it should be done. That's a common pattern used by Rails and many libraries, We shouldn't reinvent it.
So, please, implement it using the original proposed interface.
P.S. See, for example: https://github.com/palkan/active_delivery/blob/master/lib/abstract_notifier/async_adapters.rb
b9a692a
to
ebc7e13
Compare
@palkan hi! I sum up the changes Currently, we have two adapters, "Inline" and "ActiveJob" in two files The specific adapter is chosen by Also, we have a custom serializer AnyCableSubscriptionSerializer which helps do not think about an instance of class But for example here I used the arguments forwarding shorthand, which exists in ruby 2.7 (I see that ci-cd checks on ruby 2.7 and upper), but this gem has a dependency Maybe here I need not to use the arguments forwarding shorthand |
ebc7e13
to
70dfe1c
Compare
Deliver messages, which are called by method
trigger
, asynchronouslyIt`s a not final PR