Права доступа (Entitlements)

Как отделить монетизацию от кода приложения

Права доступа (entitlements) — это элегантное решение для сложной задачи монетизации софта. Они позволяют отделить модель ценообразования от логики приложения, делая изменения тарифных планов безболезненными.

Статья основана на материале Stigg.io и дополнена практическим опытом.

Три слоя контроля доступа

В приложении часто смешивают три разных механизма контроля доступа. Это приводит к хрупкому коду, который сложно поддерживать. Разделим их:

Слой Назначение Кто управляет Срок жизни
Feature Flags Инженерные практики: релизы, A/B-тесты, канарейки Разработчики Временный (дни-недели)
Entitlements Бизнес-доступ: какие фичи доступны на тарифе Продукт, бизнес Постоянный
Authorization Права пользователя: роли, permissions Админы, пользователи Постоянный
Ключевое отличие от фиче-флагов: Feature flags — это инженерный инструмент для безопасного релиза. Они должны удаляться после раскатки. Entitlements — это бизнес-инструмент для монетизации. Они остаются в системе постоянно и не зависят от релизного цикла.

Подробнее о фиче-флагах: Фиче-тогглы: полное руководство

Псевдокод, демонстрирующий разделение:

// Слой 1: Feature Flag (инженерный)
// Временный, для раскатки новой фичи
if (featureFlag.isEnabled("new-analytics-dashboard")) {

  // Слой 2: Entitlement (бизнес)
  // Постоянный, зависит от тарифного плана
  if (customer.hasEntitlement("analytics")) {

    // Слой 3: Authorization (пользователь)
    // Постоянный, зависит от роли
    if (user.hasPermission("view-analytics")) {
      showAnalyticsDashboard();
    }
  }
}

Что такое права доступа

Права доступа (entitlements) — это набор разрешений, определяющих, что клиент может делать с приложением в рамках своего тарифного плана.

Примеры entitlements

{
  "customer_id": "12345",
  "entitlements": {
    "audit_logs": true,        // Булевая фича
    "user_limit": 5,           // Числовой лимит
    "storage_gb": 10,          // Квота
    "api_requests_per_day": 1000
  },
  "usage": {
    "current_users": 4,
    "storage_used_gb": 3.2,
    "api_requests_today": 847
  }
}

Чем entitlements НЕ являются

Права доступа vs биллинг

Биллинговая система (Stripe, Paddle) отвечает за финансовые транзакции. Но до и после оплаты происходит многое:

Почему разделять entitlements и биллинг

Причина Объяснение
Защитный слой Ошибки интеграции с биллингом не блокируют доступ к фичам
Grandfathering Биллинг не умеет сохранять старые условия для существующих клиентов
Низкая задержка Проверки entitlements должны быть быстрыми (мс), биллинг не оптимизирован для этого
Разные среды Dev/staging/prod — биллинг часто не поддерживает несколько сред
Гибкость Доступ к фичам может не зависеть от статуса оплаты (trial, промо)

Альтернативные подходы и их проблемы

Проверка по ID плана

// Плохо: логика привязана к названию плана
if (currentUser.plan === "pro") {
  enableAdvancedFeatures();
}

Проблемы:

Авторизация (roles/permissions)

// Ограниченно: не учитывает бизнес-модель
if (currentUser.hasRole("admin")) {
  enableAdminPanel();
}

Проблемы:

Feature flags для монетизации

// Неправильное использование
if (Feature.enabled("premium-analytics")) {
  showPremiumAnalytics();
}

Проблемы:

Правильный подход: комбинация

// Feature flag — для раскатки (временный)
if (featureFlag.enabled("new-premium-analytics")) {

  // Entitlement — для монетизации (постоянный)
  if (customer.entitled("premium-analytics")) {

    // Authorization — для ролей (постоянный)
    if (user.can("view-analytics")) {
      showPremiumAnalytics();
    }
  }
}

Лучшие практики

1. Избегайте вложенных проверок entitlements

// Плохо
if (entitled("feature_a")) {
  if (entitled("feature_b")) {
    doSomething();
  }
}

// Хорошо: создайте новый entitlement или абстракцию
if (entitled("combined_feature")) {
  doSomething();
}

// Или wrapper
function canAccessCombinedFeature() {
  return entitled("feature_a") && entitled("feature_b");
}

2. Не предполагайте наличие новых entitlements

Новые entitlements могут отсутствовать у старых клиентов. Всегда предусматривайте fallback:

if (entitled("new_feature")) {
  enableNewFeature();
} else {
  fallbackBehavior(); // Важно!
}

3. Fail open при сбоях

При сбое сервиса entitlements — предоставляйте доступ по умолчанию, чтобы не блокировать пользователей:

async function checkEntitlement(feature) {
  try {
    return await entitlementService.check(feature);
  } catch (error) {
    logger.warn("Entitlement service unavailable, failing open");
    return true; // Разрешаем доступ при сбое
  }
}

4. Централизуйте проверки

class EntitlementChecker {
  canInviteMembers(newMembers) {
    const current = this.usage.members;
    const limit = this.entitlements.memberLimit;
    return current + newMembers <= limit;
  }

  canUploadFile(sizeBytes) {
    const usedGb = this.usage.storageGb;
    const limitGb = this.entitlements.storageGb;
    return usedGb + (sizeBytes / 1e9) <= limitGb;
  }
}

5. Удаляйте устаревшие entitlements

Сначала удалите проверки из кода, затем сам entitlement. Не накапливайте "мусор".

Резюме

Механизм Вопрос Пример
Feature Flag Готова ли фича к релизу? Раскатка нового UI на 10% пользователей
Entitlement Оплатил ли клиент эту фичу? Доступ к аналитике на плане Pro
Authorization Имеет ли пользователь право на это действие? Только админ может удалять пользователей

Ключевые принципы:

Связанные материалы: