# Cordis Service Providers Dependency Injection with Service.extend: Implementation Guide

> Implement Cordis service providers dependency injection using Service.extend. Learn how derived service instances inherit state and override context with this guide.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: implementation-guide
- Published: 2026-08-25

---

**Cordis service providers dependency injection uses `Service.extend` to create derived service instances that inherit state while allowing context overrides through a shallow copy of the proxy-based Context.**

The Cordis framework (cordiverse/cordis) implements a lightweight dependency injection system where every service receives a Context object containing registered services and configuration. The static `Service.extend` symbol enables hierarchical service derivation without duplicating underlying service instances, making it ideal for scoping configurations and test isolation.

## How Service.extend Implements Dependency Injection

`Service.extend` is a static symbol defined as `Symbol.for('cordis.extend')` in [`packages/core/src/service.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/service.ts). It provides the entry point for creating derived service instances that share the original service's state while operating with a modified context.

When invoked, this symbol triggers the protected method `[symbols.extend]` implemented in the same file. This method performs three critical operations:

1. **Creates a shallow copy of the current context** using `this.ctx.extend()`, merging any supplied properties into a new proxy.
2. **Instantiates a new service object** using the same constructor but passing the extended context.
3. **Preserves the original prototype chain**, ensuring the derived instance maintains all methods and properties of the base Service class.

The context itself is a **proxy-based container** defined in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts). Extending it does not duplicate underlying services; instead, it adds new keys or overrides existing ones specifically for the child context.

## Context Proxy Architecture

The dependency injection mechanism relies on several core utilities working together:

- **`Context.extend`** (from [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts)): Returns a new proxy that forwards property access to the original context while overlaying supplied properties.
- **`symbols.extend`** (from [`packages/core/src/utils.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/utils.ts)): The unique symbol used as the method name to prevent naming collisions.
- **`Service` base class** (in [`packages/core/src/service.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/service.ts)): Provides the static `extend` symbol and the protected `[symbols.extend]` method.

This architecture ensures that Cordis service providers dependency injection remains type-safe and runtime-efficient, avoiding expensive object cloning while supporting hierarchical configuration.

## Practical Implementation Patterns

### Scoping Configuration Per Request

Use `Service.extend` to create isolated contexts with specific configuration values without affecting the global service registry:

```typescript
import { Service, Context } from 'cordis';

class Logger extends Service {
  log(message: string) {
    console.log('[log]', message);
  }
}

// Root context with registered Logger
const root = new Context().register(new Logger());

// Derive child context with debug configuration
const child = root.extend({ level: 'debug' });
child.logger.log('child logger'); // Uses same Logger instance, but child has 'level' property

```

### Mocking Services for Testing

Override providers in derived contexts to inject mock implementations for isolated unit testing:

```typescript
class MockLogger extends Service {
  log(message: string) {
    // Capture messages for assertions or no-op
  }
}

// Replace original logger only for test context
const testCtx = root.extend({ logger: new MockLogger() });
testCtx.logger.log('this will be mocked'); // Uses MockLogger, not original

```

### Dynamic Service Composition

Implement scoped methods within services using the bracket notation to access the protected symbol:

```typescript
class Database extends Service {
  get url() { return this.ctx.dbUrl; }

  // Dynamically create scoped DB client with extra options
  scoped(options: Partial<{ timeout: number }>) {
    return this[Service.extend]({ dbOptions: options });
  }
}

const db = new Database();
const dbWithTimeout = db.scoped({ timeout: 2000 });
// dbWithTimeout shares the Database instance but includes dbOptions.timeout in its context

```

## Key Source Files

The implementation spans several files in the `@cordis/core` package:

- **[`packages/core/src/service.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/service.ts)**: Defines the `Service` base class, the static `extend` symbol, and the protected `[symbols.extend]` method.
- **[`packages/core/src/utils.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/utils.ts)**: Declares unique symbols including `symbols.extend` and `symbols.shadow` used for internal wiring.
- **[`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts)**: Implements `Context.extend`, the proxy mechanism that merges new properties while preserving original context behavior.
- **[`packages/core/tests/invoke.spec.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/tests/invoke.spec.ts)**: Contains test suites demonstrating typical `extend` usage patterns and verification of context inheritance.

## Summary

- **`Service.extend`** is a static symbol (`Symbol.for('cordis.extend')`) that triggers derivation of service instances with modified contexts.
- The mechanism creates shallow context copies via proxy-based extension in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts), avoiding duplication of underlying services.
- **Cordis service providers dependency injection** supports hierarchical configuration scoping and test mocking by allowing context property overrides.
- Protected method `[symbols.extend]` preserves the prototype chain while instantiating new service objects with extended contexts.

## Frequently Asked Questions

### What is the difference between Service.extend and Context.extend?

`Service.extend` is a static symbol exposed on the Service class that triggers the internal `[symbols.extend]` method, which in turn calls `Context.extend`. While `Context.extend` (implemented in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts)) handles the proxy creation and property merging, `Service.extend` provides the public API for service instances to create derived versions of themselves with new contexts.

### How does Service.extend preserve the prototype chain?

When `[symbols.extend]` executes in [`packages/core/src/service.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/service.ts), it instantiates a new service object using the original constructor while passing the extended context. Because it uses the same constructor rather than Object.create or manual property copying, the new instance maintains the complete prototype chain of the original service class, including all inherited methods and accessors.

### Can Service.extend be used for dependency mocking in unit tests?

Yes. By calling `root.extend({ serviceName: new MockService() })`, you create a derived context where the original service is replaced by a mock implementation. This approach isolates the mock to the specific test context without affecting the global service registry or other tests, as demonstrated in [`packages/core/tests/invoke.spec.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/tests/invoke.spec.ts).

### Where is the Service.extend symbol defined in the Cordis source code?

The static `Service.extend` symbol is defined in [`packages/core/src/service.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/service.ts) as `Symbol.for('cordis.extend')`. The corresponding method implementation using `symbols.extend` (the runtime symbol from [`packages/core/src/utils.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/utils.ts)) is located in the same file as a protected method on the Service base class.