BLoC – Компонент бизнес-логики – Часть 2
Перевод статьи https://plugfox.dev/business-logic-component-2
Вторая статья из серии статей о BLoC.
Вы можете найти предыдущую статью здесь.
В этой статье мы рассмотрим деление на слои и общую структуру проекта.
Слои и структура проекта
Если вы хотите изучить и изменить схему, загрузите исходный файл
Слои можно описать с помощью следующей таблицы
Widget | BLoC | Data | |
Описание | Конфигурационный или прикладной уровень. Содержит неизменяемые схемы приложений (Widget) и текущую изменяемую конфигурацию (Element). Управляет зависимостями между компонентами | Слой доменной логики, слой бизнес-логики. Управляет бизнес-процессом и сопоставляет события с состояниями, используя текущее состояние и интерфейс репозитория в качестве источника новых данных. Компонент бизнес-логики(Business Logic Component). Разделенные уровни конфигурации и данных по шаблону “Издатель-подписчик”. | Уровень доступа к данным, уровень сохраняемости, сеть и другие службы, необходимые для поддержки определенного бизнес-уровня). Репозитории манипулируют несколькими поставщиками данных. Поставщики данных вызывают методы у клиентов и сопоставляют результаты объектов передачи данных с данными/моделями. |
Разделение | Слой виджетов взаимодействует с BLoC через блоки. Отделяется от слоев BLoC добавлением методов, состояний и методом получения потока. | Отделяется от слоев данных методами репозиториев. | Уровень BLoC может вызывать методы и подписываться на потоки из репозиториев. Слой данных можно разделить на три части: Репозиторий, поставщик данных и источник данных |
Дерево элементов, структура виджетов
Widget | BLoC | Data | |
Содержит | Прокси Рендереры Составные части | События Состояния BLoC | Репозитории Поставщики данных, DAO Клиенты, Базы данных |
Примеры | StatelessWidget StatefulWidget InheritedModel StatelessElement StatefulElement ProxyElement BuildContext | WeatherBLoC AuthenticationState FetchSettingEvent IdleState BasketBLoC LogInEvent ErrorState | FirebaseAuthentication HttpClient Database BasketDataProvider GraphQL client SettingsDao AuthenticationRepository |
Слой пользовательского интерфейса
Да, а как насчет слоев «Presentation» или «UI», спросите вы?
User Interface/Presentation/View/Rendering — инкапсулируются непосредственно во Flutter Framework и Flutter Engine и редко управляются вручную.
Слой включает в себя:
– dart:ui
– RenderBox
– SceneBuilder
– RenderView
– Canvas
– Flutter Engine
– Skia
Widget слой
Вы можете думать о слое виджетов как о слое представления, но должны помнить, что Stateless, Stateful, Inherited и другие виджеты связаны только с зависимостями, жизненным циклом и композицией, а не с пользовательским интерфейсом. Виджеты абсолютно ничего не рисуют.
Слой виджетов также про:
– локализацию
– жизненный цикл приложения
– метрики
– распределение памяти
– навигацию (включая сохранение текущего состояния в кеше)
– зависимости приложения
– взаимодействие с платформой
– инициализации
Виджеты — это не то, что вы рисуете на экране, а то, как вы описываете и настраиваете свое приложение — это декларативный смысл фразы «все — виджет».
Так что намного прозрачнее изначально думать о виджетах как о декларативных конфигурациях приложений, а не о чем-то, что вы рисуете на холсте.
Конфигурационный или прикладной уровень. Содержит неизменяемые схемы приложений (Widget) и текущую изменяемую конфигурацию (Element). Объявите конфигурацию пользовательского интерфейса и управляйте зависимостями между компонентами.
Слой виджетов взаимодействует с пользовательским интерфейсом через менеджер BuildOwner.
Основной владелец сборки обычно принадлежит WidgetsBinding и управляется из операционной системы вместе с остальной частью конвейера сборки/макета/отрисовки.
Дополнительные владельцы сборки могут быть созданы для управления деревьями виджетов за пределами экрана.
Виджеты связаны с неизменной конфигурацией и композицией.
Мы можем разделить виджеты и элементы на типы:
1. Компоненты (например, Stateless, Stateful, Inherited widgets, Scaffold, Text, Container, GestureDetector, Navigator, Theme, MediaQuery):
- Композиция
- Управление зависимостями
- Жизненный цикл
2. Рендереры (например, Row, Column, Stack, Padding, Align, Opacity, RawImage) — классы, очень редко создаваемые вручную, конфигурация того, что будет отображаться на экране
- Размеры
- Позиция
- Макет и рендеринг
Во Flutter framework не было «Дерева виджетов».
Формулировка «Дерево виджетов» существует только для облегчения понимания, поскольку разработчики используют виджеты, но во Flutter НЕТ дерева виджетов! Вернее, правильнее сказать: «дерево элементов».
Ответственность слоя виджетов состоит в том, чтобы выяснить, как отображать себя на основе одного или нескольких состояний блока. Кроме того, он должен обрабатывать пользовательский ввод и события жизненного цикла приложения.
Кроме того, слой виджетов должен будет выяснить, что отображать на экране, основываясь на состоянии слоя блока.
Элементы — это изменяемые конфигурации, созданные экземпляром виджета.
Создание экземпляра виджета происходит в определенном месте дерева.
Также знакомый всем контекст — это ссылка на элемент виджета.
И, конечно же, состояние виджета тоже относится к элементам.
Каждый виджет соответствует одному элементу. Элементы связаны друг с другом и образуют дерево. Следовательно, элемент — это ссылка на что-то в дереве.
Вот различные типы элементов:
BuildContext — это не что иное, как сам Element, который соответствует
– виджет может перестраиваться (внутри методов сборки или компоновщика)
– StatefulWidget, связанный с состоянием, в котором вы ссылаетесь на переменную контекста.
BLoC слой
Ответственность уровня бизнес-логики состоит в том, чтобы отвечать на входные данные слоя виджетов новыми состояниями. Этот уровень может зависеть от одного или нескольких репозиториев для получения данных, необходимых для создания состояния приложения.
Основная идея использования BLoC — разделение слоев виджетов и данных с поддержкой паттерна “Издатель-подписчик”, обеспечивающее предсказуемую последовательность преобразования событий в состояния и изолирующие логические ошибки.
Виджет добавляет событие и преобразование BLoC и выдает несколько состояний.
Думайте о слое бизнес-логики как о мосте между виджетами (уровень приложений) и уровнем данных. Уровень бизнес-логики уведомляется о событиях/действиях от уровня виджетов, а затем связывается с репозиторием, чтобы создать новое состояние для использования уровнем представления.
Репозитории должны передаваться только конструктором bloc!
Виджеты могут взаимодействовать только с методом «добавить», «поток» и геттером «состояния».
Изменяемо только поле состояния внутреннего блока.
Вы можете создавать и испускать новые состояния с помощью текущего геттера «состояния», текущего «события» и методов репозитория.
Подробнее о теоретической части рассказано в предыдущей статье.
Слой данных
Слой данных можно разделить на части:
– Хранилище, Фасад
– Поставщик данных, объекты доступа к данным, адаптер, служба
– Клиент, база данных, источник данных, исполнитель
Этот уровень является самым низким уровнем приложения и взаимодействует с базами данных, сетевыми запросами и другими асинхронными источниками данных.
Клиент
Ответственность клиента заключается в предоставлении необработанных данных. Поставщик данных должен быть универсальным и универсальным. Запрос удаленных источников или баз данных.
Клиенты обычно предоставляют простые API для выполнения операций CRUD или выполнения запросов.
Как обычно, в реальности у нас нет возможности использовать клиентские интерфейсы, привязанные к реализации.
Например
– GraphQL-клиент
– HTTP-клиент
– Клиент веб-сокетов
– Клиент центрифуги
– Исполнитель базы данных
– Хранилище ключ-значение
– Файловое хранилище Firebase
– Аутентификация Firebase
class MyClient { Future<Response> execute(Request request) => ...; }
Поставщик данных
Поставщики данных управляют клиентами и возвращают бизнес-объекты (возможно, отображаемые объектом передачи данных) в репозитории.
У нас могут быть методы создания данных, чтения данных, обновления данных и удаления данных как часть нашего уровня данных, который возвращает бизнес-модели и сущности.
Конструктор должен передать клиента!
Например
– OrdersNetworksDataProvider
– AuthenticationDatabaseDataProvider
– UserCartDao
– PhotoStorage
abstract class IMyDataProvider { Future<Entity> create(EntityData data); Future<Entity> get(int id); Future<Entity> update(Entity entity); Future<void> delete(int id); } abstract class MyDataProviderImpl implements IMyDataProvider { MyDataProviderImpl({required Client client}) : _client = client; final Client _client; @override Future<Entity> create(EntityData data) => _client.execute(...).then<Entity>(DTO.decode); @override Future<Entity> get(int id)=> _client.execute(...).then<Entity>(DTO.decode); @override Future<Entity> update(Entity entity)=> _client.execute(...).then<Entity>(DTO.decode); @override Future<void> delete(int id)=> _client.execute(...); }
Репозиторий
Уровень репозитория представляет собой оболочку одного или нескольких поставщиков данных, с которыми взаимодействует уровень BLoC.
Репозиторий может взаимодействовать с несколькими поставщиками данных и выполнять преобразования данных перед передачей результата на уровень бизнес-логики.
Конструктор должен передать поставщик данных!
Как обычно, репозитории неизменяемы.
abstract class IMyRepository { Future<Entity> create(EntityData data); @useResult Future<Entity> get(int id); Future<Entity> update(Entity entity); Future<void> delete(int id); } @immutable class MyRepositoryImpl implements IMyRepository { MyRepositoryImpl({ required IMyNetworkDataProvider networkDataProvider, required IMyStorageDataProvider _databaseDataProvider, }) : _networkDataProvider = networkDataProvider , _databaseDataProvider = databaseDataProvider; final IMyNetworkDataProvider _networkDataProvider; final IMyStorageDataProvider _databaseDataProvider; @override Future<Entity> create(EntityData data) { final data = await _networkDataProvider.put(data); await _databaseDataProvider.set(data); } @override Future<Entity> get(int id) { final cache = await _databaseDataProvider.get(id); if (cache is Entity) return data; final data = await _networkDataProvider.get(id); await _databaseDataProvider.set(data); return data; } @override Future<Entity> update(Entity entity) { final data = await _networkDataProvider.update(entity); await _databaseDataProvider.set(data); return data; } @override Future<void> delete(int id) { await _networkDataProvider.remove(id); await _databaseDataProvider.remove(id); } }
Типичный поток
1. Изначально BLoC находится в состоянии ожидания.
2. Пользователь нажимает кнопку вызова обратного вызова onTap
3. BLoC.add(Событие)
4. BLoC выдает состояние Progress (копирует данные из геттера состояния)
5. Виджеты реагируют шиммерами, загрузчиками и заблокированными кнопками
6. BLoC вызывает метод реализации интерфейса Repository
7. Реализация Repository вызывает методы интерфейсов поставщиков Network и Database
8. Возврат консолидированных данных или создание исключение для BLoC
9. BLoC выдает Successful (установка данных) или Error (копия данных из получателя состояния)
10. BLoC выдает состояние ожидания (копия данных из получателя состояния)
Анатомия проекта
<platform>/ assets/ integration_test/ test/ tool/ packages/ database/ ... localization/ ... router/ ... <package_name>/ example/ lib/ src/ <package_name>.dart test/ pubspec.yaml lib/ src/ common/ util/ constant/ model/ bloc/ widget/ feature/ <feature_name>/ model/ widget/ bloc/ data/ app.dart main.dart README.md pubspec.yaml analysis_options.yaml Makefile