Системный дизайн: Как правильно спроектировать получение маршрута (route) по ID пользователя и ID по маршруту для чистых URL (SEF)?
Проектирование системы маршрутизации для чистых URL (Search Engine Friendly, SEF) — одна из классических задач системного дизайна. Основная цель — обеспечить двустороннее соответствие между человекочитаемым URL (slug/route) и внутренним идентификатором сущности (ID).
## Основные принципы
**1. Двусторонняя таблица маршрутов**
Наиболее распространённый подход — хранить маршруты в отдельной таблице базы данных. Структура таблицы:
sql
CREATE TABLE sef_routes (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
entity_type VARCHAR(64) NOT NULL, — ‘user’, ‘article’, ‘product’
entity_id BIGINT NOT NULL,
route VARCHAR(512) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE KEY uq_route (route),
INDEX idx_entity (entity_type, entity_id)
);
Это позволяет:
— Получить route по entity_id: `SELECT route FROM sef_routes WHERE entity_type=’user’ AND entity_id=42 AND is_active=1`
— Получить entity_id по route: `SELECT entity_id FROM sef_routes WHERE route=’/users/john-doe’ AND is_active=1`
**2. Поддержка редиректов и истории**
При смене slug у пользователя старый маршрут не удаляется, а помечается как неактивный с флагом redirect. Это гарантирует, что старые ссылки продолжают работать через 301-редирект.
**3. Кэширование**
Для высоконагруженных систем обязательно кэшировать оба направления в Redis:
— Ключ `route:{entity_type}:{entity_id}` → значение route
— Ключ `entity:{route}` → значение entity_id
TTL устанавливается в зависимости от частоты изменений (обычно 1–24 часа). При обновлении slug — инвалидация обоих ключей.
**4. Генерация уникального slug**
При создании пользователя slug генерируется из имени (транслитерация + lowercase + замена пробелов на дефисы). Если slug уже занят, добавляется числовой суффикс: `john-doe-2`, `john-doe-3`.
**5. Нормализация маршрутов**
Все маршруты должны храниться в нормализованном виде: без trailing slash, в нижнем регистре, без двойных слешей. Нормализация выполняется на уровне сервиса до записи в БД.
**6. Архитектура сервиса**
Рекомендуется выделить отдельный RouteService с методами:
— `getRouteByEntityId(entityType, entityId): string`
— `getEntityIdByRoute(route): {entityType, entityId}`
— `registerRoute(entityType, entityId, slug): void`
— `updateRoute(entityType, entityId, newSlug): void`
**7. Масштабирование**
При очень большом объёме маршрутов (десятки миллионов) можно использовать шардирование по entity_type или хеш-шардирование по entity_id. Также эффективно применение Bloom-фильтра для быстрой проверки существования route перед обращением к БД.
**8. Валидация и безопасность**
Route должен проходить валидацию по whitelist-паттерну (только буквы, цифры, дефисы, слеши). Это предотвращает инъекции и некорректные URL.
Таким образом, правильно спроектированная система SEF-маршрутизации сочетает реляционное хранилище для персистентности, кэш для производительности и чёткий API сервисного слоя для управления маршрутами.
