Skip to content

The Extender Pattern ​

What is the Extender Pattern? ​

The Extender Pattern is a fundamental design pattern in OSGi that enables a modular and dynamic approach to extending application functionality. In this pattern:

  • A dedicated bundle, known as the extender, monitors the lifecycle of other bundles (the extendees)
  • When an extendee bundle is started, the extender inspects it for specific metadata or resources
  • Based on this metadata, the extender provides services and functionality on behalf of the extendee
  • This relieves the extendee from implementing complex framework integration logic

How Pandino Implements the Extender Pattern ​

In Pandino, the Service Component Runtime (SCR) is the primary implementation of the Extender Pattern.

1. Component Declaration ​

Developers declare components using TypeScript decorators:

typescript
@Component({ name: 'greeting.service', immediate: true })
@Service({ interfaces: ['GreetingService'] })
class GreetingServiceComponent implements GreetingService {
  @Activate
  activate(context: ComponentContext): void {
    console.log('GreetingService component activated');
  }

  // Service methods...
}

2. Bundle Registration ​

Components are registered with a bundle, either explicitly in the bundle's activator:

typescript
async start(context: BundleContext): Promise<void> {
  const scrRef = context.getServiceReference<ServiceComponentRuntime>('ServiceComponentRuntime')!;
  const scr = context.getService(scrRef)!;
  const bundleId = context.getBundle().getBundleId();

  await scr.registerComponent(GreetingServiceComponent, bundleId);
}

Or implicitly through the bundle's components array:

typescript
export default {
  headers: {
    bundleSymbolicName: 'com.example.services',
    bundleVersion: '1.0.0',
  },
  // Components are automatically registered by SCR
  components: [UserService, OrderService],
};

3. SCR as the Extender ​

The SCR service in Pandino acts as the extender by:

  • Tracking bundle lifecycle events (installation, starting, stopping)
  • Inspecting bundles for component declarations (via decorators or explicit registration)
  • Managing the component lifecycle (activation, deactivation)
  • Handling service registration and dependency injection

When a bundle with decorated components is started, SCR:

  1. Detects the components through decorator metadata
  2. Processes the component configuration
  3. Manages service dependencies (via @Reference decorators)
  4. Activates components (calling methods marked with @Activate)
  5. Registers services in the service registry

4. Benefits in the Browser Context ​

In the browser environment, the Extender Pattern through SCR provides several advantages:

  • Reduced Boilerplate: Developers focus on business logic rather than service registration code
  • Declarative Approach: TypeScript decorators provide a clean, declarative way to define components
  • Dynamic Updates: Components can be activated/deactivated as bundles start and stop
  • Dependency Management: SCR handles service dependencies automatically
  • Lazy Loading: Components can be loaded on demand, improving initial load performance

Example: Declarative Services in Pandino ​

typescript
// Define a component with dependencies
@Component({ name: 'order.service' })
@Service({ interfaces: ['OrderService'] })
class OrderService {
  // Dependencies are injected automatically when available
  @Reference({ interface: 'UserService' })
  private userService?: UserService;

  @Reference({ interface: 'NotificationService' })
  private notificationService?: NotificationService;

  @Activate
  activate(): void {
    console.log('OrderService activated with dependencies');
  }

  async createOrder(userId: string, items: Item[]): Promise<Order> {
    const user = this.userService?.findUser(userId);
    if (!user) throw new Error('User not found');

    const order = new Order(user, items);
    this.notificationService?.notify(`Order confirmed for ${user.email}`);
    return order;
  }
}

In this example, SCR (the extender) handles:

  • Tracking when the component's bundle is started
  • Waiting for required services to be available
  • Injecting service dependencies
  • Activating the component when dependencies are satisfied
  • Managing the component's lifecycle

See also ​

Released under the Eclipse Public License 2.0.