Giới thiệu
Khi xây dựng một ứng dụng SaaS, việc quản lý các gói đăng ký với các tính năng và giới hạn khác nhau là điều không thể tránh khỏi. Một cách tiếp cận phổ biến là mã hóa cứng các tính năng của gói đăng ký trực tiếp trong mô hình của bạn. Tuy nhiên, điều này có thể dẫn đến một hệ thống cứng nhắc và khó duy trì.
Bài viết này sẽ hướng dẫn bạn cách quản lý truy cập tính năng một cách linh hoạt, cho phép bạn dễ dàng điều chỉnh các tính năng cho từng người dùng mà không cần thay đổi mã nguồn. Chúng ta sẽ cùng khám phá cách thực hiện điều này trong ứng dụng Rails của bạn.
Mô hình Truy cập
Đầu tiên, chúng ta cần tạo một mô hình để lưu trữ cấu hình truy cập của từng người dùng. Sử dụng RailsVault, ta có thể định nghĩa các thuộc tính truy cập như sau:
ruby
# app/models/user/access.rb
class User::Access < RailsVault::Base
vault_attribute :member_count, :integer, default: Config::Plans.fallback.dig(:features, :member_count)
vault_attribute :enabled_workflow_count, :integer, default: Config::Plans.fallback.dig(:features, :enabled_workflow_count)
vault_attribute :enabled_endpoint_count, :integer, default: Config::Plans.fallback.dig(:features, :enabled_endpoint_count)
vault_attribute :total_steps_per_workflow_count, :integer, default: Config::Plans.fallback.dig(:features, :total_steps_per_workflow_count)
vault_attribute :total_monthly_run_count, :integer, default: Config::Plans.fallback.dig(:features, :total_monthly_run_count)
vault_attribute :ai_enabled, :boolean, default: Config::Plans.fallback.dig(:features, :ai_enabled)
end
Mỗi thuộc tính đại diện cho một tính năng hoặc giới hạn cụ thể trong ứng dụng của bạn. Tôi thường sử dụng hậu tố _count cho các giới hạn số và _enabled cho các tính năng kiểu boolean.
Thêm Truy cập Tính năng cho Người dùng
Tiếp theo, chúng ta sẽ tạo một concern để xử lý logic truy cập tính năng:
ruby
# app/models/user/feature_access.rb
module User::FeatureAccess
extend ActiveSupport::Concern
included do
vault :access
end
def add_access(product_id)
access.update Config::Plans[product_id.to_sym][:features]
end
end
Phương thức add_access nhận một ID sản phẩm từ Stripe và áp dụng các tính năng tương ứng cho người dùng. Bạn thường gọi phương thức này từ handler webhook của Stripe khi một đăng ký được tạo hoặc cập nhật. Tuy nhiên, điều này không chỉ giới hạn cho Stripe.
Đừng quên thêm concern này vào mô hình Người dùng:
ruby
# app/models/user.rb
class User < ApplicationRecord
include FeatureAccess
# …
end
Cấu hình Gói Đăng ký
Sử dụng hệ thống cấu hình từ bài viết trước của tôi, tôi định nghĩa tất cả các gói trong một tệp YAML:
yaml
# config/configurations/plans.yml
shared:
fallback:
name: Free
features:
member_count: 1
enabled_workflow_count: 1
enabled_endpoint_count: 2
total_steps_per_workflow_count: 3
total_monthly_run_count: 100
ai_enabled: false
development:
starter_plan_id:
name: Starter
price_id: price_dev_starter
amount: 19
features:
member_count: 1
enabled_workflow_count: 5
enabled_endpoint_count: 10
total_steps_per_workflow_count: 5
total_monthly_run_count: 5_000
ai_enabled: false
pro_plan_id:
name: Pro
price_id: price_dev_pro
amount: 49
features:
member_count: 5
enabled_workflow_count: 25
enabled_endpoint_count: 50
total_steps_per_workflow_count: 15
total_monthly_run_count: 25_000
ai_enabled: true
production:
starter_plan_id:
name: Starter
price_id: price_prod_starter
amount: 19
features:
member_count: 1
enabled_workflow_count: 5
enabled_endpoint_count: 10
total_steps_per_workflow_count: 5
total_monthly_run_count: 5_000
ai_enabled: false
pro_plan_id:
name: Pro
price_id: price_prod_pro
amount: 49
features:
member_count: 5
enabled_workflow_count: 25
enabled_endpoint_count: 50
total_steps_per_workflow_count: 15
total_monthly_run_count: 25_000
ai_enabled: true
Phần fallback định nghĩa gói miễn phí mặc định mà người dùng mới nhận được. Mỗi hash features của gói tương ứng chính xác với các thuộc tính vault được định nghĩa trong mô hình Truy cập.
Sử dụng
Giờ đây, bạn có thể dễ dàng kiểm tra quyền truy cập của người dùng trong toàn bộ ứng dụng:
ruby
# Kiểm tra giới hạn
Current.user.access.member_count # => 5
Current.user.access.enabled_workflow_count # => 25
# Kiểm tra boolean với hậu tố ?
Current.user.ai_enabled? # => true
Khi xử lý webhook từ Stripe, việc áp dụng truy cập trở nên dễ dàng:
ruby
# Trong handler webhook của Stripe
def checkout_session_completed(event)
session = event.data.object
user = User.find_by(stripe_customer_id: session.customer)
# Trích xuất ID sản phẩm từ các mục dòng
product_id = session.line_items.data.first.price.product
user.add_access(product_id)
end
Mẹo và Thực tiễn Tốt nhất
- Thực hiện kiểm tra truy cập: Đảm bảo kiểm tra quyền truy cập của người dùng trước khi cho phép họ thực hiện các hành động liên quan đến tính năng.
- Ghi chú rõ ràng: Sử dụng ghi chú trong mã để giải thích các quyết định thiết kế. Điều này giúp cho việc bảo trì mã trở nên dễ dàng hơn.
- Kiểm tra A/B: Sử dụng khả năng tùy chỉnh để thực hiện các thử nghiệm A/B với các tính năng khác nhau cho người dùng khác nhau.
Những Cạm bẫy Thường Gặp
- Quên cập nhật cấu hình: Đảm bảo rằng bạn luôn cập nhật cấu hình gói khi có thay đổi. Sự không nhất quán có thể dẫn đến trải nghiệm người dùng không tốt.
- Thiếu kiểm tra quyền truy cập: Hãy chắc chắn rằng mọi tính năng yêu cầu kiểm tra quyền truy cập đều được thực hiện đúng cách để tránh các lỗ hổng bảo mật.
Kết luận
Với cách tiếp cận này, bạn có thể tạo ra một hệ thống linh hoạt cho phép bạn điều chỉnh tính năng cho từng người dùng mà không cần phải thay đổi mã nguồn. Điều này không chỉ giảm bớt gánh nặng cho đội ngũ phát triển mà còn cải thiện trải nghiệm khách hàng. Nếu bạn có thắc mắc hoặc cần thêm thông tin, hãy liên hệ với tôi! Hãy bắt đầu nâng cao trải nghiệm người dùng của bạn ngay hôm nay!
Câu hỏi thường gặp (FAQ)
-
Tôi có thể sử dụng cách tiếp cận này cho các ứng dụng không phải SaaS không?
Có, bạn có thể áp dụng cách tiếp cận này cho bất kỳ ứng dụng nào cần quản lý quyền truy cập tính năng. -
Cách nào để thực hiện kiểm tra A/B trong Rails?
Bạn có thể sử dụng các gem nhưsplithoặcabđể thực hiện thử nghiệm A/B trong Rails. -
Tôi có thể áp dụng cách tiếp cận này mà không cần Stripe không?
Hoàn toàn có thể! Cách tiếp cận này có thể được áp dụng cho bất kỳ hệ thống thanh toán nào hoặc ngay cả khi không có hệ thống thanh toán.