Мы пытались написать BFF-прокси для Redmine. Это был провал. Вот что мы сделали вместо этого

от автора

Блог компании ArcFront. Автор: Алекс (arc-core) – Product Lead & Architect, ArcFront. Перевод и адаптация: Елена (loc-elena) – i18n Engineer, ArcFront.

Devlog о провальных экспериментах, болезненных пивотах и неожиданной элегантности 20-летней системы плагинов.


Интернет полон историй успеха. «Мы сделали X, и это было здорово». Это не такая история. По крайней мере, поначалу.

Это история о том, как наша команда в ArcFront три недели двигалась в совершенно неправильном направлении, что убило первый проект, как мы сделали пивот, и что мы в итоге выпустили.


Неделя 1: BFF, который казался отличной идеей

Проблема была очевидна: интерфейс Redmine ужасен. Решение казалось простым: сделать современный React SPA поверх Redmine.

Но React SPA нужны API. А REST API Redmine не рассчитан на прямое потребление фронтендом с другого origin’а. Возникают проблемы с CORS. Сессионные куки не пробрасываются. Нужно либо настраивать Redmine на отправку CORS-заголовков (что требует модификации внутренностей или написания ещё одного плагина), либо проксировать всё через промежуточный сервер.

Мы выбрали прокси.

Мы построили Node.js Express сервер на порту 3001. Назвали его “Redmine BFF Proxy”. Даже написали документацию.

Потом попробовали объяснить setup коллеге, который хотел это потестировать:

«Окей, сначала запускаешь Redmine – убедись, что он на порту 80. Потом запускаешь Node-сервер командой npm start, убедись, что он на порту 3001. Потом настраиваешь .env файл прокси так, чтобы он указывал на твой инстанс Redmine. Кстати, нужен Node 18 или выше. И убедись, что домен сессионной куки совпадает, иначе авторизация не заработает. А если у тебя nginx, нужно добавить вот эти заголовки…»

Глаза коллеги остекленели на словах «порт 3001».

Мы поняли: это никогда не дойдёт до реальных пользователей. Сложность установки убивала продукт. Системные администраторы, которые управляют Redmine в производственных компаниях, небольших агентствах, внутренних IT-отделах – не хотят управлять Node.js процессом. У них и без того достаточно процессов.

Итог первой недели: тупик.


Неделя 2: Озарение (и стыд)

Пивот случился во время одного разговора. Наш разработчик Майк сказал кое-что, что кажется очевидным задним числом:

«Redmine – это Rails-приложение. Rails-приложения имеют плагины. Мы уже пишем плагин. Почему мы не раздаём фронтенд из плагина?»

Мы так сфокусировались на фрейме «современного стека» – React, Vite, Node.js – что упустили очевидное: Rails даёт полноценный фреймворк для расширения приложений. Можно регистрировать маршруты. Писать контроллеры. Раздавать статические ассеты из собственной директории плагина.

Asset pipeline Redmine копирует папку assets/ вашего плагина в public/plugin_assets/<имя> при выполнении rake redmine:plugins:assets. Это означает, что ваши скомпилированные JavaScript и CSS раздаются собственным веб-сервером Redmine. Никакого отдельного порта. Никакого отдельного процесса. Никакой настройки CORS.

Стыд пришёл с осознанием того, что мы потратили неделю на строительство инфраструктуры для проблемы, которой не существовало, потому что недостаточно внимательно прочитали документацию по плагинам Redmine.


Неделя 2, день 3: Proof of Concept

Proof of concept занял четыре часа. Контроллер: 8 строк. Вьюха: 9 строк HTML. Всё работало – аутентифицированно, без CORS-ошибок, без прокси, без конфигурации – потому что SPA работал на том же домене и порту, что и Redmine, и автоматически разделял сессионную куку.

Мы сидели молча некоторое время.

Потом кто-то сказал: «Мы удалили две недели работы и заменили их пятнадцатью строками кода».


Неделя 3: Построение реального продукта

С выверенной архитектурой мы двигались быстро.

Главный вызов Канбана: статусы задач в Redmine настраиваются пользователями и могут называться как угодно – “New”, “Новая”, “Open”, “À faire”. Нам нужно было маппить произвольные названия статусов на стандартные колонки. Решение: функция translateStatus() с обработкой известных русских и английских названий и fallback на substring matching. Работает на 95% реальных инстансов без какой-либо конфигурации со стороны пользователя.

Command Palette инспирирован Ctrl+K из VS Code. Построили с нуля, без библиотек – модалка, поле поиска, фильтрованный список, обработчики клавиатурных событий. Префикс > – режим команд, @ – назначение исполнителя, обычный текст – поиск задач.

Вставка картинок вышла самой приятной. Весь процесс прикрепления скриншота сократился с 8 шагов до одного: нажать Ctrl+V. Изображение автоматически загружается на нативный эндпоинт Redmine /uploads.json, плейсхолдер заменяется нативным Textile-тегом !image.png!, токен загрузки сохраняется для последующей отправки с задачей.


Что мы поняли

  1. Читайте документацию до того, как строить инфраструктуру. Система плагинов Redmine дала нам всё необходимое. Мы потратили неделю на прокси, который был не нужен.

  2. Простота на уровне деплоя – это фича. «Скопируй папку и перезапусти Redmine» – несравнимо лучший UX, чем «установи Node.js, настрой переменные окружения, запусти два процесса».

  3. Same-origin недооценён. Запуск SPA на том же домене, что и бэкенд-API, устраняет огромный класс проблем: CORS, проброс куки, кросс-origin аутентификация.

  4. UI-слой – это там, где живут пользователи. Функциональность Redmine отличная. Модель данных солидная. API хорошо задокументирован. Единственное, что не так с Redmine – это лицо, которое он каждый день показывает пользователям. И это можно исправить, не трогая ядро.


Результат

Redmarc – drop-in нативный плагин, который даёт Redmine современный React-фронтенд: Канбан, Command Palette, тёмная/светлая тема, вставка скриншотов из буфера, клавиатурная навигация.

Без прокси. Без Node.js-сервера. Без дополнительных портов. Без конфигурации.

Только папка с плагином и rake-таска.

Мы небольшая команда, и мы продолжаем строить. Если вы работаете с Redmine и у вас есть мнения о том, как он должен работать – открывайте issue. Мы читаем.

– Алекс (arc-core), ArcFront. Перевод: Елена (loc-elena), ArcFront

ссылка на оригинал статьи https://habr.com/ru/articles/1042658/