Перейти к основному содержимому

Один аккаунт, две двери: во что на самом деле обходится вход по телефону или почте

· 3 мин. чтения
Founder, DartWay

Разрешить людям входить хоть по номеру телефона, хоть по почте выглядит правкой на одну строку: расширяем поиск, принимаем оба варианта. Мы выкатили это на прошлой неделе в живом проекте, и правка на одну строку оказалась правкой модели данных — со счётом в придачу.

Вот во что она обходится на самом деле, в том порядке, в каком приходят счета.

Поиск никогда не был проблемой

Фреймворк хранил одну колонку userIdentifier и искал по строгому равенству. Попроси его найти профиль по телефону или почте — и расширять просто негде: не потому что запрос сложный, а потому что сама аутентификация требует, чтобы эта колонка существовала. Убери её — и ядро падает на старте.

Так что первая правка — не «искать по двум полям». Это дать приложению право голоса в том, как находится пользователь:

DwAuthConfig(
findUserProfileByIdentifier: (session, identifier) async {
// приложение само решает, что значит «тот же самый человек»
},
);

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

Поиск по двум полям сам по себе ничего не чинит

Вот эта часть нас и удивила, и ради неё написан этот пост.

У пользователя, который зарегистрировался по телефону, колонка с почтой пустая. Поиск по обоим полям не находит ничего нового, потому что во втором поле ничего нет. Поиск одновременно корректен и бесполезен.

Чтобы заполнить эту колонку, нужен способ привязать второй канал к существующему аккаунту — а значит, addAuthProvider и changeIdentifier должны реально работать. У нас оба бросали UnimplementedError, и экрана для этого не было ни в одном макете. Пока его нет, вход с другого канала по-прежнему тихо создаёт второй аккаунт, ровно как и раньше.

Дубли уже есть

Дальше идёт то, что отложить не получится. Мы пошли добавлять уникальные индексы на phone и email и обнаружили, что уникальных индексов не было вообще — ни на новых колонках, ни на старом userIdentifier. Две строки с одинаковым значением уже были легальны, и поиск возвращал ту, которую базе захочется отдать.

Индекс не добавить, пока дубли не слиты. А дубли существуют именно потому что вход со второго канала раньше создавал новый аккаунт. Баг и его разгребание — один и тот же объект.

Слияние — это событие, а не действие

Слияние аккаунтов необратимо: один профиль поглощает другой, и после этого никто не восстановит, что откуда пришло. Поэтому мы описали его как событие ProfileMerge, а не как вызов метода — кто слил, кого и что переехало. Действие оставляет базу консистентной, а историю потерянной; событие оставляет и то, и другое.

Что мы сказали бы себе неделю назад

  • «Вход по телефону или почте» — задача не про экран авторизации. Это задача про идентичность профиля, а экран — последние пять процентов.
  • Выкатывайте привязку второго канала в том же релизе. Без неё фича — это поиск, который ничего не находит.
  • Проверяйте уникальные индексы до того, как что-то пообещали. У нас их не было за всю жизнь проекта, и никто не заметил, потому что ничто их не требовало.
  • Проверяйте на живой базе полным кругом: регистрация по телефону, привязка почты, вход по почте в тот же аккаунт, одна строка в таблице. У нас 23 серверных теста, включая этот проход; версия этой статьи, написанная по чтению кода, была бы неверна в двух местах.

Со стороны фреймворка это приехало в ядро dartway 0.12.1 — резолвер аутентификации и работающий changeIdentifier. Если вы на более ранней версии, интересная часть — миграция, а не API.