Shahzad Bhatti Welcome to my ramblings and rants!

September 9, 2025

Dynamic Facets and Runtime Behavior Composition: Beyond Adaptive Object Models

Filed under: Computing — admin @ 7:28 pm

Background

In my previous blog of the Adaptive Object Model (AOM) pattern, I focused on dynamic schema evolution and metadata-driven architectures. However, there’s a complementary pattern that addresses a different but equally important challenge: how to compose behavior dynamically at runtime without modifying existing objects. I first saw this pattern in Voyager ORB’s “Dynamic Aggregation” and San Francisco Design Patterns: Blueprints for Business Software (Part-IV Dynamic Behavioral Patterns) in early 2000s, which has profound implications for building extensible systems. The facets pattern, also known as dynamic aggregation or extension objects, allows secondary objects (facets) to be attached to primary objects at runtime, effectively extending their capabilities without inheritance or modification. Unlike AOM, which focuses on schema flexibility, facets address behavioral composition – the ability to mix and match capabilities based on runtime requirements.

Facets Pattern

The facets pattern emerged from several key observations about real-world software systems:

  • Interface Segregation: Not every object needs every capability all the time. A User object might need audit trail capabilities in some contexts, caching in others, and validation in yet others.
  • Runtime Composition: The specific mix of capabilities often depends on runtime context – user permissions, configuration settings, or environmental factors that cannot be determined at compile time.
  • Separation of Concerns: Cross-cutting concerns like logging, security, and persistence should be composable without polluting domain objects.

Voyager ORB’s implementation demonstrated these principles elegantly:

// Voyager ORB example - attaching an account facet to an employee
IEmployee employee = new Employee("joe", "234-44-2678");
IFacets facets = Facets.of(employee);
IAccount account = (IAccount) facets.of(IAccount.class);
account.deposit(2000);

The beauty of this approach is that the Employee class knows nothing about accounting capabilities, yet the object can seamlessly provide financial operations when needed.

Modern Implementations

Let’s explore how this pattern can be implemented in modern languages, taking advantage of their unique strengths while maintaining the core principles.

Rust Implementation: Type-Safe Facet Composition

Rust’s type system and trait system provide excellent foundations for type-safe facet composition:

use std::collections::HashMap;
use std::any::{Any, TypeId};
use std::sync::RwLock;

// Core facet trait that all facets must implement
pub trait Facet: Any + Send + Sync {
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

// Faceted object that can have facets attached
pub struct FacetedObject {
    facets: RwLock<HashMap<TypeId, Box<dyn Facet>>>,
    core_object: Box<dyn Any + Send + Sync>,
}

impl FacetedObject {
    pub fn new<T: Any + Send + Sync>(core: T) -> Self {
        Self {
            facets: RwLock::new(HashMap::new()),
            core_object: Box::new(core),
        }
    }

    // Attach a facet to this object
    pub fn attach_facet<F: Facet + 'static>(&self, facet: F) -> Result<(), String> {
        let type_id = TypeId::of::<F>();
        let mut facets = self.facets.write()
            .map_err(|_| "Failed to acquire write lock")?;
        
        if facets.contains_key(&type_id) {
            return Err(format!("Facet of type {:?} already attached", type_id));
        }
        
        facets.insert(type_id, Box::new(facet));
        Ok(())
    }

    // Execute an operation that requires a specific facet (safe callback pattern)
    pub fn with_facet<F: Facet + 'static, R>(
        &self, 
        operation: impl FnOnce(&F) -> R
    ) -> Result<R, String> {
        let facets = self.facets.read()
            .map_err(|_| "Failed to acquire read lock")?;
        let type_id = TypeId::of::<F>();
        
        if let Some(facet) = facets.get(&type_id) {
            if let Some(typed_facet) = facet.as_any().downcast_ref::<F>() {
                Ok(operation(typed_facet))
            } else {
                Err("Failed to downcast facet".to_string())
            }
        } else {
            Err(format!("Required facet not found: {:?}", type_id))
        }
    }

    // Execute a mutable operation on a facet
    pub fn with_facet_mut<F: Facet + 'static, R>(
        &self,
        operation: impl FnOnce(&mut F) -> R
    ) -> Result<R, String> {
        let mut facets = self.facets.write()
            .map_err(|_| "Failed to acquire write lock")?;
        let type_id = TypeId::of::<F>();
        
        if let Some(facet) = facets.get_mut(&type_id) {
            if let Some(typed_facet) = facet.as_any_mut().downcast_mut::<F>() {
                Ok(operation(typed_facet))
            } else {
                Err("Failed to downcast facet".to_string())
            }
        } else {
            Err(format!("Required facet not found: {:?}", type_id))
        }
    }

    // Check if a facet is attached
    pub fn has_facet<F: Facet + 'static>(&self) -> bool {
        let facets = self.facets.read().unwrap();
        let type_id = TypeId::of::<F>();
        facets.contains_key(&type_id)
    }

    // Get the core object
    pub fn get_core<T: 'static>(&self) -> Option<&T> {
        self.core_object.downcast_ref::<T>()
    }
}

// Example domain object
#[derive(Debug)]
pub struct Employee {
    pub name: String,
    pub id: String,
    pub department: String,
}

impl Employee {
    pub fn new(name: &str, id: &str, department: &str) -> Self {
        Self {
            name: name.to_string(),
            id: id.to_string(),
            department: department.to_string(),
        }
    }
}

// Account facet for financial operations
#[derive(Debug)]
pub struct AccountFacet {
    balance: f64,
    account_number: String,
}

impl AccountFacet {
    pub fn new(account_number: &str) -> Self {
        Self {
            balance: 0.0,
            account_number: account_number.to_string(),
        }
    }

    pub fn deposit(&mut self, amount: f64) -> Result<f64, String> {
        if amount <= 0.0 {
            return Err("Deposit amount must be positive".to_string());
        }
        self.balance += amount;
        Ok(self.balance)
    }

    pub fn withdraw(&mut self, amount: f64) -> Result<f64, String> {
        if amount <= 0.0 {
            return Err("Withdrawal amount must be positive".to_string());
        }
        if amount > self.balance {
            return Err("Insufficient funds".to_string());
        }
        self.balance -= amount;
        Ok(self.balance)
    }

    pub fn get_balance(&self) -> f64 {
        self.balance
    }

    pub fn get_account_number(&self) -> &str {
        &self.account_number
    }
}

impl Facet for AccountFacet {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

// Audit trail facet for tracking operations
#[derive(Debug)]
pub struct AuditFacet {
    entries: Vec<AuditEntry>,
}

#[derive(Debug, Clone)]
pub struct AuditEntry {
    timestamp: std::time::SystemTime,
    operation: String,
    details: String,
}

impl AuditFacet {
    pub fn new() -> Self {
        Self {
            entries: Vec::new(),
        }
    }

    pub fn log_operation(&mut self, operation: &str, details: &str) {
        self.entries.push(AuditEntry {
            timestamp: std::time::SystemTime::now(),
            operation: operation.to_string(),
            details: details.to_string(),
        });
    }

    pub fn get_audit_trail(&self) -> &[AuditEntry] {
        &self.entries
    }

    pub fn get_recent_entries(&self, count: usize) -> &[AuditEntry] {
        let start = if self.entries.len() > count {
            self.entries.len() - count
        } else {
            0
        };
        &self.entries[start..]
    }
}

impl Facet for AuditFacet {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

// Permission facet for access control
#[derive(Debug)]
pub struct PermissionFacet {
    permissions: HashMap<String, bool>,
    role: String,
}

impl PermissionFacet {
    pub fn new(role: &str) -> Self {
        let mut permissions = HashMap::new();
        
        // Define role-based permissions
        match role {
            "admin" => {
                permissions.insert("read".to_string(), true);
                permissions.insert("write".to_string(), true);
                permissions.insert("delete".to_string(), true);
                permissions.insert("financial_operations".to_string(), true);
            },
            "manager" => {
                permissions.insert("read".to_string(), true);
                permissions.insert("write".to_string(), true);
                permissions.insert("financial_operations".to_string(), true);
            },
            "employee" => {
                permissions.insert("read".to_string(), true);
            },
            _ => {}
        }

        Self {
            permissions,
            role: role.to_string(),
        }
    }

    pub fn has_permission(&self, permission: &str) -> bool {
        self.permissions.get(permission).copied().unwrap_or(false)
    }

    pub fn grant_permission(&mut self, permission: &str) {
        self.permissions.insert(permission.to_string(), true);
    }

    pub fn revoke_permission(&mut self, permission: &str) {
        self.permissions.insert(permission.to_string(), false);
    }

    pub fn get_role(&self) -> &str {
        &self.role
    }
}

impl Facet for PermissionFacet {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

// Composite operations that work across facets
pub struct EmployeeOperations;

impl EmployeeOperations {
    pub fn perform_financial_operation<F>(
        employee_obj: &FacetedObject,
        mut operation: F,
    ) -> Result<String, String> 
    where
        F: FnMut(&mut AccountFacet) -> Result<f64, String>,
    {
        // Check permissions first
        let has_permission = employee_obj.with_facet::<PermissionFacet, bool>(|permissions| {
            permissions.has_permission("financial_operations")
        }).unwrap_or(false);

        if !has_permission {
            return Err("Access denied: insufficient permissions for financial operations".to_string());
        }

        // Get employee info for logging
        let employee_name = employee_obj.get_core::<Employee>()
            .map(|emp| emp.name.clone())
            .unwrap_or_else(|| "Unknown".to_string());

        // Perform the operation
        let result = employee_obj.with_facet_mut::<AccountFacet, Result<f64, String>>(|account| {
            operation(account)
        })?;

        let balance = result?;

        // Log the operation if audit facet is present
        let _ = employee_obj.with_facet_mut::<AuditFacet, ()>(|audit| {
            audit.log_operation("financial_operation", &format!("New balance: {}", balance));
        });

        Ok(format!("Financial operation completed for {}. New balance: {}", employee_name, balance))
    }

    pub fn get_employee_summary(employee_obj: &FacetedObject) -> String {
        let mut summary = String::new();

        // Core employee information
        if let Some(employee) = employee_obj.get_core::<Employee>() {
            summary.push_str(&format!("Employee: {} (ID: {})\n", employee.name, employee.id));
            summary.push_str(&format!("Department: {}\n", employee.department));
        }

        // Account information if available
        let account_info = employee_obj.with_facet::<AccountFacet, String>(|account| {
            format!("Account: {} (Balance: ${:.2})\n", 
                account.get_account_number(), account.get_balance())
        }).unwrap_or_else(|_| "No account information\n".to_string());
        summary.push_str(&account_info);

        // Permission information if available
        let permission_info = employee_obj.with_facet::<PermissionFacet, String>(|permissions| {
            format!("Role: {}\n", permissions.get_role())
        }).unwrap_or_else(|_| "No permission information\n".to_string());
        summary.push_str(&permission_info);

        // Audit information if available
        let audit_info = employee_obj.with_facet::<AuditFacet, String>(|audit| {
            let recent_entries = audit.get_recent_entries(3);
            if !recent_entries.is_empty() {
                let mut info = "Recent Activity:\n".to_string();
                for entry in recent_entries {
                    info.push_str(&format!("  - {:?}: {} ({})\n", 
                        entry.timestamp,
                        entry.operation, 
                        entry.details));
                }
                info
            } else {
                "No recent activity\n".to_string()
            }
        }).unwrap_or_else(|_| "No audit information\n".to_string());
        summary.push_str(&audit_info);

        summary
    }
}

// Usage example
fn example_usage() -> Result<(), String> {
    println!("=== Dynamic Facets Example ===");

    // Create an employee
    let employee = Employee::new("Alice Johnson", "EMP001", "Engineering");
    let employee_obj = FacetedObject::new(employee);

    // Attach different facets based on requirements
    employee_obj.attach_facet(AccountFacet::new("ACC001"))?;
    employee_obj.attach_facet(PermissionFacet::new("manager"))?;
    employee_obj.attach_facet(AuditFacet::new())?;

    println!("Facets attached successfully!");

    // Use facets through the composite object
    let summary = EmployeeOperations::get_employee_summary(&employee_obj);
    println!("\nEmployee Summary:\n{}", summary);

    // Attempt financial operation (deposit)
    let result = EmployeeOperations::perform_financial_operation(
        &employee_obj,
        |account| account.deposit(1000.0)
    )?;
    println!("Deposit result: {}", result);

    // Attempt another financial operation (withdrawal)
    let result = EmployeeOperations::perform_financial_operation(
        &employee_obj,
        |account| account.withdraw(250.0)
    )?;
    println!("Withdrawal result: {}", result);

    // Display final summary
    let final_summary = EmployeeOperations::get_employee_summary(&employee_obj);
    println!("\nFinal Employee Summary:\n{}", final_summary);

    Ok(())
}

fn main() {
    match example_usage() {
        Ok(_) => println!("\nFacet composition example completed successfully."),
        Err(e) => eprintln!("Error: {}", e),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_facet_attachment() {
        let employee = Employee::new("Test User", "TEST001", "Engineering");
        let employee_obj = FacetedObject::new(employee);

        // Test attaching facets
        assert!(employee_obj.attach_facet(AccountFacet::new("ACC001")).is_ok());
        assert!(employee_obj.has_facet::<AccountFacet>());

        // Test duplicate attachment fails
        assert!(employee_obj.attach_facet(AccountFacet::new("ACC002")).is_err());
    }

    #[test]
    fn test_financial_operations() {
        let employee = Employee::new("Test User", "TEST001", "Engineering");
        let employee_obj = FacetedObject::new(employee);

        employee_obj.attach_facet(AccountFacet::new("ACC001")).unwrap();
        employee_obj.attach_facet(PermissionFacet::new("manager")).unwrap();

        // Test deposit
        let result = employee_obj.with_facet_mut::<AccountFacet, Result<f64, String>>(|account| {
            account.deposit(1000.0)
        }).unwrap();

        assert_eq!(result.unwrap(), 1000.0);

        // Test balance check
        let balance = employee_obj.with_facet::<AccountFacet, f64>(|account| {
            account.get_balance()
        }).unwrap();

        assert_eq!(balance, 1000.0);
    }

    #[test]
    fn test_permission_checking() {
        let employee = Employee::new("Test User", "TEST001", "Engineering");
        let employee_obj = FacetedObject::new(employee);

        employee_obj.attach_facet(PermissionFacet::new("employee")).unwrap();

        let has_financial = employee_obj.with_facet::<PermissionFacet, bool>(|permissions| {
            permissions.has_permission("financial_operations")
        }).unwrap();

        assert_eq!(has_financial, false);

        let has_read = employee_obj.with_facet::<PermissionFacet, bool>(|permissions| {
            permissions.has_permission("read")
        }).unwrap();

        assert_eq!(has_read, true);
    }
}

The Rust implementation provides several key advantages:

  • Type Safety: The type system ensures that facets can only be cast to their correct types
  • Memory Safety: Rust’s ownership model prevents common issues with shared mutable state
  • Performance: Zero-cost abstractions mean the facet system has minimal runtime overhead
  • Concurrency: Built-in thread safety through Send and Sync traits

TypeScript Implementation: Dynamic Composition with Type Safety

TypeScript’s type system allows for sophisticated compile-time checking while maintaining JavaScript’s dynamic nature:

// Base interfaces for the facet system

// Base interfaces for the facet system
interface Facet {
  readonly facetType: string;
}

interface FacetConstructor<T extends Facet> {
  new(...args: any[]): T;
  readonly facetType: string;
}

// Core faceted object implementation
class FacetedObject<TCore = any> {
  private facets: Map<string, Facet> = new Map();
  private core: TCore;

  constructor(core: TCore) {
    this.core = core;
  }

  // Attach a facet to this object
  attachFacet<T extends Facet>(FacetClass: FacetConstructor<T>, ...args: any[]): T {
    const facet = new FacetClass(...args);
    
    if (this.facets.has(FacetClass.facetType)) {
      throw new Error(`Facet ${FacetClass.facetType} already attached`);
    }
    
    this.facets.set(FacetClass.facetType, facet);
    return facet;
  }

  // Get a facet by its constructor
  getFacet<T extends Facet>(FacetClass: FacetConstructor<T>): T | undefined {
    const facet = this.facets.get(FacetClass.facetType);
    return facet as T | undefined;
  }

  // Check if a facet is attached
  hasFacet<T extends Facet>(FacetClass: FacetConstructor<T>): boolean {
    return this.facets.has(FacetClass.facetType);
  }

  // Remove a facet
  removeFacet<T extends Facet>(FacetClass: FacetConstructor<T>): boolean {
    return this.facets.delete(FacetClass.facetType);
  }

  // Get the core object
  getCore(): TCore {
    return this.core;
  }

  // Execute operation with facet requirement checking
  withFacet<T extends Facet, R>(
    FacetClass: FacetConstructor<T>,
    operation: (facet: T) => R
  ): R {
    const facet = this.getFacet(FacetClass);
    if (!facet) {
      throw new Error(`Required facet ${FacetClass.facetType} not found`);
    }
    return operation(facet);
  }

  // Get all attached facet types
  getAttachedFacetTypes(): string[] {
    return Array.from(this.facets.keys());
  }
}

// Example domain objects
interface Employee {
  name: string;
  id: string;
  department: string;
  email: string;
}

class EmployeeImpl implements Employee {
  constructor(
    public name: string,
    public id: string,
    public department: string,
    public email: string
  ) {}
}

// Account facet for financial operations
class AccountFacet implements Facet {
  static readonly facetType = 'account';
  readonly facetType = AccountFacet.facetType;

  private balance: number = 0;
  private accountNumber: string;
  private transactions: Transaction[] = [];

  constructor(accountNumber: string, initialBalance: number = 0) {
    this.accountNumber = accountNumber;
    this.balance = initialBalance;
  }

  deposit(amount: number): number {
    if (amount <= 0) {
      throw new Error('Deposit amount must be positive');
    }
    
    this.balance += amount;
    this.transactions.push({
      type: 'deposit',
      amount,
      timestamp: new Date(),
      balanceAfter: this.balance
    });
    
    return this.balance;
  }

  withdraw(amount: number): number {
    if (amount <= 0) {
      throw new Error('Withdrawal amount must be positive');
    }
    
    if (amount > this.balance) {
      throw new Error('Insufficient funds');
    }
    
    this.balance -= amount;
    this.transactions.push({
      type: 'withdrawal',
      amount,
      timestamp: new Date(),
      balanceAfter: this.balance
    });
    
    return this.balance;
  }

  getBalance(): number {
    return this.balance;
  }

  getAccountNumber(): string {
    return this.accountNumber;
  }

  getTransactionHistory(): Transaction[] {
    return [...this.transactions];
  }

  getRecentTransactions(count: number): Transaction[] {
    return this.transactions.slice(-count);
  }
}

interface Transaction {
  type: 'deposit' | 'withdrawal';
  amount: number;
  timestamp: Date;
  balanceAfter: number;
}

// Notification facet for alerting
class NotificationFacet implements Facet {
  static readonly facetType = 'notification';
  readonly facetType = NotificationFacet.facetType;

  private subscribers: Map<string, NotificationHandler[]> = new Map();

  subscribe(eventType: string, handler: NotificationHandler): void {
    if (!this.subscribers.has(eventType)) {
      this.subscribers.set(eventType, []);
    }
    this.subscribers.get(eventType)!.push(handler);
  }

  unsubscribe(eventType: string, handler: NotificationHandler): boolean {
    const handlers = this.subscribers.get(eventType);
    if (!handlers) return false;
    
    const index = handlers.indexOf(handler);
    if (index !== -1) {
      handlers.splice(index, 1);
      return true;
    }
    return false;
  }

  notify(eventType: string, data: any): void {
    const handlers = this.subscribers.get(eventType) || [];
    handlers.forEach(handler => {
      try {
        handler(eventType, data);
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        console.error(`Notification handler error for ${eventType}:`, errorMessage);
      }
    });
  }

  getSubscriberCount(eventType: string): number {
    return this.subscribers.get(eventType)?.length || 0;
  }
}

type NotificationHandler = (eventType: string, data: any) => void;

// Cache facet for performance optimization
class CacheFacet implements Facet {
  static readonly facetType = 'cache';
  readonly facetType = CacheFacet.facetType;

  private cache: Map<string, CacheEntry> = new Map();
  private maxSize: number;
  private defaultTTL: number;

  constructor(maxSize: number = 100, defaultTTL: number = 300000) { // 5 minutes default
    this.maxSize = maxSize;
    this.defaultTTL = defaultTTL;
  }

  set<T>(key: string, value: T, ttl?: number): void {
    // Remove oldest entries if cache is full
    if (this.cache.size >= this.maxSize) {
      const oldestKey = this.cache.keys().next().value;
      if (oldestKey !== undefined) {
        this.cache.delete(oldestKey);
      }
    }

    this.cache.set(key, {
      value,
      timestamp: Date.now(),
      ttl: ttl || this.defaultTTL
    });
  }

  get<T>(key: string): T | undefined {
    const entry = this.cache.get(key);
    if (!entry) return undefined;

    // Check if entry has expired
    if (Date.now() - entry.timestamp > entry.ttl) {
      this.cache.delete(key);
      return undefined;
    }

    return entry.value as T;
  }

  has(key: string): boolean {
    const entry = this.cache.get(key);
    if (!entry) return false;

    // Check if entry has expired
    if (Date.now() - entry.timestamp > entry.ttl) {
      this.cache.delete(key);
      return false;
    }

    return true;
  }

  invalidate(key: string): boolean {
    return this.cache.delete(key);
  }

  clear(): void {
    this.cache.clear();
  }

  getStats(): CacheStats {
    return {
      size: this.cache.size,
      maxSize: this.maxSize,
      hitRate: 0 // Would need to track hits/misses for real implementation
    };
  }
}

interface CacheEntry {
  value: any;
  timestamp: number;
  ttl: number;
}

interface CacheStats {
  size: number;
  maxSize: number;
  hitRate: number;
}

// Permission facet with role-based access control
class PermissionFacet implements Facet {
  static readonly facetType = 'permission';
  readonly facetType = PermissionFacet.facetType;

  private permissions: Set<string> = new Set();
  private role: string;

  constructor(role: string) {
    this.role = role;
    this.initializeRolePermissions(role);
  }

  private initializeRolePermissions(role: string): void {
    const rolePermissions: Record<string, string[]> = {
      'admin': ['read', 'write', 'delete', 'financial', 'admin'],
      'manager': ['read', 'write', 'financial', 'manage_team'],
      'employee': ['read', 'view_profile'],
      'guest': ['read']
    };

    const perms = rolePermissions[role] || [];
    perms.forEach(perm => this.permissions.add(perm));
  }

  hasPermission(permission: string): boolean {
    return this.permissions.has(permission);
  }

  grantPermission(permission: string): void {
    this.permissions.add(permission);
  }

  revokePermission(permission: string): void {
    this.permissions.delete(permission);
  }

  getPermissions(): string[] {
    return Array.from(this.permissions);
  }

  getRole(): string {
    return this.role;
  }

  requirePermission(permission: string): void {
    if (!this.hasPermission(permission)) {
      throw new Error(`Access denied: missing permission '${permission}'`);
    }
  }
}

// Composite operations using multiple facets
class EmployeeService {
  static performSecureFinancialOperation(
    employeeObj: FacetedObject<Employee>,
    operation: (account: AccountFacet) => number,
    operationType: string
  ): number {
    // Check permissions
    const permissions = employeeObj.getFacet(PermissionFacet);
    if (permissions) {
      permissions.requirePermission('financial');
    }

    // Perform operation
    const result = employeeObj.withFacet(AccountFacet, operation);

    // Send notification if facet is available
    const notifications = employeeObj.getFacet(NotificationFacet);
    if (notifications) {
      notifications.notify('financial_operation', {
        employee: employeeObj.getCore().name,
        operation: operationType,
        timestamp: new Date()
      });
    }

    // Invalidate related cache entries
    const cache = employeeObj.getFacet(CacheFacet);
    if (cache) {
      cache.invalidate(`balance_${employeeObj.getCore().id}`);
      cache.invalidate(`transactions_${employeeObj.getCore().id}`);
    }

    return result;
  }

  static getEmployeeSummary(employeeObj: FacetedObject<Employee>): string {
    const employee = employeeObj.getCore();
    const facetTypes = employeeObj.getAttachedFacetTypes();
    
    let summary = `Employee: ${employee.name} (${employee.id})\n`;
    summary += `Department: ${employee.department}\n`;
    summary += `Email: ${employee.email}\n`;
    summary += `Active Facets: ${facetTypes.join(', ')}\n`;

    // Add account information if available
    const account = employeeObj.getFacet(AccountFacet);
    if (account) {
      summary += `Account: ${account.getAccountNumber()} (Balance: $${account.getBalance().toFixed(2)})\n`;
      
      const recentTransactions = account.getRecentTransactions(3);
      if (recentTransactions.length > 0) {
        summary += 'Recent Transactions:\n';
        recentTransactions.forEach(tx => {
          summary += `  ${tx.type}: $${tx.amount.toFixed(2)} on ${tx.timestamp.toLocaleString()}\n`;
        });
      }
    }

    // Add permission information if available
    const permissions = employeeObj.getFacet(PermissionFacet);
    if (permissions) {
      summary += `Role: ${permissions.getRole()}\n`;
      summary += `Permissions: ${permissions.getPermissions().join(', ')}\n`;
    }

    // Add cache stats if available
    const cache = employeeObj.getFacet(CacheFacet);
    if (cache) {
      const stats = cache.getStats();
      summary += `Cache: ${stats.size}/${stats.maxSize} entries\n`;
    }

    return summary;
  }

  static configureEmployeeCapabilities(
    employeeObj: FacetedObject<Employee>,
    config: EmployeeConfig
  ): void {
    // Attach facets based on configuration
    if (config.hasAccount) {
      employeeObj.attachFacet(AccountFacet, config.accountNumber, config.initialBalance);
    }

    if (config.role) {
      employeeObj.attachFacet(PermissionFacet, config.role);
    }

    if (config.enableNotifications) {
      const notifications = employeeObj.attachFacet(NotificationFacet);
      
      // Set up default notification handlers
      notifications.subscribe('financial_operation', (eventType, data) => {
        console.log(`Financial operation performed: ${JSON.stringify(data)}`);
      });
    }

    if (config.enableCaching) {
      employeeObj.attachFacet(CacheFacet, config.cacheSize, config.cacheTTL);
    }
  }
}

interface EmployeeConfig {
  hasAccount?: boolean;
  accountNumber?: string;
  initialBalance?: number;
  role?: string;
  enableNotifications?: boolean;
  enableCaching?: boolean;
  cacheSize?: number;
  cacheTTL?: number;
}

// Usage example
function demonstrateFacetComposition(): void {
  console.log('=== Dynamic Facet Composition Demo ===');

  // Create an employee
  const employee = new EmployeeImpl('Bob Smith', 'EMP002', 'Finance', 'bob.smith@company.com');
  const employeeObj = new FacetedObject(employee);

  // Configure capabilities based on requirements
  EmployeeService.configureEmployeeCapabilities(employeeObj, {
    hasAccount: true,
    accountNumber: 'ACC002',
    initialBalance: 500,
    role: 'manager',
    enableNotifications: true,
    enableCaching: true,
    cacheSize: 50,
    cacheTTL: 600000 // 10 minutes
  });

  // Display initial summary
  console.log('\nInitial Employee Summary:');
  console.log(EmployeeService.getEmployeeSummary(employeeObj));

  // Perform financial operations
  try {
    const newBalance = EmployeeService.performSecureFinancialOperation(
      employeeObj,
      (account) => account.deposit(1000),
      'deposit'
    );
    console.log(`Deposit successful. New balance: $${newBalance.toFixed(2)}`);

    const finalBalance = EmployeeService.performSecureFinancialOperation(
      employeeObj,
      (account) => account.withdraw(200),
      'withdrawal'
    );
    console.log(`Withdrawal successful. Final balance: $${finalBalance.toFixed(2)}`);

  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
    console.error('Operation failed:', errorMessage);
  }

  // Display final summary
  console.log('\nFinal Employee Summary:');
  console.log(EmployeeService.getEmployeeSummary(employeeObj));
}

// Run the demonstration
demonstrateFacetComposition();

The TypeScript implementation provides:

  • Type Safety: Compile-time type checking for facet operations
  • IntelliSense Support: Rich IDE support with autocompletion and error detection
  • Interface Segregation: Clean separation between different capabilities
  • Dynamic Composition: Runtime attachment and detachment of behaviors

Ruby Implementation: Metaprogramming-Powered Facets

Ruby’s metaprogramming capabilities make facet implementation particularly elegant:

require 'date'
require 'set'
require 'json'

# Core facet module that all facets include
module Facet
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def facet_type
      @facet_type ||= name.downcase.gsub(/facet$/, '')
    end

    def facet_type=(type)
      @facet_type = type
    end
  end

  def facet_type
    self.class.facet_type
  end
end

# Main faceted object implementation
class FacetedObject
  def initialize(core_object)
    @core_object = core_object
    @facets = {}
    @method_cache = {}
    
    # Enable method delegation
    extend_with_facet_methods
  end

  def attach_facet(facet_instance)
    facet_type = facet_instance.facet_type
    
    if @facets.key?(facet_type)
      raise ArgumentError, "Facet '#{facet_type}' already attached"
    end

    @facets[facet_type] = facet_instance
    
    # Add facet methods to this instance
    add_facet_methods(facet_instance)
    
    # Call initialization hook if facet defines it
    facet_instance.on_attached(self) if facet_instance.respond_to?(:on_attached)
    
    facet_instance
  end

  def detach_facet(facet_type_or_class)
    facet_type = case facet_type_or_class
                 when String
                   facet_type_or_class
                 when Class
                   facet_type_or_class.facet_type
                 else
                   facet_type_or_class.facet_type
                 end

    facet = @facets.delete(facet_type)
    
    if facet
      # Remove facet methods
      remove_facet_methods(facet)
      
      # Call cleanup hook if facet defines it
      facet.on_detached(self) if facet.respond_to?(:on_detached)
    end
    
    facet
  end

  def get_facet(facet_type_or_class)
    facet_type = case facet_type_or_class
                 when String
                   facet_type_or_class
                 when Class
                   facet_type_or_class.facet_type
                 else
                   facet_type_or_class.facet_type
                 end

    @facets[facet_type]
  end

  def has_facet?(facet_type_or_class)
    !get_facet(facet_type_or_class).nil?
  end

  def facet_types
    @facets.keys
  end

  def core_object
    @core_object
  end

  def with_facet(facet_type_or_class)
    facet = get_facet(facet_type_or_class)
    raise ArgumentError, "Facet not found: #{facet_type_or_class}" unless facet
    
    yield(facet)
  end

  # Require specific facets for an operation
  def requires_facets(*facet_types, &block)
    missing_facets = facet_types.select { |type| !has_facet?(type) }
    
    unless missing_facets.empty?
      raise ArgumentError, "Missing required facets: #{missing_facets.join(', ')}"
    end
    
    block.call(self) if block_given?
  end

  private

  def extend_with_facet_methods
    # Add method_missing to handle facet method calls
    singleton_class.class_eval do
      define_method :method_missing do |method_name, *args, &block|
        # Try to find the method in attached facets
        @facets.values.each do |facet|
          if facet.respond_to?(method_name)
            return facet.send(method_name, *args, &block)
          end
        end
        
        # Try the core object
        if @core_object.respond_to?(method_name)
          return @core_object.send(method_name, *args, &block)
        end
        
        super(method_name, *args, &block)
      end

      define_method :respond_to_missing? do |method_name, include_private = false|
        @facets.values.any? { |facet| facet.respond_to?(method_name, include_private) } ||
          @core_object.respond_to?(method_name, include_private) ||
          super(method_name, include_private)
      end
    end
  end

  def add_facet_methods(facet)
    facet.public_methods(false).each do |method_name|
      next if method_name == :facet_type

      # Create a delegating method for each public method of the facet
      singleton_class.class_eval do
        define_method("#{facet.facet_type}_#{method_name}") do |*args, &block|
          facet.send(method_name, *args, &block)
        end
      end
    end
  end

  def remove_facet_methods(facet)
    facet.public_methods(false).each do |method_name|
      method_to_remove = "#{facet.facet_type}_#{method_name}"
      
      if respond_to?(method_to_remove)
        singleton_class.class_eval do
          remove_method(method_to_remove) if method_defined?(method_to_remove)
        end
      end
    end
  end
end

# Example domain class
class Employee
  attr_accessor :name, :id, :department, :email, :hire_date

  def initialize(name, id, department, email, hire_date = Date.today)
    @name = name
    @id = id
    @department = department
    @email = email
    @hire_date = hire_date
  end

  def years_of_service
    ((Date.today - @hire_date) / 365.25).to_i
  end

  def to_h
    {
      name: @name,
      id: @id,
      department: @department,
      email: @email,
      hire_date: @hire_date,
      years_of_service: years_of_service
    }
  end
end

# Account facet for financial operations
class AccountFacet
  include Facet
  
  attr_reader :account_number, :balance

  def initialize(account_number, initial_balance = 0)
    @account_number = account_number
    @balance = initial_balance.to_f
    @transactions = []
  end

  def deposit(amount)
    raise ArgumentError, "Amount must be positive" unless amount > 0
    
    @balance += amount
    log_transaction('deposit', amount)
    @balance
  end

  def withdraw(amount)
    raise ArgumentError, "Amount must be positive" unless amount > 0
    raise ArgumentError, "Insufficient funds" if amount > @balance
    
    @balance -= amount
    log_transaction('withdrawal', amount)
    @balance
  end

  def transfer_to(target_account_number, amount)
    raise ArgumentError, "Cannot transfer to same account" if target_account_number == @account_number
    
    withdraw(amount)
    log_transaction('transfer_out', amount, target_account_number)
    amount
  end

  def receive_transfer(from_account_number, amount)
    deposit(amount)
    log_transaction('transfer_in', amount, from_account_number)
    @balance
  end

  def transaction_history(limit = nil)
    limit ? @transactions.last(limit) : @transactions.dup
  end

  def monthly_summary(year, month)
    start_date = Date.new(year, month, 1)
    end_date = start_date.next_month - 1
    
    monthly_transactions = @transactions.select do |tx|
      tx[:timestamp].to_date.between?(start_date, end_date)
    end

    {
      period: "#{year}-#{month.to_s.rjust(2, '0')}",
      transactions: monthly_transactions,
      total_deposits: monthly_transactions.select { |tx| tx[:type] == 'deposit' }.sum { |tx| tx[:amount] },
      total_withdrawals: monthly_transactions.select { |tx| tx[:type] == 'withdrawal' }.sum { |tx| tx[:amount] }
    }
  end

  private

  def log_transaction(type, amount, reference = nil)
    @transactions << {
      type: type,
      amount: amount,
      balance_after: @balance,
      timestamp: Time.now,
      reference: reference
    }
  end
end

# Performance tracking facet
class PerformanceFacet
  include Facet
  
  def initialize
    @metrics = {}
    @goals = {}
    @reviews = []
  end

  def set_metric(name, value, period = Date.today)
    @metrics[name] ||= []
    @metrics[name] << { value: value, period: period, timestamp: Time.now }
  end

  def get_metric(name, period = nil)
    return nil unless @metrics[name]
    
    if period
      @metrics[name].find { |m| m[:period] == period }&.fetch(:value)
    else
      @metrics[name].last&.fetch(:value)
    end
  end

  def set_goal(name, target_value, deadline)
    @goals[name] = { target: target_value, deadline: deadline, set_on: Date.today }
  end

  def goal_progress(name)
    goal = @goals[name]
    return nil unless goal
    
    current_value = get_metric(name)
    return nil unless current_value
    
    progress = (current_value.to_f / goal[:target]) * 100
    {
      goal: goal,
      current_value: current_value,
      progress_percentage: progress.round(2),
      days_remaining: (goal[:deadline] - Date.today).to_i
    }
  end

  def add_review(rating, comments, reviewer, review_date = Date.today)
    @reviews << {
      rating: rating,
      comments: comments,
      reviewer: reviewer,
      review_date: review_date,
      timestamp: Time.now
    }
  end

  def average_rating(last_n_reviews = nil)
    reviews_to_consider = last_n_reviews ? @reviews.last(last_n_reviews) : @reviews
    return 0 if reviews_to_consider.empty?
    
    total = reviews_to_consider.sum { |review| review[:rating] }
    (total.to_f / reviews_to_consider.size).round(2)
  end

  def performance_summary
    {
      metrics: @metrics.transform_values { |values| values.last },
      goals: @goals.transform_values { |goal| goal_progress(@goals.key(goal)) },
      recent_reviews: @reviews.last(3),
      average_rating: average_rating,
      total_reviews: @reviews.size
    }
  end
end

# Security facet for access control and audit
class SecurityFacet
  include Facet
  
  def initialize(security_level = 'basic')
    @security_level = security_level
    @access_log = []
    @failed_attempts = []
    @permissions = Set.new
    @active_sessions = {}
    
    setup_default_permissions
  end

  def authenticate(credentials)
    # Simulate authentication
    success = credentials[:password] == 'secret123'
    
    log_access_attempt(credentials[:user_id], success)
    
    if success
      session_id = generate_session_id
      @active_sessions[session_id] = {
        user_id: credentials[:user_id],
        start_time: Time.now,
        last_activity: Time.now
      }
      session_id
    else
      nil
    end
  end

  def validate_session(session_id)
    session = @active_sessions[session_id]
    return false unless session
    
    # Check session timeout (30 minutes)
    if Time.now - session[:last_activity] > 1800
      @active_sessions.delete(session_id)
      return false
    end
    
    session[:last_activity] = Time.now
    true
  end

  def logout(session_id)
    @active_sessions.delete(session_id)
  end

  def grant_permission(permission)
    @permissions.add(permission)
  end

  def revoke_permission(permission)
    @permissions.delete(permission)
  end

  def has_permission?(permission)
    @permissions.include?(permission) || @permissions.include?('admin')
  end

  def require_permission(permission)
    unless has_permission?(permission)
      raise SecurityError, "Access denied: missing permission '#{permission}'"
    end
  end

  def security_report
    {
      security_level: @security_level,
      permissions: @permissions.to_a,
      active_sessions: @active_sessions.size,
      recent_access_attempts: @access_log.last(10),
      failed_attempts_today: failed_attempts_today.size,
      total_access_attempts: @access_log.size
    }
  end

  private

  def setup_default_permissions
    case @security_level
    when 'admin'
      @permissions.merge(['read', 'write', 'delete', 'admin', 'financial'])
    when 'manager'
      @permissions.merge(['read', 'write', 'financial'])
    when 'employee'
      @permissions.merge(['read'])
    end
  end

  def log_access_attempt(user_id, success)
    attempt = {
      user_id: user_id,
      success: success,
      timestamp: Time.now,
      ip_address: '127.0.0.1' # Would be actual IP in real implementation
    }
    
    @access_log << attempt
    @failed_attempts << attempt unless success
  end

  def failed_attempts_today
    today = Date.today
    @failed_attempts.select { |attempt| attempt[:timestamp].to_date == today }
  end

  def generate_session_id
    "session_#{Time.now.to_i}_#{rand(10000)}"
  end
end

# Notification facet for messaging and alerts
class NotificationFacet
  include Facet
  
  def initialize
    @subscribers = Hash.new { |hash, key| hash[key] = [] }
    @message_history = []
    @preferences = {
      email: true,
      sms: false,
      push: true,
      frequency: 'immediate'
    }
  end

  def subscribe(event_type, &handler)
    @subscribers[event_type] << handler
  end

  def unsubscribe(event_type, handler)
    @subscribers[event_type].delete(handler)
  end

  def notify(event_type, data = {})
    timestamp = Time.now
    message = {
      event_type: event_type,
      data: data,
      timestamp: timestamp
    }
    
    @message_history << message
    
    # Deliver to subscribers
    @subscribers[event_type].each do |handler|
      begin
        handler.call(message)
      rescue => e
        puts "Notification handler error: #{e.message}"
      end
    end
    
    # Simulate different delivery channels based on preferences
    deliver_message(message) if should_deliver?(event_type)
  end

  def set_preference(channel, enabled)
    @preferences[channel] = enabled
  end

  def set_frequency(frequency)
    raise ArgumentError, "Invalid frequency" unless %w[immediate hourly daily].include?(frequency)
    @preferences[:frequency] = frequency
  end

  def message_history(limit = nil)
    limit ? @message_history.last(limit) : @message_history.dup
  end

  def unread_count
    # In a real implementation, this would track read status
    @message_history.count { |msg| msg[:timestamp] > Time.now - 3600 } # Last hour
  end

  private

  def should_deliver?(event_type)
    # Simple delivery logic based on preferences
    case @preferences[:frequency]
    when 'immediate'
      true
    when 'hourly'
      @message_history.select { |msg| msg[:timestamp] > Time.now - 3600 }.size <= 1
    when 'daily'
      @message_history.select { |msg| msg[:timestamp] > Time.now - 86400 }.size <= 1
    else
      true
    end
  end

  def deliver_message(message)
    puts "? Email: #{message[:event_type]} - #{message[:data]}" if @preferences[:email]
    puts "? Push: #{message[:event_type]} - #{message[:data]}" if @preferences[:push]
    puts "? SMS: #{message[:event_type]} - #{message[:data]}" if @preferences[:sms]
  end
end

# Service class for coordinated operations
class EmployeeService
  def self.create_employee(name, id, department, email, capabilities = {})
    employee = Employee.new(name, id, department, email)
    faceted_employee = FacetedObject.new(employee)
    
    # Attach facets based on capabilities
    if capabilities[:account]
      account_facet = AccountFacet.new(capabilities[:account][:number], capabilities[:account][:balance])
      faceted_employee.attach_facet(account_facet)
    end
    
    if capabilities[:security]
      security_facet = SecurityFacet.new(capabilities[:security][:level])
      capabilities[:security][:permissions]&.each { |perm| security_facet.grant_permission(perm) }
      faceted_employee.attach_facet(security_facet)
    end
    
    if capabilities[:performance_tracking]
      faceted_employee.attach_facet(PerformanceFacet.new)
    end
    
    if capabilities[:notifications]
      notification_facet = NotificationFacet.new
      
      # Set up default notification handlers
      notification_facet.subscribe('financial_transaction') do |message|
        puts "? Financial Alert: #{message[:data][:type]} of $#{message[:data][:amount]}"
      end
      
      notification_facet.subscribe('performance_update') do |message|
        puts "? Performance Update: #{message[:data][:metric]} = #{message[:data][:value]}"
      end
      
      faceted_employee.attach_facet(notification_facet)
    end
    
    faceted_employee
  end

  def self.perform_secure_transaction(employee_obj, transaction_type, amount)
    employee_obj.requires_facets('security', 'account') do |obj|
      # Authenticate and check permissions
      security = obj.get_facet('security')
      security.require_permission('financial')
      
      # Perform transaction
      account = obj.get_facet('account')
      result = case transaction_type
               when 'deposit'
                 account.deposit(amount)
               when 'withdraw'
                 account.withdraw(amount)
               else
                 raise ArgumentError, "Unknown transaction type: #{transaction_type}"
               end
      
      # Send notification if available
      if obj.has_facet?('notification')
        notification = obj.get_facet('notification')
        notification.notify('financial_transaction', {
          type: transaction_type,
          amount: amount,
          new_balance: result,
          employee: obj.core_object.name
        })
      end
      
      result
    end
  end

  def self.update_performance(employee_obj, metric_name, value)
    employee_obj.with_facet('performance') do |performance|
      performance.set_metric(metric_name, value)
      
      # Notify if notification facet is available
      if employee_obj.has_facet?('notification')
        notification = employee_obj.get_facet('notification')
        notification.notify('performance_update', {
          metric: metric_name,
          value: value,
          employee: employee_obj.core_object.name
        })
      end
    end
  end

  def self.comprehensive_report(employee_obj)
    employee = employee_obj.core_object
    
    report = {
      employee_info: employee.to_h,
      attached_facets: employee_obj.facet_types,
      timestamp: Time.now
    }
    
    # Add facet-specific information
    if employee_obj.has_facet?('account')
      account = employee_obj.get_facet('account')
      report[:financial] = {
        account_number: account.account_number,
        balance: account.balance,
        recent_transactions: account.transaction_history(5)
      }
    end
    
    if employee_obj.has_facet?('performance')
      performance = employee_obj.get_facet('performance')
      report[:performance] = performance.performance_summary
    end
    
    if employee_obj.has_facet?('security')
      security = employee_obj.get_facet('security')
      report[:security] = security.security_report
    end
    
    if employee_obj.has_facet?('notification')
      notification = employee_obj.get_facet('notification')
      report[:notifications] = {
        unread_count: notification.unread_count,
        recent_messages: notification.message_history(3)
      }
    end
    
    report
  end
end

# Usage demonstration
def demonstrate_facet_system
  puts "=== Dynamic Facet Composition Demo ==="
  
  # Create employee with various capabilities
  employee_obj = EmployeeService.create_employee(
    'Sarah Connor', 'EMP003', 'Engineering', 'sarah.connor@company.com',
    {
      account: { number: 'ACC003', balance: 1000 },
      security: { level: 'manager', permissions: ['read', 'write', 'financial'] },
      performance_tracking: true,
      notifications: true
    }
  )
  
  puts "\n--- Initial Employee State ---"
  puts "Attached facets: #{employee_obj.facet_types.join(', ')}"
  
  # Demonstrate financial operations
  puts "\n--- Financial Operations ---"
  begin
    # First authenticate (in a real system)
    security = employee_obj.get_facet('security')
    session_id = security.authenticate(user_id: 'sarah', password: 'secret123')
    puts "Authentication successful: #{session_id}"
    
    # Perform transactions
    new_balance = EmployeeService.perform_secure_transaction(employee_obj, 'deposit', 500)
    puts "Deposit completed. New balance: $#{new_balance}"
    
    new_balance = EmployeeService.perform_secure_transaction(employee_obj, 'withdraw', 200)
    puts "Withdrawal completed. New balance: $#{new_balance}"
    
  rescue => e
    puts "Transaction failed: #{e.message}"
  end
  
  # Demonstrate performance tracking
  puts "\n--- Performance Tracking ---"
  EmployeeService.update_performance(employee_obj, 'projects_completed', 5)
  EmployeeService.update_performance(employee_obj, 'customer_satisfaction', 4.5)
  
  performance = employee_obj.get_facet('performance')
  performance.set_goal('projects_completed', 10, Date.today + 90)
  
  puts "Goal progress: #{performance.goal_progress('projects_completed')}"
  
  # Generate comprehensive report
  puts "\n--- Comprehensive Employee Report ---"
  report = EmployeeService.comprehensive_report(employee_obj)
  puts JSON.pretty_generate(report)
  
  # Demonstrate dynamic facet management
  puts "\n--- Dynamic Facet Management ---"
  puts "Before detachment: #{employee_obj.facet_types.join(', ')}"
  
  # Detach performance facet
  employee_obj.detach_facet('performance')
  puts "After detaching performance: #{employee_obj.facet_types.join(', ')}"
  
  # Try to use detached facet (should fail gracefully)
  begin
    EmployeeService.update_performance(employee_obj, 'test_metric', 1)
  rescue => e
    puts "Expected error when using detached facet: #{e.message}"
  end
end

# Run the demonstration
demonstrate_facet_system

The Ruby implementation showcases:

  • Metaprogramming Power: Dynamic method addition and removal using Ruby’s metaprogramming capabilities
  • Elegant Syntax: Clean, readable code that expresses intent clearly
  • Flexible Composition: Easy attachment and detachment of facets at runtime
  • Duck Typing: Natural method delegation without complex type hierarchies

Real-World Applications

The facets pattern proves particularly valuable in several domains:

Enterprise Software Integration

Modern enterprise systems often need to integrate with multiple external services. Facets allow core business objects to gain integration capabilities dynamically:

// Core customer object
const customer = new Customer('ABC Corp', 'enterprise');
const customerObj = new FacetedObject(customer);

// Attach integration facets based on configuration
if (config.salesforce.enabled) {
  customerObj.attachFacet(SalesforceFacet, config.salesforce.credentials);
}

if (config.stripe.enabled) {
  customerObj.attachFacet(PaymentFacet, config.stripe.apiKey);
}

if (config.analytics.enabled) {
  customerObj.attachFacet(AnalyticsFacet, config.analytics.trackingId);
}

Multi-Tenant SaaS Applications

Different tenants often require different feature sets. Facets enable feature composition based on subscription levels:

// Configure tenant capabilities based on plan
match subscription_plan {
    Plan::Basic => {
        tenant_obj.attach_facet(BasicAnalyticsFacet::new())?;
    },
    Plan::Professional => {
        tenant_obj.attach_facet(AdvancedAnalyticsFacet::new())?;
        tenant_obj.attach_facet(IntegrationFacet::new())?;
    },
    Plan::Enterprise => {
        tenant_obj.attach_facet(AdvancedAnalyticsFacet::new())?;
        tenant_obj.attach_facet(IntegrationFacet::new())?;
        tenant_obj.attach_facet(WhiteLabelFacet::new())?;
        tenant_obj.attach_facet(ApiAccessFacet::new())?;
    }
}

IoT Device Management

IoT devices often have optional capabilities that depend on hardware configuration or runtime conditions:

# Device base configuration
device_obj = FacetedObject.new(IoTDevice.new(device_id, device_type))

# Attach facets based on detected capabilities
if device.has_sensor?('temperature')
  device_obj.attach_facet(TemperatureFacet.new)
end

if device.has_connectivity?('wifi')
  device_obj.attach_facet(WiFiFacet.new)
end

if device.battery_powered?
  device_obj.attach_facet(PowerManagementFacet.new)
end

Performance Considerations

While facets provide tremendous flexibility, they come with performance trade-offs that must be carefully managed:

Method Resolution Overhead

Dynamic method resolution can introduce latency. Caching strategies help mitigate this:

class OptimizedFacetedObject<TCore> extends FacetedObject<TCore> {
  private methodCache: Map<string, Facet> = new Map();
  
  getFacetForMethod(methodName: string): Facet | undefined {
    // Check cache first
    if (this.methodCache.has(methodName)) {
      return this.methodCache.get(methodName);
    }
    
    // Search facets for method
    for (const facet of this.facets.values()) {
      if (typeof (facet as any)[methodName] === 'function') {
        this.methodCache.set(methodName, facet);
        return facet;
      }
    }
    
    return undefined;
  }
}

Memory Management

Facets can create reference cycles. Proper cleanup is essential:

impl Drop for FacetedObject {
    fn drop(&mut self) {
        // Clean up facet references
        for (_, facet) in self.facets.drain() {
            // Perform any necessary cleanup
            // Call facet-specific cleanup if implemented
        }
    }
}

Serialization Challenges

Faceted objects require special handling for persistence:

class FacetedObject
  def to_serializable
    {
      core_object: @core_object,
      facets: @facets.transform_values { |facet| serialize_facet(facet) },
      facet_types: @facets.keys
    }
  end
  
  def self.from_serializable(data)
    obj = new(data[:core_object])
    
    data[:facets].each do |type, facet_data|
      facet_class = Object.const_get("#{type.camelize}Facet")
      facet = facet_class.deserialize(facet_data)
      obj.attach_facet(facet)
    end
    
    obj
  end
  
  private
  
  def serialize_facet(facet)
    if facet.respond_to?(:serialize)
      facet.serialize
    else
      # Default serialization
      facet.instance_variables.each_with_object({}) do |var, hash|
        hash[var] = facet.instance_variable_get(var)
      end
    end
  end
end

Architecture Patterns and Best Practices

Facet Discovery and Registration

Large systems benefit from automatic facet discovery:

class FacetRegistry {
  private static facetClasses: Map<string, FacetConstructor<any>> = new Map();
  
  static register<T extends Facet>(facetClass: FacetConstructor<T>): void {
    this.facetClasses.set(facetClass.facetType, facetClass);
  }
  
  static createFacet<T extends Facet>(
    facetType: string, 
    ...args: any[]
  ): T | undefined {
    const FacetClass = this.facetClasses.get(facetType);
    return FacetClass ? new FacetClass(...args) : undefined;
  }
  
  static getAvailableFacets(): string[] {
    return Array.from(this.facetClasses.keys());
  }
}

// Automatic registration
@RegisterFacet
class EmailFacet implements Facet {
  static readonly facetType = 'email';
  // ...
}

Configuration-Driven Composition

Enable declarative facet composition through configuration:

# facet-config.yml
employee_types:
  manager:
    facets:
      - type: account
        config:
          initial_balance: 1000
      - type: permission
        config:
          role: manager
      - type: notification
        config:
          channels: [email, push]
  
  admin:
    inherits: manager
    facets:
      - type: audit
        config:
          level: detailed
      - type: permission
        config:
          role: admin
pub struct FacetComposer {
    config: HashMap<String, EmployeeTypeConfig>,
}

impl FacetComposer {
    pub fn compose_employee(&self, employee_type: &str, employee: Employee) -> Result<FacetedObject, String> {
        let config = self.config.get(employee_type)
            .ok_or_else(|| format!("Unknown employee type: {}", employee_type))?;
        
        let mut employee_obj = FacetedObject::new(employee);
        
        for facet_config in &config.facets {
            let facet = self.create_facet(&facet_config.facet_type, &facet_config.config)?;
            employee_obj.attach_facet(facet)?;
        }
        
        Ok(employee_obj)
    }
}

Testing Strategies

Faceted objects require comprehensive testing approaches:

RSpec.describe FacetedObject do
  let(:employee) { Employee.new('Test User', 'TEST001', 'Engineering', 'test@example.com') }
  let(:employee_obj) { FacetedObject.new(employee) }
  
  describe 'facet composition' do
    it 'allows dynamic attachment of facets' do
      account_facet = AccountFacet.new('ACC001', 1000)
      employee_obj.attach_facet(account_facet)
      
      expect(employee_obj.has_facet?('account')).to be true
      expect(employee_obj.balance).to eq 1000
    end
    
    it 'prevents duplicate facet attachment' do
      employee_obj.attach_facet(AccountFacet.new('ACC001'))
      
      expect {
        employee_obj.attach_facet(AccountFacet.new('ACC002'))
      }.to raise_error(ArgumentError, /already attached/)
    end
  end
  
  describe 'cross-facet operations' do
    before do
      employee_obj.attach_facet(AccountFacet.new('ACC001', 1000))
      employee_obj.attach_facet(SecurityFacet.new('manager'))
      employee_obj.attach_facet(NotificationFacet.new)
    end
    
    it 'coordinates operations across multiple facets' do
      expect {
        EmployeeService.perform_secure_transaction(employee_obj, 'withdraw', 100)
      }.to change { employee_obj.balance }.by(-100)
        .and output(/Financial Alert/).to_stdout
    end
  end
end

Comparison with Related Patterns

Facets vs Decorators

While both patterns add behavior dynamically, they serve different purposes:

Decorators: Wrap objects to modify or extend their interface Facets: Compose objects from multiple behavioral aspects

// Decorator pattern - wrapping behavior
class LoggingDecorator implements Employee {
  constructor(private wrapped: Employee) {}
  
  performAction(action: string): void {
    console.log(`Performing: ${action}`);
    this.wrapped.performAction(action);
    console.log(`Completed: ${action}`);
  }
}

// Facets pattern - compositional behavior
const employee = new FacetedObject(new EmployeeImpl());
employee.attachFacet(LoggingFacet);
employee.attachFacet(SecurityFacet);
// Employee now has both logging AND security capabilities

Facets vs Mixins

Mixins operate at the class level, facets at the instance level:

# Mixin - class-level composition
module Auditable
  def log_action(action)
    puts "Action: #{action}"
  end
end

class Employee
  include Auditable  # All instances get audit capability
end

# Facets - instance-level composition
employee1 = FacetedObject.new(Employee.new)
employee1.attach_facet(AuditFacet.new)  # Only this instance gets audit capability

employee2 = FacetedObject.new(Employee.new)  # This instance doesn't have audit

Emerging Patterns

AI-Driven Facet Composition

Machine learning could optimize facet composition based on usage patterns:

class IntelligentFacetComposer {
  private usageAnalyzer: UsageAnalyzer;
  private mlModel: FacetRecommendationModel;
  
  async recommendFacets(
    objectType: string, 
    context: CompositionContext
  ): Promise<FacetRecommendation[]> {
    const usagePatterns = await this.usageAnalyzer.analyze(objectType);
    const contextFeatures = this.extractFeatures(context);
    
    return this.mlModel.predict(usagePatterns, contextFeatures);
  }
  
  async optimizeForPerformance(
    facetedObject: FacetedObject<any>
  ): Promise<OptimizationSuggestions> {
    const usage = await this.usageAnalyzer.getObjectUsage(facetedObject);
    
    return {
      facetsToPreload: usage.frequentlyUsedFacets,
      facetsToLazyLoad: usage.rarelyUsedFacets,
      cacheStrategy: usage.recommendedCacheStrategy
    };
  }
}

Blockchain and Distributed Facets

Distributed systems could benefit from blockchain-verified facet capabilities:

pub struct DistributedFacetRegistry {
    blockchain_client: BlockchainClient,
    capability_verifier: CapabilityVerifier,
}

impl DistributedFacetRegistry {
    pub async fn verify_facet_capability(
        &self,
        facet_hash: &str,
        required_permissions: &[String]
    ) -> Result<bool, DistributedError> {
        // Verify facet authenticity on blockchain
        let facet_record = self.blockchain_client
            .get_facet_record(facet_hash).await?;
        
        // Verify permissions
        self.capability_verifier
            .verify_permissions(&facet_record, required_permissions)
    }
}

Conclusion

The facets pattern represents a powerful approach to runtime behavior composition that complements the Adaptive Object Model pattern I discussed previously. While AOM focuses on schema flexibility, facets address the equally important challenge of behavioral composition. The implementations in Rust, TypeScript, and Ruby demonstrate how this pattern can be adapted to different language paradigms while maintaining its core principles. Each language brings unique strengths: Rust’s type safety and performance, TypeScript’s gradual typing and tooling support, and Ruby’s metaprogramming elegance.

Unfortunately, ObjectSpace company that created Voyager went out of business and San Francisco Design Patterns book didn’t gain traction, in part because of its ties to the now-obsolete EJB technology and the performance overhead from using runtime reflection in the extension pattern. Nevertheless, the facets/extension pattern excels in domains requiring high configurability and runtime adaptability. However, it requires careful attention to performance implications and testing strategies. The pattern works best when you have clear separation of concerns and well-defined interfaces between facets. The combination of AOM for schema evolution and facets for behavior composition provides a comprehensive approach to building truly adaptive systems. Together, these patterns enable software that can evolve gracefully with changing requirements while maintaining performance and reliability.

The sample implementations are available at the Dynamic Facets Sample Project, providing working examples in all three languages discussed. These implementations serve as a foundation for building more sophisticated facet-based systems tailored to specific domain requirements.

No Comments

No comments yet.

RSS feed for comments on this post. TrackBack URL

Sorry, the comment form is closed at this time.

Powered by WordPress