Когда ваш TypeScript-сервис встречает хаос
Представьте: ваше TypeScript-приложение работает идеально — тесты проходят, деплой проходит, пользователи довольны. Но вот наступает 3 часа ночи, и Redis-кластер решает "прилечь отдохнуть". Или CDN начинает отдавать 503 ошибки. Или, что ещё веселее, сетевой пакет теряется где-то между континентами.
В этот момент ваш аккуратный код на TypeScript, написанный с душой, может повести себя как испуганный кот. Или как супергерой. Всё зависит от того, тренировали ли вы его встречать хаос лицом к лицу.
Chaos engineering — это не про то, чтобы сломать продакшн. Это про то, чтобы научиться ломаться правильно. И TypeScript с его строгой типизацией и мощными абстракциями — отличный полигон для такой тренировки.
Что такое chaos engineering на самом деле?
Chaos engineering — это дисциплина экспериментирования над распределённой системой, чтобы выявить её слабые места до того, как это сделают пользователи. В контексте TypeScript-приложений это означает:
- Систематическое внедрение сбоев — отключение сервисов, задержки, ошибки сети
- Наблюдение за поведением — как ваш код реагирует на неожиданности
- Автоматизацию экспериментов — чтобы повторять их регулярно
- Улучшение архитектуры — на основе полученных данных
Главное правило: вы не должны узнать о проблеме от разгневанного пользователя. Лучше пусть о ней узнает ваш автоматизированный "разрушитель" в контролируемой среде.
Почему TypeScript идеален для chaos engineering?
TypeScript — это не просто надстройка над JavaScript. Это возможность писать код, который предсказуем даже в хаосе. Вот почему:
- Строгая типизация — вы точно знаете, какие данные приходят и уходят. Но что если вместо числа придёт undefined? Chaos-тесты это проверят.
- Асинхронность — Promise, async/await, Observable — всё это может "сломаться" интересными способами при сетевых проблемах.
- Экосистема — Jest, Mocha, Playwright — готовые инструменты для интеграции chaos-сценариев.
Практический пример: внедряем хаос в Express-приложение
Давайте представим, что у нас есть простое Express-приложение на TypeScript, которое обращается к внешнему API для получения данных о погоде. Мы хотим проверить, как оно поведёт себя, если этот API станет недоступен.
Шаг 1: Напишем обычный сервис
// weather.service.ts
interface WeatherResponse {
temperature: number;
humidity: number;
condition: string;
}
class WeatherService {
async getWeather(city: string): Promise<WeatherResponse> {
const response = await fetch(`https://api.weather.com/v1/${city}`);
if (!response.ok) {
throw new Error(`Weather API error: ${response.status}`);
}
return response.json() as Promise<WeatherResponse>;
}
}
export const weatherService = new WeatherService();
Шаг 2: Добавим контроллер с защитой
// weather.controller.ts
import { Request, Response } from 'express';
import { weatherService } from './weather.service';
class WeatherController {
async getWeatherHandler(req: Request, res: Response) {
const { city } = req.params;
try {
const weather = await weatherService.getWeather(city);
res.json(weather);
} catch (error) {
// Логируем ошибку
console.error(`Failed to fetch weather for ${city}:`, error);
// Возвращаем fallback-ответ
res.status(503).json({
error: 'Service temporarily unavailable',
fallback: {
temperature: 20,
condition: 'unknown'
}
});
}
}
}
Выглядит неплохо? Но мы не знаем, что произойдёт, если API начнёт отвечать очень медленно. Или если запросы будут падать с разными ошибками. Тут и приходит chaos engineering.
Шаг 3: Создаём chaos-прокси
Мы напишем middleware, который будет имитировать различные сбои. Это ключевой элемент — вы не должны менять свой код. Хаос внедряется на уровне инфраструктуры или middleware.
// chaos-proxy.middleware.ts
import { Request, Response, NextFunction } from 'express';
interface ChaosConfig {
failureRate: number; // 0.0 - 1.0
latencyMs: number; // задержка в мс
statusCode?: number; // какой код ошибки возвращать
}
class ChaosProxyMiddleware {
private config: ChaosConfig;
constructor(config: ChaosConfig) {
this.config = config;
}
async handle(req: Request, res: Response, next: NextFunction) {
// Имитация задержки
if (this.config.latencyMs > 0) {
await new Promise(resolve => setTimeout(resolve, this.config.latencyMs));
}
// Имитация сбоя
if (Math.random() < this.config.failureRate) {
const statusCode = this.config.statusCode || 500;
// Логируем хаос-событие
console.log(`[CHAOS] Injecting failure: ${statusCode} for ${req.path}`);
res.status(statusCode).json({
error: 'Simulated chaos failure',
chaosConfig: this.config
});
return;
}
next();
}
// Метод для обновления конфигурации на лету
updateConfig(newConfig: Partial<ChaosConfig>) {
this.config = { ...this.config, ...newConfig };
}
}
export const chaosProxy = new ChaosProxyMiddleware({
failureRate: 0.3, // 30% запросов будут падать
latencyMs: 500, // задержка 500мс
statusCode: 503 // Service Unavailable
});
Шаг 4: Подключаем хаос к приложению
// app.ts
import express from 'express';
import { chaosProxy } from './chaos-proxy.middleware';
import { weatherController } from './weather.controller';
const app = express();
// Подключаем chaos-proxy ТОЛЬКО для тестовой среды
if (process.env.NODE_ENV === 'chaos-test') {
app.use('/api/*', chaosProxy.handle.bind(chaosProxy));
}
app.get('/api/weather/:city', weatherController.getWeatherHandler);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Как проводить chaos-эксперименты с TypeScript?
Теперь, когда у нас есть инструмент для внедрения хаоса, давайте разберёмся, как проводить осмысленные эксперименты. Вот пошаговая методология:
1. Определите гипотезу
Не ломайте всё подряд. Сформулируйте конкретную гипотезу. Например: "Если внешний API для погоды возвращает 503 ошибку, наше приложение корректно отдаёт fallback-ответ и не падает с критической ошибкой".
2. Создайте тестовый сценарий
// chaos-experiment.test.ts
import { chaosProxy } from './chaos-proxy.middleware';
import { weatherService } from './weather.service';
describe('Chaos Experiment: Weather API failure', () => {
beforeAll(() => {
// Включаем 100% сбоев для этого эксперимента
chaosProxy.updateConfig({
failureRate: 1.0,
statusCode: 503