Kulcare Docs
DeveloperAuthentication Documentation04 features

Feature Flag System Migration: Legacy to Domain-Driven Architecture

SUPERSEDED — historical only. This document planned a migration onto Flipper, which #1409 has since removed entirely. The domains/ system is now backed by system.feature_flags + system.feature_flag_overrides with no gem involved, and any Flipper.* call below will raise NameError.

For how feature flags actually work, read Feature Flags.

The legacy parts/ analysis below is still accurate and is why the doc is kept.

Original status: Documentation Only - No implementation yet

This document outlines the migration strategy from the legacy feature flag system (in backend/parts/) to the new domain architecture (in backend/domains/), supporting three entity types (User, Tenant, Practitioner).


1. Legacy Feature Flag System Summary

1.1 Current Endpoints

EndpointControllerEntityOperations
GET /users/:user_id/user_flagsUserFeatureFlagsControllerUserread, update
PUT /users/:user_id/user_flags/:flag_idUserFeatureFlagsControllerUserupdate
GET /clinics/:clinic_id/clinic_flagsClinicFeatureFlagsControllerClinicread, update
PUT /clinics/:clinic_id/clinic_flags/:idClinicFeatureFlagsControllerClinicupdate
GET /clinic_doctors/:clinic_doctor_id/feature_flagsClinicDoctorFeatureFlagsControllerClinicDoctorread, update
PUT /clinic_doctors/:clinic_doctor_id/feature_flags/:flag_idClinicDoctorFeatureFlagsControllerClinicDoctorupdate

1.2 Legacy Database Schema

Table: public.basic_feature_flags (definition store)

ColumnTypeDescription
idbigintPrimary key
flag_namevarcharUnique flag identifier (e.g., "ENABLE_SURGERY")
entity_typevarchar"User", "Clinic", "ClinicDoctor"
default_enabledbooleanDefault state
categoryvarcharGrouping (e.g., "ui_features")
descriptiontextHuman-readable description

Table: public.feature_flags (overrides)

ColumnTypeDescription
idbigintPrimary key
basic_feature_flag_idbigintFK to basic_feature_flags
entity_idbigintuser_id, clinic_id, or clinic_doctor_id
entity_typevarcharEntity type
enabledbooleanOverride state
metadatajsonbAdditional data

1.3 Available Legacy Flags (19 total)

App Visibility (5)

  • SHOW_CMS_APP, SHOW_GROWTH_APP, SHOW_REPORTS_APP, SHOW_CONNECT_APP, SHOW_HOSPITAL_APP

Core Features (2)

  • ENABLE_PACKAGES, ENABLE_GROWTH

Analytics & Reporting (2)

  • ENABLE_REV_SHARE, ENABLE_COMMISSION_TRACKING

Experimental (1)

  • ENABLE_BETA_FEATURES

Search (1)

  • ENABLE_ADVANCED_SEARCH

Clinical Features (4)

  • ENABLE_SURGERY, ENABLE_IPD, ENABLE_ABDM, ENABLE_WORKFLOWS

AI Features (1)

  • ENABLE_AI_ASSISTANT

Scope note (#2505). #2505 retired these keys from the native system.feature_flags catalog only, where the domains encounter workflow now follows the practitioner's module entitlement instead. The legacy rows and constants documented in this section are untouched and still gate parts/ code (for example the dental examination API reads ENABLE_DENTAL_MODULE here).

Specialist Modules (3)

  • ENABLE_PSYCHIATRIST_MODULE, ENABLE_PEDIATRICIAN_MODULE, ENABLE_DERMATOLOGIST_MODULE

1.4 Legacy Business Rules

  1. One Specialty Per Doctor: When enabling a specialist module (e.g., ENABLE_PEDIATRICIAN_MODULE), other specialist modules are automatically disabled for that doctor.

  2. Authorization:

    • User flags: Requires authentication (via ApiController)
    • Clinic flags: Requires user.authorized_clinic?(clinic_id)
    • Doctor flags: Requires user.authorized_clinic?(clinic_id)

2. New Domain Architecture

2.1 Architecture Overview

The new architecture uses:

  • Flipper gem with PostgreSQL storage
  • Domain-based schema (system., reference.)
  • UUID primary keys
  • Blueprinter for serialization
  • Concern-based integration (FeatureFlaggable)

2.2 New Database Schema

Table: system.flipper_features (feature definitions)

ColumnTypeDescription
iduuidPrimary key
keyvarchar(255)Feature name
created_attimestamptzCreation timestamp
updated_attimestamptzUpdate timestamp

Table: reference.flipper_gates (feature rules)

ColumnTypeDescription
iduuidPrimary key
feature_keyvarchar(255)Reference to feature
keyvarchar(255)Gate type: "boolean", "entity_type", "actors", "percentage_of_time"
valuetextGate value
created_attimestamptzCreation timestamp
updated_attimestamptzUpdate timestamp

2.3 Entity Type Mapping

Legacy EntityNew Domain EntityEntity Type
UserIdentity::UserUser
ClinicIdentity::TenantTenant
ClinicDoctorIdentity::PractitionerPractitioner

2.4 New API Endpoints (Admin Only)

EndpointDescription
GET /system/feature_flagsList all feature flags
GET /system/feature_flags/:keyGet flag details
PUT /system/feature_flags/enable_actorEnable for actor
DELETE /system/feature_flags/disable_actorDisable for actor

2.5 New Integration Pattern

# Include in any model
class User < Identity::BaseRecord
  include Identity::Concerns::FeatureFlaggable
end

# Usage
user = User.find(id)
user.has_feature?('SHOW_CMS_APP')        # Check if enabled
user.enable_feature!('SHOW_CMS_APP')      # Enable (admin)
user.disable_feature!('SHOW_CMS_APP')     # Disable (admin)

3. Gap Analysis

3.1 Comparison Matrix

CapabilityLegacy SystemNew SystemGap
Entity TypesUser, Clinic, ClinicDoctorUser, Tenant, PractitionerPartial (mapping exists)
Read Flags✅ Yes✅ Yes (via concern)N/A
Update Flags✅ Yes✅ Yes (admin API)N/A
Default Values✅ Yes (basic_feature_flags)✅ Yes (boolean gate)N/A
Categories✅ Yes❌ NoAdd category field
Metadata✅ Yes (jsonb)✅ Yes (in gates)N/A
Specialist Rules✅ Yes (one per doctor)❌ NoImplement in logic
Public API✅ Yes❌ Admin onlyAdd public endpoints
AuthorizationPer-endpointAdmin onlyAdd role-based access

3.2 Missing Components

  1. Public API for non-admin users - Need endpoints for users to read their flags
  2. Category support - Flags should be categorizable (UI, Clinical, etc.)
  3. Specialist module business logic - One specialty per doctor rule needs implementation
  4. Legacy entity mapping - Need seamless mapping between Clinic→Tenant and ClinicDoctor→Practitioner

4. Proposed Domain Design

4.1 Domain Structure

Create a new feature_flags domain or expand the existing system domain:

backend/domains/feature_flags/
├── models/
│   ├── feature_flag.rb           # Feature definition
│   ├── flag_assignment.rb        # Entity-to-flag mappings
│   └── concerns/
│       └── feature_flaggable.rb  # Already exists in Identity
├── controllers/
│   ├── feature_flags_controller.rb    # Admin management
│   ├── user_flags_controller.rb       # User-level flags
│   ├── tenant_flags_controller.rb     # Clinic-level flags
│   └── practitioner_flags_controller.rb # Doctor-level flags
├── services/
│   └── feature_flag_service.rb    # Shared business logic
├── interactor/
│   ├── list_flags.rb
│   ├── update_flag.rb
│   └── validate_specialist_rule.rb
├── blueprints/
│   ├── feature_flag_blueprint.rb
│   └── flag_assignment_blueprint.rb
└── routes.rb

Leverage Flipper's existing capabilities:

  1. Feature definitions → system.flipper_features
  2. Entity restrictions → reference.flipper_gates (entity_type gate)
  3. Entity assignments → reference.flipper_gates (actors gate)
  4. Categories → Add category field to FlipperFeature model
  5. Metadata → Use Flipper's built-in storage or extend

5. API Design

5.1 New Endpoints

MethodEndpointDescriptionAuth
GET/users/:id/feature_flagsGet user's flagsUser or Admin
PUT/users/:id/feature_flags/:keyUpdate user's flagAdmin
GET/tenants/:id/feature_flagsGet tenant's flagsTenant auth
PUT/tenants/:id/feature_flags/:keyUpdate tenant's flagAdmin
GET/practitioners/:id/feature_flagsGet practitioner's flagsPractitioner or Admin
PUT/practitioners/:id/feature_flags/:keyUpdate practitioner's flagAdmin

5.2 Request/Response Formats

GET /users/:id/feature_flags

// Request
GET /users/019bd563-5763-7895-b971-07436bd3f2cc/feature_flags

// Response (200 OK)
{
  "data": {
    "entity_type": "User",
    "entity_id": "019bd563-5763-7895-b971-07436bd3f2cc",
    "flags": [
      {
        "key": "SHOW_CMS_APP",
        "enabled": true,
        "category": "app_visibility",
        "description": "Access to Clinical Management System",
        "is_default": false
      },
      {
        "key": "ENABLE_SURGERY",
        "enabled": false,
        "category": "clinical_modules",
        "description": "Enable surgery module",
        "is_default": false
      }
    ]
  }
}

PUT /users/:id/feature_flags/:key

// Request
PUT /users/019bd563-5763-7895-b971-07436bd3f2cc/feature_flags/SHOW_CMS_APP
Content-Type: application/json

{
  "enabled": true
}

// Response (200 OK)
{
  "success": true,
  "data": {
    "key": "SHOW_CMS_APP",
    "enabled": true,
    "entity_type": "User",
    "entity_id": "019bd563-5763-7895-b971-07436bd3f2cc"
  }
}

5.3 Authorization Rules

EndpointUserAdminClinic Owner
GET /users/:id/feature_flags✅ (own)✅❌
PUT /users/:id/feature_flags/:key❌✅❌
GET /tenants/:id/feature_flags❌✅✅ (own)
PUT /tenants/:id/feature_flags/:key❌✅❌
GET /practitioners/:id/feature_flags✅ (own)✅✅ (own clinic)
PUT /practitioners/:id/feature_flags/:key❌✅✅ (own clinic)

6. Migration Strategy

6.1 Backend Changes

Phase 1: Schema Preparation

  1. Add category field to system.flipper_features
  2. Add seed data for existing 19 flags with proper entity types
  3. Create migration script to copy legacy flag definitions

Phase 2: Controller Implementation

  1. Create new domain controllers:
    • UserFeatureFlagsController (delegates to new architecture)
    • TenantFeatureFlagsController (for clinics)
    • PractitionerFeatureFlagsController (for doctors)
  2. Update routes to point to new controllers
  3. Implement authorization policies

Phase 3: Business Logic

  1. Implement FeatureFlagService with shared logic
  2. Add specialist module rule (one per practitioner)
  3. Add default value handling

Phase 4: Deprecation

  1. Keep legacy controllers as thin wrappers
  2. Add deprecation warnings to legacy endpoints
  3. Log migration progress

6.2 Data Migration

Step 1: Map Legacy Flags to New Schema

LEGACY_FLAG_MAPPING = {
  # User-level flags
  'SHOW_CMS_APP'        => { entity_type: 'User', category: 'app_visibility' },
  'SHOW_GROWTH_APP'     => { entity_type: 'User', category: 'app_visibility' },
  'SHOW_REPORTS_APP'    => { entity_type: 'User', category: 'app_visibility' },
  'SHOW_CONNECT_APP'    => { entity_type: 'User', category: 'app_visibility' },
  'SHOW_HOSPITAL_APP'   => { entity_type: 'User', category: 'app_visibility' },
  'ENABLE_BETA_FEATURES' => { entity_type: 'User', category: 'experimental' },

  # Tenant (Clinic)-level flags
  'ENABLE_PACKAGES'     => { entity_type: 'Tenant', category: 'core_features' },
  'ENABLE_GROWTH'       => { entity_type: 'Tenant', category: 'core_features' },
  'ENABLE_REV_SHARE'    => { entity_type: 'Tenant', category: 'analytics' },
  'ENABLE_COMMISSION_TRACKING' => { entity_type: 'Tenant', category: 'analytics' },
  'ENABLE_SURGERY'      => { entity_type: 'Tenant', category: 'clinical_modules' },
  'ENABLE_IPD'          => { entity_type: 'Tenant', category: 'clinical_modules' },
  'ENABLE_ABDM'         => { entity_type: 'Tenant', category: 'clinical_modules' },
  'ENABLE_WORKFLOWS'    => { entity_type: 'Tenant', category: 'clinical_modules' },
  'ENABLE_ADVANCED_SEARCH' => { entity_type: 'Tenant', category: 'search' },
  'ENABLE_AI_ASSISTANT' => { entity_type: 'Tenant', category: 'ai' },

  # Practitioner (ClinicDoctor)-level flags
  'ENABLE_PSYCHIATRIST_MODULE'    => { entity_type: 'Practitioner', category: 'specialist_modules' },
  'ENABLE_PEDIATRICIAN_MODULE'    => { entity_type: 'Practitioner', category: 'specialist_modules' },
  'ENABLE_DERMATOLOGIST_MODULE'   => { entity_type: 'Practitioner', category: 'specialist_modules' }
}

6.3 Frontend Updates

API Service Updates

Update /frontend/packages/auth/src/services/featureFlagsApi.ts:

const API = {
  // User flags
  getUserFlags: (userId: string) =>
    api.get(`/users/${userId}/feature_flags`),
  updateUserFlag: (userId: string, flagKey: string, enabled: boolean) =>
    api.put(`/users/${userId}/feature_flags/${flagKey}`, { enabled }),

  // Tenant (Clinic) flags
  getClinicFlags: (clinicId: string) =>
    api.get(`/tenants/${clinicId}/feature_flags`),
  updateClinicFlag: (clinicId: string, flagKey: string, enabled: boolean) =>
    api.put(`/tenants/${clinicId}/feature_flags/${flagKey}`, { enabled }),

  // Practitioner flags
  getDoctorFlags: (doctorId: string) =>
    api.get(`/practitioners/${doctorId}/feature_flags`),
  updateDoctorFlag: (doctorId: string, flagKey: string, enabled: boolean) =>
    api.put(`/practitioners/${doctorId}/feature_flags/${flagKey}`, { enabled })
}

7. Implementation Timeline

PhaseTaskEffort
1Schema preparation & seed data1 day
2Controller implementation2 days
3Business logic & policies2 days
4Data migration script1 day
5Frontend API updates1 day
6Testing & validation2 days
7Parallel running (legacy + new)1 week
8Cutover & deprecation1 day

Total Estimated: ~11 days


8. Risks & Mitigation

RiskImpactMitigation
Entity ID MappingLegacy uses integer IDs, new uses UUIDsCreate LegacyIdMapper service
Specialist Module RuleOne specialty per doctor must be enforcedImplement in controller update logic
Authorization GapsDifferent auth between legacy and newDefine clear policy matrix
DowntimeMigration could cause brief downtimeZero-downtime migration, keep legacy as fallback

9. Key Files Reference

Backend - New Architecture (Modify)

FilePurpose
backend/domains/system/controllers/feature_flags_controller.rbAdmin management
backend/domains/system/models/flipper_feature.rbAdd category field
backend/config/routes.rbAdd new routes

Backend - Legacy (Deprecate)

FilePurpose
backend/parts/core/controllers/user_feature_flags_controller.rbDeprecate
backend/parts/clinic/controllers/clinic_feature_flags_controller.rbDeprecate
backend/parts/clinic/controllers/clinic_doctor_feature_flags_controller.rbDeprecate

Frontend - Modify

FilePurpose
frontend/packages/auth/src/services/featureFlagsApi.tsUpdate endpoints
frontend/packages/auth/src/constants/featureFlags.tsUpdate flag list

10. Conclusion

The migration from the legacy feature flag system to the new Flipper-based architecture is feasible and recommended because:

  1. The new system already provides a solid foundation
  2. Flipper is a well-maintained, production-ready gem
  3. The entity mapping (User→User, Clinic→Tenant, ClinicDoctor→Practitioner) is already defined
  4. The frontend can continue using existing hooks/components with minimal changes

Key work items:

  1. Add missing capabilities (categories, public API)
  2. Implement the new controllers and routes
  3. Create the data migration script
  4. Update frontend API calls

On this page