BLoC – Компонент бизнес-логики – Часть 2

Перевод статьи https://plugfox.dev/business-logic-component-2

Вторая статья из серии статей о BLoC.
Вы можете найти предыдущую статью здесь.

В этой статье мы рассмотрим деление на слои и общую структуру проекта.

Слои и структура проекта

Архитектура компонента бизнес-логики

Если вы хотите изучить и изменить схему, загрузите исходный файл

Слои можно описать с помощью следующей таблицы

WidgetBLoCData
ОписаниеКонфигурационный или прикладной уровень.
Содержит неизменяемые схемы приложений (Widget) и текущую изменяемую конфигурацию (Element).
Управляет зависимостями между компонентами
Слой доменной логики, слой бизнес-логики.
Управляет бизнес-процессом и сопоставляет события с состояниями, используя текущее состояние и интерфейс репозитория в качестве источника новых данных.
Компонент бизнес-логики(Business Logic Component). Разделенные уровни конфигурации и данных по шаблону “Издатель-подписчик”.
Уровень доступа к данным, уровень сохраняемости, сеть и другие службы, необходимые для поддержки определенного бизнес-уровня).
Репозитории манипулируют несколькими поставщиками данных.
Поставщики данных вызывают методы у клиентов и сопоставляют результаты объектов передачи данных с данными/моделями.
РазделениеСлой виджетов взаимодействует с BLoC через блоки.
Отделяется от слоев BLoC добавлением методов, состояний и методом получения потока.
Отделяется от слоев данных методами репозиториев.Уровень BLoC может вызывать методы и подписываться на потоки из репозиториев.
Слой данных можно разделить на три части:
Репозиторий, поставщик данных и источник данных

Дерево элементов, структура виджетов

WidgetBLoCData
СодержитПрокси
Рендереры
Составные части
События
Состояния
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

Обзор системы Flutter

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
 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *