Главная / Статьи / Внутри DenoX: маршрутизация файлов, MVC Slices и контракт AGENTS.md в Deno

Внутри DenoX: маршрутизация файлов, MVC Slices и контракт AGENTS.md в Deno

Как фреймворк DenoX объединяет Hono, маршрутизацию на основе файлов, фичные слайсы, средство глобальной безопасности и рабочий процесс AGENTS.md, ориентированный на спецификации, для агентов по программированию с ИИ.

1560 слов

Подключение сервера редко является ключевой частью проекта с бэкендом; важнее — выпуск функций. Rails, Laravel и Next.js привлекли разработчиков тем, что брали на себя решение всех технических вопросов, и Deno с его по умолчанию безопасными правами доступа, встроенным TypeScript и инструментарием также является отличным кандидатом на аналогичную роль. DenoX — это open-source фреймворк полного стека для Deno, созданный на основе Hono, который стремится стать именно таким же определяющим слоем. Его решения типовых структурных вопросов и способ их фиксации для ИИ-агентов, занимающихся программированием, представляют собой шаблоны, которые можно использовать в любом бэкенде на TypeScript.

Пробел, который заполняет определяющий слой

Deno 2 принес совместимость с npm, реестр JSR, зрелую стандартную библиотеку и единый бинарник, способный выполнять проверку кода, форматирование, тестирование, компиляцию и сборку (см. как Deno 2.x решил проблемы совместимости с Node и усталость от инструментов для более подробной информации). То, чего не может предоставить простой движок выполнения, — это единое понимание вопросов, по которым спорят все команды: где должна находиться бизнес-логика, как сообщаются ошибки, кто проверяет конфигурацию и где реализовано ограничение скорости запросов.

DenoX отвечает на эти вопросы с помощью одного принципа: конвенции важнее настроек, что проверяется с помощью инструментов. Документированные конвенции могут меняться; те, что проверяются в CI, остаются неизменными.

Маршрутизация на основе файлов с генерируемой и сохраняемой таблицей

Маршруты берутся из файловой системы. Добавление файла в каталог pages добавляет соответствующий URL, причем элементы в квадратных скобках становятся параметрами:

src/frontend/pages/
├── index.ts            →  /
├── about/main.ts       →  /about
├── users/main.ts       →  /users
└── posts/[id].ts       →  /posts/:id

Обнаружение маршрутов не происходит во время выполнения. Запуск команды deno task routes просматривает структуру маршрутов и генерирует статическую, детерминированную таблицу маршрутов. Два фактора делают этот подход надежным:

  • Статические маршруты всегда регистрируются до динамических, поэтому /users/new никогда не может быть поглощен маршрутом /users/:id. В роутерах с правилом первого совпадения, таких как Hono, порядок регистрации определяет поведение, и его автоматическое генерирование устраняет классические причины скрытых ошибок.
  • Генерированный файл сохраняется в репозитории, и процесс интеграционного тестирования сбрасывается, если он устарел.

Страницы — это обычные функции

Страница представляет собой обычный модуль TypeScript. Он импортирует тип Context из библиотеки Hono и инструмент для экранирования HTML:

import type { Context } from "hono";
import { escapeHtml } from "@/shared/html.ts";

Затем он экспортирует объект config, в котором указывается шаблон разметки и функция-значение, возвращающая строку HTML:

export const config = { layout: "default" } as const;export default function homePage(c: Context): string {
  const name = escapeHtml(c.req.query("name") ?? "world");
  return `<h1>Hello, ${name}!</h1>`;
}

Следует обратить внимание на функцию escapeHtml. Параметр запроса name контролируется пользователем, и прямое вставление его в маркировку стало бы классической уязвимостью типа reflected XSS. В DenoX экранирование ненадежных данных — это не рекомендация, а правило, закрепленное в техническом контракте проекта. Поскольку движок шаблонов не выполняет автоматического экранирования и возвращает сырые строки, любое вставление внешних данных должно проходить через эту вспомогательную функцию.

Фрагменты функционала с фиксированной структурой

С точки зрения API каждый фрагмент функционала представляет собой самодостаточный блок с одинаковым набором файлов, каждый из которых выполняет одну задачу:

src/api/users/
├── user.model.ts        entities only
├── user.dto.ts          unknown → typed DTO (boundary validation)
├── user.repository.ts   interface + default implementation
├── user.service.ts      business rules only — no HTTP, no HTML
├── user.controller.ts   HTTP adapter only
└── user.routes.ts       composition root (constructor injection)

Модуль DTO преобразует входные данные типа unknown в объект с определенным типом на границе, поэтому более низкие уровни стека не имеют дела с необработанными телами запросов. Сервисы содержат только бизнес-правила и не знают ничего об HTTP или HTML. Контроллеры представляют собой простые адаптеры HTTP. Файл маршрутов является центром композиции, где зависимости подключаются с помощью внедрения через конструктор.

Сервисы зависят от интерфейсов хранилищ, а не от конкретных классов. Поэтому замена внутреннего хранилища на Postgres или Deno KV означает необходимость изменения по одному файлу на каждую функциональность.

Ошибки в виде исключений с указанием типа

Бизнес-правила сигнализируют об ошибках путем выбрасывания исключений с указанием типа. Метод сервиса ниже отказывается создавать второго пользователя с уже существующей почтовой адресной строкой:

async create(dto: CreateUserDto): Promise<User> {
  const existing = await this.repository.findByEmail(dto.email);
  if (existing !== null) {
    throw new ConflictException(`Email "${dto.email}" is already registered`);
  }
  return await this.repository.create(dto);
}

Сервис не выбирает код состояния и не форматирует ответ. Один централизованный обработчик ошибок преобразует каждый тип исключения в единообразный JSON-объект и обеспечивает, чтобы трейсы стека никогда не доходили до клиентов. Следует отметить, что возврат электронного письма позволяет проводить перечисление учетных записей, чего следует избегать на публичных конечных точках.

Безопасность реализуется один раз и применяется везде

Механизмы защиты, необходимые во многих частях системы, находятся в глобальном промежуточном слое, а не в каждой отдельной функции: политика безопасности контента, усиленные заголовки ответа, правила CORS, проверки CSRF на основе источника запроса, ограничения по частоте запросов с учетом IP-адреса клиента, лимиты размера тела запроса, таймауты и маскировка внутренних ошибок. Функции используют эти механизмы, вместо того чтобы реализовывать их заново.

Конфигурация обрабатывается аналогичным образом. Каждая переменная среды анализируется, проверяется и фиксируется при запуске процесса; приложение откажется загружаться, если чего-то не хватает или данные имеют неверный формат. В производственной среде значение CORS_ORIGIN=* полностью отклоняется. Быстрое обнаружение ошибок при запуске лучше, чем отладка наполовину настроенного сервиса в производстве.

Три уровня тестирования за одним командным приказом

Система тестирования выходит за рамки простых проверок:

  • Тесты модулей охватывают чистую логику с использованием имитаций для записи вызовов и вообще не требуют разрешений Deno.
  • Интеграционные тесты проверяют полностью настроенное приложение с помощью app.request(), убеждаясь в корректности кодов состояния, структуры ответа и даже заголовков безопасности, при этом не открывая сокетов.
  • Тесты конца к концу запускают реальный Deno.serve на временном порту и отправляют к нему запросы с использованием реальных функций fetch, включая запрос, преднамеренно вызывающий ограничение по частоте запросов, чтобы подтвердить возврат кода 429.
  • Полная система контроля качества, охватывающая форматирование, проверку синтаксиса, анализ устаревших таблиц маршрутов, строгую проверку типов и все уровни тестирования, запускается с помощью команды deno task ci, причем пайплайн GitHub Actions выполняет именно эту последовательность действий.

    Одна команда развертывания, без обработки учетных данных

    В репозитории содержатся манифесты для сервисов Fly.io, Railway, Render, Docker, а также усиленный модуль systemd для VPS, плюс первоклассная поддержка Deno Deploy. Одна команда может указать цели, выполнить пробный запуск или фактически осуществить развертывание:

    deno task deploy            # list targets
    deno task deploy fly        # dry run: steps + env reminders
    deno task deploy fly --run  # execute (auth delegated to the platform CLI)
    

    Инструмент развертывания намеренно не обрабатывает учетные данные. Он проверяет предварительные условия, отображает план развертывания вместе с напоминаниями о необходимых переменных окружения и оставляет вопросы аутентификации на усмотрение официальных CLI каждой платформы. Секретные данные совершенно не присутствуют в фреймворке.

    AGENTS.md как обязательный технический контракт

    Самой выдающейся особенностью DenoX является файл AGENTS.md, расположенный в корне репозитория, который служит авторитетным контрактом как для людей-разработчиков, так и для ИИ-агентов. В нем указывается технологический стек, определяется каноническая структура директорий и перечисляются общие компоненты, которые ни в коем случае нельзя пересоздавать: логгер, иерархия исключений, структура ответа и модуль конфигурации.

    Кроме того, в этом файле описан процесс разработки, основанный на спецификациях:

    1. Спецификация вроде specs/feature.md создаётся с параметром status: draft.
    2. Человек проверяет её и меняет статус на status: approved.
    3. Только после одобрения начинается работа над архитектурой, планом, реализацией, тестированием и документацией.

    Агентам прямо указывается остановиться после написания спецификации и дождаться её одобрения человеком, поэтому агент не может сам одобрить свой план и затем переписать часть кодовой базы. Полный цикл работы с управлением пользователями показывает агентам эту схему, а системы CI механически соблюдают установленные правила, прерывая процесс сборки при ручном изменении генерированных файлов.

    По мере того как агенты пишут всё больше кода, конвенции становятся важными лишь в той мере, в которой их можно автоматически проверять; версионирование контракта рядом с кодом в сочетании с инструментами CI превращает рекомендации в строгие ограничения. Чтобы узнать больше о файлах инструкций для ассистентов, посмотрите навык AGENTS.md от Vercel по лучшим практикам React.

    Запуск локально

    Клонируйте репозиторий, сгенерируйте файл с настройками окружения по примеру и запустите сервер разработки:

    git clone https://github.com/olavomello/denox.git
    cd denox
    cp .env.example .env
    deno task dev
    

    Затем откройте http://localhost:8000, вызовите /api/users, намеренно отправьте некорректные данные и проверьте, что сообщение об ошибке останется чистым и не будет содержать трассировок стека. Также доступна рабочая версия. Проект распространяется под лицензией MIT; в его планах — адаптеры Deno KV и Postgres, автоматическая регистрация структур, специальная команда линейной оболочки, модуль аутентификации и генерация OpenAPI, причем каждый из этих элементов планируется создаваться с использованием одинакового подхода, основанного на спецификациях. Перед использованием ознакомьтесь с текущим состоянием репозитория.

    Основные выводы

    • Генерируйте таблицы маршрутов во время сборки, регистрируйте статические маршруты перед динамическими, сохраняйте результаты и пусть система CI отклоняет устаревшие файлы.
    • Для каждой функции определите фиксированную структуру: DTO для границ данных, репозитории на основе интерфейсов, сервисы без использования HTTP и легкие контроллеры.
  • Вызывайте типизированные исключения и обрабатывайте их в одном месте, чтобы ответы были единообразными и не содержали следов стека.
  • Реализуйте механизмы безопасности в глобальном промежуточном слое и проверяйте конфигурацию при запуске, отклоняя небезопасные значения, такие как шаблонные источники CORS в производственной среде.
  • Создайте файл AGENTS.md, требующий человеческого утверждения спецификаций, и применяйте его правила в системах CI, чтобы они действовали как для агентов, так и для людей.