🔥

Тред (@bespoyasov)


Как и обещал, начнём с наброса 😃 Что такое чистая архитектура, зачем нужна, плюсы, издержки. Если вы работали c ytq, расскажите о своём опыте? Что было круто, что было неудобно? Будем разбираться, действительно ли это полезный инструмент, или просто переусложнённый хайп.

Начнём с того, что такое «Чистая архитектура». Behold!
notion image

В общих чертах: весь код поделен на слои. Центральный слой (домен) — ядро приложения, максимально независим и отвечает за то, чем приложение отличается от других. Прикладной слой рулит сценариями, которые специфичны конкретно этому приложению.

Адаптеры и порты — это связь со внешним миром: БД, UI, вот это всё. Если хочется узнать побольше, то вот пара ссылок: - herbertograca.com/2017/11/16/exp… - habr.com/ru/post/269589/ - bespoyasov.ru/blog/clean-arc… Особенно советую — первую. Просто офигенная статья!

Когда я начал постить в блоге (о фронтенде) конспекты книг и статьи об архитектуре, мне стали прилетать вопросы типа «А кому и нафига это вообще надо?».

Я отчасти понимаю природу этих вопросов. Кажется, что архитектура это что-то далёкое от фронтенда: мы же просто формочки шлёпаем да кнопочки двигаем. А все эти Мартины и Физерсы как-то уж очень сильно переусложняют.

Вот нафига мне выделять «слои» в приложении, если всё моё приложение — это небольшое PWA с парой кнопочек? Есть аргументы вида «будет проще переехать с React на что-то ещё» — но я не собираюсь переезжать с React, зачем мне тогда адаптеры для него?

Да чтобы просто нарисовать схему приложения по такой архитектуре у меня времени уйдёт больше, чем на то, чтобы написать его 😃 В чём профит?

Я предлагаю начать с того, что архитектура — это прежде всего инструмент. У любого инструмента есть область применения и ограничения.

Я, пожалуй, не стану покупать шуруповёрт, чтобы вкрутить один саморез. Но если саморезов 1000, то я уже подумаю: потратить 5 тысяч на шуруповёрт или лечить в будущем артрит кисти за бóльшие деньги 😃

Архитектура, как и шуруповёрт, стоит ресурсов. Поддержка сложного проекта с лапше-кодом, как и артрит, — тоже стоит ресурсов, и тоже, как правило, больше.

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

Дело было так: я однажды попал в сложный проект на PHP с кучей легаси и запутанным кодом. Ни о какой архитектуре там речи, разумеется, не шло. Ребята зафигачили стартап, он полетел, побежали фичи и баги, а потом пришёл я 😃

Тогда я только-только начинал знакомиться с хорошими практиками в разработке софта, книжки там читать начал, всё такое. Но уже тогда было понятно, что ясного понимания, как работает система — нет, причём ни у кого 😃

Работать было невозможно, потому что добавишь чё-нибудь-куда-нибудь, где-нибудь-что-то-ещё отвалится. — Так написали бы тестов, чё. Ага, мы тоже так подумали 🙂

Не писались там тесты, как бы мы ни старались 😃 Код был написан так, что чтобы протестировать какой-то модуль, приходилось мокать вообще всё подряд. (Говоря умными словами, код был сильно зацеплен: ru.wikipedia.org/wiki/Зацеплени…)

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

Всё было намешано в кучу. Держать в голове даже один модуль было трудно: модуль мог и за шаблонизацию отвечать, и за преобразование данных. (Умными словами, нарушал SRP: ota-solid.vercel.app/srp)

Как и куда направлены зависимости тоже ясно не было. (Циклические зависимости себя не заставили долго ждать 😅)

Теперь контр-пример: прототип приложения на React. Надо быстро, поддерживать будет, скорее всего, не нужно. А если и нужно — то всё равно переписывать, потому что дизайн будет другой, UX поменяется и т. д.

Страдая от, кхм, ПТСР с прошлого опыта, я накрутил туда архитектуры по всем правилам: вот тебе и домен, вот тебе прикладной слой, адаптеры, всё независимо, найс. Только прототип никому не понадобился, а проект затух 😃

Вместо того, чтобы проверить гипотезу, приложив минимум усилий, я вбухал кучу ресурсов. Ладно хоть писал сам, а то стыдно бы потом было смотреть в глаза команде! 😃

И вот мой первый тогдашний вывод: == Издержки должны быть меньше выгоды == Да, вот так очевидно 😃

После того проекта я решил порефлексировать на него. Что бы произошло, если бы всё-таки прототип пришлось переписать. - Сколько кода я бы мог переиспользовать? - Какой код надо было бы переиспользовать?

*Сейчас пойду поработаю, а после расскажу: - какие выводы получилось сделать после этого, - как я использую ЧА сейчас, - какое минимальное количество усилий стоит прикладывать, - как понять, что пора расширять инструментарий.

Обед! Продолжим 😃 Итак, что бы произошло, если бы всё-таки прототип пришлось переписать. - Сколько кода я бы мог переиспользовать? - Какой код надо было бы переиспользовать?

Кто-то уже мог догадаться, что я клоню к домену. Домен — это самое главное, что есть в приложении. Та функциональность, которая отличает идею одного приложения от другого.

То, что мне точно пришлось бы перенести из прототипа в продукт — именно домен. Да, вероятно, с изменениями, возможно, что-то пришлось бы добавить. Но именно этот код пришлось бы переносить.

Второй вывод: === Стоит начать с домена === Сперва можно и не городить оставшиеся слои, не писать адаптеры к библиотекам, всего этого можно на первом этапе не делать. Но выделить домен — стоит обязательно.

Я это называю, кхм, «прагматичной архитектурой» 😃 Это как правило 20/80, только про дизайн систем. ru.wikipedia.org/wiki/Закон_Пар…

Без выделенного домена очень сложно вообще понять, что происходит. Так было в запутанном проекте из первого примера. Вся логика была разбросана тут и там, понять, какие есть сущности и для чего они, было почти невозможно.

Имей мы на руках функции и модули конкретных сущностей, мы бы уже знали, как они себя ведут и что с ними можно делать. Ядро системы было бы проще для понимания.

— Ок, допустим. Но вот я читаю книжки об архитектуре, там сплошное ООП. А я не хочу в свой проект его тащить. Понимаю. Могу обрадовать: архитектура и ООП — вещи ортогональные 😃

Ну то есть понятно, что большая часть книг написана с примерами на ОО-языках, но это не значит, что нам нельзя взять идею и использовать только её. (Почему с ООП проще строить грамотную архитектуру мы поговорим завтра.)

Домен можно вообще писать как хочется. Главное, чтобы код был понятным и независимым. Я, если пишу не в ОО-стиле, то люблю описывать домен в виде типов и чистых функций, которые оперируют данными этих типов: github.com/bespoyasov/www…

Профит в том, что если проект выстрелит и начнёт быстро расти, вам будет проще накрутить мяса вокруг самого важного кода, чем искать этот самый важный код по всей кодовой базе.

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

— Ладно, это всё, конечно, круто, но ты кажется забыл, что мы тут вс же на JS пишем. Какие нафиг типы? 😃 Отсутствие типов тоже не проблема для выделения домена 🙂

Ну то есть да, статичная типизация помогает проектировать, но и без неё можно справиться. Ну там JSDoc, объекты-стабы для тестов, те же классы в конце концов. (Хотя признаю, я начал по-настоящему задумываться о проектировании, когда перелез на TypeScript.)

(Без интерфейсов сложно сконцентрироваться на взаимодействии между сущностями. Труднее выделять публичное API, абстрагироваться от реализаций. У меня есть ощущение, что JS меня как бы подталкивает думать сперва о реализации, а TS — наоборот.)

Я для прототипов тесты, например, не пишу. Но как только становится понятно, что из прототипа надо делать продукт, гораздо проще покрыть тестами уже выделенный код.

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

— Окей, ладно. С доменом разобрались, допустим. Но вот зачем остальные слои? Они нужны? Короткий ответ: не всегда. Длинный ответ ↓ 😃 Когда я думал, что «используя слой адаптеров, проще съехать с React», я отвечал себе, что я и не собирался съезжать с React.

И это правда, перебраться с него на какой-то другой шаблонизатор сложно. У него богатая экосистема, куча уже написанных компонентов. Но что, если я заменю “React” на “Redux” 🙂

Кто-то наверняка задумывался о том, чтобы сменить стейт-менеджер. Кто-то, наверное, даже успешно его менял на какой-нибудь MobX или что-нибудь ещё. Так вот, заменить стейт-менеджер обычно — затратное мероприятие.

Он обычно затрагивает много кода: хранилище, события всякие, привязка к UI. Вместе со всем этим кодом надо и тесты переписывать — а это ещё раза в два больше работы.

С адаптером для стейт-менеджера переезд попроще 🙂 Слой адаптеров — это барьер, который говорит, где заканчивается сторонний код и начинается наш.

Адаптеры и порты делят внешний мир от нашего приложения как мембрана клетки отделяет её от окружающей среды. И все изменения окружающей среды влияют только на мембрану: появилось что-то, что можно съесть — съели, остальное отсеиваем.

Адаптеры как бы ограничивают распространение изменений. Мы пишем такие «переходники», которые делают внешний мир более удобным для нашего приложения.

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

Это, кстати, ещё и ограничивает распространение ошибок 🙂 Об этом писал Ганмер в «Паттернах отказоустойчивых приложений»: bespoyasov.ru/blog/patterns-… (Офигенная книжка, очень советую.)

Кроме ошибок, это ещё и помогает рефакторить код. Разделение по слоям — идеальный «шов», как называет его Физерс в «Эффективной работе с легаси»: bespoyasov.ru/blog/working-e… (Тоже советую 😃)

Итак, к этому моменту: - Архитектура — это инструмент. У неё есть издержки и выгоды. - В какой мере инструмент использовать — определяет разница между издержками и выгодами. - Не знаете, с чего начать — начните с домена. - Старайтесь привязывать 3-party код адаптерами...

...Но если это очень дорого и бессмысленно (проект точно не доживёт до момента, когда мы захотим поменять React на что-то ещё) просто держите это в уме (а лучше в документации).

Хорошо, вот мы поняли, что нашему проекту на Реакте нужна суровая масштабируемость, и одним выделением доменного слоя мы не обойдёмся. Что делать?

Теперь немного о собственно проектировании. Допустим, мы знаем, что нашему проекту нужна суровая масштабируемость. Что делать? Первым делом стоит взять ручку, бумажку и пойти «программировать ногами» 😃 twitter.com/lizuschiykotik…
Писать код ещё рано. Проектиурем! twitter.com/jsunderhood/st…

Да! ^_^ Недавно меня уже спрашивали в Твитере, что-куда-и-как можно вынести. Я ответил на примере приложения с котиками 😼 twitter.com/ch_ronik/statu…
Прорабатываем взаимодействие модулей: twitter.com/jsunderhood/st…

Пишем код домена. Начать лучше именно с него, потому что всё приложение мы будет строить под его нужды. Можно писать как угодно: если достаточно типов пары сущностей и одной-двух функций — замечательно. Это будет доменом. При необходимости дополняем.

Прикладной слой и порты с адаптерами. Я, кстати, видел даже неплохие стартовые шаблоны для того же Реакта: - github.com/eduardomoroni/… - github.com/bailabs/react_…

Держим при написании в голове разницу между выгодами и издержками. Если вы чувствуете, что оверижинирите, опишите в документации описание и не плодите лишний код. Если вы чувствуете, что вот это место, как слишком сильно сцеплено с 3-party кодом — добавляйте адаптеры.

Архитектура — это не только возможность масштабироваться, но ещё и увеличенный порог входа. Возможно, будет нужен онбординг. (Я, например, когда маленьким был, долго не мог вдуплить, что такое DI и как он работает.)

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

И вот мой первый тогдашний вывод: == Издержки должны быть меньше выгоды == Да, вот так очевидно 😃
Плохо умею в треды, продолжение вот тут, простите 😅 twitter.com/jsunderhood/st…