Авокадо с зубами подсказывает, что так код легче поддерживать, дописывать и рефакторить. Мы всё теперь пишем только так.

Привет, Хабр! У нас была проблема: каждый писал код как хотел. Было очень тяжело это поддерживать и ревьюить. Мы сначала думали, что достаточно написать стандарт кода. Оказалось, недостаточно, ему ещё надо обучить. Чтобы обучить, мы открыли для ревью эталоны кода, чтобы покрыть ими самую частую логику взаимодействия с компонентами. Тоже не хватило. А заодно я узнал, что мои же «золотые» образцы противоречили моему же стандарту кода (сначала было смешно, а потом пришлось переписывать).

В итоге я сделал кукбук с большим количеством примеров, чтобы объяснить культуру и методологию не через абстракции, а очень предметно. Начал вроде как просто для себя, оказалось полезно — и внедрил в работу команды.

Всё это было нужно для того, чтобы мы все в команде понимали, что такое наш стандарт кода, делали одинаковые вещи примерно одинаково, имели набор готовых шаблонов на системные или платформенные обращения и уменьшили объёмы как самого ревью, так и диалогов после.

Началось с того, что однажды я увидел очередной большой кусок кода, где логика из одного сервиса ушла в другой, а интеграционных тестов было аж 54 штуки. Исправленная опечатка в одном из запросов потребовала трёх часов работы, хотя я заранее знал с точностью до строчки и знака, где именно эта опечатка.

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

На сегодня ни в одной компании в России я не видел целостного подхода в сборке с кукбуком. Какие-то части встречаются, в сборке — нет. Вот я и хочу рассказать, как мы это сделали у себя в банке.

Посвящается всем, кто коллекционирует элегантные решения без привязки к языку, фрэймворку, Фаундлингам и Software Craftsmanʼам.

Погнали.

Самое главное


Вот ссылка на опубликованный CookBook: https://git.codemonsters.team/guides/ddd-code-toolkit/src/branch/dev

Почему не хватило стандартов разработки?


Потому что в моих исходных кодах по стандартам содержались предпосылки к ошибкам. Вот пример:


Сервис с интеграциями и бизнес-логикой

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

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

Cила этой истории в том, что она собрала много полезных паттернов вместе, и это очень хорошо работает.



Впрочем, давайте прямо с самого начала.

Кукбук: зачем он нужен


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

Кукбук содержит в себе паттерны применения стратегических и тактических паттернов DDD, Type Driven Development, Reach Domain Model, запущенных в функциональной парадигме с прагматичными юнит-тестами бизнес-логики без исключений по паттерну R.O.P (Railway Oriented Programming).

Всё это помогает:

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

Временами после работы я рублюсь на Flutter + kotlin backend и радуюсь, что подходы из кукбука — как неокиберпоэзия: работают на всех уровнях — от тупого фронта до хитроумного мидла с бэком.

Вся эта история проверена на практике промом и совершенно разными командами и задачами.

Как он появился


За 2,5 года в роли тимлида и техлида разработки, отвечающего как за качество delivery, так и за качество кода, я с командами затащил один проект в прод, и сейчас катим, как танки, второй.

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

Делегируя разработку с достойными наработками по трёхзвенной архитектуре со слабой доменной моделью и классами-сервисами, в которых зашита логика, интеграционными тестами на логику, внешними огуречными тестами, я увидел, как разработка начинает огребать в функциональной реактивной парадигме по гайдам от Роберта Мартина с понятными именами, методами и чрезмерно гранулированными классами по SOLID.

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

Обрисую постфактум мою задачу:



Без алкоголя сложно порой высвободить внутреннего Паланика, Хэмингуэя или Аллена. Погнали дальше, Максим.

Благодаря крутым работам инженеров Скота Влашина, Владимира Хорикова, Роберта Мартина, мадам Рефлексии и поискам удалось собрать воедино кукбук по реализации сервиса с бизнес-логикой (агрегирующие сервисы, перекладчики и прочие вспомогательные истории веб-разработки он также покрывает).

Новичку он призван показать, насколько важны методологии DDD, TDD (прагматичные тесты), как прекрасен Type Driven Development, как необходима грамотная коммуникация, что в функциональной парадигме можно понятно и просто писать. Можно и без неё. И что суперважно быть экспертом в той области, которую моделируешь.

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

Это достаточно жёсткое кунг-фу, пропитанное олдскульной классической школой тестирования, сильной доменной моделью и функциональной парадигмой.

Просто делайте по кукбуку тикет — будет хорошо, и начинайте изучать подробнее кирпичики, без которых его не выстроить. Баста!

Когда возникают проблемы, можно было описать пару template-сервисов и сказать: «Делайте так!» — но чтобы грамотно определить место бизнес-логике, без DDD не обойтись.

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

Но я любитель проектов двух типов: мандатных и супервыгодных компаний. В таких условиях драйв другой, вызовы, темп, премии.

Нужна история более строгая и масштабируемая.

Кукбук — как раз об этом: как превратить реактивный код в документацию и юнит-тестами покрыть бизнес-логику.

Если вы ищете элегантные решения — он точно вас зацепит, и, хочется верить, вы унесёте с собой его часть или целиком.

Если вас не впирает мой стиль повествования — кукбук сразу по ссылке в конце поста, прокручивайте вниз, берите и пользуйтесь.

Для въедливых я немного пройдусь по каждому из разделов.

DDD — самое главное


Мы начали с того, что разработчик становится экспертом в той области, которую он описывает, и много внимания уделяем общему языку. Применяем паттерн Ubiquity Language.

Качественная разработка — это результат качественной коммуникации, а не программирование по постановке.

Я видел истории про страдающих и за всё отвечающих аналитиков, такие истории до добра не доводят. Я знаю, что есть команды, где аналитики всё за всех решают и говорят, как всё должно работать. Ещё и артефакты катят в пром. А разработчики сидят за удобной оградкой — ждут спецификации, и чуть что: «У меня лапки, мне так аналитики сказали».

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

Пока разработчик не поймёт, что нужно разработать, — ничего путного не разработает.

Что утаивали и Мартин, и Кент, который Бекк, — что разработчик должен стать солюшеном (ссылки на их труды — тоже внизу поста). Они такие крутые книги написали, потому как сами во всём разобрались, были архитекторами и порционно выдавали рекомендацию. А дальше зовите нас — мы проконсультируем.

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

Твой код — твоя зона ответственности. Мы обсуждаем задачу и просто описываем, что нужно сделать. Не разбираемся с талмудами аналитики, которая приходит как постановка, а просто задаёмся вопросом: какую задачу мы решаем?

Пример

Нам нужно актуализировать данные абонента в системе. В примере кукбука сервис решает задачу обновления данных вымышленного абонента. Если упростить:

  • Есть текущий стэйт абонента.
  • Есть внешняя система, из которой актуальные данные поступают в сервис «Актуальность».
  • Он формирует запрос в сервис «Обновлялкин». Бизнес-логика тут такая, что сервис принимает решение, нужно ли обновлять данные, если нужно — формирует запрос на актуализацию данных и отправляет в систему «Абоненты».
  • «Абоненты» — единая точка добавления и обновления абонентов. Эта система знает о взаимосвязях абонента с другими сущностями и то, за какие нити нужно подёргать на случай обновления атрибута, например, mobileRegionId.

Я в кукбуке опишу «Обновлялкин» — это наше связующее звено между «Абонентами» и «Актуальностью». Задача простая, если ты прокачался и правильно рисуешь границы.

Визуализация:



Граница ответственности Bounded Context-сервиса по обновлению данных — обновление данных. Он может запросить сервис «Абоненты», чтобы получить актуальные данные абонента, и может получить запрос на обновление от «Актуальности». Суть его бизнес-логики:

  • Понять, что обновление необходимо по вошедшему запросу на обновление данных.
  • Сформировать запрос на обновление.
  • Отправить его в «Абоненты». Как обновлять атрибуты, «Абоненты» знает.

Так мы и опишем «Обновлялкин» в кукбуке.

Стратегические паттерны DDD — Bounded Context, Ubiquity Language — я описывать не буду, всё это найдёте по ссылкам в конце поста. Едем дальше.

Очень круто помнить о важности коммуникации, общего языка — в DDD 15 Years.

Наша задача при грамотно определённой границе сервиса и общем языке в команде — описать его верхнеуровнево в документации в конфлю. И так описать на Kotlin(Java), чтобы сам код был документацией. Сделать это на F# проще, но кровавый интерпайз не оставил нам выбора, Гарри.

Мы пишем на Kotlin.

Опишем в документации:



И в финале мы получим код, который описывает бизнес-логику:



Чем плохи долгие постановки в конфлю с кучей информации по имплементации от аналитика?

  • Их нужно аналитить разработчику или снять ответственность и тупо кодить по аналитике. Второй подход мы исключаем. Первый нам не подходит: это дорого.
  • Имплементация может измениться при рефакторинге — и вот у вас неактуальная документация. Для разработки это лишний гемор и усложнённый процесс, регламент, нужно следить. Кто хочет быть пастухом актуализации документации?

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

В кукбуке я привёл пример плохой документации и сценарий подхода к этому:



Далее по кукбуку необходимо перестроить мышление команды.

Думай моделями и бизнес-процессами, а не тем, как модель хранится в БД.

Забудьте Table-Driven Design (Database Oriented-мышление) — используйте только доменные объекты при обсуждении задачи. Не думайте о низкоуровневой реализации.

Не говорите: «Нам нужно обновить поле в таблице». Это вообще может быть не так, и процесс может быть намного сложнее.

Хорошо, мы договорились — общаемся объектами и процессами, становимся экспертами, описываем кратко суть задачи. Дело осталось за малым: описать всё в функциональной парадигме на Kotlin и покрыть юнит-тестами бизнес-логику.

Получить такой элегантный код нам помогут одна простая гениальная идея R.O.P и Type Driven Development при реализации тактических паттернов DDD (Aggragate, Value Object) и пара хороших паттернов, таких, как Rich Domain Model, Can Execute/ Execute.

Проклятие слабой доменной модели


В двух банках я видел и рос по такой структуре кода:

  • /rest-пакет с контроллерами.
  • /domain-пакет с Data Transfer Object (DTO).
  • /services-пакет с сервисами, в которых хорошо или в стиле описана бизнес-логика.

И кругом — антипаттерн, слабая доменная модель. Что это такое? Это классы, контейнеры атрибутов, а имя им POJO в мире JVM.

Например:



Чем это плохо? Тем что при таком подходе вся бизнес-логика концентрируется в классах сервисов.



Я так жил почти всю программерскую жизнь и сервисы структурировал хорошо. Мы тестировали бизнес-логику в сервисах аккуратными интеграционными тестами, но в этом её недостаток. Каждый кейс — отдельный набор конфигураций для интеграционных тестов (например, wiremock) или mock. Это превращается в дополнительную кропотливую и злобную работу, а есть желание кода писать поменьше.

Заложил кривой паттерн.





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

Чтобы тестировать логику юнит-тестами, необходимо изолировать доменную модель от интеграций.

Поможет в этом древний паттерн «Сильная доменная модель». Она содержит в себе бизнес-логику.

Например, Aggregate.

SubscriberDataUpdate


Класс обновления данных и его бизнес-логика таковы: сгенерировать запрос или сообщить нам, что обновление не требуется.



Логика по обновлению тут:



Про Aggregate и ValueObject хорошо написал Эрик Еванс и отдельно в видосах с Владимиром Хориковым и Ахтям Сакаевым в списке ниже, а также я отдельно выделил определение в кукбуке.

А визуализировать изоляцию доменной модели от интеграций хорошо помогает архитектура в стиле зубатого авокадо. Луковичная архитектура Onion Architecture-приложения:



В итоге мы получим изолированную сильную доменную модель от интеграций. Уровень сервисов приложений мы будем использовать как тупой поток.

Наша цель — простой и тупой сервис:



Чтобы добиться этого, нам необходимо следовать правилу: наша доменная модель должна быть всегда валидна и рождаться в приложении только благодаря фабричным методам.

А что делать, если модель не может возникнуть и мы должны выбросить ошибку? Мы не используем исключения, а применяем R.O.P. Скотт на своей страничке поясняет, что это название хорошо подходит для визуализации процесса.

Railway Oriented Programming — error handling in functional languages

На этой простой идее в работе с ошибками далее мы построим весь процесс.

Рождение сильной доменной модели, интеграции с внешними системами.
R.O.P.



Используем two track type Result<Data, Error> на выходе у функции, если она может зафэйлиться — чаше всего интеграции и фабричные методы доменной модели.

На вход в самом просто случае может прийти класс, как в случае с фабричным методом emerge ValueObject SubscribertId.

SubscribertId — не пустая строка, которая содержит только цифры и длина которой не превышает шести.



Мы используем самые важные строительные блоки DDD — тактический паттерн Value Object, который может возникнуть только благодаря фабричным методам.

А если ValueObject не может возникнуть по бизнес-логике — мы без исключения (Exception) возвращаем Two Track Type:



И тут же раскрывается сила Type Driven Development: в ядре нашей доменной модели нет места примитивам. Все строительные блоки содержат в себе релевантную бизнес-логику ограничений, и их просто протестировать.

Тут нам нужно немного перестроить мышление, чтобы всё сложилось от входа на REST до выхода на REST.

Поможет следующая старая мысль: любой бизнес-процесс мы можем описать в функциональном стиле а-ля unix pipe.



Декомпозируем эту мысль на задачу реализации рест-сервиса от входного запроса до результата. Мы можем без примитивов в сердце доменной модели (классов) описать в функциональном стиле переход алгебраических типов из одного в другой следующим образом:

Что у нас приходит на вход в REST-ресурс? Непроверенный запрос на обновление — UnvalidatedDataUpdateRequest.

Описываем:



Далее мы представляем Happy Path. И непроверенный запрос на обновление превращается в валидный запрос на обновление.

ValidatedDataUpdateRequest



А если непроверенный запрос содержит в себе ошибку — фабричный метод ValidatedDataUpdateRequest.emerge -> вернёт нам Result

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



Если на каком-то участке цепи возникает ошибка — мы её проталкиваем далее по пайпу R.O.P. благодаря Two Track Type Result<Data, Error>.

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

Бизнес-логика находится там, где ей и место, а сам код, описывая её, превращается в документацию.

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

Пример простого ValueObject DataUpdateId:



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

Пример элегантного барона теста ниже:



Иммутабильность и валидность доменной модели


Чем плохи невалидные мутабельные классы в сердце домена? Они с ноги порождают проблемы проверки на null и проверки валидности: больше кода нужно писать.



Уф! Самое важное обсудили.

R.O.P. преисполнились — нам осталось запустить наше доменное ядро по сервису с интеграциями без исключений и всё это протестировать.

Цель — получить код, описывающий бизнес-логику сервиса.



На каждом шаге пайпа транспортного уровня сервиса функция получает:

На входе Result<Data, Error> отрабатывает логику, опрашивая доменную модель, а если на вход пришла ошибка — возвращает сразу эту ошибку.

Например, если на этапе поиска текущего статуса абонента findSubscriberForUpdate у нас ошибка уровня интеграции — это Result, на выходе функции findSubscriberByRest.



Любая функция, что может зафэйлиться, возвращает Result

Таким образом, R.O.P. как идея пронизывает весь дизайн приложения. REST Gateway возвращает также Result или Mono<Result> whatever.

Паттерн CanExecute/Execute в данном случае в функции findSubscriberForUpdate отрабатывает на входе как функция fold (fold семейство функций высшего порядка).

Суть паттерна CanExecute/Execute в названии — спроси доменный класс, в нашем случае Result — можно ли исполнить дальнейший шаг бизнес-логики и найти актуальные данные по абоненту вызовом findSubscriberForUpdate). Если нет — тогда вернуть ошибку, что пришла на вход.



Получаем часть рецепта кукбука.

Запусти иммутабельную всегда валидную доменную модель по транспортному тоннелю «бизнес-процесс» без исключений, на шлюзах при сбоях вам помогут two track type Result<Data, Error> и canExecute/execute.

Фух! Погнали дальше, кратко обсудим тесты, и по коду сразу можно просмотреть основные паттерны юнит-тестов.

Юнит-тестирование


Я обожаю тесты! Мне нравятся классический TDD и Red Green Refactoring, но в нашем стремительном темпе я пришёл к практике: сначала часть кода — потом тесты на эту часть.

Не всё сразу на этапе прототипирования и моделирования по кукбуку удобно начинать с тестов. Суть простая: описал часть бизнес-логики — написал тесты. Коммит содержит в себе в идеале и классы, и тесты. Хочешь по классике — давай. На мой взгляд, классический TDD — самый весёлый и захватывающий процесс и игра.

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

Выше я приводил пример злых кусающихся интеграционных тестов. Что мы делаем в своём строгом кунг-фу с тестами — будет дальше. Мне поможет мысль: чего мы точно не хотим делать — это писать больше кода.

Значит, мы отказываемся от mock’ов в тестах ну или сводим их к минимуму. Интеграции (рест-шлюзы, рест-клиенты) проверяем одним интеграционным тестом, тут mock используем. Чем плохи mock’и:

  • Они концентрируют разработчика на деталях имплементации, а не на выходных параметрах.
  • Их нужно поддерживать и при рефакторинге — это дополнительная работа.

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

Максимум тестов сконцентрирован на бизнес-логике в простых юнит-тестах, а тривиальный код сервиса мы тестируем одним интеграционным тестом.



Если рука бойца колоть устала — можно покрыть интеграционным тестом rest-сервис. Он обходится нам дорого: нужна mock-конфигурация или надо поднимать контекст спринга. При этом он за один тест покроет максимальное количество кода: rest-контроллер, сервис приложения и доменных классов, конфигурации.

Пример достойного интеграционного теста:



Самый кайф — все крайние точки и всю бизнес-логику мы тестируем юнит-тестами от ValueObject до агрегатов.

Когда у меня всё это получилось, я помню этот момент светлой радости внутри и ликования: так можно, и это работает! )

Простой тест ValueObject мы разобрали выше. Разберём кратко тест агрегата по паттерну AAA:



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

Погнали по деталям:

  1. Подготавливаем валидные DTOʼшки (строки 13–23).
  2. Воссоздаём состояние на пайпе сервиса SubscriberDataUpdateService (строка 26 скрина ниже).
  3. Успешного вызова функции findSubscriberByRest (строка 42 скрина ниже).


В тесте это выглядит так (строка 28 скрина ниже):



Следовательно, System Under Test -> sut:



В точности моделирует конкретное состояние системы:

На вход приходят валидные доменные классы DataUpdate и Subscriber, и у них отличается поле mobileRegionId (строки 17, 22 ниже на скрине).



И мы тестируем в SUT SubscriberDataUpdate суть его бизнес-логики.

Подготовка запроса на обновление абонента:



Такой тест соответствует прекрасным оценкам по четырём аспектам хороших юнит-тестов:

  1. Защита от багов.
  2. Устойчивость к рефакторингу.
  3. Быстрая обратная связь.
  4. Простота поддержки.

Получаем финальный ингредиент кукбука:

  • Классическая школа тестирования.
  • Прагматичный набор тестов, сфокусированный на бизнес-логике в валидных иммутабильных классах.
  • Возводим в абсолют тестирование чёрной коробки — тестирование выходных данных функции. Интеграционных тестов минимум — хватает одного теста на Happy Path на REST in: out.

При таком подходе к дизайну кода мы получаем на выходе качественное покрытие тестами алгебраических типов, которые описывают бизнес-логику. Мы спокойны.

А ещё мы ровно, как по циркулю, отстраиваем пирамиду тестирования. Только представьте, как это всё красиво может считаться с E2E автотестами!

Прежде чем записать рецепт, обратите внимание: кукбук очень красиво вплетается в функциональную парадигму благодаря стремлению получить максимум юнит-тестов на выходные параметры функций. Скотт, спасибо за R.O.P.

Мы получаем понятный строгий дизайн и паттерн в переходах на монадах, честные и чистые функции и, что суперважно, — реактивный понятный код.

Я видел последствия функциональных ужасов очередной кибер-бойни загончика для миньонов № 5 — уверен, вы видели тоже, и ПТСР реактивного кода на 30–50 непонятных строк пережить очень трудно.

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

Рецепт:

  1. Станьте экспертом предметной области — разберитесь, что и как должно работать на всех уровнях. Ваш код — ваша ответственность. И помните: качественная разработка — это результат качественной коммуникации.
  2. Опишите в функциональном стиле бизнес-процесс с доменными классами.
  3. Реализуйте в функциональном стиле всегда валидную богатую доменную модель без примитивов.
  4. Покройте юнит-тестами бизнес-логику, которая содержится в доменной модели.
  5. Запустите доменную модель по тоннелю «бизнес-процесс» без исключений, на шлюзах помогут two track type Result<Data, Error> и canExecute/execute.

Если готовите по рецепту — можно получить в качестве результата:

  • Простую строгую структуру приложения — хороший дизайн кода в функциональным стиле.
  • Код будет оснащён эффективным набором простых юнит-тестов, которые сфокусированы на изолированной от интеграций бизнес-логике.
  • Количество интеграционных тестов сведено к достаточному минимуму.
  • Интеграционные тесты более дорогие в сопровождении и поддержке.
  • Mock не используются вообще или в крайне исключительных ситуациях.


Собираюсь со всем этим на конфе выступить — не раз и не два получал позитивный фидбэк, и хочется нарытым поделиться. Апрель Jpoint 2023.

This is The Way.

Что дальше, Максим? Добавлю BDD в эту историю.

P. S. Это один из возможных работающих путей, не пуля и не кол.

Но если быть откровенным, в моих задачах этот кукбук помог загнать серебряную пулю в сердце хаоса в качестве кода.

Footnotes:

There is no I in Software Craftsmanship
Книга: Domain Modeling Made Functional
Книга: Принципы юнит-тестирования
Книга: Domain-Driven DesignThe First 15 Years
Видео: Scott Wlaschin — Railway Oriented Programming — error handling in functional languages
Видео: Владимир Хориков — Domain-driven design: Cамое важное
Видео: Ахтям Сакаев — DDDamn good!
Vladimir Khorikov, Refactoring from Anemic Domain Model Towards a Rich One

Комментарии (0)