Backend Typescript 1.0.0 Help

Аспектно-ориентированное программирование

О чем эта тема и зачем АОП

Аспектно-ориентированное программирование (АОП) — подход, который помогает изолировать сквозные функциональности (логирование, метрики, безопасность, транзакции, кэширование) от бизнес-кода. Идея: вы описываете, где (в каких точках выполнения) и что делать дополнительно, а инфраструктура «вплетает» это поведение в ваш код автоматически.

Базовые понятия

Аспект — модуль, в котором описано дополнительное поведение (советы) и критерии применения (срезы).

Точка соединения (Join Point) — конкретное место в выполнении программы (вызов метода, обработка исключения, доступ к полю), где аспект может быть применен.

Срез (Pointcut) — выражение, отбирающее множество точек соединения (например, «все public-методы в пакете service..»).

Совет (Advice) — код, исполняемый в выбранных точках: before, after, after returning, after throwing, around (обертка).

Сплетение (Weaving) — процесс внедрения аспектов в приложение: на этапе компиляции, загрузки классов или во время выполнения (через прокси или модификацию байткода).

Схема взаимодействия (модель исполнения)

  • Клиент вызывает целевой метод.

  • Вызов проходит через инфраструктуру (прокси/интерсепторы), формируется цепочка аспектов.

  • Around-совет получает контроль, может вызвать proceed() для продолжения или прервать выполнение.

  • Внутри/после выполнения целевого метода срабатывают before/after/after returning/after throwing.

  • Результат (или исключение) возвращается, возможно, уже модифицированный аспектом.

Типы советов (advices) и когда их применять

  • Before — валидация прав, привязка контекста трассировки, дешевые пред-проверки.

  • After — очистка ресурсов, сброс контекста, публикация событий «всегда».

  • After returning — пост-обработка успешного результата, метрики «успех».

  • After throwing — унификация ошибок, метрики «ошибка», алерты.

  • Around — измерение времени, ретраи, таймауты, кэширование, транзакции.

Способы сплетения (weaving)

  • Во время компиляции — модификация артефактов сборки. Плюсы: производительность. Минусы: сложность сборки.

  • При загрузке классов (LTW) — агенты/расширители загрузчика. Баланс гибкости и производительности.

  • Во время выполнения — динамические прокси/интерсепторы. Проще интегрировать, возможна цена в производительности.

Где применять АОП на практике

  • Логирование и трассировка (корреляция запросов, контекст трассировки).

  • Метрики (latency, error rate, счетчики вызовов).

  • Безопасность (проверка ролей/прав).

  • Транзакции и единицы работы.

  • Кэширование, дедупликация, идемпотентность.

  • Валидация входных данных, idempotency-keys, rate-limit.

  • Аудит и соответствие требованиям.

Минимальные примеры: один и тот же аспект логирования на пяти языках

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { @Around("execution(* com.example..service..*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { long t0 = System.nanoTime(); try { Object rs = pjp.proceed(); System.out.println("OK " + pjp.getSignature() + " took=" + (System.nanoTime()-t0)/1_000_000 + "ms"); // output: OK ... return rs; } catch (Throwable ex) { System.out.println("ERR " + pjp.getSignature() + " ex=" + ex.getClass().getSimpleName()); // output: ERR ... throw ex; } } }
function Log(): MethodDecorator { return (_t, _k, desc: PropertyDescriptor) => { const orig = desc.value; desc.value = async function (...args: any[]) { const t0 = performance.now(); try { const rs = await orig.apply(this, args); console.log("OK", (performance.now() - t0).toFixed(2) + "ms"); // output: OK 3.12ms return rs; } catch (e) { console.log("ERR", (performance.now() - t0).toFixed(2) + "ms"); // output: ERR 1.02ms throw e; } }; }; } class Service { @Log() async work() { return 42; } }
function logProxy(obj) { return new Proxy(obj, { get(t, k, r) { const v = Reflect.get(t, k, r); if (typeof v !== "function") return v; return async function (...args) { const t0 = performance.now(); try { const rs = await v.apply(this, args); console.log("OK", k, (performance.now() - t0).toFixed(2) + "ms"); // output: OK method 2.01ms return rs; } catch (e) { console.log("ERR", k, (performance.now() - t0).toFixed(2) + "ms"); // output: ERR method 0.77ms throw e; } }; } }); }
package main import ( "log" "net/http" "time" ) func Log(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t0 := time.Now() next.ServeHTTP(w, r) log.Println("OK", r.URL.Path, time.Since(t0)) // output: OK /api 12ms }) } func main() { http.Handle("/api", Log(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) }))) http.ListenAndServe(":8080", nil) }
#include <iostream> #include <chrono> #include <string> struct ScopeAspect { std::string name; std::chrono::high_resolution_clock::time_point t0; ScopeAspect(const std::string& n): name(n), t0(std::chrono::high_resolution_clock::now()) {} ~ScopeAspect() { auto dt = std::chrono::duration_cast<std: :chrono: :milliseconds>( std::chrono::high_resolution_clock::now() - t0).count(); std::cout << "OK " << name << " " << dt << "ms\n"; // output: OK work 1ms } }; int work() { ScopeAspect _a("work"); return 42; }

Правила написания срезов (pointcuts) безопасно

  • Отбирайте только нужные пакеты/классы/аннотации (минимальный охват).

  • Фильтруйте по сигнатурам: модификатор доступа, имя метода, количество/типы аргументов.

  • Используйте аннотации-маркеры для явного таргетинга.

Порядок и композиция аспектов

Когда аспектов несколько, важен порядок. Например: трассировка — внешняя оболочка, затем аутентификация, потом транзакция, затем кэш. Так вы получите корректные спаны, контролируемые транзакционные границы и метрики.

Асинхронность, потоки и контекст

В асинхронном коде переносите контекст явно (TraceId, UserId), не полагайтесь только на ThreadLocal. Для реактивных/корутинных сред используйте инструменты контекст-пропагации фреймворка.

Производительность

Накладные расходы приходятся на построение цепочки перехватчиков, извлечение метаданных и возможные аллокации. Around может добавить 3–30% времени в горячих точках при большом числе аспектов. В критичных путях используйте LTW/compile-time или локальные обертки без рефлексии.

Тестирование и отладка аспектов

  • Юнит-тесты для советов (проверка побочных эффектов и порядка вызовов).

  • Интеграционные тесты со «включенным» фреймворком и срезами.

  • Визуализация цепочек (логирование порядка срабатывания аспектов).

Когда нужен АОП?

  • Декоратор — хороший выбор для нескольких целевых методов.

  • Middleware/пипелайн — оптимален для HTTP/сообщений.

  • События/хуки — когда важна реактивность и слабая связность.

Чеклист внедрения

  • Выделите сквозные требования и зафиксируйте цели.

  • Определите точки соединения и критерии (срезы).

  • Выберите технику сплетения (compile/LTW/runtime).

  • Опишите порядок аспектов.

  • Покройте тестами и метриками, замерьте регресс производительности.

  • Документируйте правила добавления новых аспектов.

Last modified: 01 October 2025