Information
- Rating
- 520-th
- Location
- Кольцово, Новосибирская обл., Россия
- Date of birth
- Registered
- Activity
Specialization
Chief Technology Officer (CTO), Software Architect
Lead
From 500,000 ₽
Functional programming
Object-oriented design
Design information systems
TDD/BDD
Kotlin
PostgreSQL
Java Spring Framework
Linux
Git
Docker
Спасибо:)
List<Any?>
- является подтипомAny
."оператор" + - это на самом деле просто синтаксический сахар для создания копии мапы и добавления в неё элемента (копирование вместо добавления в имеющуюся - из-за ФП головного мозга)
Соответственно:
Метод
groupBy
возвращаетTable
akaList<Map<String, Any>>
Лямбда, которая передаётся в
rowGroups.map
возвращаетMap<String, List<Any?>>
А сам метод map возвращает
List<Map<String, List<Any?>>>
И при возврате из метода
groupBy
выполняется upcast (приведение к супертипу)List<Map<String, List<Any?>>>
->List<Map<String, Any>>
@razon
И как вы находите границы микросервисов и ограниченных контекстов?
Условно ко мне пришёл продакт, говорит хочу фичу логистики, она должна делать то-то и то-то - в результате какого процесса у меня появятся артефакты/термины "ПВЗ Customer", "ПВЗ Маппер" и т.д. и "Ограниченный контекст ПВЗ", "Ограниченный контекст Геоданные" и т.д.
@razon
Поясните, пожалуйста - куда и на каком уровне абстракции фронт встраивается в вашей модели?
В моей практике фронтовые приложения/компоненты зачастую работают со множеством (всеми) слабосвязанных между собой компонентов бэка и, кажется, сломают всю стройную картину - если фронт засунуть в один из ограниченных контекстов - он его coupling убьёт, а если в отдельный - у него самого ещё хуже coupling будет.
Так ни я в примечании к этому посте, ни Хориков в посте, на который я ссылаюсь, не пишем, что от моков надо полностью отказаться.
Я пишу, что отношение тестов без моков к тестам с моками может быть 100 к 1. То есть без моков никак - бывает в одном случае из ста. В моей практике последних 4 лет - 9 коммерческих проектов суммарно на ~100К строк Котлин-кода.
При этом среднее время теста по моим проектам - порядка 50мс:
Тут ещё, пожалуй, стоит пояснить, что под "моками" я понимаю именно мокирование собственных типов с помощью условного Mockito. А против условных WireMock, GreenMail и т.п. я ничего не имею, включая верификацию вызовов - это часть наблюдаемого (снаружи) повдениия системы, которая должна быть специфицирована в тестах.
Добавлю ещё, что эти тезисы подтверждаются фактами из моей практики
В моём предыдущем проекте с тестами без моков за 500 человеко-часов, которые я рассматривал в ретроспективе было 0.5 бага на задачу или 0.05 бага/человеко-час (подробности).
В текущем проекте за примерно человеко/год разработки нашли 2 бага (не соответствие поведения софта требованиям) и 0 регрессий. Проект ещё не завершён, поэтому ретру не проводил и точных цифр пока нет.
В этом же проекте с 93% покрытием кода тестами без моков я недавно сделал пару серьёзных изменений в модели и рефакторингов: сделал один из агрегатов частью другого, ввёл разделение доменной модели и модели персистанса, добавил версионирование частей агрегата (условно таблички и её строк) - в ходе этой работы не было внесено ни одной регрессии.
А рефакторинг (20 затронутых файлов файлов, 482 добавленных строки, 172 удалённых строки) не содержал ни одной строки изменений в тестах.
Это красноречивая иллюстрация того, что хорошие тесты проверяют поведение системы, а не её реализацию и, как следствие, не меняются при рефакторинге - изменении структуры кода, без изменения его поведения.
Я удивлён, что коммент выше заминусили в свете того, что все классики TDD предостерегают от повсеместного использования моков.
Оставлю здесь мудрость древних для будущих поколений:
Я пожалуй даже код левого блока оркистрации скину:)
- кажется, он очень похож на ваш пример кода и, похож на ваш пример с созданием версии - тут как раз создаётся новая версия некой (хитрой по сути и динамически настраиваемой по структуре) таблицы.
Ещё так попробую проиллюстрировать свою точку зрения.
Разделением ИО и логики я борюсь с этим:
Легенда:
Синие блоки - оркестрация, блоки которые взывают более одного типа других блоков и имеют когнитивную сложность <= 4
Красные блоки - ввод, блоки которые делают ввод и имеют когнитивную сложность <= 4
Зелёные блоки - бизнес-логика, блоки, которые не вызывают ввод или вывод и имеют когнитивную сложность <= 15
Жёлтые блоки - вывод, блоки, которые делают вывод и имеют когнитивную сложность <= 4
Оранжевые блоки - месиво, блоки которые и делают ио (их сложно тестить и сложно понимать из-за необходимости учитывать перформанс, обработку ошибок и порядок выполнения) и имеют когнитивную сложность > 4 (их сложно читать и хочется тестить)
Или вот ещё не раскрашенный пример:
И в идеале я стремлюсь (и очень часто у меня получается) к такому:
Но вы правы, что не всегда всё так просто и красиво. Однако достичь вот такого - вполне реально и в "волосатых" случаях:
Эта картинка как раз ближе к IOSP/IODA, чем к ФА. Ну и да, оранжевый блок я подчищу в понедельник - этот кусок только-только дописали просрав дедлайн из-за недооценки "волосатости" задачи:)
Разница в сложности задачи.
В вашем примере я не вижу сложной бизнес-логики, которую стоит выделять.
А если вы приведёте пример с действительно сложной в моём пониманиии бизнес-логикой - мне скорее всего будет а) лень б) не хватать вводных, чтобы отрефакторить его и разделить логику и ио:)
ПФА - это частный случай того, что вы называете многослойной архитектуры (как и чистая, и просто функциональная), который накладывает доп. ограничение в виде неизменямых данных.
Но! В моём окружающем мире люди называют многослойной архитектуру, в которой domain logic зависит от persistence layer (который может выглядеть интерфейсами Spring Data репозиториев). И от такой вариации многослойной архитектуры, ПФА отличается ещё и запретом на обращение к persistence layer из domain logic.
На самом деле, видимо, тут же кроется и ответ на ваш первый комментарий: если делать многослойную архитектуру так, как её делают вокруг меня - она ни от чего не спасает и проекты по ней быстро превращается в большой ком грязи. Тот же Project Daniel - там были даже отдельные Maven-модули application-services и domain-services. Но это не спасло проект от превращения в большой ком грязи.
А ваша многослойная архитектура с правилом "доменная логика, которая не связана с вводом-выводом" как таки и позволяет "уйти от жёсткости и хрупкости".
Добавьте туда модель данных в виде неизменямых классов и это ровно то, что я называю "Промышленной функциональной архитектурой".
Почему я считаю, что незименямость важна я писал тут. А реальный кейс (но без кода, к сожалению) как "конфетка превращается в шпинат" - здесь
Ещё, кстати, прикольная идея в этом ключе и без фокуса на разделении ио и логики - Integration Operation Separation Principle и Integration Operation Data API Architecture
Да ну как сказать. React (да и Compose на андроиде) именно так и делает, насколько я знаю, и не чё "пипл юзает".
Да, полагаю, под капотом они хачат реальную модель "на месте", но сами UI компоненты и их состояние (с Redux-ом по крайней мере) с точки зраения разработчика - чистые функции и неизменяемые данные. Опять же насколько знаю, сам ни на React, ни на Compose не писал ничего.
Но вообще я возьму самоотвод - UI в целом и его рендеринг в частности - не моя специализация.
Как я писал - в вашем конкретном примере я бы ничего не стал глобально менять. Там нет логики, на которую хотелось бы писать десятки тестов - ваш конкретный случай я бы вообще покрыл 4 интеграционными тестами - хеппи пас, и по тесту на каждую из ошибок.
Ещё, пожалуй, стоит проговорить, что цепочка - ввод -> бизнес-логика -> вывод - это идеал, которого часто, но не всегда можно достичь.
А вот штука, которой можно достичь практически всегда - это сендвич - io -> logic -> io -> logic -> io.
Но большой вопрос стоит ли оно того.
Плюс по моим канонам, сам io на внутри себя и на своём уровне абстракции может содержать логику. Вот тут у меня описан пример такого случая.
Вот ещё пара ссылок по теме, как упоровшись по ФП выжимать возможный максимум разделения ио и логики:
https://blog.ploeh.dk/2017/02/02/dependency-rejection/
https://blog.ploeh.dk/2019/12/02/refactoring-registration-flow-to-functional-architecture/
Не правильно.
Идеи тащтельного проектирования АПИ и разделения ио и логики - универсальные и годятся веде.
Код серверного рендеринга - естественно придётся выкинуть в трубу при разработке десктопного клиента.
Код бизнес-логики от вида клиента никак не зависит и будет работать и дальше.
Если вы захотите запустить весь сервис на дескотопе в одном процессе - выкинуть придётся код контроллеров (связывающий HTTP и бизнес-логику).
Если вы при этом захотите отказаться и от реляционки (хотя бы в виде SQLlite) в пользу файликов - так же придётся написать занового код репозиториев (связывающий бизнес-логику с постоянным хранилищем)
:)
Судя по этому сообщению, вы, путаете клиентский и серверный рендеринг:) Это такая технология древних, когда браузер отправляет HTTP-запросы, получает HTML и, действительно, ререндерит всю страницу. Вообще без JS-а.
Советую ознакомится с HTMX и идеями за этой либой - позволяет делать "good enough" UI за очень дёшево.
Технически (в нормальных языках :troll:) это можно сделать так:
Но стоит ли оно того в данном конкретном случае - для меня большой вопрос.
PS>
Ну а если у вас Прям Нормальный Язык для ФА (Haskell) - оно всё автоматом будет лениво тянуться
На самом деле это довольно сложно в имеющихся условиях.
Во-первых, структура графа вызовов - это только один из трёх аспектов архитектуры, по которой я сейчас работаю.
И, возможно, если бы у меня был бы доступ до всей вашей кодовой базы и цель перепроектировать - получилось бы что-то совсем другое. А может и нет.
Во-вторых, вы меня даже вменяемого нейминга лишили и мне приходится работать только с синтаксисом. А дизайн - про семантику, а не синтаксис.
В-третьих, в вашем примере практически нет бизнес-логики.
Но тем не менее, давайте я попробую.
Что бы я точно сделал:
Собрал вычисление h в отдельный метод
Сделал бы данные неизменяемыми, а сохранение обновлений явным
Возможно ещё обернул бы логические выражения проверок в методы с вменяемыми именами, особенно для b и c:
Но это всё косметика. Тут надо либо сильно глубже смотреть, либо в этом примере и правда бизнес-логика предельно простая и самоочевидная.
Публичных примеров прям кода прям с бизнес-логикой у меня ещё нет. Но есть:
Пример кода оркестрации и графа вызовов операции для операции со сложной бизнес-логикой
Пример построения сложной модели представления
Тривиальная оркестрация
Развесистая логика на чистых функциях
Пример построения docx-дока
Тривиальная оркестрация
Относительно развесистая логика построения в виде чистой функции
Тут код - прям наскоряк написанный говнокод - это был прототип фичи, которая оказалось не востребованной, поэтому рефакторить я его не стал. Но это чистый говнокод - если ему в fetchImage подсунуть Map::get - он для одних и тех же аргументов всегда будет возвращать один и тот же результат.
Здесь, здесь, здесь и здесь
Вот он
Чуть позже
Ответ на ваши опасения относительно Функциональной (а Симан в дополнение к Фрактальной топит и за Функциональную) архитектуру так же есть в посте: