Tại sao nên dùng Service Object trong code của bạn?
Bài đăng này đã không được cập nhật trong 5 năm
1. Service Objects là gì
Như ta đã biết, Rails là một trong những adaptors sử dụng mô hình MVC (Model, View, Controller). Đối với dự án nhỏ, khi các chức năng chưa có nhiều, thì việc sử dụng mô hình này tương đối hiệu quả. Tuy nhiên, đối với các dự án lớn hơn khi các method có xu hướng đẩy vào các ActiveRecord models thì chúng ta sẽ thấy việc đọc hiểu dựa vào mô hình này tương đối phức tạp. Do đó, chúng ta phải chia tách các chức năng thành các Service Objects.
Hay nói cách khác Service Objects là đoạn code chứa các business logic, nhìn vào nó, người lập trình có thể biết là application có những chức năng như thế nào.
Ví dụ:
Khi chúng ta tạo chứng năng push notification
cho clients sau khi tạo 1 record nào đó, Bạn sử dụng controller làm nơi viết code
# app/controllers/test_controller.rb #create
def create
...
if @test.save
# code push notification
else
# code
end
...
Thời gian đầu, chức năng của bạn chạy ổn. Nhưng khi khách hàng thêm yêu cầu, tôi muốn push notification sau khi click 1 button nào đó
. Okie, thật sự không ổn nếu viết đi viết lại đoạn code trên vào các controller với từng loại button. Bạn nghĩ đến việc ném nó vào model
# app/models/control_test_push_notification.rb
class ControlTestPushNotification < ActiveRecord::Base
# code
# this method is called from the controller
def push_notifications(record)
# code
end
end
Code lại chạy ổn, và cứ mỗi lần có yêu cầu, bạn lại thêm 1 method thoả mãn yêu cầu => model trên sẽ phình to, dẫn đến việc khó test, khó maintain. Mỗi lần viết rspec là bạn phải tạo 1 đống dữ liệu liên quan để test
- Và điều thứ 2 chúng ta cần nhận ra: send notifications không phải là method cốt lõi của ControlTestPushNotification. vì chức năng côt lỗi của ControlTestPushNotification là các method của ControlTestPushNotification Class
Để giải quyết triệt để vấn đề này chúng ta cần di chuyện các logic của push notification vào một ServiceObject
2. Xây dựng ServiceObject
- Chúng ta sẽ tạo 1 file trong thư mục service của app
app/
assets/
controllers/
helpers/
mailers/
models/
services/
push_notification_service.rb
views/
Tiếp theo chúng ta tạo 1 PushNotificationService trong đó
# app/services/push_notification_service.rb
# Gửi thông báo tới người dùng
class PushNotificationService
def initialize
# code
end
# gửi thông báo
def notify(record)
#code
end
end
Rất đơn giản, muốn sử dụng method notify để send thông báo ở mọi chỗ chúng ta sử dụng
# app/controllers/test_controller.rb #create
def create
...
if @test.save
PushNotificationService.new.notify(@test)
# code push notification
else
# code
end
...
3. Test Service Object
Việc test service object này cũng tương đối đơn giản, giống như chúng ta test cho controller:
require 'rails_helper'
describe UserNotificationService do
let(:test) { FactoryBot.create(:test) }
subject(:notification) do
PushNotificationService.new.notify(test)
end
context 'when send notification success' do
it 'send an notification' do
# code
notification
end
end
...
end
4. Khi nào thì nên sử dụng Service Objects
Sự thật là không có một quy tắc cứng nào cho việc sử dụng service objects. Thông thường, các services sẽ tốt hơn ở các hệ thống tầm vừa và lớn. Bất cứ khi nào bạn thấy một đoạn code có thể không thuộc về các thư mục mà bạn đã sử dụng, một ý tưởng tốt để suy nghĩ và xem xét là nó có thể sử dụng một service thay thế. Ví dụ như, một method chứa logic liên quan đến nhiều model, không thuộc về một model riêng biệt nào, hoặc method đó có chứa business logic, hoặc method đó gọi đến một external API chẳng hạn.
5. Kết luận
Service objects là một cách tuyệt vời để tổ chức codes và business logic và nó không hề quá khó. Chúng sẽ làm codes của bạn dễ đọc hơn, dễ bảo trì và test. Vì vậy, về cơ bản, từ bây giờ, hãy thử sử dụng services objects bất cứ khi nào có thể ứng dụng, và ghi nhớ để giữ cho services của bạn gọn gàng và dễ quản lý.
6. Tài liệu tham khảo
1.https://www.netguru.co/blog/service-objects-in-rails-will-help
2.https://reinteractive.com/posts/268-keeping-your-classes-small-and-maintainable-with-service-objects
All rights reserved