Текстовые урлы в Эгее
Хочу рассказать о том, как устроены текстовые урлы в Эгее. Эту заметку я публикую, отчасти, для энтузиастов, которые пытаются изменять урлы своих заметок, ковыряясь в базе, чтобы они хотя бы поняли, как эта база устроена, и почему. Ну и просто для тех, кому это может быть любопытно.
Раньше урлы составлялись из дат: у этой заметки был бы урл /2012/08/30/1/, потому что это первая по счёту заметка за 30 августа 2012 года. Проблема таких урлов в полной бессмысленности и незапоминаемости. Более того, из-за того, что в таком урле содержится порядковый номер заметки в дне, чтобы его сгенерировать/разобрать, приходится делать запросы, затрагивающие соседние заметки, и пересчитывать каждую из них с учётом её часового пояса (да, ведь за день может быть написано несколько заметок из разных часовых поясов).
Ещё одна проблема была в том, что если удалить заметку, то урлы написанных после неё в тот же день съедут на единицу вниз. Соответственно, ссылки поломаются. Мало приятного, короче.
Я давно хотел реализовать текстовые урлы, но с ними всё тоже неоднозначно. Во-первых, их надо как-то генерировать. Транслитерировать? А как быть с многоязыковой поддержкой (например, как транслитерировать с иврита)? Или просто давать пользователю ввести самому? Или не обламываться писать по-русски, как делает Википедия? Во-вторых, что делать, если название заметки изменится после публикации? Оставлять старый урл? Менять? Но тогда снова сломаются ссылки. В-третих, что делать со старыми заметками? Оставить со старыми урлами или конвертировать автоматически?
В результате долгих раздумий я решил, что должно быть так:
- Урлы должны генериться автоматически, потому что большинству людей нет до них дела, и лишнее поле в форме написания заметки — это лажа.
- Любой язык должен уметь транслитерироваться в латиницу (то есть файл языка помимо всяких слов и предложений должен содержать функцию транслитерации).
- Пользователь должен иметь возможность поменять урл, если ему сильно надо, и при этом ссылки на старый адрес не должны сломаться. (Это касается только внутренней инфраструктуры; интерфейса для изменения урлов в версии 2.2 пока нет, потому что её нужно было выпустить, и это не входило в список фич для этого релиза. Тем не менее, важно было сразу под капотом всё сделать нормально, чтобы потом было легко добавить это.)
- Выходит, мы должны помнить историю урлов каждой заметки, и знать, какой из них «настоящий» (то есть последний). Все старые урлы должны редиректить (301) на новый.
- Урлы старых заметок при переходе на новую версию движка меняться не должны, потому что автор этого не увидит, а автоматически изменять что-то в блоге за спиной у автора — это нехорошо. Вдруг там что-то коряво получится. Я не хочу, чтобы моим заметкам прописались какие-то там непонятные урлы без меня.
- Каждая заметка должна знать, под каким урлом она была опубликована изначально — это нужно для того, чтобы корректно работали всякие социокнопочки. Им ведь нужен урл заметки, и они ничего не знают о том, что он может поменяться. Если заметку уже залайкали 20 человек под урлом X, то изменение её урла на Y не должно убить эти лайки.
В итоге я реализовал следующую структуру.
В таблице заметок Notes я добавил поле OriginalAlias — это исходный алиас заметки (название для урла), созданный при её публикации. Если это поле пустое, значит заметка была опубликована до Эгеи 2.2, когда алиасов не было, и поэтому её урл должен по старинке составляться из даты и порядкового номера. Поле OriginalAlias вообще никогда не должно меняться после публикации заметки. Соответственно, у «старых» заметок оно навсегда останется пустым (благодаря нему у них не сломаются социолайки, завязанные на урлы из дат, даже если пользователь их поменяет на текстовые).
Для хранения настоящих урлов теперь есть специальная таблица Aliases с полями ID (ключ), EntityID (ключ заметки в таблице Notes), Alias (сам алиас) и Stamp (таймштамп, когда этот алиас был назначен заметке). Для того, чтобы поменять заметке урл, нужно просто дописать в эту таблицу запись с новым алиасом и таймштампом. Урл заметки формируется из алиаса с наибольшим таймштампом, а все старые алиасы редиректят на новые.