Для чего нужна архитектура?
В мире программирования наличие общепринятой архитектуры проекта имеет решающее значение для успешного выполнения и поддержания проекта. Архитектура определяет структуру, взаимосвязь между компонентами и их взаимодействия. Четко разработанная архитектура не только упрощает процесс разработки, но и обеспечивает масштабируемость, гибкость и удобство сопровождения кода. В конечном итоге, архитектурное согласие в команде повышает производительность, качество конечного продукта и снижает затраты на разработку и поддержку.
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/
Добавить комментарий