#flutter #authentication #architecture #state #clean-architecture
#flutter #аутентификация #архитектура #состояние #чистая архитектура
Вопрос:
Я пытался использовать адаптацию Reso Coder для Flutter чистой архитектуры дяди Боба.
Мое приложение подключается к API, и для большинства запросов (кроме входа в систему) требуется токен аутентификации. Кроме того, при входе в систему информация о профиле пользователя (например, имя и изображение профиля) принимается.
Мне нужен способ сохранить эти данные при входе в систему и использовать их как в будущих запросах API, так и в пользовательском интерфейсе моего приложения.
Поскольку я новичок в чистой архитектуре дяди Боба, я не совсем уверен, где эти данные принадлежат. Вот идеи, которые я придумал, все они связаны с сохранением данных в User
объекте:
-
Храните
User
на уровне репозитория, вauthentication
каталоге функций. Другие методы уровня репозитория могут передавать ее соответствующим методам источника данных.Кажется, это имеет наибольший смысл; другие методы уровня хранилища, которые вызывают другие вызовы API, могут
User
легко использовать сохраненные данные, передавая их методам на уровне источника данных.Если это правильный путь, я не совсем уверен, как другие функции (которые используют API) будут получать доступ к
User
— нормально ли, чтобы репозиторий зависел от другого и передавалauthentication
репозиторий в новый репозиторий функций? -
Храните
User
на уровне репозитория, вauthentication
каталоге функций. Другие (не связанные с входом) варианты использования могут зависеть как от этого репозитория, так и от одного, относящегося к их собственной функции, передавая методыUser
в их репозиторий.Это также нарушает вертикальный функциональный барьер, но может быть чище, чем идея 1.
Для обеих этих идей вот как выглядит мой репозиторий:
abstract class AuthenticationRepository {
/// The current user.
User get currentUser;
/// True if logged in.
bool get loggedIn;
/// Logs in, saving the [User].
Future<void> login(AuthenticationParams params);
/// Logs out, disposing of the [User].
Future<void> logout();
/// Same as [logout], but logs out of all devices.
Future<void> logoutAll();
/// Retrieves stored login credentials.
Future<AuthenticationParams> retrieveStoredCredentials();
}
Являются ли эти идеи «действительными» и есть ли какие-либо лучшие способы сделать это?
Комментарии:
1. Как вы в конечном итоге это сделали и появились ли какие-либо плюсы и минусы в отношении каждой идеи? Я также подумал о том, чтобы иметь специальную функцию аутентификации, реализующую только страницы входа и регистрации, как
presentation
при реализацииdomain
data
слоя иcore
, чтобы я мог легко запускать случаи использования аутентификации из всех других функций (например, кнопка выхода).2. Может быть, это должно быть в usecase на уровне домена, вызывающем сбой при сбое обновления сеанса. Все варианты использования должны расширять класс, который должен фактически выполнять работу по проверке и обновлению токена доступа.
3. Вы нашли предпочтительный способ сделать это? Я тоже застрял в этом.
Ответ №1:
Я вижу другой вариант решения проблемы. Решение, о котором я хочу поговорить, исходит из доменно-ориентированного проектирования и представляет собой подход, основанный на событиях.
В DDD у вас есть концепция ограниченного контекста. Бизнес-объект (сущность дяди Боба) может иметь разные значения в разных ограниченных контекстах. Взгляните на свой бизнес-объект user. Данные и методы, используемые в некоторых вариантах использования, часто отличаются от данных и методов, используемых в других вариантах использования. Вот почему у вас разные пользовательские объекты в разных ограниченных контекстах. Они представляют собой своего рода перспективу, которую каждый вариант использования имеет для одного и того же бизнес-объекта.
Если бизнес-объект изменяется в одном ограниченном контексте, он может выдать бизнес-событие. Другая функция может прослушивать эти события. Механизм событий может быть либо простым шаблоном наблюдателя, либо, если вам нужно распространять функции вашего приложения через микросервисы, очередь сообщений. В случае, если вы используете простой шаблон наблюдателя, отправитель событий и обработчик событий могут выполняться в рамках одной транзакции источника данных. Но они также могут выполняться в разных. Это зависит от ваших потребностей.
Поэтому, когда вариант использования регистрации регистрирует нового пользователя, он выдает UserSignedUpEvent
. Теперь другие функции могут прослушивать это событие. Событие содержит информацию о пользователе, такую как адрес электронной почты, имя, изображение профиля и другую информацию, которую пользователь предоставил при регистрации. Другие функции теперь могут сохранять необходимую им часть данных в своем собственном источнике данных. Это может быть то же самое, что и в случае регистрации (просто другие таблицы или другая схема). Но также возможно, что это совершенно другой источник данных, возможно, другой источник данных, такой как nosql db. Часть, которую я написал выше о транзакциях, конечно, сложнее, если у вас разные источники данных.
Главное, что каждая функция имеет свои собственные данные и управляет ими. Это может быть копия всей информации о пользователе, но во многих случаях это всего лишь подмножество.
Подход, основанный на событиях, может дать вам идеальную модульность. Но, как всегда, когда что-то выглядит великолепно, за это приходится платить. Вам нужно дублировать какую-то часть или даже все данные. Когда вы думаете об архитектуре микросервиса, и некоторые функции находятся в разных микросервисах, это означает, что дублирование повышает доступность сервиса. Служба может работать, даже если основная служба, которая управляет данными, не работает, потому что существует локальная копия. Но теперь вам приходится иметь дело с проблемами согласованности — возможной согласованности.
На этом этапе я хотел бы остановиться и направить вас к другим источникам для получения подробной информации:
- Глава 8: События домена, реализация разработки, управляемой доменом, Вон Вернон
- Много значений архитектуры, управляемой событиями, Мартин Фаулер