Найти в Дзене
Что такое микросервис и с чем его едят?)
Всем привет! Поговорим про микросервисы и их преимущества. Статей будет несколько, от плюсов и минусов до саги и прочих паттернов. Для начала что такое микросервис. Я дам определение в виде набора обязательных признаков. Обязательны все из них. 1) одна команда 2) отдельная кодовая база 3) отдельная БД 4) отдельный pipeline 5) отдельный релизный цикл 6) небольшой объем кода 7) взаимодействие с другими сервисами через межпроцессное API. REST, gRPC, GraphQL, Kafka - это то, что сейчас на слуху, на самом деле технологий больше. Так какие же плюсы у микросервисов: 1) один сервис разрабатывает одна команда...
2 года назад
Всем привет! Сегодня расскажу про такую штуку, как rate limiters. Как следует из названия это компонент для ограничения числа запросов в интервал времени. Может возникнуть вопрос - причем здесь Java? Да, обычно такие штуки разворачивают на инфраструктуре - например, в k8s или nginx. Если так можно сделать - так и нужно делать) Когда так сделать не получится: 1) алгоритм ограничения числа запросов нестандартный, поэтому нужна Java, чтобы его запрограммировать) 2) нужна возможность продвинутого мониторинга числа пропущенных и отбитых запросов 3) при изменении параметров rate limiting нужна умная ребалансировка. Т.е. нельзя просто сбросить счетчики в ноль, т.к. это приведет к падению сервиса, который защищает rate limiter. 4) ну и наконец нет подходящей инфраструктуры Второй вопрос - в каких кейсах это может понадобиться: 1) не уронить стоящий за вами сервис 2) приоритизировать какие-то запросы вашего API по сравнению с другими чтобы не упасть самому 3) ограничить число запросов по тарифному плану клиента Вот неплохая статья про существующие алгоритмы rate limiting https://habr.com/ru/post/448438 Неплохая библиотека для Java - Backet4J https://github.com/bucket4j/bucket4j Развивается порядка 8 лет, к слову разработчики из России. Вот хорошее видео от разработчиков с примерам настройки - https://www.youtube.com/watch?v=OSNFNxgZZ3A В простых случаях можно использовать Resilience4j, удобно со Spring Boot посредством аннотаций https://www.baeldung.com/spring-boot-resilience4j Resilience4j библиотека более широкого профиля, отвечает за отказоустойчивость, см. детали в статье #java #libraries #highload #interview_question
2 года назад
Всем привет! Сегодня рассмотрим циклические зависимости. Начнем с уровня классов и дойдем до слоев приложения. 1) классы. Технически Java компилятор умеет компилировать взаимозависимые классы за счет того, что является многопроходным - https://ru.wikipedia.org/wiki/Многопроходный_компилятор Какие могуть быть проблемы? Главная - ухудшается читаемость кода. Да, я снова про нее) Вначале мы читаем метод класса А, затем переходим в зависимый класс Б, потом снова в А - в общем логику кода понять сложно. И это я привел достаточно простой пример. Соответственно такой код сложнее рефакторить, в примере выше изменение public Java API класса А или Б приведет к изменению и второго класса. Еще минус - невозможно использовать Dependency Injection через конструктор, а это наиболее естественный путь. 2) пакеты и модули. Проблема с вынесением общего кода. Если модуль содержит какую-то общую логику и при этом ни от чего не зависит - его вынести и переиспользовать в других проектах элементарно. А если за направлением зависимостей не следить - со временем общая логика начнет зависеть от прикладной и возникнет цикл. Об том, что это плохо говорит буква D из SOLID - абстракция не должна зависеть от деталей. 3) слои приложения. Число слоев может быть разным. В гексагональной архитектуре их по сути два - бизнес-логика и слой сервисов: веб-контроллеры, доступ к БД, вызовы внешних сервисов... https://lumpov.blogspot.com/2021/01/hexagonal-architecture.html В MVC - view, controller и model. Еще распространен такой вариант - presentation, services, model, storage. Но если образуется цикл, то вся система рушится как карточный домик. Слои имеют смысл, когда можно проследить направление запросов и зависимостей. А цель их применения: разделение бизнес-логики и инфраструктурного кода, что дает возможность достаточно легко сменить\добавить storage или внешнее API. Плюс слои по разному тестируются. #arch
2 года назад
Всем привет! Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет. С одной стороны паттерны - это лишь часть арсенала программиста. Можно заучить все паттерны, но не научиться кодить. И тут возникает второй вопрос - о каких паттернах мы говорим? 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
2 года назад
Всем привет! Не JUnit-ом единым... Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про важность читаемости кода, в т.ч тестового я уже писал. Пример для затравки из AssertJ assertThat(testList).hasSize(2)     .containsExactly("index3", "index4")     .isUnmodifiable(); Больше примеров см по ссылкам в конце поста. Библиотек три. 1) Hamcrest. Старичок, родоначальник жанра, уже не развивается, не рекомендуется к использованию 2) AssertJ - в отличие от hamcrest построен на принципе method chaining, что позволяет использовать автопополнение IDE и выглядит более читаемо. Выводит более понятное сообщение об ошибке, что тоже важно. Есть фича Soft Assertion, позволяющая лениво описать n проверок и выполнить их за раз. 3) Truth - очень похож по принципу работы - method chaining - на AssertJ, при этом менее известен. В качестве преимущества его разработчики указывают более компактное API и более понятное логирование ошибок. Как AssertJ, так и Truth позволяют создавать свои проверки. За деталями предлагаю пойти сюда: https://dzone.com/articles/hamcrest-vs-assertj-assertion-frameworks-which-one https://habr.com/ru/post/675778/ https://truth.dev/comparison.html #unittests
2 года назад
Всем привет! Ещё одно ревью на книгу, которую упоминал: Владимир Хориков Принципы юнит-тестирования. https://habr.com/ru/company/piter/blog/528872/ Можно сказать уникальная книга про то, как писать юнит тесты. Я знаю таких всего две - эту и Шаблоны тестирования XUnit. Must read. Некоторые моменты можно обсуждать, но откровенных косяков или воды не нашёл. Что запомнилось. 1) разделение кода на четыре группы: бизнес-логика, контролёр в широком смысле этого слова как код, обрабатывающий входящие запросы, тривиальный код и переусложненный код, в котором есть и внешние зависимости и бизнес-логика. От последнего нужно избавляться через рефакторинг, сводя его к бизнес-логике или контроллерам. На бизнес-логику пишем модульные тесты, на контроллеры - интеграционные. На тривиальный код ничего не пишем) 2) не нужно писать отдельные тесты на каждой класс бизнес-логики, нужно тестировать процессы и стремиться к максимальному покрытию. Для бизнес-логики 3) использование минимум mock в модульных тестах, в идеальном случае их вообще не должно быть. Т.е идея в том, что классы бизнес-логики получают и возвращают DTO, которые можно создать в тесте без всяких mock 4) в случае интеграционных тестов mock нужно делать на неконтролируемые нами внешние зависимости, типа очередей, внешних АС, email транспорта. БД в случае если это не наша частная БД. Если же у нас микросервис со своей БД, которая обновляется вместе с приложением и внешние клиенты в нее не ходят - ее mock-ать не нужно, более того и заменять ее HBase тоже не нужно, т.к иначе мы не сможем полноценно оттестировать работу с БД, да и возникнут накладные расходы на поддержание двух версий скрипов. Это как раз то, где можно дискутировать, но милая мне нравится. 5) чем меньше в приложении слоёв, тем лучше, проще тестировать 6) логирование может являться контрактом, например по требованию сопровождения или если на его основе работает мониторинг, тогда его тоже нужно mock-ать и тестировать. 7) организовать отправку событий, например, логирования, сообщений в Kafka или SMTP можно через стандартизацию механизма событий в доменной модели и обработку событий в слое «контроллеров». 8) если вернутся к модели тестов AAA - Arrange, Act, Assert, то Arrange и Assert в одном тесте может быть много, а вот Act должен быть один, иначе у нас проблемы с нашим Java API. Исключение - интеграционные тесты где сложно поднять контекст. 9) методы с инициализацией тестов типа @Before плохи тем, что сложно читать текст, т.к часть Assert в другом методе. Поэтому лучше использовать отдельные говорящие private/protected методы с параметрами, чтобы можно было настроить тестовые данные под каждый тест 10) название тестового метода должно читаться как фраза, разделять можно подчёркиваниями в случае Java, фраза не обязательно должна быть шаблонной как в BDD, краткость более важна. Кстати, в Kotlin слова в названии теста можно разделять пробелами. 11) интеграционные тесты дают хорошее покрытие, за счёт этого увеличивают защиту от ошибок в коде. Поэтому их лучше всего писать на позитивные сценарии, покрывающие максимум кода и те негативные сценарии, которые не удалось покрыть юнит тестами 12) антихрупкость тестов, о которой я уже писал, становится особенно важна по мере развития проекта. Т.е после некоторого времени более важно, чтобы тесты были антихрупкими, чем чтобы они находили все потенциальные ошибки. Т.к для последнего есть QA и приёмочные тесты Я перечислил лишь то, что запомнилось, интересным идеей в книге намного больше. Интересных и практических. Повторюсь - must read! #books #unittests
2 года назад
Всем привет! Уже упоминал книгу Идеальная работа Роберта Мартина https://habr.com/ru/company/piter/blog/679378/ Дочитал до конца и вот более полный отзыв. Вначале о хорошем: главы про применение TDD на практических задачах с livecoding - must read. Хороша глава про принцип YAGNI - you aren't gonna need it, особенно про то, как скорость современных компьютеров сделала этот принцип более актуальным. Хорошо написано про структуру presentation layer для упрощения тестирования этого слоя. Шаблоны тестирования там же. Хорошо про вязкость процесса программирования - скорость сборки и прогона тестов. Интересные данные про влияние парного программирования на скорость разработки. Только из-за этого книгу могу рекомендовать к прочтению. Но есть на мой взгляд и минус. Сквозь всю книгу проходит тезис про ответственность программиста за код. Вообще говоря мысль правильная. За любую работу нужно отвечать и делать ее качественно. Смущают три момента. 1) пафос, с которым это подаётся. Сразу вспоминается «Даешь пятилетку за четыре года». Или такой персонаж как Тони Робинсон. Делай хорошо, не делай плохо. Да, все тезисы обоснованы, но я бы добавил больше юмора, практических примеров и меньше пафоса. 2) ко многим вещам ты приходишь с опытом. Поэтому требовать одинаково с сеньора и джуна нельзя. Автор про это говорит, один раз, и все. Для кого тогда эти главы? 3) для доказательства хороших идей подтасовки не нужны. Пример с программистами из Volkswagen, которые обманули программу измерения выхлопов, выглядит именно подтасовкой. Вкратце - чтобы пройти новый экологический стандарт для дизельных двигателей руководство компании дало указание разработчикам так настроить программу работы двигателя, чтобы в тестовом режиме все было ок, хотя по факту он выделял больше вредных веществ, чем разрешено. Но штука в том, что с внедрением этого стандарта старые машины продолжают ездить по улицам. И писать что из-за плохих программистов машины причиняют вред здоровью людей... ну такое. Но, повторюсь - читайте главы про TDD, простоту кода, тестирования БД и UI, парное программирование и настройку проекта. #books #tdd
2 года назад
Всем привет. Немного про цену создания объектов в Java. Для короткоживущих объектов на последних версиях JVM выигрыш от переиспользования объектов про сравнению с созданием составляет пример 25%. Справедливости ради на Java 8 разница была в 40%, т.е garbage collection развивается. Описание эксперимента тут http://blog.vanillajava.blog/2022/09/java-is-very-fast-if-you-dont-create.html С другой стороны Java = объекты. Сборщики мусора стали достаточно умными, чтобы запускаться только тогда, когда памяти не хватает, и достаточно быстро убирать короткоживущихе объекты. Можно выбрать сборщик мусора либо с минимальными паузами, либо с минимальным overhead по ресурсам. Про выбор можно почитать тут hwww.baeldung.com/...thmПерераспределение памяти по ходу работы программы и роста heap можно убрать установив одинаковые xms и xmx, тогда JRE заберёт эту память из системы «навсегда» при старте приложения. Если на сервере много памяти и вы уверены, что heap-а точно хватит на все время работы программы - есть фейковый GC, который имеет ровно одну фичу - падать когда память кончается) hopenjdk.org/...318Когда это все не работает - примерно на десятках миллионах RPS как в статье из примера выше) #java #gc #performance
2 года назад
Всем привет! Снова про алгоритмы. Представим ситуацию, когда для каждого нашего объекта нужно получить адрес, где его хранить. Это может быть сервер в случае шардированной БД или корзина в HashMap. Хорошим решением является функция, которая однозначно по уникальному идентификатору нашей сущности выдаёт адрес сервера. Альтернативой является хранение таблицы соответствия отдельно, в случае распределённой системы это будет отдельный микросервис, что означает +1 запрос и увеличение задержек. Тривиальной реализацией является простой алгоритм - остаток от деления на число серверов. Но у такого есть минус - при изменении числа серверов распределение объектов по серверам или корзинам сильно меняется. Мы же хотим равномерное распределение. Если перестроение HashMap в памяти обычно (!) не проблема, то в случае десятка серверов в сети - ещё какая проблема. Для решения этой проблемы есть алгоритм консистентного хэширования. Он не защищает от перераспределения данных, но фиксирует их размер. https://jaidayo.livejournal.com/2645.html #algorithm #distributedsystem P.S. HashMap данный алгоритм не использует, на размерах порядка 1 млн записей рекомендуется сразу задавать начальный размер равный предполагаемому умноженному на 1.5 P.P.S. Хозяйке на заметку: в nginx, при использовании его в качестве балансировщика включить консистентное вычисление хэша от URL запроса можно одной строкой hash $request_uri consistent
2 года назад
Всем привет! Сегодня речь пойдет не про разработку, а про одну малоизвестную фичу git. Возможно кому-то пригодится. Встречайте - git notes. Что это такое? notes, они же заметки дают возможность добавить какие-то данные к commit не меняя commit. Может возникнуть вопрос - есть же amend? amend меняет commit, как файлы, так и его hash. Формат заметок может быть любым, в т.ч. бинарным. Ограничений по размеру не нашел. Где это может быть полезно? В notes можно хранить данные о сборке из CI системы, информацию, о прошедшем код-ревью. В теории даже артифакты сборки можно хранить, но я этого не говорил) Технически notes - это отдельные ветки с маcкой refs/notes/* При этом каждая note связана с commit. По умолчанию commit - текущий HEAD, ветка - refs/notes/commits. notes из ветки по умолчанию отображаются в выводе git log. Переключить текущую ветку можно в настройках [core] notesRef = Заметки можно добавлять, удалять, обновлять, подробнее см. https://git-scm.com/docs/git-notes Ветки с заметками можно мержить между собой, естественно, перед этим стоит продумать формат данных для избежания конфликтов. Т.к. заметки хранятся в отдельной ветке, ее нужно отдельно пушить и пулить. git push refs/notes/* git fetch origin refs/notes/*:refs/notes/* Также можно добавить ветки с notes в настройки git origin, и они будут забираться с сервера автоматически при git pull git config --add remote.origin.fetch +refs/notes/*:refs/notes/* На и наконец еще хорошая статья на эту тему: https://alblue.bandlem.com/2011/11/git-tip-of-week-git-notes.html #git #devops
2 года назад
Всем привет! Наткнулся недавно на статью в двух частях о Java stacktrace. Рекомендую! Из статьи можно узнать: 1) насколько "дороже" возврат из метода через исключения по сравнению с return. Спойлер - сильно дороже 2) как можно съэкономить при создании Exception 3) Как зависит "стоимость" исключения от глубины вызовов. Спойлерить не буду, чтобы не быть кэпом) 4) Плюсы нового API для разбора исключения java.lang.StackWalker. Это Java 9 5) Сколько вызовов влезет в стек при стандартных размерах стека. Спойлер - it depends Пример стектрейса для затравки: https://mattwarren.org/images/2016/12/Huge%20Java%20Stack%20Trace.png 6) Все возможные методы получения stacktrace у работающего и зависшего процесса. Спойлер - их много) Можно даже из консоли Windows\Linux 7) Немного о том, как работают профилировщики 8) И наконец как взломать JVM и украсть пароли из полей класса https://habr.com/ru/company/jugru/blog/324932/ https://habr.com/ru/company/jugru/blog/325064/ #java #debug
2 года назад
Всем привет! Хочу вернуться к теме "правильных" модульных тестов и подчеркнуть пару важных моментов. 1) должен быть быстрый набор тестов, который можно запускать после каждого изменения в коде. Почему это важно: после небольшого изменения в коде всегда понятна причина падения. После нескольких изменений подряд - уже сложнее. Быстрый - понятие нечеткое, но пойдем от обратного - 10 минут точно не быстро) 5 - тоже многовато IMHO. Идеально - минута, две. 2) как я уже писал - тесты должны быть антихрупкими. Хрупкие тесты с течением времени приводят к такому же результату, как и их отсутствие. Тесты часто падают, их отключают или не запускают, рефакторинг делать страшно, код объявляется legacy и переписывается. Как этого можно добиться: - не писать тесты на код, который постоянно меняется. Это один из возможных вариантов, не панацея!) Если это не бизнес-логика - это допустимо. В этом случае модульные тесты можно заменить на интеграционные, проверящие более высокоуровневый результат, которые реже меняется. - не проверять в тесте детали реализации, тестировать результат, который потребитель тестируемого метода ожидает от него. Хорошая статья на эту тему - https://habr.com/ru/company/jugru/blog/571126/ Тестируя только результат мы теряем точность теста, но увеличиваем антихрупкость. Это необходимый компромис. Исключение: сложные утилитные методы, где алгоритм - порядок вызовов - важен. 3) покрытие кода тестами - не панацея. С одной стороны покрытие 30-50% - плохо, т.к. показывает, что много кода не покрыто тестами. С другой стороны покрытие 100% не говорит, что код хороший. Почему: - не добавляяя Assert, добавив E2E тесты и закрыв их в try catch можно достичь очень хороших результатов по покрытию) - важно различать Line Coverage и Condition (Branch) coverage. Первое считает процент покрытых срок, второе - процент протестированных путей, по которым можно прийти от начала к концу метода. В случае SonarQube тут все хорошо - он считает свою метрику, которая совмещает обе https://docs.sonarqube.org/latest/user-guide/metric-definitions/ В теории если покрыты все условия, то и по строчкам кода должно быть 100%. Или в проекте есть код, который не используется. В общем метрике SonarQube можно верить) - предположим мы написали на первый взгляд полноценный набор тестов, с Assert-ми, все как положено. Покрытие 100% по новому коду. Но есть метод с побочным эффектом - он не только возвращает результат, но и сохраняет его внутри класса для отчетности или сравнения в будущих вызовах. Если этого не знать \ забыть - получим неполноценный тест. Конечно, есть Single Responsibility, неожиданные побочные эффекты это плохо, при TDD забыть про только что написанный код сложно, но в других случаях ситация может быть на практике. Другой кейс - тестируемый метод может вызывать библиотечную функцию, внутренности который мы не знаем. Соответственно, все возможные комбинации параметров для нее тоже не знаем. Не факт, что все такие комбинации нужно тестировать в конретном тесте - мы же не тестируем внешние библиотеки. Но факт, что какие-то важные кейсы для нашего бизнес-процесса можно упустить. 4) принцип Single Responsibility для теста звучит так: тест отвечает за одну бизнес-операцию, единицу поведения. Соотношение 1 тестовый класс = 1 тестируемый объект - не правило. Соответственно, в названии тестового класса не обязательно привязываться к названию тестируемого класса, тем более, что его в будущем могут отрефакторить. 5) ну и финальный момент - серебрянной пули нет, перед написанием тестов надо думать, что и как тестировать и выбирать наилучщий вариант. P.S. Также хочу посоветовать хорошую книгу про тесты от автора статьи из 2) - https://habr.com/ru/company/piter/blog/528872/ #unittests #books
2 года назад