Пример с AuthGuard и OnlyMeGuard
Возьмём пример из feeldown-backend (ссылка вне зоны доступа).
В проекте Feeldown аутентификация и авторизация разделены на два независимых
Guard-а: AuthGuard и OnlyMeGuard. Это отличный пример Single
Responsibility Principle (SRP) в действии.
AuthGuard – отвечает только за аутентификацию
@Injectable()
export class AuthGuard implements CanActivate {
public constructor(
private readonly service: AuthGuardService,
private readonly metadataHandler: MetadataHandler,
private readonly publicHandler: PublicHandler,
private readonly skipAuthGuardHandler: SkipAuthGuardHandler,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const metadataValidated = await this.validateMetadata(context);
if (metadataValidated) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
const validate = () => this.service.validateRequest(request);
return tryCatchThrow(validate);
}
private async validateMetadata(context: ExecutionContext) {
return this.metadataHandler
.apply(this.publicHandler, this.skipAuthGuardHandler)
.execute(context);
}
}Что хорошо:
AuthGuardне знает, как именно проверяется токен – это делегированоAuthGuardService.- Для обработки метаданных используется цепочка обработчиков (
MetadataHandler,PublicHandler,SkipAuthGuardHandler), что делает код гибким и открытым для расширения (OCP). - Все зависимости внедряются через конструктор – нет нарушений Dependency Injection.
- У класса одна причина для изменения: изменение логики аутентификации (например, переход с JWT на сессии).
OnlyMeGuard – отвечает только за проверку владельца ресурса
@Injectable()
export class OnlyMeGuard implements CanActivate {
public constructor(
private readonly reflector: Reflector,
private readonly service: OnlyMeGuardService,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const handler = context.getHandler();
const metadata = this.reflector.get<OnlyMeMetadata>(
Metadata.isOnlyMe,
handler,
);
if (!metadata) {
return true;
}
const validated = await this.service.execute(metadata, request);
return validated;
}
}Что хорошо:
OnlyMeGuardтолько проверяет наличие метаданных@OnlyMe()и делегирует основную логику сервисуOnlyMeGuardService.- Он не занимается парсингом токенов, работой с БД или трансформацией параметров – всё это вынесено.
- Его единственная ответственность – решить, пропускать запрос или нет, основываясь на метаданных и результате сервиса.
- Изменение правил проверки владельца затрагивает только
OnlyMeGuardService, а сам Guard остаётся нетронутым.
Разделение аутентификации и авторизации
Вместо одного «толстого» Guard-а, который делал всё, мы получили два узкоспециализированных класса:
| Guard | Ответственность |
|---|---|
AuthGuard | Проверить, что пользователь аутентифицирован (токен валиден) |
OnlyMeGuard | Проверить, что текущий пользователь является владельцем ресурса |
Это позволяет гибко комбинировать их в контроллерах:
@UseGuards(AuthGuard, OnlyMeGuard)
@Delete(ROUTES.DELETE)
public delete(@Param("id") id: string) {
// ...
}Что мы получаем
- Чёткое разделение ответственности – каждый Guard делает ровно одно дело.
- Лёгкость тестирования – каждый Guard и его сервис тестируются изолированно.
- Гибкость – можно легко добавить новый Guard (например,
RolesGuard) без изменения существующих. - Соблюдение DIP – зависимости внедряются, а не создаются внутри.
Вывод:
AuthGuardиOnlyMeGuard– это эталонный пример SRP в реальном проекте.
Они показывают, как грамотно разбить сложную логику на маленькие, легко поддерживаемые куски.
Last updated on