Publish-Subcribe Pattern (hay viết tắt là pub-sub pattern) là mẫu gửi thông điệp mà người gửi (publishers), không lập trình thông điệp gửi trực tiếp tới người nhận cụ thể (subscribers). Thay vào đó, lập trình viên “gửi” các thông điệp (sự kiện), mà không hề biết gì về người nhận.
Tương tự như vậy, người nhận thể hiện sở thích vào một hay nhiều sự kiện (events), và chỉ nhận thông điệp họ mong muốn, mà không hề biết về thông tin người gửi.
Để thực hiện được điều đó, một “người trung gian” (“message brocker” or “event bus”), tiếp nhận các thông điệp, và sau đó chuyển chúng tới những người nhận mong muốn.
Nói một cách khác, pub/sub là kiểu mẫu được sử dụng để kết nối các thông điệp giữa các hệ thống khác nhau mà các hệ thống đó không cần biết thông tin cụ thể về hệ thống khác.
Kiểu mô hình này tuy không mới, nhưng lại không được sử dụng rộng rãi trong Rails. Có một số công cụ giúp phát triển mô hình này vào code của bạn, ví dụ:
Các công cụ này có những cài đặt ngầm khác nhau, nhưng chúng đều có những thế mạnh nhất định cho ứng dụng Rails
Thế mạnh
Giảm thiểu độ phình của Model/Controller
Chúng ta thường thấy những các models và controllers phình to trong Rails app, nhưng đó không phải là cách làm tốt.
Mẫu pub/sub có thể dễ dàng giúp bạn tránh được việc này.
Giảm số lượng callbacks
Việc có nhiều callbacks giữa các models khiến cho chúng bị ràng buộc và rất khó duy trì hay phát triển.
Ví dụ một như model Post
sau:
# app/models/post.rb
class Post
# ...
field: content, type: String
# ...
after_create :create_feed, :notify_followers
# ...
def create_feed
Feed.create!(self)
end
def notify_followers
User::NotifyFollowers.call(self)
end
end
Hay một đoạn code trong Post
controller có thể như sau:
# app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < Api::V1::ApiController
# ...
def create
@post = current_user.posts.build(post_params)
if @post.save
render_created(@post)
else
render_unprocessable_entity(@post.errors)
end
end
# ...
end
Bạn có thể thấy Post
model có các callbacks
gắn với model Feed
và User::NotifyFollowers
service. Với mẫu pub/sub, đoạn code đó có thể được refactor với Wisper như sau:
# app/models/post.rb
class Post
# ...
field: content, type: String
# ...
# no callbacks in the models!
end
Publisher tạo event
# app/controllers/api/v1/posts_controller.rb
# corresponds to the publisher in the previous figure
class Api::V1::PostsController < Api::V1::ApiController
include Wisper::Publisher
# ...
def create
@post = current_user.posts.build(post_params)
if @post.save
# Publish event about post creation for any interested listeners
publish(:post_create, @post)
render_created(@post)
else
# Publish event about post error for any interested listeners
publish(:post_errors, @post)
render_unprocessable_entity(@post.errors)
end
end
# ...
end
Subscribers chỉ tiếp nhận events cần thiết
# app/listener/feed_listener.rb
class FeedListener
def post_create(post)
Feed.create!(post)
end
end
# app/listener/user_listener.rb
class UserListener
def post_create(post)
User::NotifyFollowers.call(self)
end
end
Event Bus đăng kí tạo các subscribers khác nhau trong hệ thống.
# config/initializers/wisper.rb
Wisper.subscribe(FeedListener.new)
Wisper.subscribe(UserListener.new)
Trong ví dụ này, mẫu pub/sub hoàn toàn loại bỏ các callbacks trong Post
model và giúp các models hoạt động hoàn toàn độc lập với những model khác. Việc mở rộng ra các hành vi khác chỉ là vấn đề liên kết với event mong muốn.
Nguyên lý đơn nhiệm
Nguyên lý đơn nhiệm thực sự rất hữu ích cho việc phát triển một code ‘sạch’. Nhưng có một vấn đề là đôi khi các lớp chúng ta tạo ra lại không có một vai trò rõ ràng. Điều này là dễ gặp với các MVCs frameworks, (ví dụ như Rails).
Models chỉ nên quản lý persistence các associations, không nên bao gồm những thứ khác.
Controllers chỉ nên quản lý các yêu cầu từ phía người dùng, đóng gói business logic (Service Objects).
Service Objects nên đóng gói các chức năng của business logic, cung cấp cách thức truy cập cho các services bên ngoài, hoặc đóng vai trò như một thay thế với những gì model quan tâm tới.
Pub/Sub pattern có thể tạo ra các single responsibility service object để đóng gói business logic, và ngăn cản được việc business logic lấn sang phần sân của models hay controllers. Điều này giúp cho code được sạch sẽ, dễ đọc, dễ maintain, và dễ dàng scale up.
Dưới đây là một ví dụ:
Publisher
# app/service/financial/order_review.rb
class Financial::OrderReview
include Wisper::Publisher
# ...
def self.call(order)
if order.approved?
publish(:order_create, order)
else
publish(:order_decline, order)
end
end
# ...
Subscribers
# app/listener/client_listener.rb
class ClientListener
def order_create(order)
# can implement transaction using different service objects
Client::Charge.call(order)
Inventory::UpdateStock.call(order)
end
def order_decline(order)
Client::NotifyDeclinedOrder(order)
end
end
Performance
Pub/Sub pattern có thể có ảnh hưởng tích cự, hoặc tiêu cực tới performance, tùy theo cách bạn sử dụng.
Wisper có rất nhiều adapters hỗ trợ xử lý các sự kiện không đồng bộ và thực thi nhiều threads, điều này có thể giúp bạn cải thiện đáng kể về performance.
Tuy nhiên, Nếu bạn xử lý dữ liệu ở một server remote, việc sử dụng nhiều pub/sub có thể dẫn tới hệ quả không tốt về performance, vì nó phải tải qua network.
Source:
http://www.toptal.com/
Bình luận