Архитектура Flutter проекта простым языком. Clean Arch (MVVM, DI, Bloc, Inversion of Control)

от автора

Для чего нужна архитектура?

В мире программирования наличие общепринятой архитектуры проекта имеет решающее значение для успешного выполнения и поддержания проекта. Архитектура определяет структуру, взаимосвязь между компонентами и их взаимодействия. Четко разработанная архитектура не только упрощает процесс разработки, но и обеспечивает масштабируемость, гибкость и удобство сопровождения кода. В конечном итоге, архитектурное согласие в команде повышает производительность, качество конечного продукта и снижает затраты на разработку и поддержку.

Clean Architecture

Clean Architecture является одной из самых популярных архитектур в мире разработки программного обеспечения, особенно для крупных и средних проектов. Она разработана Робертом Мартином (Uncle Bob) и получила широкое распространение благодаря своей модульности, гибкости и легкости в сопровождении.

Почему же она завоевала такую популярность и симпатию среди Flutter разработчиков и не только? Вот несколько причин, почему эта архитектура столь популярна и эффективна:

Четкое разделение ответственности на слои:

  • Презентационный слой (Presentation): Отвечает за пользовательский интерфейс и взаимодействие с пользователем. Здесь находятся виджеты Flutter, которые отображают данные и обрабатывают пользовательские действия. Кроме интерфейса в текущем слое находится наш менеджер состояния и view модель.

  • Слой домена (Domain): Содержит бизнес-логику и правила приложения. Это сердцевина нашей фичи или приложения, которая независима от фреймворков и внешних библиотек.

  • Слой данных (Data): Отвечает за управление данными, включая работу с базами данных, сетевыми запросами и кешированием.

Позже мы разберем где и какие именно папки, классы и их реализации должны находится.

Какие шаблоны проектирования нужны для чистой архитектуры?

MVVM — Model View View Model — это архитектурный паттерн, который используется для разделения логики представления (UI) и бизнес-логики в приложениях, стоит использовать только в том случае если в нашем виджете , который должен быть максимально “глупым”, существует зависимости от репозитория или же методов. Если присутствует большое количество переменных, переопределенных методов по типу initState так же нужно создать viewModel, в которую мы перенесем всю ненужную «глупому» виджету логику. Ниже нарисованная схема поможет лучше понять смысл этого паттерна.

Inversion of Control (инверсия управления) — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.

Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа помимо этого есть еще: Service Locator — широко известный анти-шаблон. А чем он занимается? Предоставляет доступ одних объектов к другим объектам.

Принципы ViewModel

В каждом проекте вы неизбежно столкнетесь с использованием ViewModel. Понимание этой концепции часто вызывает затруднения у начинающих разработчиков. Поэтому не будет лишним её разобрать.

Model — Модель представляет данные и бизнес-логику приложения. Она отвечает за управление данными и обеспечение согласованности и целостности приложения. В нашем случае Model будет абстрактным репозиторием из Domain слоя.
View — View отвечает за представление данных пользователю и захват пользовательских взаимодействий. Это пользовательский интерфейс, с которым взаимодействуют пользователи. Сохраняйте виджеты максимально «глупыми», минимизируя логику в компонентах пользовательского интерфейса вынося их в ViewModel.
ViewModel — это посредник между Model и View. Он содержит логику представления, раскрывая данные и команды, к которым View может привязываться. ViewModel разработан для тестирования независимо от UI. Он также часто инкапсулирует состояние View и обрабатывает ввод и взаимодействие пользователя.

Пример ViewModel:

class MapViewModel {   MapViewModel({required this.context,required this.infoLocationRepository,required this.locationRepository});   final BuildContext context;   final LocationRepository locationRepository;   final InfoLocationRepository infoLocationRepository;      late final UserLocationBloc locationBloc = UserLocationBloc(repository: locationRepository);   late final UserInfoLocationBloc userInfoLocationBloc = UserInfoLocationBloc(infoLocationRepository: infoLocationRepository);   late GoogleMapController mapController;      ValueNotifier selectLat = ValueNotifier([double]);   ValueNotifier selectLng = ValueNotifier([double]);   String styleMap = '';   get onMapCreated => _onMapCreated;   get initialize => _initialize;    void _onMapCreated(GoogleMapController controller) {     mapController = controller;   }    void _initialize() {     locationBloc.add(GetStartUserLocation());     DefaultAssetBundle.of(context).loadString('style.json').then((onValue) {       styleMap = onValue;     });   } }

Теперь наш виджет по настоящему «глупый», в нем нет реализации методов и прочего, теперь это все в нашей view model.

Структура проекта:

Utils

Utils содержит в себе все настройки и коллекции приложения. Тема, цвета нашего приложения. Коллекции с нашими svg/png картинками. Какая-то часть разработчиков называют данную папку theme.

Features

Features папка будет содержать все функции приложения, такие как аутентификация, профиль… А уже каждая фича будет разделяться на слои:

Data Layer

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

  • Repositories — Здесь хранится вся реализация бизнес логики, которая имплементируется от абстрактных классов описанных в Domain.

  • Models — Модельки наших ответов с сервера.

  • Service — Тут хранится реализация логики обращения к API или же к БД. В моем случае я использую библиотеку Retrofit, которая не нуждается в обработке и долгой реализации, именно поэтому наш абстрактный класс будет реализован сразу в Data. Как вы видите благодаря многим инструментам мы можем отойти от строгости структурирования чистой архитектуры.

Domain Layer

Domain Layer, также известный как Business Logic или Use Case Layer, содержит основные бизнес-правила и логику приложения. Он представляет собой сердце программной системы, инкапсулируя существенную функциональность, которая не зависит от какой-либо конкретной структуры.

  • Repositories — Здесь мы объявляем наши абстрактные классы, которые реализованы в data.

  • Entity — наши сущности, отличаются от моделей тем, что модельки используются для того, чтобы распарсить данные, а сущности для того чтобы использовать их.

  • Use_case — часто реализуются в виде отдельных классов или методов, которые инкапсулируют бизнес-логику. Это позволяет отделить бизнес-логику от деталей реализации (интерфейсы пользователя, базы данных и т.д.) и сделать код более модульным и тестируемым.

Presentation Layer

Presentation Layer (слой представления) в программировании — это слой архитектуры приложения, который отвечает за отображение данных пользователю и обработку взаимодействий пользователя с приложением. Он играет важную роль в том, как пользователи взаимодействуют с приложением и как данные и команды передаются между пользователем и бизнес-логикой.

  • Bloc — У вас эта папка может называться по другому , все зависит от вашего стейт-менеджера, у меня это блок.

  • Page — здесь хранятся наша верстка.

  • View Model — Здесь находится модель наших экранов

  • Widget — Виджеты которые дают верстке не читабельный вид и требуют вынесения в отдельные файлы (например из-за своей громоздкости).

Core

Папка core представляет собой фундаментальный модуль, содержащий ключевые компоненты, такие как утилиты, маршруты, сеть, службы, валидаторы и т. д. Часть разработчиков кучу папок суют в core, а кто-то разделяет как и features на слои: domain, data, presentation; как это делаю я и большинство разработчиков. Так легче ориентироваться и находить нужные тебе файлы.

Data:

  • Storage — Описание работы с нашим хранилищем.

  • Translation — наша логика перевода.

Domain:

  • Di — di container в котором мы проинициализируем зависимости приложения (классы которые должны быть single ton).

  • Repository — здесь хранится вся логика наших шаблонов применяемых к большому количеству реализаций. Например Dio Interceptor — настраивает все запросы (В моем случае добавляет в хедеры токен, который используется во всех запросах) или ответы. (У меня при ответе сервера 401 ошибки выполняется перезаписывание токена и повторное обращение к серверу)

  • Router — тут находятся наши пути, по которым мы будем перемещаться между экранами.

  • Use_case — тот же шаблон как и repository но применяемый не для самой реализации, а для обработки существующей реализации. Например шаблон ответа от сервера:

sealed class Result<T> {   bool get isSuccess;    const factory Result.data(T data) = DataResult;    const factory Result.error(List<String> errorList) = ErrorResult; }  class DataResult<T> implements Result<T> {   final T data;    const DataResult(this.data);    @override   bool get isSuccess => true; }  class VoidResult<T> implements DataResult<T?> {   @override   bool get isSuccess => true;    @override   final T? data;    const VoidResult([this.data]); }  class ErrorResult<T> implements Result<T> {   final List<String> errorList;    const ErrorResult(this.errorList);    @override   bool get isSuccess => false; }

То есть мы обрабатываем уже ответ от существующей реализации.

Presentation:

  • Page — тут находятся наши базовые скрины, такие как BottomNavigation. Которые управляют навигацией всего приложения.

  • Widget — тут все, часто переиспользуемые виджеты.

У чистой архитектуры нет строгих принципов. Это лишь базовые папки и подходы, которые были описаны в этой статье. В каждой компании свои подходы, но познакомившись с той структурой, что описал я, не будет никаких проблем разобраться в каком либо уже существующем проекте. Я вам гарантирую, что в core/domain не будут только те папки, сказаны мною выше. Там может находится логика обработки push уведомлений, логика входа с помощью отпечатка пальца и так далее. Каждый проект несет за собой свои особенности и фичи.

Но если вам скажут создать новый функционал, например реализовать передачу местоположения пользователя в фоном режиме, вы уже знаете что это будет описано по пути core/domain/repository и core/data/repository. В core потому это не является какой-то фичей приложения, у нее нет ui, она работает в любом месте, в любых условиях. В domain потому что у нас выполняется логика — достать место положение. Нам придется создать абстрактный класс:

abstract class Location { Location getLocation(); }

Далее в папке core/data/repository- мы уже выполняем его реализацию

class LocationImpl implements Location { @override Location getLocation(){ //code }; }

Надеюсь, эта статья была для вас полезна, желаю вам удачи ?


ссылка на оригинал статьи https://habr.com/ru/articles/833760/


Комментарии

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

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