Ещё пункт забыл
Неожиданная встреча
Случайно встретил бывшего студента.
Он рассказал, что следующему поколению советовал идти ко мне на курс по БД. А я не пришел - взял "декрет".
Но все равно приятно - снова задумался вернутся в следующем году.
А ещё (к вопросу про JPA в комментах) рассказал, что у них в большой корпорации пришел СТО, сказал что они не умеют в JPA и теперь они то ли все новое пишут, то ли старое переписывают на SDJ.
#spring_data_jdbc@ergonomic_code
Неожиданная встреча
Случайно встретил бывшего студента.
Он рассказал, что следующему поколению советовал идти ко мне на курс по БД. А я не пришел - взял "декрет".
Но все равно приятно - снова задумался вернутся в следующем году.
А ещё (к вопросу про JPA в комментах) рассказал, что у них в большой корпорации пришел СТО, сказал что они не умеют в JPA и теперь они то ли все новое пишут, то ли старое переписывают на SDJ.
#spring_data_jdbc@ergonomic_code
👍2
Привет!
Фух, "папа вырвался на свободу" наконец-то закончилось. С понедельника спал часов 15 максимум, а не пью первый день.
Но было круто - конфа глазами спикера выглядит намного интереснее, чем участника - советую попробовать.
Я весной попробую податься на snowone или codefest с вариацией на тему одного из докладов Джокера, а осенью попробую опять на Джокере ченить про тестирование рассказать.
Фух, "папа вырвался на свободу" наконец-то закончилось. С понедельника спал часов 15 максимум, а не пью первый день.
Но было круто - конфа глазами спикера выглядит намного интереснее, чем участника - советую попробовать.
Я весной попробую податься на snowone или codefest с вариацией на тему одного из докладов Джокера, а осенью попробую опять на Джокере ченить про тестирование рассказать.
👍17
Привет!
Ну чтош, ютуб решил всё за нас - без подтверждения загрузить видео более чем на 15 минут не даёт и подтвердить акк на мой номер телефона тоже не даёт.
Так что представляю вам свой канал на Рутубе и первый видос на нём - РЕПЕТИЦИЯ доклада "Функциональная архитектура и Spring Data JDBC. 4 года в проде, полёт отличный"
#talks@ergonomic_code #ergo_approach@ergonomic_code #functional_architecture@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
Ну чтош, ютуб решил всё за нас - без подтверждения загрузить видео более чем на 15 минут не даёт и подтвердить акк на мой номер телефона тоже не даёт.
Так что представляю вам свой канал на Рутубе и первый видос на нём - РЕПЕТИЦИЯ доклада "Функциональная архитектура и Spring Data JDBC. 4 года в проде, полёт отличный"
#talks@ergonomic_code #ergo_approach@ergonomic_code #functional_architecture@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
RUTUBE
Эргономичный код — полная коллекция видео на RUTUBE
Канал о разработе поддерживаемых бакэндов - модульных монолитов с неизменяемой моделью данных и функциональной архитектурой, написанных в стиле data-oriented programming и покрытых тестами без моков.
https://t.me/ergonomic_code
https://azhidkov.pro
https://t.me/ergonomic_code
https://azhidkov.pro
❤16👍6🤡4
Привет!
Залил на рутуб тестовый прогон своего выступления на конференции Joker'24 от JUG Ru Group.
Официальная запись самого выступления с нормальным звуком будет доступна бесплатно до следующей весны - напишу в канале, как её опубликуют.
#talks@ergonomic_code #ergo_approach@ergonomic_code #structured_design@ergonomic_code
Залил на рутуб тестовый прогон своего выступления на конференции Joker'24 от JUG Ru Group.
Официальная запись самого выступления с нормальным звуком будет доступна бесплатно до следующей весны - напишу в канале, как её опубликуют.
#talks@ergonomic_code #ergo_approach@ergonomic_code #structured_design@ergonomic_code
RUTUBE
РЕПЕТИЦИЯ 241007 - Структурный дизайн. Древний секрет простого и быстрого кода.
Запись последней репетиции моего доклада на Joker 24 от JUG Ru Group (https://jugru.org/).
Страница доклада на сайте конференции: https://jokerconf.com/talks/f9b11eab0fc14bc2b2dc7c3ad62834d0/?referer=%2Fschedule%2Fdays%2F
Страница доклада на сайте конференции: https://jokerconf.com/talks/f9b11eab0fc14bc2b2dc7c3ad62834d0/?referer=%2Fschedule%2Fdays%2F
👍7🤡3
Привет!
Вы когда-нибудь задавались вопросом, почему некоторые разработчики (например анкл Боб в чистой архитектуре) методы или классы, реализующие один эндпонит, называют юзкейсами? Ведь юзкейс - это многошаговый сценарий, в котором каждый шаг - вызов отдельного эндпоинта.
Я - задавался.
А сейчас начал читать матчасть по юзкейсам и узнал почему.
Оказывается, юзкейсы рекурсивны.
На самом верхнем уровне - бизнес юзкейсы (Кокбёрн называет их ещё белыми), они состоят из пользовательских юзкейсов (синие) - это то, что я понимал под юзкейсами -, которые, в свою очередь, состоят из юзкейсов-подфункций (индиго/чёрные) - вот это и есть те самые юзкейсы из чистой архитектуры.
А ещё, кажется, я тут нашёл стратегию декомпозиции слоя приложения - собственно по юзкейсам. Правда она предполагает, что эти самые юз кейсы есть и структурированы, но сейчас у меня такое ощущение, что их наличие - действительно важно для успеха проекта и если это не делает аналитик/бизнес - это должен делать техлид в рамках проектирования.
Ну и книгу я ещё прочитал только процентов на 50, но уже готов её порекомендовать.
#books@ergonomic_code #ergo_approach@ergonomic_code #ergo_arch@ergonomic_code
Вы когда-нибудь задавались вопросом, почему некоторые разработчики (например анкл Боб в чистой архитектуре) методы или классы, реализующие один эндпонит, называют юзкейсами? Ведь юзкейс - это многошаговый сценарий, в котором каждый шаг - вызов отдельного эндпоинта.
Я - задавался.
А сейчас начал читать матчасть по юзкейсам и узнал почему.
Оказывается, юзкейсы рекурсивны.
На самом верхнем уровне - бизнес юзкейсы (Кокбёрн называет их ещё белыми), они состоят из пользовательских юзкейсов (синие) - это то, что я понимал под юзкейсами -, которые, в свою очередь, состоят из юзкейсов-подфункций (индиго/чёрные) - вот это и есть те самые юзкейсы из чистой архитектуры.
А ещё, кажется, я тут нашёл стратегию декомпозиции слоя приложения - собственно по юзкейсам. Правда она предполагает, что эти самые юз кейсы есть и структурированы, но сейчас у меня такое ощущение, что их наличие - действительно важно для успеха проекта и если это не делает аналитик/бизнес - это должен делать техлид в рамках проектирования.
Ну и книгу я ещё прочитал только процентов на 50, но уже готов её порекомендовать.
#books@ergonomic_code #ergo_approach@ergonomic_code #ergo_arch@ergonomic_code
👍6🔥1
Forwarded from Spring АйО
При юнит-тестировании часто требуется проверить методы, которые используют цепочки вызовов, например, в WebClient из Spring WebFlux.
⚠️ Если задача позволяет, лучше воспользоваться MockWebServer или WireMock для гибкого тестирования HTTP-клиентов. Здесь WebClient выбран лишь из-за своей наглядности и популярности.
Представим клиент, запрашивающий случайную цитату у удалённого сервиса:
public class InspirationalQuotesClient {
private final WebClient webClient;
public InspirationalQuotesClient(WebClient webClient) {
this.webClient = webClient;
}
public String fetchRandomQuote() {
try {
return this.webClient
.get()
.uri("/api/quotes")
.retrieve()
.bodyToMono(String.class)
.block();
} catch (WebClientException e) {
return "Every time a mock returns, a mock a fairy dies.";
}
}
}
Для тестирования такого клиента стандартным способом нужно замокировать каждый вызов в цепочке, чтобы избежать
NullPointerException. Это может сделать тест слишком громоздким:
@ExtendWith(MockitoExtension.class)
class InspirationalQuotesClientTest {
@Mock private WebClient webClient;
@InjectMocks private InspirationalQuotesClient cut;
@Test
void shouldReturnInMockingHell() {
WebClient.RequestHeadersUriSpec requestHeadersUriSpec = Mockito.mock(WebClient.RequestHeadersUriSpec.class);
WebClient.ResponseSpec responseSpec = Mockito.mock(WebClient.ResponseSpec.class);
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri("/api/quotes")).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just("We've escaped hell"));
String result = cut.fetchRandomQuote();
assertEquals("We've escaped hell", result);
}
}
Решение? Deep Stubs в Mockito. С ними можно упростить тест, описав всю цепочку вызовов сразу в конструкции
when. Это значительно уменьшит количество кода:
@ExtendWith(MockitoExtension.class)
class DeepStubClientTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private WebClient webClient;
@InjectMocks private InspirationalQuotesClient cut;
@Test
void shouldReturnQuoteFromRemoteSystem() {
Mockito.when(webClient.get().uri("/api/quotes").retrieve().bodyToMono(String.class))
.thenReturn(Mono.just("Less setup hell - but not better"));
String result = cut.fetchRandomQuote();
assertEquals("Less setup hell - but not better", result);
}
}
Используя параметр
answer = Answers.RETURNS_DEEP_STUBS, мы возвращаем заглушку для каждого метода в цепочке. Но помните, что этот подход увеличивает зависимость теста от конкретной структуры вызовов, и любые изменения в цепочке потребуют изменений в тесте.P.S. А как вы мокируете вызовы внешних API?
Ставьте 🔥 если считаете функциональность полезной.
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет!
Говорят, чёрный пиар - это тоже пиар, поэтому надеюсь пацаны из Spring АйО (Саша, привет! 👋 ) не сильно обидятся, что я их пост разнесу чутка:)
Пример теста в посте имеет две проблемы
Проблема первая - непонятно, что тестирует этот тест
Вообще, вроде как, он должен тестировать клиента внешней системы. Но фактически, он тестирует не клиента внешней системы, а просто фиксирует вызовы Spring-овго АПИ (читай: детали реализации).
И если внешняя система поменяет формат ответа на json - код сломается, а тест продолжит проходить.
А если я перепишу клиента на restTemplate (и проверю руками) - код будет работать, а тест сломается.
Проблема вторая - он нарушает хрестоматийное правило тестов с моками
Мокайте только собственные типы (см. Growing Object-Oriented Software, Guided by Tests, Chapter 8)
—
Как это надо было делать
Вариант №1: идеальный
Поднять в тестконтейнере latest-версию внешней системы и написать тест, который бы работал с ней.
Такой тест:
1. ни как бы не был завязан на реализацию - вы с внешней системой могли бы хоть на protobuf перейти и в самом тесте вообще ничего менять бы не пришлось
1.1. Как следствие он бы подстраховал вас и если бы вы решили поменять либу для работы с хттп (на тот же RestTemplate, например)
2. поймает ошибки обратной совместимости внешнего АПИ, особенно если его регулярно на CI запускать
3. поймает регрессии в Spring
4. Ну и, очевидно, поймает если нарушите контракт (например, при рефакторинге идея затупит и поменяет слово в пути)
5. а ещё вы сможете его использовать в процессе разработки и соответственно быстро итерироваться с кодом реализации клиента, не рестаруя всё приложение целиком или не костыляя какой-то отдельный main-метод для этих целей
В общем он будет вашим другом - когда и если вы захотите поменять код клиента, он не будет вам мешать просто так и подскажет, если вы что-то сломаете.
Но это (внешняя система в докере) - практически нереальный вариант.
Вариант №2: реальный
Соответственно следующий вариант - написать тест, который работает с тестовым контуром внешней системы. Скорее всего, его придётся засунуть под флаг и запускать только на CI.
Он продолжит быть вашим другом, но и принесёт ряд проблем:
1. скорее всего он будет медленный
2. возможно он будет мигать
3. сообщать о проблемах он будет позже, чем хотелось бы
Вариант №3: cомнительно, но окей
Если варианты выше не возможны, либо не приемлемы (слишком медленно, слишком часто мигает) - можно взять WireMock.
В этом варианте вы потеряете контроль соблюдения контракта вами и внешней системой, но хотя бы останется контроль, соблюдения контракта вами и Spring-ом. Соответственно такой тест подстрахует вас, если вы решите поменять либу работы с ХТТП, либо обновите версию Spring.
Но я бы на такие тесты вообще не заморачивался - это всё будет покрыто тестами, клиентов нашего клиента, которым для работы надо будет, чтобы InspirationalQuotesClient смог взять данные.
Вариант №4: кто-то в интернете считает, что так правильно, но он не прав
Собственно мокисты считают, что правильно:
1. InspirationalQuotesClient покрыть интеграционным тестом (варианты 1-3 выше), либо не покрывать тестами вообще
2. А в тесте клиента InspirationalQuotesClient замокать уже сам InspirationalQuotesClient, а не WebClient
Но на самом деле правильно:
1. операцию, в которой задействован InspirationalQuotesClient покрыть одним интеграционным тестом, который:
1.1. всю управляемую инфраструктуру (БД, очередь сообщений, внутренние сервисы) поднимает в тест контейнерах и работает с ней
1.2. везде где возможно для неуправляемой инфраструктуры поднимает фейки на транспортном уровне (WireMock)
1.3. В самом крайнем случае мокает свои типы, которые абстрагируют внешнюю инфру, которую по другому не симулировать никак
2. Эту операцию сделать сбалансированной
3. И получить возможность всю бизнес-логику (которая даже косвенно через интерфейсы не зависит от внешних систем) покрыть чистыми юнит тестами, без спринга, моков, тестконтейнеров и т.д.
#ergo_testing@ergonomic_code
Говорят, чёрный пиар - это тоже пиар, поэтому надеюсь пацаны из Spring АйО (Саша, привет! 👋 ) не сильно обидятся, что я их пост разнесу чутка:)
Пример теста в посте имеет две проблемы
Проблема первая - непонятно, что тестирует этот тест
Вообще, вроде как, он должен тестировать клиента внешней системы. Но фактически, он тестирует не клиента внешней системы, а просто фиксирует вызовы Spring-овго АПИ (читай: детали реализации).
И если внешняя система поменяет формат ответа на json - код сломается, а тест продолжит проходить.
А если я перепишу клиента на restTemplate (и проверю руками) - код будет работать, а тест сломается.
Проблема вторая - он нарушает хрестоматийное правило тестов с моками
Мокайте только собственные типы (см. Growing Object-Oriented Software, Guided by Tests, Chapter 8)
—
Как это надо было делать
Вариант №1: идеальный
Поднять в тестконтейнере latest-версию внешней системы и написать тест, который бы работал с ней.
Такой тест:
1. ни как бы не был завязан на реализацию - вы с внешней системой могли бы хоть на protobuf перейти и в самом тесте вообще ничего менять бы не пришлось
1.1. Как следствие он бы подстраховал вас и если бы вы решили поменять либу для работы с хттп (на тот же RestTemplate, например)
2. поймает ошибки обратной совместимости внешнего АПИ, особенно если его регулярно на CI запускать
3. поймает регрессии в Spring
4. Ну и, очевидно, поймает если нарушите контракт (например, при рефакторинге идея затупит и поменяет слово в пути)
5. а ещё вы сможете его использовать в процессе разработки и соответственно быстро итерироваться с кодом реализации клиента, не рестаруя всё приложение целиком или не костыляя какой-то отдельный main-метод для этих целей
В общем он будет вашим другом - когда и если вы захотите поменять код клиента, он не будет вам мешать просто так и подскажет, если вы что-то сломаете.
Но это (внешняя система в докере) - практически нереальный вариант.
Вариант №2: реальный
Соответственно следующий вариант - написать тест, который работает с тестовым контуром внешней системы. Скорее всего, его придётся засунуть под флаг и запускать только на CI.
Он продолжит быть вашим другом, но и принесёт ряд проблем:
1. скорее всего он будет медленный
2. возможно он будет мигать
3. сообщать о проблемах он будет позже, чем хотелось бы
Вариант №3: cомнительно, но окей
Если варианты выше не возможны, либо не приемлемы (слишком медленно, слишком часто мигает) - можно взять WireMock.
В этом варианте вы потеряете контроль соблюдения контракта вами и внешней системой, но хотя бы останется контроль, соблюдения контракта вами и Spring-ом. Соответственно такой тест подстрахует вас, если вы решите поменять либу работы с ХТТП, либо обновите версию Spring.
Но я бы на такие тесты вообще не заморачивался - это всё будет покрыто тестами, клиентов нашего клиента, которым для работы надо будет, чтобы InspirationalQuotesClient смог взять данные.
Вариант №4: кто-то в интернете считает, что так правильно, но он не прав
Собственно мокисты считают, что правильно:
1. InspirationalQuotesClient покрыть интеграционным тестом (варианты 1-3 выше), либо не покрывать тестами вообще
2. А в тесте клиента InspirationalQuotesClient замокать уже сам InspirationalQuotesClient, а не WebClient
Но на самом деле правильно:
1. операцию, в которой задействован InspirationalQuotesClient покрыть одним интеграционным тестом, который:
1.1. всю управляемую инфраструктуру (БД, очередь сообщений, внутренние сервисы) поднимает в тест контейнерах и работает с ней
1.2. везде где возможно для неуправляемой инфраструктуры поднимает фейки на транспортном уровне (WireMock)
1.3. В самом крайнем случае мокает свои типы, которые абстрагируют внешнюю инфру, которую по другому не симулировать никак
2. Эту операцию сделать сбалансированной
3. И получить возможность всю бизнес-логику (которая даже косвенно через интерфейсы не зависит от внешних систем) покрыть чистыми юнит тестами, без спринга, моков, тестконтейнеров и т.д.
#ergo_testing@ergonomic_code
Telegram
Эргономичный код
🔗 Создание Deep Stubs в Mockito
При юнит-тестировании часто требуется проверить методы, которые используют цепочки вызовов, например, в WebClient из Spring WebFlux.
⚠️ Если задача позволяет, лучше воспользоваться MockWebServer или WireMock для гибкого тестирования…
При юнит-тестировании часто требуется проверить методы, которые используют цепочки вызовов, например, в WebClient из Spring WebFlux.
⚠️ Если задача позволяет, лучше воспользоваться MockWebServer или WireMock для гибкого тестирования…
👍4❤3
Что ещё почитать на тему тестирования
Принципы юнит тестирования - самая крутая книга на эту тему
TDD Revisited - Ian Cooper - NDC Porto 2023 - суть здорового тестирования за час пятнадцать
Пачка постов у меня про тестирование Trainer Advisor
Подборки постов у меня в канале - #tdd #nomocks
#ergo_testing@ergonomic_code #books@ergonomic_code #talks@ergonomic_code #posts@ergonomic_code
Принципы юнит тестирования - самая крутая книга на эту тему
TDD Revisited - Ian Cooper - NDC Porto 2023 - суть здорового тестирования за час пятнадцать
Пачка постов у меня про тестирование Trainer Advisor
Подборки постов у меня в канале - #tdd #nomocks
#ergo_testing@ergonomic_code #books@ergonomic_code #talks@ergonomic_code #posts@ergonomic_code
www.piter.com
Принципы юнит-тестирования
Практика модульного тестирования - это практическое руководство по современным методам модульного тестирования.
👍9
Привет!
Ну всё, сходил к @javaswag - минут 5 возились с микрофоном, чтобы был анонсированный боХатый голос 🤣
Походу подкаста, кажись осознал свою фишку - у меня есть ссылка на все случае жизни - я, наверное, их штук 20 наобещать успел:)
PS>
Картинка в тему - из свежего рабочего чата
Ну всё, сходил к @javaswag - минут 5 возились с микрофоном, чтобы был анонсированный боХатый голос 🤣
Походу подкаста, кажись осознал свою фишку - у меня есть ссылка на все случае жизни - я, наверное, их штук 20 наобещать успел:)
PS>
Картинка в тему - из свежего рабочего чата
🔥6👍2
Продолжая топик ссылок на любой случай жизни, может кому-то пригодится как я читаю книги:
Юниор
Как-то техническую литературу еще плотно не приходилось читать.
Ты как ее читал? По выходным в охотку? Когда возникали конкретные запросы и нужно было найти что-то в книгах? Делал ли заметки и как долго переваривал прочитанное? Или это должно просто где-то отложиться и выстрелить в нужный момент?
Я
Ох. мне кажется это всё строго индивидуально. Конкретно я:
1. По выходным в охотку? - В разные периоды и разные книги по разному. В среднем можно считать, что ежедневно полчаса-час перед сном, либо с утра до работы
2. Когда возникали конкретные... - обычно, выбирая книгу, я беру то, что сейчас охота прочитать. Но обычно охота - то что сейчас актуально. Потом, когда возникает конкретный вопрос у меня сами вспоминаются книги, где был ответ и их иду перечитываю. Несколько раз ещё была отдельная штука тематических статей, типа агрегатов - вот тут уже читал конкретные главы конкретных книг в "рабочее на себя время"
3. Делал ли заметки - не считая тематических постов, я никогда не делаю заметки. Иногда делаю закладки (в электронной книге). Ещё реже пишу минипосты с рецензией по ним.
4. как долго переваривал прочитанное - смотря что ты имеешь ввиду. Систематических перерывов в чтении между книгами у меня не бывает - закончил одну, начал вторую. К прочитанным книгам периодически возвращаюсь, когда возникают вопросы.
5. Или это должно просто где-то отложиться и выстрелить в нужный момент? - ну с одной стороны, наверное да. Но конкретно у меня чтение подкрепляется перманентной практикой применения идей из книг в пет проектах
Алексей Жидков
Агрегаты - Алексей Жидков
В основе поддерживаемых информационных систем лежат агрегаты. В этом посте я расскажу что такое агрегат, как его проектировать и какие ошибки проектирования агрегатов встречаются чаще всего
👍2
Привет!
JB прислали напоминалку, что заканчивается лицензия, решил приобщиться к импортозамещению и попробовать посидеть на gigaide, gigacode и amplicode.
Пока выглядит так, что мне не хватает только HTTP Client-а.
Через недельку напишу как впечатления
#tools@ergonomic_code
JB прислали напоминалку, что заканчивается лицензия, решил приобщиться к импортозамещению и попробовать посидеть на gigaide, gigacode и amplicode.
Пока выглядит так, что мне не хватает только HTTP Client-а.
Через недельку напишу как впечатления
#tools@ergonomic_code
gitverse.ru
GitVerse — Платформа для работы с исходным кодом, Git-репозиторий
✅ GitVerse — платформа для разработчиков. ✅ Создавайте и развивайте проекты с открытым и закрытым исходным кодом. ✅ Загрузка репозиториев, импорт и зеркалирование из других git-сервисов.
👍8
GitHub
GitHub - ergonomic-code/Trainer-Advisor: Информационная система йогатерапевта и демонстрационный проект Эргономичного подхода
Информационная система йогатерапевта и демонстрационный проект Эргономичного подхода - ergonomic-code/Trainer-Advisor
Привет!
На выходных спинным мозгом чутка подвигал Trainer Advisor
Переезд на Kotlin 2.0.20
Начал с того, что с третьей попытки наконец осилил переехать с Kotlin 1.9.20 на 2.0.20.
Летом при переезде на 2.0.0 (и, возможно, 2.0.10 - не помню уже точно) было какое-то совершенно невнятное сообщение об ошибке компиляции, которое ставило меня в тупик.
В этот раз было даже не сообщение об ошибке компиляции, а вообще какая-то кровавая жесть из кишёк компилятора, но победить её удалось. Случайно.
Я начал пытаться локализовать строчку, на которой компилятор взрывается (он писал файл). И долокализовался до того, что файл вообще удалил, а компилятор продолжил взрываться 🤯
Потом каким-то чудом я обнаружил, что у меня есть одноимённый файл в Gradle Test Fixtures source set. И вот когда я туда залез - там у меня ужеIDEA GigaIDE начала взрываться, что не найдены Spring-овые классы. Пошёл гуглить WTF и по первой же ссылке выяснил, что во 2-ой версии, impl-зависимости продового кода не перекидываются в testFixture - прокинул, завелось.
Мораль - чёрт его знает. Проект на 13К нетривиального кода (люблю я знаете ли проверять компиляторы на прочность) перевести мне удалось. Но чудом. Если ждали знака, чтобы попробовать перевести свой проект - вот он. Но будьте готовы к приключениям.
Переезд на HTMX 2.0.3
Следующим шагом переехал на новую мажорную версию HTMX. Просто обновил и всё заработало. Вряд ли это кому-то ещё интересно, но если вдруг - на HTMX 2 можно достаточно смело ехать.
Greenmail для локальной работы в докере
Но кое-какой нюанс с переездом всё таки был - если покрытие серверного кода, включая рендеринг HTML у меня близок к 100%, то динамика UI (у меня там ещё и alpine.js немного есть) у меня очень слабо покрыта e2e тестами - есть только тесты на критичные сценарии - регистрация и логин и создание карточки клиента и приёма.
Поэтому я на всякий случай после переезда на HTMX 2 решил руками прогнать полный регресс.
И упёрся в небольшую шероховатость DevX-а проекта.
Наполовину по историческим причинам и наполовину во имя простоты бэка, сейчас при регистрации пароль отправляется письмом на почту юзера.
Примерно по тем же причинам, в проде для отправки почты у меня используется бесплатный ящик на Яндексе. И креды от него, по понятным причинам, куда-то заныканы.
Соответственно, при локальной разработке, после чекаута проекта зарегаться нельзя - надо сначала как-то настроить отправку писем.
Об это я и споткнулся, когда начал делать регресс в новом проекте - обычно я 99% работы делаю через тесты и ещё для 0.99% работы мне достаточно юзера, который добавляется в демо-данных при локальной разработке.
А вот регистрация из коробки не работала.
Вот это я и залечил, добавив Greenmail в проект компоуза локальной инфры.
И написав shell-однострочник для выковыривания пароля оттуда:
PS>
Напоминаю, что мне в ТА можно поконтрибьютить.
Если вы опытный разработчик и вам не комфортно работать в том стиле, в котором пишете сейчас - это хороший способ пощупать ЭП руками и понять нравится ли вам такой DevX.
Если вы молодой разработчик - это хороший способ получить мой менторинг на реальном проекте в замен за работу. Я очень тщательно провожу ревью и в целом стараюсь давать максимум полезной обратной связи.
Код двух подписчиков уже есть в TA - так что это вполне реально:)
Отзыв одного из них:
Если интересно - пишите в личку, договоримся о звонке для онбоардинга
#trainer_advisor@ergonomic_code #tools@ergonomic_code #kotlin@ergonomic_code #devx@ergonomic_code
На выходных спинным мозгом чутка подвигал Trainer Advisor
Переезд на Kotlin 2.0.20
Начал с того, что с третьей попытки наконец осилил переехать с Kotlin 1.9.20 на 2.0.20.
Летом при переезде на 2.0.0 (и, возможно, 2.0.10 - не помню уже точно) было какое-то совершенно невнятное сообщение об ошибке компиляции, которое ставило меня в тупик.
В этот раз было даже не сообщение об ошибке компиляции, а вообще какая-то кровавая жесть из кишёк компилятора, но победить её удалось. Случайно.
Я начал пытаться локализовать строчку, на которой компилятор взрывается (он писал файл). И долокализовался до того, что файл вообще удалил, а компилятор продолжил взрываться 🤯
Потом каким-то чудом я обнаружил, что у меня есть одноимённый файл в Gradle Test Fixtures source set. И вот когда я туда залез - там у меня уже
Мораль - чёрт его знает. Проект на 13К нетривиального кода (люблю я знаете ли проверять компиляторы на прочность) перевести мне удалось. Но чудом. Если ждали знака, чтобы попробовать перевести свой проект - вот он. Но будьте готовы к приключениям.
Переезд на HTMX 2.0.3
Следующим шагом переехал на новую мажорную версию HTMX. Просто обновил и всё заработало. Вряд ли это кому-то ещё интересно, но если вдруг - на HTMX 2 можно достаточно смело ехать.
Greenmail для локальной работы в докере
Но кое-какой нюанс с переездом всё таки был - если покрытие серверного кода, включая рендеринг HTML у меня близок к 100%, то динамика UI (у меня там ещё и alpine.js немного есть) у меня очень слабо покрыта e2e тестами - есть только тесты на критичные сценарии - регистрация и логин и создание карточки клиента и приёма.
Поэтому я на всякий случай после переезда на HTMX 2 решил руками прогнать полный регресс.
И упёрся в небольшую шероховатость DevX-а проекта.
Наполовину по историческим причинам и наполовину во имя простоты бэка, сейчас при регистрации пароль отправляется письмом на почту юзера.
Примерно по тем же причинам, в проде для отправки почты у меня используется бесплатный ящик на Яндексе. И креды от него, по понятным причинам, куда-то заныканы.
Соответственно, при локальной разработке, после чекаута проекта зарегаться нельзя - надо сначала как-то настроить отправку писем.
Об это я и споткнулся, когда начал делать регресс в новом проекте - обычно я 99% работы делаю через тесты и ещё для 0.99% работы мне достаточно юзера, который добавляется в демо-данных при локальной разработке.
А вот регистрация из коробки не работала.
Вот это я и залечил, добавив Greenmail в проект компоуза локальной инфры.
И написав shell-однострочник для выковыривания пароля оттуда:
curl -X GET "http://localhost:58080/api/user/test%40ya.ru/messages/" \
-H 'accept: application/json' | jq -r '.[] | .mimeMessage ' | grep -A7 0JfQ | python3 -m base64 -d | grep 'Пароль'
PS>
Напоминаю, что мне в ТА можно поконтрибьютить.
Если вы опытный разработчик и вам не комфортно работать в том стиле, в котором пишете сейчас - это хороший способ пощупать ЭП руками и понять нравится ли вам такой DevX.
Если вы молодой разработчик - это хороший способ получить мой менторинг на реальном проекте в замен за работу. Я очень тщательно провожу ревью и в целом стараюсь давать максимум полезной обратной связи.
Код двух подписчиков уже есть в TA - так что это вполне реально:)
Отзыв одного из них:
В целом, мне понравилось делать изменения у тебя. Не могу сказать, что согласен со всем. Мне пока кажется почти идеальным вот этот layout https://github.com/gushakov/cargo-clean
Если интересно - пишите в личку, договоримся о звонке для онбоардинга
#trainer_advisor@ergonomic_code #tools@ergonomic_code #kotlin@ergonomic_code #devx@ergonomic_code
🔥6👍2🥰2
Привет!
Ползеняшка
В Идее есть такая штука, как структурный поиск. С её помощью можно найти, например, все классы в которых есть поля id и name, в независимости от их типа и форматирования в коде (в каком порядке, с какими аннотациями, комментами и т.д.)
Ещё пример использования этой штуки - удаление модификтора suspend у всех методов всех классов репозиториев
Я вообще искал класс, в котором есть только эти поля, но сходу не осилил как это сделать - если кто-то знает как - научите в комментах, пожалуйста.
#tools@ergonomic_code
Ползеняшка
В Идее есть такая штука, как структурный поиск. С её помощью можно найти, например, все классы в которых есть поля id и name, в независимости от их типа и форматирования в коде (в каком порядке, с какими аннотациями, комментами и т.д.)
Ещё пример использования этой штуки - удаление модификтора suspend у всех методов всех классов репозиториев
Я вообще искал класс, в котором есть только эти поля, но сходу не осилил как это сделать - если кто-то знает как - научите в комментах, пожалуйста.
#tools@ergonomic_code
2❤5
Привет!
Немного размышлений о чистоте модели предметной области по мотивам рассылки On domain model purity от Хорикова.
Он там пишет:
То есть Хориков полагает, что наличие БД влияет на дизайн системы.
И когда-то я думал так же. В частности, qbit был попыткой сделать такую систему хранения, которая позволила бы писать программы так, как будто все данные живут в памяти.
А сейчас я думаю, что с точки зрения дизайна у меня ничего не поменяется.
Я всё равно буду моделировать состояние системы как набор изменяемых Map неизменяемых структур данных (ака Spring Data JDBC Repository).
Возможно, у меня будет меньше специализированных под юз кейсы ДТОшек.
Совершенно точно я начну спокойно ходить по ссылкам между сущностями внутри бизнес-логики (а-ля Datomic Pull API)..
Но совершенно точно не перейду на модель огромного графа изменяемых объектов (которую, судя по всему Хориков считает идеально чистой).
При том у Хорикова в самом примере есть противоречие:
С одной стороны, он пишет:
Но с другой стороны, в примере он использует репоз для того, чтобы обеспечить уникльность емейла студента. И вот куда он засунет поиск студента по емейлу? В
Кажется, единственное разумное место, куда можно эту проверку засунуть - StudentsRepository. Где бы он не хранил свои данные - в памяти или БД.
В общем, функциональная модель не только лучше отражает модель в голове конечного пользователя, но и лучше отражает природу информационных систем.
#functional_architecture@ergonomic_code #ddd@ergonomic_code
Немного размышлений о чистоте модели предметной области по мотивам рассылки On domain model purity от Хорикова.
Он там пишет:
To see how pure your domain model is, you need to imagine how you would design it if you didn’t have to persist any data to the database, and all you had to do is keep that data in the application memory. Then you need to compare this design to what you ended up with in your project. The difference would be the dent in domain model purity
Чтобы увидеть, насколько чиста ваша модель предметной области, вам нужно представить, как бы вы ее спроектировали, если бы вам не нужно было сохранять какие-либо данные в базе данных, а все, что вам нужно было сделать, это сохранить эти данные в памяти приложения. Затем вам нужно сравнить этот дизайн с тем, что в итоге получилось в вашем проекте. Разница между ними и будет дырой в чистоте вашей модели.
То есть Хориков полагает, что наличие БД влияет на дизайн системы.
И когда-то я думал так же. В частности, qbit был попыткой сделать такую систему хранения, которая позволила бы писать программы так, как будто все данные живут в памяти.
А сейчас я думаю, что с точки зрения дизайна у меня ничего не поменяется.
Я всё равно буду моделировать состояние системы как набор изменяемых Map неизменяемых структур данных (ака Spring Data JDBC Repository).
Возможно, у меня будет меньше специализированных под юз кейсы ДТОшек.
Совершенно точно я начну спокойно ходить по ссылкам между сущностями внутри бизнес-логики (а-ля Datomic Pull API)..
Но совершенно точно не перейду на модель огромного графа изменяемых объектов (которую, судя по всему Хориков считает идеально чистой).
При том у Хорикова в самом примере есть противоречие:
С одной стороны, он пишет:
And the difference here is the IStudentRepository interface. The domain class simply wouldn’t need to work with this interface in a setting with no database.
Но с другой стороны, в примере он использует репоз для того, чтобы обеспечить уникльность емейла студента. И вот куда он засунет поиск студента по емейлу? В
class Domain(val students: Set<Student>)?Кажется, единственное разумное место, куда можно эту проверку засунуть - StudentsRepository. Где бы он не хранил свои данные - в памяти или БД.
В общем, функциональная модель не только лучше отражает модель в голове конечного пользователя, но и лучше отражает природу информационных систем.
#functional_architecture@ergonomic_code #ddd@ergonomic_code
GitHub
GitHub - d-r-q/qbit: qbit is a kotlin-multiplatform embeddable decentralized DBMS with object-relational information model
qbit is a kotlin-multiplatform embeddable decentralized DBMS with object-relational information model - d-r-q/qbit
❤4👍3
Привет!
И снова мне попалась дичь в переводе английской книги, которую я рекомендую к прочтению.
В книге по большому счёту ни слова об Андроиде. Но переводчики, то ли от собственной некомпетентности, то ли по злому маркетинговому умыслу написали, что книга про Андроид разработку.
Не слушайте их - книгу можно и нужно читать и бакэндерам тоже.
И читайте книги только в оригиналах. Пусть это будет одна, а не 12 книг в год, но вы хотя бы что-то полезное усвоите, а не глюки ИИ, который занимался переводом под редакторством дилетантов, которые код в глаза не видели
#books@ergonomic_code
И снова мне попалась дичь в переводе английской книги, которую я рекомендую к прочтению.
В книге по большому счёту ни слова об Андроиде. Но переводчики, то ли от собственной некомпетентности, то ли по злому маркетинговому умыслу написали, что книга про Андроид разработку.
Не слушайте их - книгу можно и нужно читать и бакэндерам тоже.
И читайте книги только в оригиналах. Пусть это будет одна, а не 12 книг в год, но вы хотя бы что-то полезное усвоите, а не глюки ИИ, который занимался переводом под редакторством дилетантов, которые код в глаза не видели
#books@ergonomic_code
😢6👍4
Привет!
Полезняшка
Представьте, что у вас есть json-файл с массивом на 500 объектов.
И вам надо добавить в эти 500 объектов по ключу externalId с разными UUID-ами.
Есть вариант влоб - написать скриптик.
А есть вариант попроще.
Зайти в neovim и написать:
1. qq - запустить запись макроса
2. /lastExistingKey - найти следующую строку, в которой содержится подстрока с именем последнего существующего ключа
3. A - переместить курсор в конец строки и войти в режим печати
4.
5. <esc> - вернуться в командный режим
5. :read!uuidgen<enter> - запустить программу uuidgen и вставить её вывод в новой строке файла после курсора
6. I - перейти в начало строки и войти в режим печати
7.
8. <esc> - вернуться в командный режим
9. A - перейти в конец строки и войти в режим печати
10.
11. <esc> - вернуться в командный режим
12. q - завершить запись макроса
13. 499@q - 499 раз исполнить макрос
Вопрос примерно 2 минут вместе с гуглением uuidgen и задача решена.
Быстрее, наверное, только на sed/awk - но с ними у меня проблема, что такие задачи у меня раз в месяц появляются и за этот месяц у меня этот клингонский из головы выветривается. А вимом я пользуюсь каждый день и он у меня уже на подкорке.
Поддержка vim-режима есть во всех популярных ИДЕ/редакторов включая Emacs (😄 - если вы понимаете о чём я)
#tools@ergonomic_code
Полезняшка
Представьте, что у вас есть json-файл с массивом на 500 объектов.
И вам надо добавить в эти 500 объектов по ключу externalId с разными UUID-ами.
Есть вариант влоб - написать скриптик.
А есть вариант попроще.
Зайти в neovim и написать:
1. qq - запустить запись макроса
2. /lastExistingKey - найти следующую строку, в которой содержится подстрока с именем последнего существующего ключа
3. A - переместить курсор в конец строки и войти в режим печати
4.
, - добавить запятую в конце строки5. <esc> - вернуться в командный режим
5. :read!uuidgen<enter> - запустить программу uuidgen и вставить её вывод в новой строке файла после курсора
6. I - перейти в начало строки и войти в режим печати
7.
"externalId": " - добавить в начале стоки отступ, имя ключа и кавычку перед значением8. <esc> - вернуться в командный режим
9. A - перейти в конец строки и войти в режим печати
10.
" - добавить в конце строки кавычки11. <esc> - вернуться в командный режим
12. q - завершить запись макроса
13. 499@q - 499 раз исполнить макрос
Вопрос примерно 2 минут вместе с гуглением uuidgen и задача решена.
Быстрее, наверное, только на sed/awk - но с ними у меня проблема, что такие задачи у меня раз в месяц появляются и за этот месяц у меня этот клингонский из головы выветривается. А вимом я пользуюсь каждый день и он у меня уже на подкорке.
Поддержка vim-режима есть во всех популярных ИДЕ/редакторов включая Emacs (
#tools@ergonomic_code
Please open Telegram to view this post
VIEW IN TELEGRAM
Neovim
Hyperextensible Vim-based text editor
🔥4👍3😁2
Привет!
Теперь меня не только почитать, посмотреть (а некоторым даже и пощупать) можно, но и послушать:)
#talks@ergonomic_code #ergo_approach@ergonomic_code
Теперь меня не только почитать, посмотреть (а некоторым даже и пощупать) можно, но и послушать:)
#talks@ergonomic_code #ergo_approach@ergonomic_code
😁4
Forwarded from javaswag
https://javaswag.github.io/episode/71/
Слушать подкаст в Apple | Spotify | Yandex
В 71 выпуске подкаста Javaswag поговорили с Алексеем Жидковым об эргономичном подходе для разработки архитектуры проекта
00:00 Начало
12:06 Работа консультанта
17:38 Эргономичный подход и его принципы
26:44 Практика применения принципов разработки
30:55 Трудности внедрения DDD на практике
37:15 Популярность DDD и его реальная эффективность
39:33 TDD и его место в эргономичном подходе
41:00 Тестирование как основа разработки
43:55 Проблемы с моками в тестировании
48:50 Архитектурные подходы и JPA
51:01 Функциональная архитектура и ее влияние на разработку
55:36 Проблемы с ORM и Hibernate
01:00:03 Эргономичность и альтернативы ORM
01:01:53 Неизменяемая модель данных
01:05:58 Эргономичный подход в разработке
01:08:32 Обсуждение стека технологий и его эволюция
01:11:21 Эргономичный подход в разработке проектов
01:17:14 Проблемы объектно-ориентированного программирования
01:20:56 Декомпозиция системы и создание API
01:22:38 Тестирование и разработка по TDD
01:27:24 Экономика эргономичной архитектуры
01:30:59 Элементы эргономичного подхода
01:40:15 Проблемы многопоточности
01:42:58 Непопулярное мнение
Гость - https://t.me/ergonomic_code
Канал в телеграме @ergonomic_code
Сайт Алексея
Кип сейф! 🖖
Слушать подкаст в Apple | Spotify | Yandex
В 71 выпуске подкаста Javaswag поговорили с Алексеем Жидковым об эргономичном подходе для разработки архитектуры проекта
00:00 Начало
12:06 Работа консультанта
17:38 Эргономичный подход и его принципы
26:44 Практика применения принципов разработки
30:55 Трудности внедрения DDD на практике
37:15 Популярность DDD и его реальная эффективность
39:33 TDD и его место в эргономичном подходе
41:00 Тестирование как основа разработки
43:55 Проблемы с моками в тестировании
48:50 Архитектурные подходы и JPA
51:01 Функциональная архитектура и ее влияние на разработку
55:36 Проблемы с ORM и Hibernate
01:00:03 Эргономичность и альтернативы ORM
01:01:53 Неизменяемая модель данных
01:05:58 Эргономичный подход в разработке
01:08:32 Обсуждение стека технологий и его эволюция
01:11:21 Эргономичный подход в разработке проектов
01:17:14 Проблемы объектно-ориентированного программирования
01:20:56 Декомпозиция системы и создание API
01:22:38 Тестирование и разработка по TDD
01:27:24 Экономика эргономичной архитектуры
01:30:59 Элементы эргономичного подхода
01:40:15 Проблемы многопоточности
01:42:58 Непопулярное мнение
Гость - https://t.me/ergonomic_code
Канал в телеграме @ergonomic_code
Сайт Алексея
Кип сейф! 🖖
Javaswag
#71 - Алексей Жидков - эргономичный подход и декомпозиция архитектуры
В 71 выпуске подкаста Javaswag поговорили с Алексеем Жидковым об эргономичном подходе для разработки архитектуры проекта
🔥14👍8❤3
Привет!
Опубликовал пост по мотивам доклада на Joker.
#structured_design@ergonomic_code #ergo_approach@ergonomic_code #talks@ergonomic_code #posts@ergonomic_code
Опубликовал пост по мотивам доклада на Joker.
#structured_design@ergonomic_code #ergo_approach@ergonomic_code #talks@ergonomic_code #posts@ergonomic_code
Алексей Жидков
Структурный дизайн. Древний секрет простого и быстрого кода - Алексей Жидков
https://azhidkov.pro/
👍6❤4🔥4🥰2
Эргономичный код
Привет! Кажется у меня по мотивам Проекта Э на горизонте замаячил очередной кризис Эргономичного подхода. При том зашатались одновременно два столпа - декомпозиция на базе эффектов и функциональная архитектура. С декомпозицией на базе эффектов в одном из…
Привет!
Вы, наверное, забыли, что полтора года назад в Эргономичном подходе было две проблемы (см. цитируемое сообщение), а я - нет:)
В общем я тут внезапно осознал, что гамак-дривен девелопмент сработал и я тихой сапой решил обе проблемы:)
Проблема №1
Тут сейчас ответ такой: пофиг:) Лишь бы команде было удобно ориентироваться. Можете делать так же, как сейчас раскладываете контроллеры. Может плясать от страниц/экранов/компонентов UI, можете от ролей пользователей, можете от юз кейсов, можете от ресурсов REST API.
В Trainer-Advisor, например, сейчас код приложения сначала разбит по ролям пользователей, потом по страницам/компонентам
Проблема №2
Во-первых, я сформулировал к каким систематическим проблемам это ведёт. К заметанию сложности под ковёр. И в итоге появлению таких скрытых монстров, как на картинке №1 - чтобы осознать весь масштаб трагедии этой операции, мне пришлось 4 часа методично код читать.
Во-вторых, я нашёл решение этой проблемы.
Оно состоит из двух частей:
1. Я отказался от сервисов приложений в пользу классов-операций - класс, который имеет один публичный метод, реализующий одну операцию (эндпоинт) системы и имеющий поля для всех ресурсов (гейтвеев, репозов, клиентов внешних систем), требующихся для реализации этой операции. См, например операцию регистрации терапевта в Trainer-Advisor
2. Я завёл штуки, которые сейчас называю "доменные операции" - это процедуры (Kotlin top-level function), которые нужные им ресурсы получают в качестве параметров. В том же коде регистрации терапевта надо CreateTherapistUserWorkflow заменить на процедуру
с кодом его реализации, которая зависимости получает явно в качестве параметров. Реального публичного кода с иллюстрацией у меня пока нет, но есть картинка №2:)
В результате, для того чтобы оценить масштаб трагедии достаточно посмотреть на кол-во зависимостей класса-операции. А для того чтобы ограничить масштаб трагедии - достаточно ввести ограничение на кол-во зависимостей.
При том вы можете избегать дублирования "подопераций" и в целом ограничивать длину методов операций и вводить промежуточные уровни абстракции.
Параметры с ресурсами, конечно, придётся таскать - но это будет напоминалкой, чтобы искать возможность повысить уровень абстракции ресурсов и/или уменьшить количество вещей, которые делает операция. В примере из TA, например, пришло время завести UsersService, который скрывает за собой usersRepo и usersFactory
вин-вин-вин, я считаю:)
#ergo_approach@ergonomic_code #ergo_arch@ergonomic_code
Вы, наверное, забыли, что полтора года назад в Эргономичном подходе было две проблемы (см. цитируемое сообщение), а я - нет:)
В общем я тут внезапно осознал, что гамак-дривен девелопмент сработал и я тихой сапой решил обе проблемы:)
Проблема №1
А вот как декомозировать слой приложения - я пока не знаю.
Тут сейчас ответ такой: пофиг:) Лишь бы команде было удобно ориентироваться. Можете делать так же, как сейчас раскладываете контроллеры. Может плясать от страниц/экранов/компонентов UI, можете от ролей пользователей, можете от юз кейсов, можете от ресурсов REST API.
В Trainer-Advisor, например, сейчас код приложения сначала разбит по ролям пользователей, потом по страницам/компонентам
Проблема №2
Однако на практике у меня некоторые операции ещё и другие операции дёргают. И мне это кажется нарушением принципа одного уровня абстракции.
Но я пока не могу сформулировать к каким систематическим проблемам это ведёт.
Во-первых, я сформулировал к каким систематическим проблемам это ведёт. К заметанию сложности под ковёр. И в итоге появлению таких скрытых монстров, как на картинке №1 - чтобы осознать весь масштаб трагедии этой операции, мне пришлось 4 часа методично код читать.
Во-вторых, я нашёл решение этой проблемы.
Оно состоит из двух частей:
1. Я отказался от сервисов приложений в пользу классов-операций - класс, который имеет один публичный метод, реализующий одну операцию (эндпоинт) системы и имеющий поля для всех ресурсов (гейтвеев, репозов, клиентов внешних систем), требующихся для реализации этой операции. См, например операцию регистрации терапевта в Trainer-Advisor
2. Я завёл штуки, которые сейчас называю "доменные операции" - это процедуры (Kotlin top-level function), которые нужные им ресурсы получают в качестве параметров. В том же коде регистрации терапевта надо CreateTherapistUserWorkflow заменить на процедуру
fun createTherapistUser(
usersRepo: UsersRepo,
therapistsRepo: TherapistsRepo,
usersFactory: UsersFactory,
registerTherapistRequest: RegisterTherapistReques,
password: CharSequence) {
...
}
с кодом его реализации, которая зависимости получает явно в качестве параметров. Реального публичного кода с иллюстрацией у меня пока нет, но есть картинка №2:)
В результате, для того чтобы оценить масштаб трагедии достаточно посмотреть на кол-во зависимостей класса-операции. А для того чтобы ограничить масштаб трагедии - достаточно ввести ограничение на кол-во зависимостей.
При том вы можете избегать дублирования "подопераций" и в целом ограничивать длину методов операций и вводить промежуточные уровни абстракции.
Параметры с ресурсами, конечно, придётся таскать - но это будет напоминалкой, чтобы искать возможность повысить уровень абстракции ресурсов и/или уменьшить количество вещей, которые делает операция. В примере из TA, например, пришло время завести UsersService, который скрывает за собой usersRepo и usersFactory
вин-вин-вин, я считаю:)
#ergo_approach@ergonomic_code #ergo_arch@ergonomic_code
👍9🔥4❤3