multistrategy

package
v0.11.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 19, 2025 License: BSD-3-Clause-Clear Imports: 19 Imported by: 0

README

Multi-Strategy Entity Resolution Service (PREVIEW)

⚠️ Preview Status: This service is in preview. APIs and configurations may change.

The Multi-Strategy Entity Resolution Service enables unified access to heterogeneous identity systems through intelligent strategy selection based on JWT context. It consolidates SQL, LDAP, and JWT Claims providers into a single service with advanced data transformation capabilities.

Stability Status

  • Service: 🚧 Preview (V2 only)
  • SQL Provider: 🚧 Preview
  • LDAP Provider: 🚧 Preview
  • Claims Provider: ✅ Stable (delegates to stable Claims ERS)

Overview

Key Features
  • 🔀 Multiple Backends: SQL, LDAP, and JWT Claims in one service
  • 🧠 Intelligent Routing: Dynamic strategy selection based on JWT context
  • 🔄 Cross-Backend Failover: Automatic failover between different identity systems
  • ⚡ Data Transformations: 16 transformation types for data normalization
  • 📊 Field-Agnostic Design: No hardcoded assumptions about field names or schemas
  • 🚀 Performance Optimization: JWT-first routing for zero-latency resolution
Architecture
JWT Request → Strategy Matcher → Provider Selection → Data Transformation → Normalized Claims
     ↓              ↓                    ↓                     ↓                    ↓
   Context      Condition         SQL/LDAP/Claims        csv_to_array         Standard
  Analysis      Evaluation         Execution            ldap_dn_to_cn         Format

Quick Start

1. Basic Configuration
services:
  entityresolution:
    mode: "multi-strategy"
    failure_strategy: "continue"  # Try all strategies until one succeeds
    
    providers:
      jwt_claims:
        type: claims
      primary_db:
        type: sql
        connection:
          driver: postgres
          host: localhost
          port: 5432
          database: identity_db
          username: ers_user
          password: ers_password
      corporate_ldap:
        type: ldap
        connection:
          host: ldap.company.com
          port: 636
          use_tls: true
          bind_dn: "cn=service,ou=apps,dc=company,dc=com"
          bind_password: "secret"
2. Strategy Configuration
    mapping_strategies:
      # Fast path: Use JWT if it has rich data
      - name: jwt_fast_path
        provider: jwt_claims
        entity_type: subject
        conditions:
          jwt_claims:
            - claim: department
              operator: exists
            - claim: groups
              operator: exists
        output_mapping:
          - source_claim: email
            claim_name: primary_identifier
          - source_claim: groups
            claim_name: group_memberships
            transformation: csv_to_array
            
      # Fallback: Database lookup for enrichment  
      - name: database_lookup
        provider: primary_db
        entity_type: subject
        conditions:
          jwt_claims:
            - claim: email
              operator: exists
        query: |
          SELECT email, department, string_agg(g.name, ',') as groups
          FROM users u
          LEFT JOIN user_groups ug ON u.id = ug.user_id  
          LEFT JOIN groups g ON ug.group_id = g.id
          WHERE u.email = $1
          GROUP BY u.email, u.department
        input_mapping:
          - jwt_claim: email
            parameter: email
            required: true
        output_mapping:
          - source_column: email
            claim_name: primary_identifier
          - source_column: department
            claim_name: organizational_unit
          - source_column: groups
            claim_name: group_memberships
            transformation: csv_to_array

Data Transformations

Common Transformations (All Providers)
Transformation Input Output Use Case
csv_to_array "admin,user,finance" ["admin", "user", "finance"] Legacy CSV data
array "single_value" ["single_value"] Normalize to arrays
string 123 "123" Type conversion
lowercase "ADMIN" "admin" Case normalization
uppercase "admin" "ADMIN" Case normalization
SQL-Specific Transformations
Transformation Input Output Use Case
postgres_array "{apple,banana,cherry}" ["apple", "banana", "cherry"] PostgreSQL arrays
LDAP-Specific Transformations
Transformation Input Output Use Case
ldap_dn_to_cn_array ["CN=Users,OU=Groups", "CN=Admins,OU=Groups"] ["Users", "Admins"] Extract common names
ldap_dn_to_cn "CN=John Doe,OU=Users" "John Doe" Single DN extraction
ldap_attribute_values Multi-valued LDAP attribute ["value1", "value2"] LDAP multi-values
ad_group_name "CN=Domain Users,CN=Groups" "Domain Users" Active Directory groups
Claims-Specific Transformations
Transformation Input Output Use Case
jwt_extract_scope "read write admin" ["read", "write", "admin"] OAuth2 scopes
jwt_normalize_groups "finance,admin" or "finance admin" ["finance", "admin"] Various group formats

Strategy Selection

Condition Operators
  • exists: Check if JWT claim exists
  • equals: Exact value match (case-insensitive)
  • contains: Check if claim contains any of the specified values
  • regex: Regular expression matching
Example Strategy Patterns
Performance-Optimized (JWT First)
mapping_strategies:
  - name: jwt_fast_path
    provider: jwt_claims
    conditions:
      jwt_claims:
        - claim: groups
          operator: exists
    # Zero-latency resolution
    
  - name: database_fallback  
    provider: primary_db
    conditions:
      jwt_claims:
        - claim: email
          operator: exists
    # Database enrichment when JWT lacks data
Authority-Optimized (Database First)
mapping_strategies:
  - name: database_authoritative
    provider: primary_db
    conditions:
      jwt_claims:
        - claim: email
          operator: exists
    # Always get latest data from system of record
    
  - name: jwt_fallback
    provider: jwt_claims  
    conditions:
      jwt_claims:
        - claim: email
          operator: exists
    # Fallback when database unavailable
Context-Sensitive Routing
mapping_strategies:
  - name: internal_users_jwt
    provider: jwt_claims
    conditions:
      jwt_claims:
        - claim: aud
          operator: contains
          values: ["internal"]
    # Fast path for internal users
    
  - name: external_users_ldap
    provider: corporate_ldap
    conditions:
      jwt_claims:
        - claim: aud
          operator: contains  
          values: ["external", "partner"]
    # LDAP lookup for external users

Failure Strategies

The failure strategy determines how Multi-Strategy ERS handles failures when executing strategies. This is a global setting that applies to all strategy execution in the service.

Global Failure Strategy Options
fail-fast (Default)
  • Behavior: Stop immediately when any strategy fails
  • Use Case: When you want strict error handling and immediate failure notification
  • Result: Returns error on first strategy failure, no fallback attempts
continue
  • Behavior: Try all matching strategies until one succeeds
  • Use Case: When you want resilient failover with multiple fallback options
  • Result: Only fails if all matching strategies fail
services:
  entityresolution:
    failure_strategy: "continue"  # Global setting for entire service
    # OR
    failure_strategy: "fail-fast"  # Default - immediate failure
Failure Strategy Behavior Examples
Example 1: fail-fast Strategy
services:
  entityresolution:
    failure_strategy: "fail-fast"  # Default
    
mapping_strategies:
  - name: primary_db
    provider: main_database
    conditions: [...]
    
  - name: backup_db  
    provider: backup_database
    conditions: [...]  # Same conditions as primary

Scenario: Primary database is down

  • Request matches both strategies (same conditions)
  • Primary strategy fails (database connection error)
  • 🛑 Service immediately returns error (fail-fast behavior)
  • Backup strategy never attempted
Example 2: continue Strategy
services:
  entityresolution:
    failure_strategy: "continue"  # Resilient failover
    
mapping_strategies:
  - name: primary_db
    provider: main_database
    conditions: [...]
    
  - name: backup_db
    provider: backup_database  
    conditions: [...]  # Same conditions as primary

Scenario: Primary database is down

  • Request matches both strategies (same conditions)
  • Primary strategy fails (database connection error)
  • ➡️ Continue to next strategy (continue behavior)
  • Backup strategy succeeds (returns data)
  • Request succeeds with backup data
Common Failure Scenarios
Scenario 1: Database Connection Failure
failure_strategy: "continue"  # Enable failover

strategies:
  - name: primary_database
    provider: main_db
    # ❌ Database server is down
    
  - name: backup_database  
    provider: backup_db
    # ✅ Backup server is healthy

Result: Request succeeds using backup database

Scenario 2: Missing JWT Claims
failure_strategy: "continue"  # Enable fallback

strategies:
  - name: jwt_fast_path
    provider: jwt_claims
    conditions:
      - claim: "groups"
        operator: "exists"
    # ❌ JWT missing 'groups' claim - condition fails
    
  - name: database_enrichment
    provider: user_db
    conditions:
      - claim: "email" 
        operator: "exists"
    # ✅ JWT has 'email' claim - condition matches

Result: Falls back to database lookup when JWT lacks required claims

Scenario 3: No Matching Strategies
# JWT contains: {"sub": "user123", "aud": ["unknown"]}

strategies:
  - name: internal_users
    conditions:
      - claim: "aud"
        operator: "contains"
        values: ["internal"]  # ❌ No match
        
  - name: external_users  
    conditions:
      - claim: "aud"
        operator: "contains" 
        values: ["external"]  # ❌ No match

Result: Error - "no matching strategies found" (regardless of failure_strategy)

Failover Patterns
Linear Failover (JWT → SQL → LDAP)
failure_strategy: "continue"  # Required for failover

mapping_strategies:
  - name: jwt_primary
    provider: jwt_claims
    conditions: [...same conditions...]
    
  - name: sql_secondary  
    provider: primary_db
    conditions: [...same conditions...]
    
  - name: ldap_tertiary
    provider: corporate_ldap
    conditions: [...same conditions...]

Execution Flow:

  1. Try JWT claims first (fastest)
  2. If JWT fails → Try SQL database
  3. If SQL fails → Try LDAP directory
  4. If LDAP fails → Return error (all strategies exhausted)
Contextual Failover
mapping_strategies:
  - name: employees_primary_db
    provider: primary_db
    conditions:
      jwt_claims:
        - claim: employee_id
          operator: exists
          
  - name: employees_ldap_backup
    provider: corporate_ldap
    conditions:
      jwt_claims:
        - claim: employee_id
          operator: exists
        - claim: backup_mode
          operator: equals
          values: ["true"]
Failure Strategy Best Practices
When to Use fail-fast (Default)

Use fail-fast when:

  • Development/Testing: Quick feedback on configuration errors
  • Strict Requirements: System must fail if primary data source is unavailable
  • Single Provider: Only one strategy/provider configured (no fallback needed)
  • Critical Operations: Better to fail than use potentially stale backup data

Avoid fail-fast when:

  • High Availability Required: System must stay operational during provider outages
  • Multiple Providers: You have backup/fallback providers configured
When to Use continue

Use continue when:

  • High Availability: System must remain operational during provider failures
  • Graceful Degradation: Acceptable to use backup/cached data during outages
  • Multiple Providers: You have primary → backup → fallback chains configured
  • Production Environments: Resilience is more important than strict consistency

Avoid continue when:

  • Data Consistency Critical: Using stale backup data could cause security issues
  • Single Provider: No fallback options available (continue won't help)
Configuration Examples
Production: High Availability Setup
services:
  entityresolution:
    failure_strategy: "continue"  # Resilient failover
    
    providers:
      primary_db:
        type: sql
        connection: {...}
      backup_db:
        type: sql  
        connection: {...}  # Different server
      jwt_fallback:
        type: claims       # Always available
        
    mapping_strategies:
      # Try database first (authoritative)
      - name: primary_database
        provider: primary_db
        conditions: [...]
        
      # Fallback to backup database  
      - name: backup_database
        provider: backup_db
        conditions: [...]  # Same conditions
        
      # Ultimate fallback using JWT
      - name: jwt_claims_fallback
        provider: jwt_fallback
        conditions: [...]  # Same conditions
Development: Fast Feedback Setup
services:
  entityresolution:
    failure_strategy: "fail-fast"  # Quick error detection
    
    providers:
      dev_db:
        type: sql
        connection: {...}
        
    mapping_strategies:
      - name: single_strategy
        provider: dev_db
        conditions: [...]
        
# Fails immediately if database is misconfigured
# Provides clear error messages for development
Important Notes

⚠️ Key Behaviors:

  • Strategy Selection: failure_strategy only affects strategy execution, not strategy matching
  • No Matching Strategies: Always returns error regardless of failure_strategy setting
  • Global Setting: failure_strategy applies to all strategies in the service
  • Condition vs Execution: Unmatched conditions ≠ execution failure

🔍 Troubleshooting:

  • "No matching strategies": Check JWT claim conditions, not failure_strategy
  • Immediate failures: Verify failure_strategy is set to "continue" for failover
  • Backup never used: Ensure backup strategies have identical conditions to primary

Configuration Reference

Provider Types
SQL Provider
providers:
  my_db:
    type: sql
    connection:
      driver: postgres          # postgres, mysql, sqlite
      host: localhost
      port: 5432
      database: identity_db
      username: ers_user
      password: ers_password
      ssl_mode: require
      max_open_connections: 25
      max_idle_connections: 5
      connection_max_lifetime: 1h
      query_timeout: 30s
LDAP Provider
providers:
  my_ldap:
    type: ldap
    connection:
      host: ldap.company.com
      port: 636
      use_tls: true
      bind_dn: "cn=service,ou=apps,dc=company,dc=com"
      bind_password: "secret"
      timeout: 30s
      connection_pool_size: 10
Claims Provider
providers:
  my_claims:
    type: claims
    # No connection configuration needed
Mapping Strategy Properties
mapping_strategies:
  - name: "strategy_name"
    provider: "provider_name"
    entity_type: "subject"     # or "environment"
    conditions:
      jwt_claims:
        - claim: "claim_name"
          operator: "exists"    # exists, equals, contains, regex
          values: ["value1"]    # for equals, contains, regex
    query: "SELECT ..."        # SQL only
    ldap_search:               # LDAP only
      base_dn: "ou=users,dc=company,dc=com"
      filter: "(&(objectClass=person)(uid={username}))"
      scope: "subtree"
      attributes: ["uid", "cn", "memberOf"]
    input_mapping:
      - jwt_claim: "email"
        parameter: "email"     # SQL parameter or LDAP template variable
        required: true
    output_mapping:
      - source_column: "email"         # SQL
        source_attribute: "mail"       # LDAP  
        source_claim: "email"          # Claims
        claim_name: "primary_identifier"
        transformation: "csv_to_array"

Performance Characteristics

Strategy Pattern Latency Throughput Availability
JWT-First <1ms Very High 100%
Database-First 5-50ms High 99.9%
LDAP-First 10-100ms Medium 99.5%
Cross-Backend Failover Variable Medium 99.99%

Best Practices

1. Strategy Ordering
  • Put fastest strategies first (JWT Claims → SQL → LDAP)
  • Order by data freshness requirements (Database → JWT fallback)
  • Consider failure probability (most reliable first)
2. Condition Design
  • Be specific with conditions to avoid strategy conflicts
  • Use audience claims to differentiate user types
  • Test condition logic with realistic JWT tokens
3. Transformation Usage
  • Use common transformations when possible for consistency
  • Apply transformations at output to normalize data formats
  • Document transformation choices in configuration comments
4. Security Considerations
  • Use parameterized queries for all SQL providers
  • Enable TLS/SSL for all external connections
  • Limit connection pool sizes to prevent resource exhaustion
  • Monitor failed authentication attempts

Testing

Integration Testing
# Test multi-strategy functionality
cd service/entityresolution/integration
go test -v -run TestMultiStrategy

# Test specific provider combinations
go test -v -run TestMultiStrategy_SQL
go test -v -run TestMultiStrategy_LDAP  
go test -v -run TestMultiStrategy_Claims
Strategy Testing

Create test scenarios for different JWT contexts:

func TestStrategySelectorWithInternalJWT(t *testing.T) {
    jwt := map[string]interface{}{
        "aud": []string{"internal"},
        "email": "user@company.com", 
        "department": "Engineering",
    }
    // Should select jwt_fast_path strategy
}

func TestFailoverFromSQLToLDAP(t *testing.T) {
    // Simulate SQL failure, verify LDAP fallback
}

Migration from Single-Backend ERS

From SQL ERS
  1. Configure SQL provider with existing connection details
  2. Create fallback strategy using JWT Claims provider
  3. Add transformations to handle data format differences
  4. Test with production-like JWT tokens
From LDAP ERS
  1. Configure LDAP provider with existing connection details
  2. Add LDAP-specific transformations for DN handling
  3. Create JWT fallback for high availability
  4. Verify attribute mapping matches existing behavior
From Claims ERS
  1. Keep existing JWT strategy as primary
  2. Add database provider for data enrichment
  3. Configure intelligent routing based on JWT completeness
  4. Test performance impact of database lookups

Troubleshooting

Common Issues
  1. No matching strategy found

    • Check JWT claim conditions
    • Verify claim names match exactly
    • Test with debug logging enabled
  2. Strategy selection not working

    • Review strategy order in configuration
    • Verify condition operators and values
    • Check for overlapping conditions
  3. Transformation failures

    • Ensure transformation is supported by provider
    • Verify input data format matches expectations
    • Check for null/empty values
  4. Connection failures

    • Verify provider connection settings
    • Test connectivity from application server
    • Check authentication credentials
Debug Mode
# Enable detailed logging
logger:
  level: debug
  
# Test configuration
services:
  entityresolution:
    mode: multi-strategy
    # Add test strategies with simple conditions

Examples

See example-config.yaml for a comprehensive configuration example demonstrating:

  • Multiple provider types (SQL, LDAP, Claims)
  • Strategy-based routing patterns
  • Data transformation usage
  • Cross-backend failover scenarios
  • Health check configuration

For implementation details and architecture decisions, see the Multi-Strategy ERS ADR.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrFieldNotFound = errors.New("field not found in raw result")

Functions

This section is empty.

Types

type BaseMapper

type BaseMapper struct {
	// contains filtered or unexported fields
}

BaseMapper provides common functionality for all mapper implementations

func NewBaseMapper

func NewBaseMapper(providerType string) *BaseMapper

NewBaseMapper creates a new base mapper

func (*BaseMapper) ApplyTransformation

func (m *BaseMapper) ApplyTransformation(value interface{}, transformation string) (interface{}, error)

ApplyTransformation applies standard transformations to values

func (*BaseMapper) ExtractParameters

func (m *BaseMapper) ExtractParameters(jwtClaims types.JWTClaims, inputMapping []types.InputMapping) (map[string]interface{}, error)

ExtractParameters provides default implementation for parameter extraction

func (*BaseMapper) GetCommonTransformations

func (m *BaseMapper) GetCommonTransformations() []string

GetCommonTransformations returns transformations supported by all mappers

func (*BaseMapper) ValidateInputMapping

func (m *BaseMapper) ValidateInputMapping(inputMapping []types.InputMapping) error

ValidateInputMapping provides default validation for input mapping

func (*BaseMapper) ValidateOutputMapping

func (m *BaseMapper) ValidateOutputMapping(outputMapping []types.OutputMapping) error

ValidateOutputMapping provides default validation for output mapping

type ERS

type ERS struct {
	entityresolution.UnimplementedEntityResolutionServiceServer

	trace.Tracer
	// contains filtered or unexported fields
}

ERS implements the EntityResolutionServiceHandler for multi-strategy resolution

func NewERS

func NewERS(ctx context.Context, config types.MultiStrategyConfig, logger *logger.Logger) (*ERS, error)

NewERS creates a new multi-strategy ERS

func RegisterERS

func RegisterERS(config map[string]interface{}, logger *logger.Logger) (*ERS, serviceregistry.HandlerServer)

RegisterMultiStrategyERS registers the multi-strategy ERS service

func (*ERS) CreateEntityChainFromJwt

CreateEntityChainFromJwt implements the EntityResolutionServiceHandler interface

func (*ERS) ResolveEntities

ResolveEntities implements the EntityResolutionServiceHandler interface

type OutputMapper

type OutputMapper struct{}

OutputMapper handles transformation of raw provider results to final entity claims

func NewOutputMapper

func NewOutputMapper() *OutputMapper

NewOutputMapper creates a new output mapper

func (*OutputMapper) MapResult

func (om *OutputMapper) MapResult(rawResult *types.RawResult, outputMappings []types.OutputMapping, originalID string) (*types.EntityResult, error)

MapResult transforms raw provider result to final entity result using output mapping rules

type ProviderRegistry

type ProviderRegistry struct {
	// contains filtered or unexported fields
}

ProviderRegistry manages provider instances

func NewProviderRegistry

func NewProviderRegistry() *ProviderRegistry

NewProviderRegistry creates a new provider registry

func (*ProviderRegistry) Close

func (r *ProviderRegistry) Close() error

Close closes all providers

func (*ProviderRegistry) GetAllProviders

func (r *ProviderRegistry) GetAllProviders() map[string]types.Provider

GetAllProviders returns all registered providers

func (*ProviderRegistry) GetProvider

func (r *ProviderRegistry) GetProvider(name string) (types.Provider, error)

GetProvider retrieves a provider by name

func (*ProviderRegistry) RegisterProvider

func (r *ProviderRegistry) RegisterProvider(name string, provider types.Provider) error

RegisterProvider registers a provider instance

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service implements the multi-strategy entity resolution service

func NewService

func NewService(ctx context.Context, config types.MultiStrategyConfig, logger *logger.Logger) (*Service, error)

NewService creates a new multi-strategy entity resolution service

func (*Service) Close

func (s *Service) Close() error

Close closes all providers and cleans up resources

func (*Service) GetConfig

func (s *Service) GetConfig() types.MultiStrategyConfig

GetConfig returns the configuration for external access

func (*Service) GetProviders

func (s *Service) GetProviders() map[string]string

GetProviders returns information about registered providers

func (*Service) GetStrategies

func (s *Service) GetStrategies() []types.MappingStrategy

GetStrategies returns the configured mapping strategies

func (*Service) GetStrategyMatcher

func (s *Service) GetStrategyMatcher() *StrategyMatcher

GetStrategyMatcher returns the strategy matcher for external access

func (*Service) HealthCheck

func (s *Service) HealthCheck(ctx context.Context) error

HealthCheck performs health checks on all providers

func (*Service) ResolveEntity

func (s *Service) ResolveEntity(ctx context.Context, entityID string, jwtClaims types.JWTClaims) (*types.EntityResult, error)

ResolveEntity resolves entity information using the configured strategies

type StrategyMatcher

type StrategyMatcher struct {
	// contains filtered or unexported fields
}

StrategyMatcher handles strategy selection based on JWT context

func NewStrategyMatcher

func NewStrategyMatcher(strategies []types.MappingStrategy) *StrategyMatcher

NewStrategyMatcher creates a new strategy matcher

func (*StrategyMatcher) SelectStrategies

func (sm *StrategyMatcher) SelectStrategies(_ context.Context, claims types.JWTClaims) ([]*types.MappingStrategy, error)

SelectStrategies returns all strategies that match the JWT claims in configuration order

func (*StrategyMatcher) SelectStrategy

func (sm *StrategyMatcher) SelectStrategy(_ context.Context, claims types.JWTClaims) (*types.MappingStrategy, error)

SelectStrategy selects the first strategy that matches the JWT claims

Directories

Path Synopsis
providers
sql

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL