Context System and Dependency Injection

Last Updated: May 11, 2025

The Context System in Typus Development Framework provides a powerful mechanism for dependency injection and request-scoped data management. This document explains how the context system works and how it enables clean, decoupled code through dependency injection.

Overview

HTTP Request

Context Middleware

Context Creation

Request Processing

Controller

Service

Repository

Context Access

Dependency Retrieval

Request Data

User Information

Core Components

Context Class

The Context class is the foundation of the context system. It provides a container for request-scoped data and dependencies:

export interface IContext {
  readonly id: string;
  readonly startTime: Date;
  set(key: string, value: any): void;
  get<T>(key: string): T | undefined;
  has(key: string): boolean;
  getDuration(): number;
  getSnapshot(): Record<string, any>;
}

Context Manager

The ContextManager is a singleton that manages context instances using Node.js AsyncLocalStorage:

export interface IContextManager {
  run<T>(callback: () => T): T;
  runAsync<T>(callback: () => Promise<T>): Promise<T>;
  getCurrentContext(): IContext | undefined;
  middleware(): (req: any, res: any, next: any) => void;
}

Dependency Injection Container

The context system is extended with a dependency injection container that manages service instances:

export interface IContainer {
  register(name: string, service: any): void;
  get<T>(name: string): T;
}

How It Works

1. Context Creation

For each HTTP request, the context middleware creates a new context:

app.use(ContextManager.getInstance().middleware());

This ensures that each request has its own isolated context.

2. Service Registration

During application startup, services are registered with the container:

const container = Container.getInstance();
container.register('UserService', new UserService());
container.register('AuthService', new AuthService());
container.register('EmailService', new EmailService());

3. Dependency Injection

Components can access services through the context:

const contextManager = ContextManager.getInstance();
const context = contextManager.getCurrentContext();

if (context) {
  const userService = context.get<UserService>('UserService');
  const user = await userService.getCurrentUser();
}

Request Flow with Context

RepositoryServiceControllerContextManagerMiddlewareClientRepositoryServiceControllerContextManagerMiddlewareClientHTTP RequestCreate ContextContext CreatedRequest with ContextGet Current ContextReturn ContextCall Service MethodGet Current ContextReturn ContextDatabase OperationGet Current ContextReturn ContextReturn DataReturn ResultHTTP Response

Context in Frontend

The frontend also has a context system, often implemented using Vue's provide/inject or a similar pattern, to manage dependencies and shared state within the client-side application.

This allows components to access shared services without prop drilling.

Benefits of the Context System

1. Request Isolation

Each request has its own context, ensuring data isolation between concurrent requests.

2. Dependency Injection

Services and components can be injected without tight coupling:

// Without context/DI
constructor(
  private userService: UserService,
  private authService: AuthService,
  private emailService: EmailService
) {}

// With context/DI
const context = ContextManager.getInstance().getCurrentContext();
const userService = context.get<UserService>('UserService');

3. Request-Scoped Data

The context can store request-specific data that is accessible throughout the request lifecycle:

// In authentication middleware
context.set('user', authenticatedUser);

// Later in a service
const user = context.get('user');

4. Simplified Testing

Components can be tested with mock dependencies:

// In a test
const mockContext = new Context();
mockContext.set('UserService', mockUserService);
ContextManager.getInstance().run(() => {
  // Test code that uses UserService
});

5. Cross-Cutting Concerns

The context system facilitates implementation of cross-cutting concerns like logging, performance monitoring, and tracing:

// Add request ID for tracing
context.set('requestId', uuidv4());

// Log with context information
logger.info('Processing request', {
  requestId: context.get('requestId'),
  user: context.get('user')?.id,
  path: context.get('path')
});

Best Practices

  1. Service Registration: Register services during application startup
  2. Context Access: Always check if context exists before accessing it
  3. Scoped Data: Use context for request-scoped data, not for global state
  4. Performance: Be mindful of storing large objects in context
  5. Type Safety: Use generics when retrieving values from context
  6. Testing: Create mock contexts for unit testing
  7. Documentation: Document what services and data are available in context

WARNING

Failed to fetch dynamically imported module: https://typus.dev/assets/RecursiveNavItem-Cep7andh.js

{ "stack": "AppError: Failed to fetch dynamically imported module: https://typus.dev/assets/RecursiveNavItem-Cep7andh.js\n at https://typus.dev/assets/index-DS79FI73.js:315:420\n at dn (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:13:1385)\n at We (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:13:1455)\n at Ws.t.__weh.t.__weh (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:14:7364)\n at jt (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:13:1866)\n at v (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:14:4019)\n at https://typus.dev/assets/vue-vendor-Ct83yDeK.js:14:4097" }