The Clean Way to Handle Sendbird Webhook Using Ruby on Rails
Hi, as I said before in my first blog I want to share about design patterns for Ruby. So I will share substantial reasons for the existence of design patterns and how a design pattern solves your common problems in Ruby. And to make it clear and understandable, I will explain it using a good example: Sendbird Webhook. Before start, you can read the documentation here.
And for you who doesn’t know about design pattern, a design pattern is a general, typical solution to common problems in Software Engineering. So I think it’s a must for a developer to know at least one design pattern, especially Ruby developer. Why? Because Ruby is flexible, so we need something that can keep our codebases clean and understandable for every developer.
In this blog, I will explain my favorite design pattern, which is the Command Pattern. So are you ready? Let’s start then.
Like the others, Sendbird only needs one endpoint to handle all kinds of events. That part is very interesting because the command pattern can solve that problem. So firstly create a controller for it.
**app
|_controllers
|_sendbird_controller.rb**
And create a new action on it: webhook. Don’t forget to add it to routes.rb.
class SendbirdController
def webhook
status: 200
end
end
Since Sendbird doesn’t care about our process, just respond with status: 200 *immediately. And create a worker to handle the payload from Sendbird. Why using the worker? First, because Sendbird only sends the request 3 times until it receives *status: 200. And our workers can save the payload and retry the process as many as we want if we got a problem until the problem is gone. Second, because we need to respond immediately to avoid too many requests to our server. Third, hmm I think that’s it.
app
|_controllers
|_sendbird_controller.rb
** |_workers
|_sendbird
|_webhook_worker.rb**
And put the worker on sendbird_controller.
class SendbirdController
def webhook
::Sendbird::WebhookWorker.perform_later(params)
status: 200
end
end
Before we start coding the worker, let’s see Sendbird request params:
{
'category': 'open_channel:create',
'created_at': 1540866408000,
'operators': [
{
'user_id': 'Jay',
'nickname': 'Mighty',
'profile_url': '[https://sendbird.com/main/img/profiles/profile_26_512px.png'](https://sendbird.com/main/img/profiles/profile_26_512px.png'),
'metadata': {}
}
],
'channel': {
'name': 'Jeff and friends',
'channel_url': 'sendbird_open_channel_1_2681099203cd6b78414fe672927a43fcf3a30f09',
'custom_type': '',
'is_distinct': false,
'is_public': false,
'is_super': false,
'is_ephemeral': false,
'is_discoverable': false,
'data': ''
},
'app_id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
}
The params above represent the command. And there is category *that represents the event of the command and can be a key for command pattern. We can see two parts on *category *value: *open_channel which is the resource and *create *which is the event of the resource. If we’re using the traditional way, the worker code will be like this:
module Sendbird
class WebhookWorker
def perform(params)
if params['category'] == 'open_channel:create'
# do something
elsif params['category] == 'open_channel:update'
# do something
...
end
end
end
end
Or
module Sendbird
class WebhookWorker
def perform(params)
case params['category']
when 'open_channel:create'
# do something
when 'open_channel:update'
# do something
...
end
end
end
end
So what will happen next if we want to implement all kinds of events? Can you imagine that? LOL
So here the clean way to solve that problem:
module Sendbird
class WebhookWorker
attr_reader :params, :klass
def self.perform(params)
new(params).perform
end
def initialize(params)
module_name, klass_name = params['category'].split(':')
@params = params
@klass = "::Sendbird::Webhook::#{module_name.camelize}::#{klass.camelize}".constantize
end
def perform
klass.new(params).perform
end
end
end
To put the logic for each resource and event, we only need to create a new service. For example, open_channel:create. Create a new service here:
app
|_controllers
|_sendbird_controller.rb
**|_services
|_sendbird
|_webhook
|_open_channel
|_create.rb**
|_workers
|_sendbird
|_webhook_worker.rb
With this code:
module Sendbird
module Webhook
module OpenChannel
class Create
attr_reader params
def initialize(params)
@params = params
end
def perform
# do something when create open_channel event happens
end
end
end
end
end
If we want to handle a new event, simply create a new service. For example, now we want to handle *group_channel:update. *Just create a new service:
app
|_controllers
|_sendbird_controller.rb
|_services
|_sendbird
|_webhook
**|_group_channel
|_update.rb**
|_open_channel
|_create.rb
|_workers
|_sendbird
|_webhook_worker.rb
With this code:
module Sendbird
module Webhook
module GroupChannel
class Update
attr_reader params
def initialize(params)
@params = params
end
def perform
# do something when update group_channel event happens
end
end
end
end
end
Simple right? With this method, we can follow *rubocop *rules to avoid long class or method line length and make the file readable. But you will have many files, which is that’s okay for me.
I think that’s all. Thank you!
This post originally shared at Medium