Всем привет!
В предыдущем посте я упомянул про защиту от DOS аттак в коде. Раскрою тему.
Для начала стоит различать DDOS и DOS - (Distribted) Denial of Service.
Первый - это когда злоумышленник долбит миллионами запросов в секунду. Такое не выдержит ни один сервис, не поможет даже k8s, т.к. ресурсы кластера не резиновые - https://kubernetes.io/docs/setup/best-practices/cluster-large/ да и подымаются новые ноды не мгновенно. Следовательно, от DDOS должна защищать сетевая инфраструктура, прикладной разработчик тут ничего сделать не может.
Другое дело DOS - RPS на порядки меньше, эксплуатируются уязвимости в коде. Вопрос - откуда злоумышленники про них узнают?
Во-первых они могут действовать наугад, во-вторых - всегда могут быть болтливые сотрудники, а главное - защита типа "об этом никто никогда не узнает" - плохая защиты.
Суть всех уязвимостией при DOS одна - поднять на сервере столько потоков одновременно, чтобы закончилась память или загрузка процессора ушла под 100%
Итак, как можно улучшить код для защиты от DOS.
1) проводить нагрузочное тестирование (НТ). НТ позволяет точно определеить сколько нужно серверов, чтобы держать расчетную и пиковую нагрузку. Пиковую нагрузку можно взять как расчетная умножить на два. Утечки памяти, неоптимальный код - все это с большой вероятностью можно увидеть на НТ
2) нет бесконечным и большим таймаутам. Если смежник упал, а у нас бесконечный таймаут - потоки и память кончатся быстро. Что касается больших таймаутов - это минуты, или таймауты необоснованные с точки зрения бизнес-задачи.
2) таймауты должны быть согласованы. Если мы обрабатываем запрос с таймутом 5 секунд, он синхронный, а вызываем смежника с таймутом 10 секунд - мы зря тратим его и свои ресурсы. Согласование может быть ручным, либо можно слать, например в заголовках, свой таймаут смежнику, чтобы он не ждал зря.
3) использовать circuit breaker, он же предохранитель, он же техперерыв. Если известно, что смежная система прилегла - не надо ее добивать и тратить на это свои ресурсы. Берем данные из кэша если это возможно или возвращаем клиенту ошибку. Принцип fail fast. Стоит отметить, что настройку таймаутов, предохранителя, и числа повторов можно делать либо в коде, либо отдать на откуп Istio или аналогичной системе если мы в облаке. Что лучше - это отдельная тема
4) защищаться от уязвимостей типа Injection. Суть их в том, что злоумышленник передает в параметрах входящего запроса что-то, что приводит к нелинейному потреблению ресуросов или тяжелым запросам в БД. Примеры первого вида DTD схемы https://habr.com/ru/post/170333/, регулярки - https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS, второго - SQL Injection со сложными JOIN. Решение: валидация параметров, регулярно сканировать библиотеки на наличие уязвимостей, регулярно обновляться в части хотфиксов
5) логика сервиса не должна линейно зависеть от числа входных параметров либо число параметров должно ограничено. Чем-то похоже на предыдущий пункт, но тут приложение спроектировано криво, поэтому никакие уязвимости не нужны)
6) использовать пулы потоков. Во многих случаях они уже используются - обработка входящих веб-запросов, JDBC запросы к БД. Но есть потоки, которые создаем мы сами. Если на каждый входной запрос мы будем создавать дополнительно хотя бы +1 поток, то это примерно удвоит потребление ресурсов. А если больше одного... Пул потоков защищает от такой ситуации
7) не забывать закрывать ресурсы - файлы, коннекты к БД. Все что java.io.Closeable. И делать это правильно - try with resources. В отличие от памяти в куче ресурсы никто за вас не закроет. А они жрут память и часто ограничены: максимальное число открытых файлов в Linux, максимальное число запросов, которое может обрабатывать СУБД
8) не использовать тяжелые JOIN и GROUP BY запросы к БД. Создавать индексы, смотреть план выполнения запроса. Об этом должен позаботиться ваш DBA, но, увы, не всегда он есть
9) не использовать излишне сильные уровни блокировки в БД, не использовать блокировки файлов без явной необходимости
#code_quality #security #patterns
В предыдущем посте я упомянул про защиту от DOS аттак в коде. Раскрою тему.
Для начала стоит различать DDOS и DOS - (Distribted) Denial of Service.
Первый - это когда злоумышленник долбит миллионами запросов в секунду. Такое не выдержит ни один сервис, не поможет даже k8s, т.к. ресурсы кластера не резиновые - https://kubernetes.io/docs/setup/best-practices/cluster-large/ да и подымаются новые ноды не мгновенно. Следовательно, от DDOS должна защищать сетевая инфраструктура, прикладной разработчик тут ничего сделать не может.
Другое дело DOS - RPS на порядки меньше, эксплуатируются уязвимости в коде. Вопрос - откуда злоумышленники про них узнают?
Во-первых они могут действовать наугад, во-вторых - всегда могут быть болтливые сотрудники, а главное - защита типа "об этом никто никогда не узнает" - плохая защиты.
Суть всех уязвимостией при DOS одна - поднять на сервере столько потоков одновременно, чтобы закончилась память или загрузка процессора ушла под 100%
Итак, как можно улучшить код для защиты от DOS.
1) проводить нагрузочное тестирование (НТ). НТ позволяет точно определеить сколько нужно серверов, чтобы держать расчетную и пиковую нагрузку. Пиковую нагрузку можно взять как расчетная умножить на два. Утечки памяти, неоптимальный код - все это с большой вероятностью можно увидеть на НТ
2) нет бесконечным и большим таймаутам. Если смежник упал, а у нас бесконечный таймаут - потоки и память кончатся быстро. Что касается больших таймаутов - это минуты, или таймауты необоснованные с точки зрения бизнес-задачи.
2) таймауты должны быть согласованы. Если мы обрабатываем запрос с таймутом 5 секунд, он синхронный, а вызываем смежника с таймутом 10 секунд - мы зря тратим его и свои ресурсы. Согласование может быть ручным, либо можно слать, например в заголовках, свой таймаут смежнику, чтобы он не ждал зря.
3) использовать circuit breaker, он же предохранитель, он же техперерыв. Если известно, что смежная система прилегла - не надо ее добивать и тратить на это свои ресурсы. Берем данные из кэша если это возможно или возвращаем клиенту ошибку. Принцип fail fast. Стоит отметить, что настройку таймаутов, предохранителя, и числа повторов можно делать либо в коде, либо отдать на откуп Istio или аналогичной системе если мы в облаке. Что лучше - это отдельная тема
4) защищаться от уязвимостей типа Injection. Суть их в том, что злоумышленник передает в параметрах входящего запроса что-то, что приводит к нелинейному потреблению ресуросов или тяжелым запросам в БД. Примеры первого вида DTD схемы https://habr.com/ru/post/170333/, регулярки - https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS, второго - SQL Injection со сложными JOIN. Решение: валидация параметров, регулярно сканировать библиотеки на наличие уязвимостей, регулярно обновляться в части хотфиксов
5) логика сервиса не должна линейно зависеть от числа входных параметров либо число параметров должно ограничено. Чем-то похоже на предыдущий пункт, но тут приложение спроектировано криво, поэтому никакие уязвимости не нужны)
6) использовать пулы потоков. Во многих случаях они уже используются - обработка входящих веб-запросов, JDBC запросы к БД. Но есть потоки, которые создаем мы сами. Если на каждый входной запрос мы будем создавать дополнительно хотя бы +1 поток, то это примерно удвоит потребление ресурсов. А если больше одного... Пул потоков защищает от такой ситуации
7) не забывать закрывать ресурсы - файлы, коннекты к БД. Все что java.io.Closeable. И делать это правильно - try with resources. В отличие от памяти в куче ресурсы никто за вас не закроет. А они жрут память и часто ограничены: максимальное число открытых файлов в Linux, максимальное число запросов, которое может обрабатывать СУБД
8) не использовать тяжелые JOIN и GROUP BY запросы к БД. Создавать индексы, смотреть план выполнения запроса. Об этом должен позаботиться ваш DBA, но, увы, не всегда он есть
9) не использовать излишне сильные уровни блокировки в БД, не использовать блокировки файлов без явной необходимости
#code_quality #security #patterns
Kubernetes
Considerations for large clusters
A cluster is a set of nodes (physical or virtual machines) running Kubernetes agents, managed by the control plane. Kubernetes v1.33 supports clusters with up to 5,000 nodes. More specifically, Kubernetes is designed to accommodate configurations that meet…
Всем привет!
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста. Можно заучить все паттерны, но не научиться кодить.
И тут возникает второй вопрос - о каких паттернах мы говорим?
1) самые известные - паттерны проектирования из книги «банды четырёх» https://refactoring.guru/ru/design-patterns/catalog
Это синглтон, фабричный метод, билдер и все все все
2) паттерны Enterprise архитектуры от Фаулера https://martinfowler.com/eaaCatalog/
3) паттерны рефакторинга https://refactoring.com/catalog/ Про них также говорится в книге Идеальная работа Мартина
4) паттерны модульных тестов http://xunitpatterns.com/ и снова в книге Идеальная работа
5) паттерны интеграции корпоративных приложений https://www.enterpriseintegrationpatterns.com/patterns/messaging/toc.html многие из которых можно встретить в стандарте JMS
6) паттерны микросервисных приложений https://microservices.io/patterns/index.html
7) даже у Kubernates есть паттерны https://www.redhat.com/cms/managed-files/cm-oreilly-kubernetes-patterns-ebook-f19824-201910-en.pdf
8) не говоря уже про антипаттерны https://javarush.ru/groups/posts/2622-chto-takoe-antipatternih-razbiraem-primerih-chastjh-1
9) 10) ...
Из этого списка можно сделать вывод, что паттерны могут быть везде. А из этого второй вывод: паттерны - это удобный способ описания какой-то области разработки. Собственно это и есть их ценность. Шаблоны помогают изучить новую технологию, читать статьи, книги и главное читать код и тесты. Ну и проектировать систему, обсуждать ее архитектуру с коллегами. По сути паттерны - это язык проектирования. А идеальный способ их использования - когда они уже реализованы в неком фреймворке: Singleton и MVC в Spring, Builder в Lombok, Sidecar в k8s, или в языке как Singleton и Decorator в Kotlin.
#patterns #refactoring #unittests
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста. Можно заучить все паттерны, но не научиться кодить.
И тут возникает второй вопрос - о каких паттернах мы говорим?
1) самые известные - паттерны проектирования из книги «банды четырёх» https://refactoring.guru/ru/design-patterns/catalog
Это синглтон, фабричный метод, билдер и все все все
2) паттерны Enterprise архитектуры от Фаулера https://martinfowler.com/eaaCatalog/
3) паттерны рефакторинга https://refactoring.com/catalog/ Про них также говорится в книге Идеальная работа Мартина
4) паттерны модульных тестов http://xunitpatterns.com/ и снова в книге Идеальная работа
5) паттерны интеграции корпоративных приложений https://www.enterpriseintegrationpatterns.com/patterns/messaging/toc.html многие из которых можно встретить в стандарте JMS
6) паттерны микросервисных приложений https://microservices.io/patterns/index.html
7) даже у Kubernates есть паттерны https://www.redhat.com/cms/managed-files/cm-oreilly-kubernetes-patterns-ebook-f19824-201910-en.pdf
8) не говоря уже про антипаттерны https://javarush.ru/groups/posts/2622-chto-takoe-antipatternih-razbiraem-primerih-chastjh-1
9) 10) ...
Из этого списка можно сделать вывод, что паттерны могут быть везде. А из этого второй вывод: паттерны - это удобный способ описания какой-то области разработки. Собственно это и есть их ценность. Шаблоны помогают изучить новую технологию, читать статьи, книги и главное читать код и тесты. Ну и проектировать систему, обсуждать ее архитектуру с коллегами. По сути паттерны - это язык проектирования. А идеальный способ их использования - когда они уже реализованы в неком фреймворке: Singleton и MVC в Spring, Builder в Lombok, Sidecar в k8s, или в языке как Singleton и Decorator в Kotlin.
#patterns #refactoring #unittests
refactoring.guru
Каталог паттернов проектирования
Список паттернов проектирования, сгруппированый по предназанчению, сложности и популярности паттернов. В каталог включены объектно ориентированные паттерны, а также некоторые архитектурные паттерны.
Всем привет!
Еще один широиспользуемый паттерн, более низкого уровня, чем описанные ранее: LMAX Disruptor.
https://lmax-exchange.github.io/disruptor/disruptor.html
Это готовая библиотека, решающая следующую задачу: есть упорядоченная очередь из каких-то данных, пишет в нее один поток, обрабатывать данные нужно в несколько потоков без блокировок. Реализована в виде кольцевого буфера и набора указателей на текущую ячейку буфера, по одному для каждого потока-читателя\писателя. В каждый момент времени в буфер пишет один поток, блокировки не ставятся, каждый поток может прочитать указатели других потоков и т.об. понять, с какими ячейками можно работать. Библиотеку достаточно хорошо пиарят, даже сам Мартин Фаулер: https://martinfowler.com/articles/lmax.html Использует log4j https://logging.apache.org/log4j/2.x/manual/async.html#UnderTheHood
Но вернемся к более общим архитектурным принципам: при реализации этой библиотеки используется принцип Mechanical Sympathy https://www.baeldung.com/lmax-disruptor-concurrency#1-mechanical-sympathy.
Суть его в следующем: хотя язык программирования и JVM в случае Java скрывают от нас кишочки компьютера - регистры процессора, кэши процессора 1,2,3 уровня, особенности работы процессора - для максимальной производительности их нужно учитывать. На примере LMAX Disruptor:
1) кольцевой буфер позволяет переиспользовать объекты в куче, уменьшая нагрузку на Garbage Collector
2) кольцевой буфер выделяется одним "куском", поэтому использует последовательные адреса в памяти, что ускоряет пакетное чтение из буфера - как за счет собственно последовательного чтения, так и зачет упреждающего кэширования процессором
3) одновременная запись в память приводит к взаимным сбросам кэша у различных ядер процессора, что плохо сказывается на производительности. В LMAX Disruptor, как я уже говорил, в каждый момент времени пишет в буфер один поток.
Все это вместе с отсутствием блокировок приводит к хорошей производительности.
Но к слову есть люди, считающие библиотеку слишком распиаренной - см. комментарии к статье https://dev.cheremin.info/2011/09/disruptor-1.html
#patterns #library
Еще один широиспользуемый паттерн, более низкого уровня, чем описанные ранее: LMAX Disruptor.
https://lmax-exchange.github.io/disruptor/disruptor.html
Это готовая библиотека, решающая следующую задачу: есть упорядоченная очередь из каких-то данных, пишет в нее один поток, обрабатывать данные нужно в несколько потоков без блокировок. Реализована в виде кольцевого буфера и набора указателей на текущую ячейку буфера, по одному для каждого потока-читателя\писателя. В каждый момент времени в буфер пишет один поток, блокировки не ставятся, каждый поток может прочитать указатели других потоков и т.об. понять, с какими ячейками можно работать. Библиотеку достаточно хорошо пиарят, даже сам Мартин Фаулер: https://martinfowler.com/articles/lmax.html Использует log4j https://logging.apache.org/log4j/2.x/manual/async.html#UnderTheHood
Но вернемся к более общим архитектурным принципам: при реализации этой библиотеки используется принцип Mechanical Sympathy https://www.baeldung.com/lmax-disruptor-concurrency#1-mechanical-sympathy.
Суть его в следующем: хотя язык программирования и JVM в случае Java скрывают от нас кишочки компьютера - регистры процессора, кэши процессора 1,2,3 уровня, особенности работы процессора - для максимальной производительности их нужно учитывать. На примере LMAX Disruptor:
1) кольцевой буфер позволяет переиспользовать объекты в куче, уменьшая нагрузку на Garbage Collector
2) кольцевой буфер выделяется одним "куском", поэтому использует последовательные адреса в памяти, что ускоряет пакетное чтение из буфера - как за счет собственно последовательного чтения, так и зачет упреждающего кэширования процессором
3) одновременная запись в память приводит к взаимным сбросам кэша у различных ядер процессора, что плохо сказывается на производительности. В LMAX Disruptor, как я уже говорил, в каждый момент времени пишет в буфер один поток.
Все это вместе с отсутствием блокировок приводит к хорошей производительности.
Но к слову есть люди, считающие библиотеку слишком распиаренной - см. комментарии к статье https://dev.cheremin.info/2011/09/disruptor-1.html
#patterns #library
lmax-exchange.github.io
LMAX Disruptor: High performance alternative to bounded queues for exchanging data between concurrent threads
Всем привет!
Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шаблоны_проектирования
И среди них есть много на первый взгляд похожих паттернов для подмены одного объекта\сервиса другим. Да и на второй тоже сложно понять где что использовать)
Как же их различать?
Здесь и далее я употребляю слово интерфейс, но если паттерн используется для внешних интеграций, то его можно заменить на API.
Декоратор - добавляет поведение не меняя исходный интерфейс. Пример: добавить к сервису логирование, метрики или аудит.
Прокси - обеспечивает контроль доступа не меняя исходный интерфейс. Пример: проверка доступа, rate limiting.
Фасад - упрощает интерфейс для работы с объектом. Пример: SLF4
Адаптер - адаптирует один существующий интерфейс к другому. Пример: реализация Iterable для объекта, чтобы по нему можно было итерироваться
Gateway (шлюз) - паттерн уровня приложения https://martinfowler.com/articles/gateway-pattern.html в отличие от указанных выше паттернов проектирования, служит для добавления единого интерфейса к внешней системе. Пример: шлюз для доступа к любому внешнему сервису.
Если я что-то забыл или не согласны с чем-то - пишите.
Да, еще важный момент. Лично меня бесит, когда все классы с логикой в приложении называются Service. Не забывайте про 5 шаблонов, описанных выше!
#patterns
Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шаблоны_проектирования
И среди них есть много на первый взгляд похожих паттернов для подмены одного объекта\сервиса другим. Да и на второй тоже сложно понять где что использовать)
Как же их различать?
Здесь и далее я употребляю слово интерфейс, но если паттерн используется для внешних интеграций, то его можно заменить на API.
Декоратор - добавляет поведение не меняя исходный интерфейс. Пример: добавить к сервису логирование, метрики или аудит.
Прокси - обеспечивает контроль доступа не меняя исходный интерфейс. Пример: проверка доступа, rate limiting.
Фасад - упрощает интерфейс для работы с объектом. Пример: SLF4
Адаптер - адаптирует один существующий интерфейс к другому. Пример: реализация Iterable для объекта, чтобы по нему можно было итерироваться
Gateway (шлюз) - паттерн уровня приложения https://martinfowler.com/articles/gateway-pattern.html в отличие от указанных выше паттернов проектирования, служит для добавления единого интерфейса к внешней системе. Пример: шлюз для доступа к любому внешнему сервису.
Если я что-то забыл или не согласны с чем-то - пишите.
Да, еще важный момент. Лично меня бесит, когда все классы с логикой в приложении называются Service. Не забывайте про 5 шаблонов, описанных выше!
#patterns
Wikipedia
Структурные шаблоны проектирования
Структурные шаблоны (англ. Structural patterns) — шаблоны проектирования, в которых рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры.
Всем привет!
Выпил бокал пива и захотелось немного пофилософствовать)
У меня часть просят готовое решение какой-то проблемы. Это может быть способ интеграции, выбор места для хранения данных, языка программирования, способ разбиения проекта на микросервисы и модули, как сделать правильное API...
Что на это хочется сказать.
1) иметь каталог готовых решений - это круто. Именно он оправдывает существование архитекторов и техлидов в команде. Использование паттернов, #patterns - это как раз про это, про переиспользование знаний. И я над таким каталогом работаю)
2) вместе с тем приходится признать, что далеко не всегда есть готовые и главное наиболее подходящие к конкретной ситуации решения.
Хорошо если бы всегда было так: "Как нам сделать ххх?". Думаешь 10 минут. "Делайте так и так, а так не делайте". Всё понятно, все счастливы, приложение легко и безболезненно попадает на ПРОД. Мечты, мечты)
По факту самый лучший способ принять оптимальное решение по проекту - это понимать все возможные варианты и выбрать из них наиболее подходящий исходя из:
а) команды
б) компании
в) проекта
г) сроков
д) чего-то еще что я забыл)
И лучше всего, по принципам Agile, если понимая все возможные варианты, их плюсы и минусы, решение будет принимать команда. Почему лучше? Потому что решение, полученное извне, IMHO будет "чужеродным". Причины его принятия могут со временем потеряться.
Сторонний архитектор может банально не знать особенностей команды, как устроена кодовая база и т.д.
В этом плане само название должности - архитектор - неверное. "Каменную" архитектуру сложно отрефакторить. Программную при правильном подходе к разработке - легко. Если бы все дома вокруг строились по индивидуальным проектам - это банально было бы очень дорого. С ПО - это суровая правда жизни.
Вывод: надо изучать паттерны и существующие решения. После любого проекта нужна "ретроспектива" - индивидуальная или общекомандная, на которой стоит отметить что "выстрелило", а что лучше в будущем не использовать. Ну и конечно учиться, учиться и учиться...)))
#arch #arch_compromises
Выпил бокал пива и захотелось немного пофилософствовать)
У меня часть просят готовое решение какой-то проблемы. Это может быть способ интеграции, выбор места для хранения данных, языка программирования, способ разбиения проекта на микросервисы и модули, как сделать правильное API...
Что на это хочется сказать.
1) иметь каталог готовых решений - это круто. Именно он оправдывает существование архитекторов и техлидов в команде. Использование паттернов, #patterns - это как раз про это, про переиспользование знаний. И я над таким каталогом работаю)
2) вместе с тем приходится признать, что далеко не всегда есть готовые и главное наиболее подходящие к конкретной ситуации решения.
Хорошо если бы всегда было так: "Как нам сделать ххх?". Думаешь 10 минут. "Делайте так и так, а так не делайте". Всё понятно, все счастливы, приложение легко и безболезненно попадает на ПРОД. Мечты, мечты)
По факту самый лучший способ принять оптимальное решение по проекту - это понимать все возможные варианты и выбрать из них наиболее подходящий исходя из:
а) команды
б) компании
в) проекта
г) сроков
д) чего-то еще что я забыл)
И лучше всего, по принципам Agile, если понимая все возможные варианты, их плюсы и минусы, решение будет принимать команда. Почему лучше? Потому что решение, полученное извне, IMHO будет "чужеродным". Причины его принятия могут со временем потеряться.
Сторонний архитектор может банально не знать особенностей команды, как устроена кодовая база и т.д.
В этом плане само название должности - архитектор - неверное. "Каменную" архитектуру сложно отрефакторить. Программную при правильном подходе к разработке - легко. Если бы все дома вокруг строились по индивидуальным проектам - это банально было бы очень дорого. С ПО - это суровая правда жизни.
Вывод: надо изучать паттерны и существующие решения. После любого проекта нужна "ретроспектива" - индивидуальная или общекомандная, на которой стоит отметить что "выстрелило", а что лучше в будущем не использовать. Ну и конечно учиться, учиться и учиться...)))
#arch #arch_compromises
Всем привет!
В последние годы стала "модной" тема null safety. Суть в том, что не нужно хранить и передавать null значения, чтобы не напороться на Null Pointer Exception. В том же Kotlin null safety встроена в язык - все типы по умолчанию не могут содержать null.
И на самом деле это правильный подход. Но есть нюансы)
Рассмотрим такой случай - мы идем куда-то за данными, данные по бизнес-процессу там обязаны быть. Например, мы прихранили id записи где-то в пользовательском контексте в начале процесса и идем за данными в конце процесса. Но данных нет. Следуя null safety можно просто создать пустой объект - например, с помощью конструктора. Как вариант, часть полей этого объекта будет проинициализирована значениями по умолчанию.
Так вот - в случае, когда данных нет из-за какой-то нештатной редко воспроизводимой ситуации: неверные тестовые данные, на сервис идет атака с перебором всех возможных значений, в процессе операции данные некорректно мигрировали, кривая архитектура - лучше просто "упасть", т.е. выбросить исключение. Есть такой принцип - fail fast. Т.к. создавая пустой объект, мы во-первых надеемся что он будет корректно обработан выше, а это может быть не так. А во-вторых - а зачем передавать управление дальше?
P.S. Как всегда - напомню каждую ситуацию нужно рассматривать индивидуально, чтобы различать отсутствие данных как часть бизнес-процесса и нештатную ситуацию.
#kotlin #code #patterns #principles #nullsafety #fail_fast
В последние годы стала "модной" тема null safety. Суть в том, что не нужно хранить и передавать null значения, чтобы не напороться на Null Pointer Exception. В том же Kotlin null safety встроена в язык - все типы по умолчанию не могут содержать null.
И на самом деле это правильный подход. Но есть нюансы)
Рассмотрим такой случай - мы идем куда-то за данными, данные по бизнес-процессу там обязаны быть. Например, мы прихранили id записи где-то в пользовательском контексте в начале процесса и идем за данными в конце процесса. Но данных нет. Следуя null safety можно просто создать пустой объект - например, с помощью конструктора. Как вариант, часть полей этого объекта будет проинициализирована значениями по умолчанию.
Так вот - в случае, когда данных нет из-за какой-то нештатной редко воспроизводимой ситуации: неверные тестовые данные, на сервис идет атака с перебором всех возможных значений, в процессе операции данные некорректно мигрировали, кривая архитектура - лучше просто "упасть", т.е. выбросить исключение. Есть такой принцип - fail fast. Т.к. создавая пустой объект, мы во-первых надеемся что он будет корректно обработан выше, а это может быть не так. А во-вторых - а зачем передавать управление дальше?
P.S. Как всегда - напомню каждую ситуацию нужно рассматривать индивидуально, чтобы различать отсутствие данных как часть бизнес-процесса и нештатную ситуацию.
#kotlin #code #patterns #principles #nullsafety #fail_fast
Всем привет!
Я уже писал про паттерны https://t.me/javaKotlinDevOps/52 и их важность. Но есть штука поважнее паттернов - базовые принципы разработки. Чтобы стало понятнее приведу пример - SOLID.
Почему принципы важнее паттернов? Паттерн - это решение частной задачи. Лично я знаю больше паттернов, чем применял на практике) А в разработке я давно. Принципы же применимы практически к любой задачи.
Тот же S из SOLID - Single Responsibility: дорабатываешь какой-то метода - применим, создаешь новый класс - тоже, делишь код по модулям - аналогично, проектируешь набор микросервисов...
Я не люблю повторять то, что уже хорошо описано в интернете, поэтому вот статья с неплохим описанием - https://skillbox.ru/media/code/eto-klassika-eto-znat-nado-dry-kiss-solid-yagni-i-drugie-poleznye-sokrashcheniya/
Оффтопик - не ожидал от skillbox, обычно все ссылки у меня на Хабр или baeldung.
Что бы я добавил к описанным в статье принципам:
1) null safety. Плюсы: не получишь NullPointerException, не нужен код с проверкой на null, не нужно думать - так, на уровень выше я уже на null проверяю, тут вроде не нужно.. но если в будущем этот метод будет вызываться откуда-то еще. Жаль в Java ее достичь сложно, есть куча библиотек с аннотациями @Null\@NotNull, действуют они по разному, на эту тему можно отдельную статью написать. Важно то, что простого решения в Java нет. Зато есть в Kotlin)
2) иммутабельность. Главный плюс - большая устойчивость к ошибкам. Приведу пример: объект - это ссылка на область в памяти. Где еще в сервисе используется объект - часто быстро определить сложно. Вывод - меняя что-то в переданном в метод объекте можно поломать программу в неожиданном месте. Также неожиданным плюсом может быть большая производительность. Самый очевидный пример - иммутабельность строк. Еще - если у вас есть List и нужно убрать из него лишнее - возможно (возможно, надо проводить тесты!) оптимальнее будет создать новый список с нужными объектами, т.к. каждая модификация существующего - это перемещения в heap. Главное чтобы памяти было много и использовался современный сборщик мусора. Еще плюс - если объект иммутабельный, то его можно спокойно использовать в многопоточной программе. Изменения состояния нет, синхронизация доступа не нужна. Ну и бонусом - иммутабельный объект можно использовать как ключ, в том же HashSet\HashMap. В Java для иммутабельности есть records и final, в Kotlin - data class.
3) понятные наименования - я про переменные, методы, классы. Часто вижу две ошибки. Первая - злоупотребление сокращениями. Вторая - ситуативные названия. Т.е. при реализации конкретной фичи название кажется очевидным для автора кода. Но вот приходит новый разработчик. Он знает только о сервисе в целом, никакую старую аналитику он читать не будет, сразу полезет в код - в итоге многие названия покажутся ему непонятными. Общий принцип - называйте так, чтобы назначение кода было понятно новому разработчику. А любые более менее сложные условия выносите в методы с говорящими названиями. За подробностями - снова порекомендую книгу "Чистый код" Мартина.
#arch #patterns #solid
Я уже писал про паттерны https://t.me/javaKotlinDevOps/52 и их важность. Но есть штука поважнее паттернов - базовые принципы разработки. Чтобы стало понятнее приведу пример - SOLID.
Почему принципы важнее паттернов? Паттерн - это решение частной задачи. Лично я знаю больше паттернов, чем применял на практике) А в разработке я давно. Принципы же применимы практически к любой задачи.
Тот же S из SOLID - Single Responsibility: дорабатываешь какой-то метода - применим, создаешь новый класс - тоже, делишь код по модулям - аналогично, проектируешь набор микросервисов...
Я не люблю повторять то, что уже хорошо описано в интернете, поэтому вот статья с неплохим описанием - https://skillbox.ru/media/code/eto-klassika-eto-znat-nado-dry-kiss-solid-yagni-i-drugie-poleznye-sokrashcheniya/
Оффтопик - не ожидал от skillbox, обычно все ссылки у меня на Хабр или baeldung.
Что бы я добавил к описанным в статье принципам:
1) null safety. Плюсы: не получишь NullPointerException, не нужен код с проверкой на null, не нужно думать - так, на уровень выше я уже на null проверяю, тут вроде не нужно.. но если в будущем этот метод будет вызываться откуда-то еще. Жаль в Java ее достичь сложно, есть куча библиотек с аннотациями @Null\@NotNull, действуют они по разному, на эту тему можно отдельную статью написать. Важно то, что простого решения в Java нет. Зато есть в Kotlin)
2) иммутабельность. Главный плюс - большая устойчивость к ошибкам. Приведу пример: объект - это ссылка на область в памяти. Где еще в сервисе используется объект - часто быстро определить сложно. Вывод - меняя что-то в переданном в метод объекте можно поломать программу в неожиданном месте. Также неожиданным плюсом может быть большая производительность. Самый очевидный пример - иммутабельность строк. Еще - если у вас есть List и нужно убрать из него лишнее - возможно (возможно, надо проводить тесты!) оптимальнее будет создать новый список с нужными объектами, т.к. каждая модификация существующего - это перемещения в heap. Главное чтобы памяти было много и использовался современный сборщик мусора. Еще плюс - если объект иммутабельный, то его можно спокойно использовать в многопоточной программе. Изменения состояния нет, синхронизация доступа не нужна. Ну и бонусом - иммутабельный объект можно использовать как ключ, в том же HashSet\HashMap. В Java для иммутабельности есть records и final, в Kotlin - data class.
3) понятные наименования - я про переменные, методы, классы. Часто вижу две ошибки. Первая - злоупотребление сокращениями. Вторая - ситуативные названия. Т.е. при реализации конкретной фичи название кажется очевидным для автора кода. Но вот приходит новый разработчик. Он знает только о сервисе в целом, никакую старую аналитику он читать не будет, сразу полезет в код - в итоге многие названия покажутся ему непонятными. Общий принцип - называйте так, чтобы назначение кода было понятно новому разработчику. А любые более менее сложные условия выносите в методы с говорящими названиями. За подробностями - снова порекомендую книгу "Чистый код" Мартина.
#arch #patterns #solid
Telegram
(java || kotlin) && devOps
Всем привет!
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста.…
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста.…
Всем привет!
Про DI и DI.
Аббревиатура DI может расшифровываться на Dependency Inversion, а может как Dependency Injection.
Dependency Inversion - это буква D из SOLID - базовых принципов разработки.
Означает, что высокоуровневые классы не должны зависеть от конкретных реализации, и в Java API любых классов лучше использовать интерфейсы везде, где это возможно. Почему такая ремарка: интерфейс с единственной реализацией - очень странная штука) Но я отвлекся) Следование принципу облегчает тестирование и расширение функциональности системы, т.к. позволяет легко заменить любую реализацию.
Dependency Injection - это механизм внедрения зависимостей, важнейшая особенность которого - собственно внедрение зависимостей отдается на откуп внешнему модулю. Самые известный пример - Spring c его IoC контейнером, но есть и другие заточенные конкретно на эту задачу и поэтому более шустрые альтернативы.
Если подходить формально - это два разных понятия, кроме аббревиатуры никак не связанные. Но с другой стороны Dependency Injection по сути - это инструмент, сильно облегчающий реализацию принципа Dependency Inversion. А хороший инструмент помогает писать правильный код. Важное замечание - Spring IoC не обеспечит за вас реализацию инверсии зависимостей. Если метод API завязывается на конкретную реализацию или уровни приложения связаны циклически - Spring тут не поможет. Поможет предварительное проектирование на уровне кода и TDD.
#code_architecture #interview_question #arch #patterns #solid
Про DI и DI.
Аббревиатура DI может расшифровываться на Dependency Inversion, а может как Dependency Injection.
Dependency Inversion - это буква D из SOLID - базовых принципов разработки.
Означает, что высокоуровневые классы не должны зависеть от конкретных реализации, и в Java API любых классов лучше использовать интерфейсы везде, где это возможно. Почему такая ремарка: интерфейс с единственной реализацией - очень странная штука) Но я отвлекся) Следование принципу облегчает тестирование и расширение функциональности системы, т.к. позволяет легко заменить любую реализацию.
Dependency Injection - это механизм внедрения зависимостей, важнейшая особенность которого - собственно внедрение зависимостей отдается на откуп внешнему модулю. Самые известный пример - Spring c его IoC контейнером, но есть и другие заточенные конкретно на эту задачу и поэтому более шустрые альтернативы.
Если подходить формально - это два разных понятия, кроме аббревиатуры никак не связанные. Но с другой стороны Dependency Injection по сути - это инструмент, сильно облегчающий реализацию принципа Dependency Inversion. А хороший инструмент помогает писать правильный код. Важное замечание - Spring IoC не обеспечит за вас реализацию инверсии зависимостей. Если метод API завязывается на конкретную реализацию или уровни приложения связаны циклически - Spring тут не поможет. Поможет предварительное проектирование на уровне кода и TDD.
#code_architecture #interview_question #arch #patterns #solid
Всем привет!
Я уже поднимал тему boolean параметров как антипаттерна https://t.me/javaKotlinDevOps/229. Давайте расширим ее до вопроса - когда стоит использовать if?
Является ли if антипаттерном?
По мнению некоторых товарищей - да, является: https://www.antiifprogramming.com/about-the-anti-if.php
Как по мне - не всегда, зависит от ситуации.
Чем плох if? // да, switch - это по сути тот же if.
1) может нарушать принцип Single Responsibility. Почему - думаю объяснять не нужно.
2) может ухудшать читаемость кода, я которую я всегда "топлю") Т.е. нарушает принцип KISS. Усугубляет ситуацию тот факт, что код как правило не остается неизменным. И обычный if else со временем может превратится в многоуровневого нечитаемого монстра.
3) может нарушать принцип Don't Repeat Yourself. Тут два очевидных варианта - либо во всех ветках if выражения есть дублирующийся код, либо чтобы обработать возврат некого метода всегда нужен if.
4) если в коде слишком много if (x != null) - это признак того, что вы неправильно работаете с nullability. Тут могу посоветовать Kotlin, т.к. он может сообщать о null значениях на этапе компиляции. Optional и его альтернативы в Java избавляют от NPE, но не избавляет от проверок на null. Я видел советы - просто не пишите код, который возвращает null - тогда проверки будут не нужны. Но это надежда на человеческий фактор, и компилятор (я про Kotlin) работает лучше)))
Да, я специально пишу везде слово "может". Бывают if-ы, которые не нарушают ни один из принципов.
Когда стоит волноваться?
1) подключаем SonarQube или Checkstyle и не игнорируем ошибки, связанные с цикломатической сложностью методов, см. https://t.me/javaKotlinDevOps/197
2) код просто сложно становится читать. Особенно хорошо эта проверка проходит на новых разработчиках)
Идеально конечно не писать код, приводящий к лишним if. Но я уже писал про человеческий фактор выше)
Что можно сделать? // будет некоторый повтор написанного тут https://t.me/javaKotlinDevOps/229
1) выделяем сложный код условия в отдельный метод.
2) вместо двух или более веток оператора if делаем несколько методов. Помогает в случае, если условно метод А всегда вызывает метод С с значением true, а метод Б - с значением false. Иначе будет как на знаменитой картинке - проблема не на моей стороне)))
3) используем not null объекты и переходим Kotlin
4) перепроектируем код, чтобы проверки выполнялись в одном месте, а не дублировались по коду. Для этого их придется перенести из вызывающего кода в вызываемый. И придумать правильное значение по умолчанию.
5) при необходимости вводим иерархию классов, чтобы каждый класс отвечал за одну ветку switch
6) используем паттерн Стратегия - по сути частный случай введения иерархии классов
7) используем паттерн Состояние (State), который кроме хранения состояния выполняет обработку, связанную с различными состояниями, тем самым убирая if из вызывающего кода
#antipatterns #if_antipattern #java #kotlin #solid #patterns #dev_compromises
Я уже поднимал тему boolean параметров как антипаттерна https://t.me/javaKotlinDevOps/229. Давайте расширим ее до вопроса - когда стоит использовать if?
Является ли if антипаттерном?
По мнению некоторых товарищей - да, является: https://www.antiifprogramming.com/about-the-anti-if.php
Как по мне - не всегда, зависит от ситуации.
Чем плох if? // да, switch - это по сути тот же if.
1) может нарушать принцип Single Responsibility. Почему - думаю объяснять не нужно.
2) может ухудшать читаемость кода, я которую я всегда "топлю") Т.е. нарушает принцип KISS. Усугубляет ситуацию тот факт, что код как правило не остается неизменным. И обычный if else со временем может превратится в многоуровневого нечитаемого монстра.
3) может нарушать принцип Don't Repeat Yourself. Тут два очевидных варианта - либо во всех ветках if выражения есть дублирующийся код, либо чтобы обработать возврат некого метода всегда нужен if.
4) если в коде слишком много if (x != null) - это признак того, что вы неправильно работаете с nullability. Тут могу посоветовать Kotlin, т.к. он может сообщать о null значениях на этапе компиляции. Optional и его альтернативы в Java избавляют от NPE, но не избавляет от проверок на null. Я видел советы - просто не пишите код, который возвращает null - тогда проверки будут не нужны. Но это надежда на человеческий фактор, и компилятор (я про Kotlin) работает лучше)))
Да, я специально пишу везде слово "может". Бывают if-ы, которые не нарушают ни один из принципов.
Когда стоит волноваться?
1) подключаем SonarQube или Checkstyle и не игнорируем ошибки, связанные с цикломатической сложностью методов, см. https://t.me/javaKotlinDevOps/197
2) код просто сложно становится читать. Особенно хорошо эта проверка проходит на новых разработчиках)
Идеально конечно не писать код, приводящий к лишним if. Но я уже писал про человеческий фактор выше)
Что можно сделать? // будет некоторый повтор написанного тут https://t.me/javaKotlinDevOps/229
1) выделяем сложный код условия в отдельный метод.
2) вместо двух или более веток оператора if делаем несколько методов. Помогает в случае, если условно метод А всегда вызывает метод С с значением true, а метод Б - с значением false. Иначе будет как на знаменитой картинке - проблема не на моей стороне)))
3) используем not null объекты и переходим Kotlin
4) перепроектируем код, чтобы проверки выполнялись в одном месте, а не дублировались по коду. Для этого их придется перенести из вызывающего кода в вызываемый. И придумать правильное значение по умолчанию.
5) при необходимости вводим иерархию классов, чтобы каждый класс отвечал за одну ветку switch
6) используем паттерн Стратегия - по сути частный случай введения иерархии классов
7) используем паттерн Состояние (State), который кроме хранения состояния выполняет обработку, связанную с различными состояниями, тем самым убирая if из вызывающего кода
#antipatterns #if_antipattern #java #kotlin #solid #patterns #dev_compromises
Telegram
(java || kotlin) && devOps
Всем привет!
Хочу рассказать про наверное самый способ улучшить читаемость. Например, у вас есть сложное условие из нескольких уровней, каждый из которых состоит из ряда проверок. Или длинный метод с кучей условий, который сложно понять и на который справедливо…
Хочу рассказать про наверное самый способ улучшить читаемость. Например, у вас есть сложное условие из нескольких уровней, каждый из которых состоит из ряда проверок. Или длинный метод с кучей условий, который сложно понять и на который справедливо…
Всем привет!
Является ли ООП - объекто-ориентированное программирование - чем-то плохим? Ответ - ну нет. Благодаря объектам мы можем воспроизвести в коде реальные бизнес-объекты, а это стирает барьеры между заказчиком, аналитиком и разработчиком. Конечно, есть альтернативные подходы, например, функциональный. Или если посмотреть в другую сторону - декларативный. Но ООП жил, жив и будет жить)
Является ли архитектура сервиса, состоящая из нескольких слоев абстракции, какой-то излишней или неправильной? Нет, это стандартный подход в архитектуре - вводить новые уровни абстракции. Даже шутка на этот счет есть) Spring, Hibernate и куча других библиотек - это тоже новые слои абстракции. Цель введения нового слоя - упростить использование какой-то библиотеки или адаптировать ее для новой предметной области.
Что плавно подводит нас к паттернам Адаптер, Прокси и иже с ними https://t.me/javaKotlinDevOps/124 Паттерны - штука полезная, и да, я снова об этом уже писал https://t.me/javaKotlinDevOps/52 )))
И последний (риторический) вопрос: являются ли принципы DRY - Don't Repeat Yourself - и Single Responsibility вредными? Наоборот, они делают код более устойчивым к изменениям и упрощают его изучение.
Но почему же тогда в мире ПО не редкость встретить вот такую фабрику фабрик фабрик: https://factoryfactoryfactory.net ?
Ответ: ООП, принципы и паттерны не заменяют здравый смысл и чувство меры)
#oop #patterns #craftmanship #dev_compromises
Является ли ООП - объекто-ориентированное программирование - чем-то плохим? Ответ - ну нет. Благодаря объектам мы можем воспроизвести в коде реальные бизнес-объекты, а это стирает барьеры между заказчиком, аналитиком и разработчиком. Конечно, есть альтернативные подходы, например, функциональный. Или если посмотреть в другую сторону - декларативный. Но ООП жил, жив и будет жить)
Является ли архитектура сервиса, состоящая из нескольких слоев абстракции, какой-то излишней или неправильной? Нет, это стандартный подход в архитектуре - вводить новые уровни абстракции. Даже шутка на этот счет есть) Spring, Hibernate и куча других библиотек - это тоже новые слои абстракции. Цель введения нового слоя - упростить использование какой-то библиотеки или адаптировать ее для новой предметной области.
Что плавно подводит нас к паттернам Адаптер, Прокси и иже с ними https://t.me/javaKotlinDevOps/124 Паттерны - штука полезная, и да, я снова об этом уже писал https://t.me/javaKotlinDevOps/52 )))
И последний (риторический) вопрос: являются ли принципы DRY - Don't Repeat Yourself - и Single Responsibility вредными? Наоборот, они делают код более устойчивым к изменениям и упрощают его изучение.
Но почему же тогда в мире ПО не редкость встретить вот такую фабрику фабрик фабрик: https://factoryfactoryfactory.net ?
Ответ: ООП, принципы и паттерны не заменяют здравый смысл и чувство меры)
#oop #patterns #craftmanship #dev_compromises
Enterprise Craftsmanship
Domain model purity vs. domain model completeness (DDD Trilemma)
I’ve been meaning to write this article for a long time and, finally, here it is: the topic of domain model purity versus domain model completeness.
Всем привет!
Вытяну ссылку из комментариев сюда: https://youtu.be/hUzpe73Oa3g?si=c_dY1YU2Cc_F8YiY
Хороший ролик про границы применимости паттерна Value Object.
На всякий случай - что такое Value Object и чем он отличается от DTO - https://matthiasnoback.nl/2022/09/is-it-a-dto-or-a-value-object/
Также стоит отметить, что данный паттерн является одним из основных в DDD - Domain Driven Development.
А по видео у меня такой краткий вывод - а точнее два:
1) у любого паттерна есть своя область применения
2) когда вы придумали некий хитрый лайфхак, перед тем как реализовывать его в коде стоит взять паузу и подумать.
Насколько он понятен для новичка? Не усложнит ли он код? Насколько? Не станет ли поддержка такого кода сложнее? Не добавит ли он в вашу модель "уязвимость", позволяющую использовать классы и методы не так, как задумывалось изначально?
Часто лучше написать больше простого кода, чем меньше, но неочевидного и допускающего неверное использование. И далее либо разбить этот код на микросервисы, либо на модули - например, см. мой пост про Modulith - https://t.me/javaKotlinDevOps/143
#patterns #arch #dev_compromises
Вытяну ссылку из комментариев сюда: https://youtu.be/hUzpe73Oa3g?si=c_dY1YU2Cc_F8YiY
Хороший ролик про границы применимости паттерна Value Object.
На всякий случай - что такое Value Object и чем он отличается от DTO - https://matthiasnoback.nl/2022/09/is-it-a-dto-or-a-value-object/
Также стоит отметить, что данный паттерн является одним из основных в DDD - Domain Driven Development.
А по видео у меня такой краткий вывод - а точнее два:
1) у любого паттерна есть своя область применения
2) когда вы придумали некий хитрый лайфхак, перед тем как реализовывать его в коде стоит взять паузу и подумать.
Насколько он понятен для новичка? Не усложнит ли он код? Насколько? Не станет ли поддержка такого кода сложнее? Не добавит ли он в вашу модель "уязвимость", позволяющую использовать классы и методы не так, как задумывалось изначально?
Часто лучше написать больше простого кода, чем меньше, но неочевидного и допускающего неверное использование. И далее либо разбить этот код на микросервисы, либо на модули - например, см. мой пост про Modulith - https://t.me/javaKotlinDevOps/143
#patterns #arch #dev_compromises
YouTube
Семен Киреков — Spring, Hibernate, паттерн Value Object и границы его применения
Ближайшая конференция — JPoint 2025, 3–4 апреля (Москва + трансляция).
Подробности и билеты: https://jrg.su/T2zfbS
— —
При разработке ПО всегда заходит вопрос о валидации и корректной работе с данными. Если выполнить бизнес-операцию с неверными входными…
Подробности и билеты: https://jrg.su/T2zfbS
— —
При разработке ПО всегда заходит вопрос о валидации и корректной работе с данными. Если выполнить бизнес-операцию с неверными входными…
Всем привет!
Сегодня будет пост про паттерн Saga.
Saga - это способ осуществить распределённую транзакцию. Обычная транзакция осуществляется в рамках одной сущности, как правило базы данных. Распределённая - между несколькими. Проблема здесь в том, что для одной системы - реляционной БД или кластера Kafka - можно воспользоваться встроенным механизмом транзакций, для распределённой - нет.
В общем случае распределённые транзакции могут понадобиться и для операций между несколькими монолитными приложениями, но наиболее актуальны они стали при переходе на микросервисы, т.к. при этом расширились границы существующих бизнес транзакций.
Возможно кто-то слышал в применении к распределённым транзакциям и Java такие аббревиатуры как XA или JTA. Это стандарт Java EE (Jakarta EE) для осуществления распределённых транзакций между JDBC и JMS источниками. Существует давно, есть работающие реализации - https://samolisov.blogspot.com/2011/02/xa-jta-javase-spring-atomikos-2.html
Что же с ним не так, раз понадобился новый патерн?
1) т.к. в JTA появляется новая сущность - координатор транзакций - и сам процесс двухфазный - подготовка и фиксация транзакции - то это приводит к накладным расходам на сетевые вызовы и увеличению задержек (latency)
2) JTA стандарт ограничивает нас JRE совместимыми языками. Более того, не все поддерживают JDBC - см. noSQL хранилища - и JMS - см. Kafka. Причём последняя не стала добавлять поддержку JMS/JTA принципиально https://docs.confluent.io/platform/current/clients/kafka-jms-client/index.html
3) диспетчер транзакций - это ещё одна точка отказа. Пусть их и так много - для транзакции из 3 фаз это минимум 9 = (сервис + хранилище + сеть) х 3, и это не учитывая датацентры, СХД... И кажется, что добавление ещё одной сильно ситуацию не ухудшит. Но эта диспетчер - это централизованная (единая) точка отказа, при сбое диспетчера придётся повторять всю транзакцию с начала.
Но вернёмся к саге. Во-первых у нее есть 2 варианта реализации - оркестрация и хореография. Оркестрация - транзакция управляется из одного микросервиса, хореография - нет единой точки управления, просто идёт обмен сообщениями между микросервисами. Оркестрацией проще управлять и тестировать, хореография более надёжна, т.к. нет единой точки отказа.
Вот тут неплохое описание отличий https://learn.microsoft.com/ru-ru/azure/architecture/reference-architectures/saga/saga
Во-вторых: сага - это не стандарт или библиотека, это архитектурный патерн - реализацию нужно будет писать самому.
Суть саги - одну большую транзакцию мы делим на ряд локальных транзакций, в рамках которых обеспечивается строгая тразакционность. Плюс все локальные транзакции мы упорядочиваем таким образом, что вначале идут компенсируемые транзакции, а потом - повторяемые. Первые в случае сбоя мы компенсируем - т.е. откатываем, вторые - докатываем. Соответственно, в середине есть поворотная (pivot) локальная транзакция, после успешного выполнения которой все последующие транзакции мы обязаны докатить.
To be continued...
#patterns #microservices #saga
Сегодня будет пост про паттерн Saga.
Saga - это способ осуществить распределённую транзакцию. Обычная транзакция осуществляется в рамках одной сущности, как правило базы данных. Распределённая - между несколькими. Проблема здесь в том, что для одной системы - реляционной БД или кластера Kafka - можно воспользоваться встроенным механизмом транзакций, для распределённой - нет.
В общем случае распределённые транзакции могут понадобиться и для операций между несколькими монолитными приложениями, но наиболее актуальны они стали при переходе на микросервисы, т.к. при этом расширились границы существующих бизнес транзакций.
Возможно кто-то слышал в применении к распределённым транзакциям и Java такие аббревиатуры как XA или JTA. Это стандарт Java EE (Jakarta EE) для осуществления распределённых транзакций между JDBC и JMS источниками. Существует давно, есть работающие реализации - https://samolisov.blogspot.com/2011/02/xa-jta-javase-spring-atomikos-2.html
Что же с ним не так, раз понадобился новый патерн?
1) т.к. в JTA появляется новая сущность - координатор транзакций - и сам процесс двухфазный - подготовка и фиксация транзакции - то это приводит к накладным расходам на сетевые вызовы и увеличению задержек (latency)
2) JTA стандарт ограничивает нас JRE совместимыми языками. Более того, не все поддерживают JDBC - см. noSQL хранилища - и JMS - см. Kafka. Причём последняя не стала добавлять поддержку JMS/JTA принципиально https://docs.confluent.io/platform/current/clients/kafka-jms-client/index.html
3) диспетчер транзакций - это ещё одна точка отказа. Пусть их и так много - для транзакции из 3 фаз это минимум 9 = (сервис + хранилище + сеть) х 3, и это не учитывая датацентры, СХД... И кажется, что добавление ещё одной сильно ситуацию не ухудшит. Но эта диспетчер - это централизованная (единая) точка отказа, при сбое диспетчера придётся повторять всю транзакцию с начала.
Но вернёмся к саге. Во-первых у нее есть 2 варианта реализации - оркестрация и хореография. Оркестрация - транзакция управляется из одного микросервиса, хореография - нет единой точки управления, просто идёт обмен сообщениями между микросервисами. Оркестрацией проще управлять и тестировать, хореография более надёжна, т.к. нет единой точки отказа.
Вот тут неплохое описание отличий https://learn.microsoft.com/ru-ru/azure/architecture/reference-architectures/saga/saga
Во-вторых: сага - это не стандарт или библиотека, это архитектурный патерн - реализацию нужно будет писать самому.
Суть саги - одну большую транзакцию мы делим на ряд локальных транзакций, в рамках которых обеспечивается строгая тразакционность. Плюс все локальные транзакции мы упорядочиваем таким образом, что вначале идут компенсируемые транзакции, а потом - повторяемые. Первые в случае сбоя мы компенсируем - т.е. откатываем, вторые - докатываем. Соответственно, в середине есть поворотная (pivot) локальная транзакция, после успешного выполнения которой все последующие транзакции мы обязаны докатить.
To be continued...
#patterns #microservices #saga
Blogspot
Распределенные транзакции (XA) с помощью JTA в JavaSE (на примере Spring + Atomikos)
При интеграции приложений в единую информационную систему наиболее остро встает проблема обеспечения целостности и непротиворечивости данны...
Всем привет!
Продолжение про сагу.
Когда мы говорим про транзакции, сначала всплывает аббревиатура ACID. Транзакции должны обеспечивать принципы ACID. Посмотрим что тут у нас с сагой.
A - атомарность: или все выполняется, или все откатывается. Собственно атормарность есть в определении паттерна, см. выше. Единственное отличие - у нас нет волшебного rollback на всю распределённую транзакцию, бизнес логику отката придётся писать руками.
C - консистентность данных. Сага обеспечивает т.наз. eventually consistentcy - конечную согласованность. Т.е. данные будут согласованы только после окончания распределённой транзакции. В течение транзакции данные в разных микросервисах могут расходится. Транзакция в БД может обеспечить строгую согласованность изменяемых данных с нужным уровнем изоляции. Поэтому переходим к
I - изоляции изменений внутри транзакции от других операций. Сага не обеспечивает ее совсем, что с этим можно сделать описано в статье про это патерн от Microsoft по ссылке выше. Важный момент - в отличие от транзакции в БД, которая как правило длится миллисекунды, распределённая транзакция - это секунды, может даже десятки секунд. Несогласованность данных в течение этого времени из-за отсутствия изоляции нужно иметь в виду. В дополнение к описанным в статье по ссылке способом скажу ещё один - завершать транзакцию как можно быстрее и игнорировать несогласованность данных) Пример: клиент вряд ли будет жаловаться в службу поддержки, если после отмены заказа деньги и бонусы вернутся на счёт в течение минуты. И скорее всего будет - если это не будет сделано через час.
D - надёжность хранения данных, к саге отношения напрямую не имеет, обеспечивается используемыми хранилищами.
Т.к. в итоге мы получили ACD, причем неполноценный, то для распределенных транзакций придумали новую аббревиатуру - Basically Available, Soft-state, Eventually consistent - https://ru.m.wikipedia.org/wiki/Теорема_CAP#BASE-архитектура
Ещё один интересный момент про сагу: определение последовательности шагов - локальных транзакций. Единственно верной схемы нет, но есть рекомендации. Первая - fail fast. Т.е. если есть локальная транзакция, которая упадёт с большей вероятностью - ее нужно ставить вначале. Пример: резерв билета или товара. Вторая - если какая-то локальная транзакция проводит к критичной для клиента несогласованность данных - ее нужно выполнять как можно позже. Что делать, если эти рекомендации противоречат друг другу - зависит от сценария, но в целом я бы выбрал уменьшение времени неконсистентности.
Еще интересный момент касается саги в виде оркестрации. Т.к. ее главный плюс - сделать простой и понятной бизнес логику саги, то самая очевидная ее реализация вот такая:
class OrderSaga {
SagaResult execute() {
// шаг 1
// шаг 2
// ...
}
}
Назовём этот подход Transaction Script, есть такой Паттерн организации бизнес логики.
Просто - да. Но если процесс сложный, каждый шаг тоже, то мы ухудшим читаемость кода, получим замечание SonarQube про длину метода да и нарушим S из SOLID, принцип единой ответственности. Что делать? Использовать event driven подход:
class OrderSaga {
PrepareEvent start(...) {..}
ReserveEvent makeReservation(...) {...}
// ...
}
При необходимости обработку событий можно разнести в разные классы. Чтобы было понимание как работает процесс нужно написать пару модульных тестов - позитивный и негативные сценарии, ведь тесты в идеале - лучшая документация к коду. Ещё один плюс - в событийной стиле легко сделать весь процесс неблокирующим, например, через адаптер отправляя и принимая все события в Kafka. Да, есть ещё БД, запись в БД в эту парадигме - это такое же событие. В этом случае стоит посмотреть в сторону R2DBC https://www.baeldung.com/r2dbc Для REST endpoint и client есть Spring WebFlux.
К слову, Transaction script тоже может обеспечить неблокирующее выполнение, но только в языках программирования с async await: c#, python, rust https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/await
To be continued...
#patterns #saga #microservices #acid #arch_compromises
Продолжение про сагу.
Когда мы говорим про транзакции, сначала всплывает аббревиатура ACID. Транзакции должны обеспечивать принципы ACID. Посмотрим что тут у нас с сагой.
A - атомарность: или все выполняется, или все откатывается. Собственно атормарность есть в определении паттерна, см. выше. Единственное отличие - у нас нет волшебного rollback на всю распределённую транзакцию, бизнес логику отката придётся писать руками.
C - консистентность данных. Сага обеспечивает т.наз. eventually consistentcy - конечную согласованность. Т.е. данные будут согласованы только после окончания распределённой транзакции. В течение транзакции данные в разных микросервисах могут расходится. Транзакция в БД может обеспечить строгую согласованность изменяемых данных с нужным уровнем изоляции. Поэтому переходим к
I - изоляции изменений внутри транзакции от других операций. Сага не обеспечивает ее совсем, что с этим можно сделать описано в статье про это патерн от Microsoft по ссылке выше. Важный момент - в отличие от транзакции в БД, которая как правило длится миллисекунды, распределённая транзакция - это секунды, может даже десятки секунд. Несогласованность данных в течение этого времени из-за отсутствия изоляции нужно иметь в виду. В дополнение к описанным в статье по ссылке способом скажу ещё один - завершать транзакцию как можно быстрее и игнорировать несогласованность данных) Пример: клиент вряд ли будет жаловаться в службу поддержки, если после отмены заказа деньги и бонусы вернутся на счёт в течение минуты. И скорее всего будет - если это не будет сделано через час.
D - надёжность хранения данных, к саге отношения напрямую не имеет, обеспечивается используемыми хранилищами.
Т.к. в итоге мы получили ACD, причем неполноценный, то для распределенных транзакций придумали новую аббревиатуру - Basically Available, Soft-state, Eventually consistent - https://ru.m.wikipedia.org/wiki/Теорема_CAP#BASE-архитектура
Ещё один интересный момент про сагу: определение последовательности шагов - локальных транзакций. Единственно верной схемы нет, но есть рекомендации. Первая - fail fast. Т.е. если есть локальная транзакция, которая упадёт с большей вероятностью - ее нужно ставить вначале. Пример: резерв билета или товара. Вторая - если какая-то локальная транзакция проводит к критичной для клиента несогласованность данных - ее нужно выполнять как можно позже. Что делать, если эти рекомендации противоречат друг другу - зависит от сценария, но в целом я бы выбрал уменьшение времени неконсистентности.
Еще интересный момент касается саги в виде оркестрации. Т.к. ее главный плюс - сделать простой и понятной бизнес логику саги, то самая очевидная ее реализация вот такая:
class OrderSaga {
SagaResult execute() {
// шаг 1
// шаг 2
// ...
}
}
Назовём этот подход Transaction Script, есть такой Паттерн организации бизнес логики.
Просто - да. Но если процесс сложный, каждый шаг тоже, то мы ухудшим читаемость кода, получим замечание SonarQube про длину метода да и нарушим S из SOLID, принцип единой ответственности. Что делать? Использовать event driven подход:
class OrderSaga {
PrepareEvent start(...) {..}
ReserveEvent makeReservation(...) {...}
// ...
}
При необходимости обработку событий можно разнести в разные классы. Чтобы было понимание как работает процесс нужно написать пару модульных тестов - позитивный и негативные сценарии, ведь тесты в идеале - лучшая документация к коду. Ещё один плюс - в событийной стиле легко сделать весь процесс неблокирующим, например, через адаптер отправляя и принимая все события в Kafka. Да, есть ещё БД, запись в БД в эту парадигме - это такое же событие. В этом случае стоит посмотреть в сторону R2DBC https://www.baeldung.com/r2dbc Для REST endpoint и client есть Spring WebFlux.
К слову, Transaction script тоже может обеспечить неблокирующее выполнение, но только в языках программирования с async await: c#, python, rust https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/await
To be continued...
#patterns #saga #microservices #acid #arch_compromises
Baeldung
R2DBC – Reactive Relational Database Connectivity | Baeldung
A quick and practical overview of R2DBC - reactive database connectivity.
Всем привет!
Этим постом завершается серия по паттерну Сага.
В предыдущем посте забыл упомянуть 3-й и 4-й способ реализации Саги.
Третий - если вы используете BPMN движок, например, Camunda, то он отлично подходит для оркестратора Саги. Более того, использовать BPMN как оркестратор - лучшая идея, чем использовать его как среду для low-code разработки. Ну не верю я в low-code, не сталкивался с работающими кейсами) Главные плюсы BPMN в данном - случае готовая state machine и визуализация Саги. К слову сама Camunda поставила этот use case на первое место в списке https://camunda.com/solutions/microservices-orchestration/ что как бы намекает. На всякий случай: Camunda - это самый распространенный BPMN движок, собственно движок - opensource, платить нужно только за UI консоль.
Аналогично - если вы уже используете Apache Camel - он тоже умеет в сагу, https://camel.apache.org/components/4.4.x/eips/saga-eip.html
Тут встает вопрос - стоит ли внедрять данные инструменты только ради Саги? Базовый ответ нет, идеальный кейс: если какой-то из этих компонент уже у вас используется - логично реализовать оркестрацию с его помощью. Я бы внедрял, если бы были какие-то еще плюсы от использования, кроме собственно реализации паттерна.
Еще важный момент при реализации оркестратора - stateless или statefull? Да, любая бизнес операция имеет как минимум ID и состояние, которые нужно хранить. Но необязательно это делать в классе Саги. Особенно используя event driven подход, можно просто передавать все не необходимые данные в событиях\командах. Напомню, при этом сохранение состояния операции в БД - это тоже событие. Плюс такого подхода - не нужно думать о букве D из ACID, т.е. персистентности, для данных, хранимых в оркестраторе. А где персистентность, там и кэширование, т.к. обращение к БД - дорого. И восстановление данных из БД при сбоях. Поэтому если вы все же решили хранить состояние операции в коде - я бы рекомендовал не изобретать велосипед, а воспользоваться готовым фреймворком. Два из них я уже упомянул выше, но они достаточно "тяжелые". Вот еще несколько, заточенных собственно под паттерн Сага и под DDD, который в общем-то тесно связан с сагой. Ведь если мы делим систему на ограниченный контексты, Bounded Context, то их данные лежат в разных БД, а следовательно возникает распределенная транзакция...
1) Axios https://docs.axoniq.io/reference-guide/v/3.1/part-ii-domain-logic/sagas
2) Eventuate Tram Saga https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html
3) Seata https://www.seata.io/docs/user/mode/saga
Фреймворк помогает нам с:
а) персистентностью
б) кэшированием
в) созданием экземпляра саги для конкретной бизнес-операции
г) удобной работой с параметрами операции
При этом он не отменяет написания кода оркестрации и компенсирующих действий.
На этом пожалуй все.
Хотя нет. Остается вопрос - как же лучше реализовать Сагу? Ответ - лучше сделать свой ограниченный контекст = микросервис таким, чтобы Сага была не нужна)
А если серьезно.
1) постарайтесь использовать только локальные транзакции
2) если это не возможно, и у вас 2-4 шага - используйте хореографию
3) если шагов от 4+ и сервис создаётся с нуля - используйте оркестратор, для начала самописный, stateless event driven
4) у вас уже используется Camunda или Camel - делайте оркестратор на их основе
5) если вас нужен state - используйте фреймворки из последнего списка, например, Axios
6) если нужна сага и state machine - Camunda или Seata
#saga #microservices #ddd #patterns
Этим постом завершается серия по паттерну Сага.
В предыдущем посте забыл упомянуть 3-й и 4-й способ реализации Саги.
Третий - если вы используете BPMN движок, например, Camunda, то он отлично подходит для оркестратора Саги. Более того, использовать BPMN как оркестратор - лучшая идея, чем использовать его как среду для low-code разработки. Ну не верю я в low-code, не сталкивался с работающими кейсами) Главные плюсы BPMN в данном - случае готовая state machine и визуализация Саги. К слову сама Camunda поставила этот use case на первое место в списке https://camunda.com/solutions/microservices-orchestration/ что как бы намекает. На всякий случай: Camunda - это самый распространенный BPMN движок, собственно движок - opensource, платить нужно только за UI консоль.
Аналогично - если вы уже используете Apache Camel - он тоже умеет в сагу, https://camel.apache.org/components/4.4.x/eips/saga-eip.html
Тут встает вопрос - стоит ли внедрять данные инструменты только ради Саги? Базовый ответ нет, идеальный кейс: если какой-то из этих компонент уже у вас используется - логично реализовать оркестрацию с его помощью. Я бы внедрял, если бы были какие-то еще плюсы от использования, кроме собственно реализации паттерна.
Еще важный момент при реализации оркестратора - stateless или statefull? Да, любая бизнес операция имеет как минимум ID и состояние, которые нужно хранить. Но необязательно это делать в классе Саги. Особенно используя event driven подход, можно просто передавать все не необходимые данные в событиях\командах. Напомню, при этом сохранение состояния операции в БД - это тоже событие. Плюс такого подхода - не нужно думать о букве D из ACID, т.е. персистентности, для данных, хранимых в оркестраторе. А где персистентность, там и кэширование, т.к. обращение к БД - дорого. И восстановление данных из БД при сбоях. Поэтому если вы все же решили хранить состояние операции в коде - я бы рекомендовал не изобретать велосипед, а воспользоваться готовым фреймворком. Два из них я уже упомянул выше, но они достаточно "тяжелые". Вот еще несколько, заточенных собственно под паттерн Сага и под DDD, который в общем-то тесно связан с сагой. Ведь если мы делим систему на ограниченный контексты, Bounded Context, то их данные лежат в разных БД, а следовательно возникает распределенная транзакция...
1) Axios https://docs.axoniq.io/reference-guide/v/3.1/part-ii-domain-logic/sagas
2) Eventuate Tram Saga https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html
3) Seata https://www.seata.io/docs/user/mode/saga
Фреймворк помогает нам с:
а) персистентностью
б) кэшированием
в) созданием экземпляра саги для конкретной бизнес-операции
г) удобной работой с параметрами операции
При этом он не отменяет написания кода оркестрации и компенсирующих действий.
На этом пожалуй все.
Хотя нет. Остается вопрос - как же лучше реализовать Сагу? Ответ - лучше сделать свой ограниченный контекст = микросервис таким, чтобы Сага была не нужна)
А если серьезно.
1) постарайтесь использовать только локальные транзакции
2) если это не возможно, и у вас 2-4 шага - используйте хореографию
3) если шагов от 4+ и сервис создаётся с нуля - используйте оркестратор, для начала самописный, stateless event driven
4) у вас уже используется Camunda или Camel - делайте оркестратор на их основе
5) если вас нужен state - используйте фреймворки из последнего списка, например, Axios
6) если нужна сага и state machine - Camunda или Seata
#saga #microservices #ddd #patterns
Camunda
Orchestrate Microservices | Camunda
Overcome challenges of microservices orchestration with Camunda. Get speed, scale, and resiliency without compromising microservice autonomy. Learn how.
Всем привет!
Одна из моих любимых тем, вторая после читаемости кода, это скажем так "разработка - искусство компромиссов".
Редко встречаются ситуации, когда какой-то паттерн или принцип можно применять по формальным критериям всегда и везде. Более того, часто паттерны и принципы противоречат друг другу. Рассмотрим пример.
Есть такой паттерн, по моему опыту проведения интервью самый известный после синлетона - фабрика. А точнее абстрактная фабрика, именно такой паттерн описан в классике - Design Patterns: Elements of Reusable Object-Oriented Software. Суть его в том, что функция создания новых объектов выносится в отдельный класс. Создаваемые объекты являются наследниками какого-то базового класса или интерфейса. Классов фабрик может быть несколько, при этом каждая фабрика создает все (!) необходимые для проведения некой операции совместимые (!) между собой объекты. Вот более подробное описание с примером https://habr.com/ru/articles/465835/
Т.е. на первый взгляд паттерн работает четко в соответствии с принципом единственной ответственности (Single Responsibility) - создание отделено от доменного объекта, более того, для разных потребителей процесс создания объектов разнесен по разным фабрикам - ToyotaFactory и FordFactory из статьи выше.
А теперь изменим пример из статьи - будем создавать не разные типы кузовов автомобилей, а детали автомобиля. А деталей в авто подозреваю несколько тысяч... И список их более изменчивый, чем список типов кузовов. Т.е. по сути объединив в одном классе создание нескольких объектов мы уже заложили мину замедленного действия. Где находится грань между работой по Single Responsibility и его нарушением?
Базовый ответ был в первом абзаце - все зависит от бизнес-процесса. Но попробуем добавить конкретики.
Для начала можно вспомнить про лайфхак - можно оставить в фабрике один метод и передавать ему на вход Enum с типом создаваемого объекта.
class Factory {
SomeItem createSomeItem();
OtherItem createOtherItem();
}
vs
class Factory {
Item createItem(ItemType type);
}
Он немного упрощает добавление новых классов, т.к. не надо менять API фабрики, но в итоге приводит к тому же результату. Но дает нам подсказку: когда в Enum становится слишком много элементов - значит с фабрикой надо что-то делать.
Еще вариант - посмотреть, что скажет SonarQube. Он предлагает ограничиться 35 методами и 750 строками кода для одного класса. Как по мне - это много, я бы начинал делить фабрику на части раньше, при появлении 10-15 методов или по мере появления логических сущностей, позволяющих взять группу связанных методов из большой фабрики и вынести их в отдельную фабрику.
#patterns #solid #dev_compromises
Одна из моих любимых тем, вторая после читаемости кода, это скажем так "разработка - искусство компромиссов".
Редко встречаются ситуации, когда какой-то паттерн или принцип можно применять по формальным критериям всегда и везде. Более того, часто паттерны и принципы противоречат друг другу. Рассмотрим пример.
Есть такой паттерн, по моему опыту проведения интервью самый известный после синлетона - фабрика. А точнее абстрактная фабрика, именно такой паттерн описан в классике - Design Patterns: Elements of Reusable Object-Oriented Software. Суть его в том, что функция создания новых объектов выносится в отдельный класс. Создаваемые объекты являются наследниками какого-то базового класса или интерфейса. Классов фабрик может быть несколько, при этом каждая фабрика создает все (!) необходимые для проведения некой операции совместимые (!) между собой объекты. Вот более подробное описание с примером https://habr.com/ru/articles/465835/
Т.е. на первый взгляд паттерн работает четко в соответствии с принципом единственной ответственности (Single Responsibility) - создание отделено от доменного объекта, более того, для разных потребителей процесс создания объектов разнесен по разным фабрикам - ToyotaFactory и FordFactory из статьи выше.
А теперь изменим пример из статьи - будем создавать не разные типы кузовов автомобилей, а детали автомобиля. А деталей в авто подозреваю несколько тысяч... И список их более изменчивый, чем список типов кузовов. Т.е. по сути объединив в одном классе создание нескольких объектов мы уже заложили мину замедленного действия. Где находится грань между работой по Single Responsibility и его нарушением?
Базовый ответ был в первом абзаце - все зависит от бизнес-процесса. Но попробуем добавить конкретики.
Для начала можно вспомнить про лайфхак - можно оставить в фабрике один метод и передавать ему на вход Enum с типом создаваемого объекта.
class Factory {
SomeItem createSomeItem();
OtherItem createOtherItem();
}
vs
class Factory {
Item createItem(ItemType type);
}
Он немного упрощает добавление новых классов, т.к. не надо менять API фабрики, но в итоге приводит к тому же результату. Но дает нам подсказку: когда в Enum становится слишком много элементов - значит с фабрикой надо что-то делать.
Еще вариант - посмотреть, что скажет SonarQube. Он предлагает ограничиться 35 методами и 750 строками кода для одного класса. Как по мне - это много, я бы начинал делить фабрику на части раньше, при появлении 10-15 методов или по мере появления логических сущностей, позволяющих взять группу связанных методов из большой фабрики и вынести их в отдельную фабрику.
#patterns #solid #dev_compromises
Enterprise Craftsmanship
Domain model purity vs. domain model completeness (DDD Trilemma)
I’ve been meaning to write this article for a long time and, finally, here it is: the topic of domain model purity versus domain model completeness.
Всем привет!
AI быстро развивается, интегрируется с традиционным ПО и было бы странно, если бы и для AI не появились ... свои паттерны)
Встречаем, от одного из лучших специалистов по паттернам: https://martinfowler.com/articles/gen-ai-patterns/
Маленький комментарий: да, AI паттерны могут показаться элементарными, но свою роль они выполняют - это некий язык, кубики, из которых строится архитектура приложения/корпоративная архитектура.
Еще хорошо написано про такую важную штуку как оценка (eval). Ведь модели не идемпотентны - могут менять свой ответ на одних и тех же входных данных. А значит традиционные практики тестирования не подходят. Модель тестирующая сама себя - прямой путь к скайнету) А вот если взять другую модель, а для страховки отдать результат на проверку человеком...
#llm #ai #testing #patterns
AI быстро развивается, интегрируется с традиционным ПО и было бы странно, если бы и для AI не появились ... свои паттерны)
Встречаем, от одного из лучших специалистов по паттернам: https://martinfowler.com/articles/gen-ai-patterns/
Маленький комментарий: да, AI паттерны могут показаться элементарными, но свою роль они выполняют - это некий язык, кубики, из которых строится архитектура приложения/корпоративная архитектура.
Еще хорошо написано про такую важную штуку как оценка (eval). Ведь модели не идемпотентны - могут менять свой ответ на одних и тех же входных данных. А значит традиционные практики тестирования не подходят. Модель тестирующая сама себя - прямой путь к скайнету) А вот если взять другую модель, а для страховки отдать результат на проверку человеком...
#llm #ai #testing #patterns
martinfowler.com
Emerging Patterns in Building GenAI Products
Patterns from our colleagues' work building with Generative AI
Всем привет!
Я уже подымал тему готовых архитектурных решений, а точнее их отсутствия в большинстве случаев https://t.me/javaKotlinDevOps/134
Хочу развернуть тему с другой стороны.
Стоит ли тратить силы на поиск целевого архитектурного решения?
Написал эту фразу, и понял, что не всем она может быть понятна) Расшифрую. В больших компаниях ака "кровавый enterprise" есть некий список разрешенных технологий и архитектурных принципов. Оформленный в виде техрадара, карты технологических стеков и сборника архитектурных стандартов. Это и есть целевая архитектура.
Так вот, беда с этим стандартами одна - со временем их становится слишком много, понять - как сделать правильно, чтобы работало годами без переделки - сложно. Нужно на это тратить время: для того, чтобы обойти всех заинтересованных архитекторов, смежные команды, и выработать целевое решение.
Так вот - а надо ли его искать? Несмотря на то, что ответ вроде бы очевиден, хотел бы подсветить несколько потенциальных проблем.
Затрачивая время на поиск и согласование целевого решения мы взамен хотим получить уверенность, что решение с нами останется "на века". Так ли это? Нет, не так. Во-первых мир меняется очень сильно, бизнес задачи меняются вместе с ним. Во-вторых технологии меняются еще сильнее. В-третьих - "кассандр" среди нас мало, и если есть несколько разрешенных технологий - угадать правильную сложно.
К чему это приводит? Мы потратили время на выбор и реализацию "целевки", а через год нам говорят - переделывайте. Отрицание, гнев, фрустрация. Обида на архитекторов. Причем даже если архитектор признает ошибку (и вообще эта ошибка была) - вряд ли он поможет переписать код. Обида на менеджеров - да они издеваются что ли, вечно меняют правила игры, вечная миграция... Желание сменить компанию...
Поэтому видится, что есть более надежный подход.
1) смириться с тем, что все течет, все меняется, и миграции будут всегда
2) искать целевые решение, но всегда держать в уме, что это целевое решение на данный момент
3) разделить весь код на ядро и инфраструктурный код. Ядро стараться писать на чистой Java \ Kotlin, с минимальным использованием фреймворков. Особенно, внутренних, которые еще не доказали свою стабильность. Внешние интеграции закрывать - предохранительный слой (anticorruption layer), шлюзы (gateway), адаптеры.
4) очень важно - уметь и хотеть быстро выпускать релизы, разбивать любые доработки на небольшие инкременты. Это можно сделать как улучшением качества проектирования, увеличением покрытия тестами и автотестами, так и различного рода договоренностями со смежниками, (не забываем, что мы в "кровавом enterprise")
Если вам показывался знакомым последний пункт - то да, это Agile. Или то самое снижение Lead Time (LT), о котором любят говорить менеджеры. И не только говорить) Но в данном случае они правы.
Еще пример - фондовый рынок и диверсификация. Диверсификация считается основным принципом разумного инвестора, и означает, что нельзя "класть все яйца в одну корзину". Т.е. нужно покупать разные классы активов: акции, облигации, вклады, кэш, золото, недвижимость. Причина - сложно угадать, что именно "выстрелит". В случае кода сложно конечно реализовать диверсификацию прямолинейно: часть данных хранить в PostgreSQL, а часть - в Oracle. Да и не нужно. Но предусмотреть возможность замены поставщика - нужно.
#agile #arch #arch_compromisses
Я уже подымал тему готовых архитектурных решений, а точнее их отсутствия в большинстве случаев https://t.me/javaKotlinDevOps/134
Хочу развернуть тему с другой стороны.
Стоит ли тратить силы на поиск целевого архитектурного решения?
Написал эту фразу, и понял, что не всем она может быть понятна) Расшифрую. В больших компаниях ака "кровавый enterprise" есть некий список разрешенных технологий и архитектурных принципов. Оформленный в виде техрадара, карты технологических стеков и сборника архитектурных стандартов. Это и есть целевая архитектура.
Так вот, беда с этим стандартами одна - со временем их становится слишком много, понять - как сделать правильно, чтобы работало годами без переделки - сложно. Нужно на это тратить время: для того, чтобы обойти всех заинтересованных архитекторов, смежные команды, и выработать целевое решение.
Так вот - а надо ли его искать? Несмотря на то, что ответ вроде бы очевиден, хотел бы подсветить несколько потенциальных проблем.
Затрачивая время на поиск и согласование целевого решения мы взамен хотим получить уверенность, что решение с нами останется "на века". Так ли это? Нет, не так. Во-первых мир меняется очень сильно, бизнес задачи меняются вместе с ним. Во-вторых технологии меняются еще сильнее. В-третьих - "кассандр" среди нас мало, и если есть несколько разрешенных технологий - угадать правильную сложно.
К чему это приводит? Мы потратили время на выбор и реализацию "целевки", а через год нам говорят - переделывайте. Отрицание, гнев, фрустрация. Обида на архитекторов. Причем даже если архитектор признает ошибку (и вообще эта ошибка была) - вряд ли он поможет переписать код. Обида на менеджеров - да они издеваются что ли, вечно меняют правила игры, вечная миграция... Желание сменить компанию...
Поэтому видится, что есть более надежный подход.
1) смириться с тем, что все течет, все меняется, и миграции будут всегда
2) искать целевые решение, но всегда держать в уме, что это целевое решение на данный момент
3) разделить весь код на ядро и инфраструктурный код. Ядро стараться писать на чистой Java \ Kotlin, с минимальным использованием фреймворков. Особенно, внутренних, которые еще не доказали свою стабильность. Внешние интеграции закрывать - предохранительный слой (anticorruption layer), шлюзы (gateway), адаптеры.
4) очень важно - уметь и хотеть быстро выпускать релизы, разбивать любые доработки на небольшие инкременты. Это можно сделать как улучшением качества проектирования, увеличением покрытия тестами и автотестами, так и различного рода договоренностями со смежниками, (не забываем, что мы в "кровавом enterprise")
Если вам показывался знакомым последний пункт - то да, это Agile. Или то самое снижение Lead Time (LT), о котором любят говорить менеджеры. И не только говорить) Но в данном случае они правы.
Еще пример - фондовый рынок и диверсификация. Диверсификация считается основным принципом разумного инвестора, и означает, что нельзя "класть все яйца в одну корзину". Т.е. нужно покупать разные классы активов: акции, облигации, вклады, кэш, золото, недвижимость. Причина - сложно угадать, что именно "выстрелит". В случае кода сложно конечно реализовать диверсификацию прямолинейно: часть данных хранить в PostgreSQL, а часть - в Oracle. Да и не нужно. Но предусмотреть возможность замены поставщика - нужно.
#agile #arch #arch_compromisses
Telegram
(java || kotlin) && devOps
Всем привет!
Выпил бокал пива и захотелось немного пофилософствовать)
У меня часть просят готовое решение какой-то проблемы. Это может быть способ интеграции, выбор места для хранения данных, языка программирования, способ разбиения проекта на микросервисы…
Выпил бокал пива и захотелось немного пофилософствовать)
У меня часть просят готовое решение какой-то проблемы. Это может быть способ интеграции, выбор места для хранения данных, языка программирования, способ разбиения проекта на микросервисы…