Java: fill the gaps
12.1K subscribers
6 photos
191 links
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк

🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Какой основной тестовый фреймворк у вас на проекте?
Anonymous Poll
25%
JUnit 4
57%
JUnit 5
6%
TestNG
11%
Что-то другое
JUnit, часть 1: джентельменский набор

Список фич, которые пригодятся для большинства тестов. Если что-то не видели - почитайте, вдруг пригодится. У JUnit чудесная документация, так что подробно расписывать не буду.

1️⃣ Жизненный цикл теста

Каждый тест - это метод с аннотацией @Test.

Через аннотацию @DisplayName задаётся симпатичное имя теста в отчёте.

Чтобы выполнить что-то до или после выполнения теста, используются методы с аннотациями
▫️ @Before, @BeforeAll
▫️ @After, @AfterAll

JUnit создаёт новый экземпляр класса на каждый тестовый метод. Класс ServiceTest с пятью методами @Test во время запуска превратится в 5 экземпляров класса ServiceTest.

Благодаря этому тесты выполняются независимо.

Этим JUnit отличается от TestNG, где создаётся один экземпляр класса на все тестовые методы. Если хочется как в TestNG, добавьте над классом аннотацию @TestInstance(Lifecycle.PER_CLASS)

2️⃣ Проверки

Сердце каждого теста - методы с приставкой assert*:
🔸 assertTrue
🔸 assertEquals
🔸 assertInstanceOf

В самом JUnit мало методов, более удобные ассерты есть в библиотеках Hamсrest и AssertJ. AssertJ, на мой взгляд, более читабельный, но Hamсrest используется чаще.

3️⃣ Группировка тестов

Аннотация @Tag("groupName") объединяет тесты в группы. Работает и для одного теста, и для класса.

Можно указывать тэги в системе сборки и при запуске тестов из IDE.

4️⃣ Отключение тестов

Аннотация @Disabled. Продвинутые варианты для:
▫️ операционной системы
@DisabledOnOs(WINDOWS)

▫️ версии java
@DisabledOnJre(JAVA_9)
@DisabledForJreRange(min = JAVA_9)

▫️ системных переменных:
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")

5️⃣ Параметризированные тесты

Помогают запустить один тест с разными аргументами. Выглядит так:

@ParameterizedTest
@ValueSource(ints={100,-14})
public void test(int input) {}

Такой тест запустится дважды - с аргументом 100 и -14.

Вместо готового списка можно брать значения
🔸 из CSV файла @CsvSource
🔸 из метода @MethodSource

6️⃣ Проверка таймаута

▫️ Через ассерт
assertTimeout(ofMinutes(2), ()->{});

▫️ Через аннотацию
@Timeout(value=42,unit=SECONDS)

7️⃣ Полезные библиотеки

▫️ Hamсrest, AssertJ - расширенные библиотеки методов-ассертов
▫️ Mockito для заглушек. Добавляете библиотеку в pom.xml или build.gradle, а в тест - аннотацию @ExtendWith(MockitoExtension.class)
▫️ Testcontainers для запуска внешних компонентов в докере. Добавляем библиотеку, аннотацию @Testcontainers над классом и @Container над компонентом
▫️ Java Faker - генератор данных для тестов
JUnit, часть 2: зачем всё менять?

В JUnit сложилась странная ситуация.

JUnit 4 - топовый java фреймворк для тестов, который используется более чем в 100 тысячах проектов.

JUnit 5 вышел спустя 10 лет и обещает много нового. Но нельзя просто взять и перейти на 5 версию:
▪️ Аннотации вообще другие
▪️ Зависимостей стало больше
▪️ Для совместимости с JUnit 4 надо подключать какой-то винтаж.

Обратная совместимость - обычное дело для java приложений. В новой версии всё работает как раньше. Постепенно добавляешь новые фичи.

Почему JUnit не пошёл по этому пути?

Чтобы ответить на вопрос, нужно копнуть в архитектуру и кейсы использования.

JUnit создавался как удобная библиотека для написания тестов. Для разработчика есть API - аннотации @Test, @Before, методы assertEquals и тд. Здесь всё классно.

Дальше эти тесты запускает IDE или система сборки.

И вот им приходится тяжело. В JUnit 4 API для запуска и анализа тестов очень ограниченный, поэтому IDE и сборщики используют рефлекшн и другие обходные пути.

Чем плох такой подход - понятно. Любое изменение внутренней реализации ломает логику внутри IDE/системы сборки.

JUnit 5 учёл эту проблему и содержит три отдельных артефакта:
🔸 Jupiter - апи для разработчиков
🔸 Platform - апи для запуска и анализа тестов. Целевая аудитория - IDE, плагины и системы сборки. Теперь каждый из них может использовать библиотеку, а не писать свой велосипед
🔸 Vintage - для запуска JUnit 4 тестов на новой платформе

Почему у JUnit 4 и 5 разные аннотации?

У JUnit 5 абсолютно другая кодовая база. Для совместимости с 4 версией пришлось бы наворотить много кода. Гораздо практичнее вынести все адаптеры в отдельный компонент.

Тогда
▫️ Старые тесты будут работать
▫️ Чётко видно, где старые тесты, а где новые. А значит есть шанс, что со временем кодовая база с тестами перейдёт на новую версию.

Что здесь особенного?

В целом выглядит как обычный рефакторинг. Продукт развивается, мир меняется, монолит делится на составные части.

Но в этой истории есть две важные детали.

1️⃣ На страничке принципов разработки команды JUnit есть такие строки:

▫️ JUnit has never tried to be a swiss army knife
▫️ Third party developers move more quickly than we do

Отсюда видна ещё одна мотивация: поощрение развития других библиотек и фреймворков.

Другие разработчики тестовых библиотек теперь могут использовать JUnit платформу и автоматически получать поддержку библиотек во всех IDE и системах сборки.

2️⃣ Вторая инициатива команды JUnit - проект Open Test Alliance for the JVM.

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

Идея проекта - создать общую спецификацию для тестовых библиотек. Проект поддержали TestNG, Spock, Hamcrest, AssertJ, Eclipse, IntelliJ, Gradle, Maven и Allure.

Неизвестно, закончится ли эта история удачно, но идея классная.

Здорово, когда компания делает не только хороший продукт, но и способствует развитию отрасли в целом😇
Как вы думаете, во сколько обошлась переделка архитектуры JUnit и написание практически новой кодовой базы?
Anonymous Poll
11%
2 тысячи евро
13%
25 тысяч евро
29%
100 тысяч евро
32%
5 миллионов евро
14%
20 миллионов евро
JUnit, часть 3: модели кастомизации

Изменение архитектуры - не всё, чем JUnit 5 отличается от предыдущей версии. Второе отличие касается модели кастомизации.

В этом посте поговорим, зачем это нужно в тестовом фреймворке, и про разницу между 4 и 5 версией.

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

Допустим, нужно измерить время выполнения каждого теста: запустить таймер вначале и вывести время выполнения в конце.

Для одного класса это несложно - просто добавляем методы с аннотациями @Before и @After.

А как посчитать время для всех классов? Здесь варианта два:

🔸 Вынести общий код в отдельный класс, в каждый класс-тест добавить методы Before и After. Решение рабочее, но придётся копипастить методы в каждый класс.

🔸 Внедрить логику где-то на верхнем уровне и включать/выключать её через настройки или аннотации.

Это и есть кастомизация - предусмотренные библиотекой места "встраивания" новой логики. JUnit 4 и 5 используют для этого разные механизмы. Давайте кратко их обсудим.

JUnit 4 Runner

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

Примеры:
▫️ @RunWith(Parameterized.class) запускает параметризованные тесты
▫️ @RunWith(Suite.class) запускает наборы тестов
▫️ @RunWith(SpringJUnit4ClassRunner.class) добавляет спринговые активности до и после запуска теста
▫️ @RunWith(MockitoJUnitRunner.class) позволяет использовать заглушки

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

JUnit 4 Rule

Переопределяем интерфейс TestRule и задаём действие до и после выполнения теста. В тестах выглядит как просто поле:

@Rule
public Timeout globalTimeout = Timeout.seconds(10);

В JUnit 4 есть несколько готовых правил:
▪️ TemporaryFolder - создать временную папку для теста
▪️ ExternalResource - открыть и закрыть внешний ресурс(файл, сокет, БД)

Плюсы-минусы:

Можно использовать несколько rule в одном классе
Работает в рамках одного метода и по сути похож на before/after.

JUnit 5 Extension

Жизненный цикл теста разбивается на 10+ фаз. К каждой из них можно присоединиться, если переопределить нужный интерфейс:
▫️BeforeAllCallback - действие перед всеми тестами
▫️ParameterResolver - передача параметров в тест

Реализуем нужные интерфейсы, регистрируем класс и готово. Похожий механизм используется в Spring.

Класс может использовать несколько экстеншенов
Можно вклиниться на любых этапах жизненного цикла
В интерфейсах доступен контекст выполнения и вся информация про тесты, в итоге возможностей гораздо больше

В JUnit 5 полностью убрали поддержку Runner и Rule, всё переписано на Extension API. Кодовые базы стали несовместимы между собой, поэтому и нужна библиотека Vintage с адаптерами.
______

Разбирать чужие кейсы полезно, но не всегда увлекательно. Поэтому вот интересный факт про разработку JUnit.

JUnit - опенсорсный проект, где никто никому не платил за работу.

Но рефакторинг назревал много лет. Однажды ребята решили, что такие грандиозные планы требуют фулл тайм и объявили краудфандинг на JUnit 5.

Сумма требовалась небольшая - 25 тысяч евро, меньше двух миллионов рублей. В итоге собрали в 2 раза больше, и уже через 6 недель был готов первый прототип.

Меня это очень впечатляет, особенно в сравнении со стоимостью и скоростью разработки в энтерпрайзе🙈
Статистика по микросервисам

К концу года появляется много отчётов и статистики о состоянии JVM экосистемы. В целом ситуация не сильно отличается от прошлого года:

🔸 Самая популярная версия java до сих пор 8, используется на 69% проектов. Java 11 встречается на 36% проектов, а 12 и выше - на 16%.

🔸 Самый популярный фреймворк - Spring Boot: 65% проектов. На втором месте Spring MVC: 40% проектов.

🔸 Самая популярная IDE - Intellij IDEA, система сборки - Maven.

🔸 Трендовые GraalVM, Quarkus и Micronaut нашли применение где-то на 4% проектов, цифра примерно как в прошлом году.

Поэтому в этом посте поделюсь статистикой не по джаве, а по всему, что связано с микросервисами. Данные взяла из JRebel Java Developer Productivity Report 2021 и JetBrains The State of Developer Ecosystem 2021.

Демография: Россия - 3% участников, в основном ответы из США, Китая и Европы.

Итак,

49% проектов используют микросервисы
42% сидят с монолитом
10% применяют serverless архитектуру

На чём пишут микросервисы:
41% Java
37% JavaScript
25% Python

Сколько микросервисов на проекте:
34% Меньше пяти
36% 5-10
14% 10-20
16% 20 и больше

Как микросервисы общаются между собой?
83% HTTP-вызовы
47% Месседж брокер
25% WebSocket
20% RPC

С чем возникает много проблем:
30% Общение между сервисами
26% Масштабирование и производительность
24% Развернуть что-нибудь локально 🥲
14% Проблемы в коде микросервисов

Популярные performance проблемы:
54% Долгий Response Time
39% High CPU Usage
35% Утечки памяти
26% Много открытых соединений

Сколько времени занимает поднятие окружения на удалённой машине:
42% Меньше трёх минут
38% 3-10 минут
21% Дольше 10 минут

Только 20% разработчиков во время деплоя переключаются на другие рабочие задачи. Остальные 80% ходят за чаем, общаются с семьёй и листают соцсети🙂
Intellij IDEA: редактирование кода

Несколько простых приёмов для быстрой работы.

Легендарные комбинации
Ctrl + C
Ctrl + V
Ctrl + X
работают со всей строкой, на которой стоит курсор, не нужно ничего выделять.

Удалить всю строку:
Ctrl + Y

Дублировать строку:
Ctrl + D

Выделить часть кода:
Ctrl + W
При каждом нажатии W захватывается всё большая область.

Переместить выделенный код:
Ctrl + Shift + ⬆️
Ctrl + Shift + ⬇️
L is for Liskov

— SOLID принципы знаете?

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

Расшифровку знают многие. А вот практические знания часто ограничиваются Single Responsibility и Interface Segregation.

Как применять остальные буквы в ежедневной работе — не всегда понятно. Больше всего вопросов возникает насчёт L — Liskov Substitution Principle. О нём и будет сегодняшний пост.

Полное определение звучит так:

Пусть q(x) является свойством верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

По-простому: если заменить класс А на подкласс B, то система будет работать корректно и без неожиданных сайд-эффектов.

A service1 = new A();
A service2 = new B();

Для наблюдателя service1 и service2 ведут себя совершенно одинаково. Класс-наследник дополняет поведение родителя, а не замещает его. В результате система работает более предсказуемо.

Как это выглядит на практике:

1️⃣ Выходной тип метода в наследнике такой же как у родителя или расширенный

Базовый класс: Info getInfo()

Наследник:
 BigInfo getInfo()
Object getInfo()

2️⃣ Подклассы не бросают дополнительных исключений, но могут уменьшить их список

Базовый класс: void save() throws FileNotFoundException

Наследник:
 void save()
void save() throws FileNotFoundEx, InterruptedEx

Java — типизированный язык, поэтому пункты 1 и 2 контролируются компилятором.

3️⃣ Типы входных параметров те же или менее строгие. Пункт для общего понимания, тк для Java это неприменимо

Базовый класс: void add(Account acc)

Наследник:
 void add(Object acc)
void add(AdminAccount acc)

Следующие пункты компилятор уже не проверит, это целиком ответственность программиста.

4️⃣ Метод подкласса делает то же, что и метод базового класса

Базовый класс: метод countVisitors считает пользователей

Наследник:
Считает пользователей чуть по-другому
Считает пользователей, обновляет статистику, сохраняет результат в БД

5️⃣ Метод наследника взаимодействует с теми же сущностями:
▪️ Метод родителя увеличивает счётчик - подкласс тоже увеличивает
▪️ Метод родителя не меняет поле - подкласс тоже не меняет
▪️ Метод родителя вызывает другие методы в определённом порядке - подкласс делает то же самое

А что можно вообще?

Если в подклассе объявлены новые поля, то методы подкласса могут делать с ними что угодно. На этом всё🙂

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

Нарушения принципа подстановки — повод пересмотреть иерархию наследования или совсем от неё отказаться.
Паттерн проектирования, который в соответствии с принципом единственной обязанности передает другому объекту ответственность построения требуемых ему зависимостей внешнему, специально предназначенному для этого общему механизму - это:
Anonymous Poll
39%
Dependency Injection
9%
Dependency Invertion
39%
Inversion of Control
13%
Factory Method
Чем отличаются Dependency injection, Dependency invertion и Inversion of Control

Прошлый пост про Liskov, как говорится, "взорвал мой директ", поэтому на этой неделе расскажу про ещё два популярных принципа.

Сегодня про букву D из SOLID — Dependency Inversion. Что это, и чем отличается от Dependency injection и Inversion of Control. Понимание пригодится на собеседованиях, при чтении статей по дизайну и архитектуре.

Будем разбираться на простом примере: класс Service записывает логи в файл через класс FileLogger:

class FileLogger {…}
class Service {
FileLogger logger=new FileLogger();
}

Сделаем код чуть лучше с помощью разных принципов:

1️⃣ Dependency injection
— компоненты создаются не внутри класса, а где-то в другом месте.

Как реализовать: перенести инициализацию логгера в конструктор или сеттер:

class Service {
FileLogger logger;
Service (FileLogger logger) {
this.logger=logger;
}
}

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

⚔️Историческая справка
Когда Spring ещё не был популярен, в проектах использовался паттерн Service Locator.

Суть: компоненты создаются в классе ServiceLocator, а другие классы получают к ним доступ через статические методы:

class ServiceLocator {
static Logger logger = …
static Logger getLogger() {
return logger;
}
}

class Service {
Logger logger=ServiceLocator.getLogger();
}

2️⃣ Dependency invertion (D из SOLID)
▫️Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций
▫️Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции

Как реализовать: использовать интерфейс логгера, а не конкретный класс

interface Logger {…}
class FileLogger implements Logger {…}

class Service {
Logger logger=new FileLogger();
}

Интерфейс проще использовать, так как методов меньше
Реализацию легко заменить
Оба класса проще тестировать

Термин "абстракция" используется, потому что SOLID не привязан только к джаве. Группу методов можно выделить в интерфейс, в абстрактный класс и даже в обычный класс. Но интерфейс — наилучший вариант

3️⃣ IoC - Inversion of Control
В маленьких программах жизнь начинается в методе main. Программист создаёт объекты и вызывает их методы, все шаги явно прописаны.

Inversion of Control — это когда ход выполнения программы задаёт фреймворк. Например, Spring создаёт объекты, принимает запросы и не даёт программе завершиться.

Как реализовать: использовать аннотации фреймворка

@Component class FileLogger {…}
@Component class Service {
@Autowired
FileLogger logger;
}

Меньше скучного кода
Низкая связность — код легко читать, менять и тестировать

Резюме:
🔸Dependency injection — класс не создаёт компоненты напрямую, они передаются через конструктор или сеттер
🔸Dependency invertion — класс работает с другими компонентами через интерфейс
🔸Inversion of Control — ход программы задаёт фреймворк

❗️Ответ на вопрос перед постом:
Это словоблудие относится к Dependency injection
DRY для джуниора и сеньора

Раз уж пошли по базовым принципам, то сегодня разберём DRY: Don't Repeat Yourself.

Такой популярный и такой обманчиво простой.

Отношение к DRY эволюционирует по мере роста разработчика и проходит через четыре этапа:

🔸 Этап 1. Стажёр

Боготворит DRY, считает дублирование кода ужасным грехом. Действительно, зачем писать несколько раз одно и то же?

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

🔸 Этап 2. Джуниор

Любит DRY, но понимает, что любить — значит страдать.

Проект становится больше, а бизнес-логика — сложнее. Добавить в метод ещё один if уже не так просто. Сложно разбираться в коде, сложно писать тесты, но чего не сделаешь ради хорошего кода. А хороший код на этом этапе — это максимально сжатый код🙂

🔸 Этап 3. Мидл

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

🔸 Этап 4. Сеньор

Распилил монолит на сервисы, реализовал десятки крупных фич и отдал сердце SOLID.

А теперь по делу

Часто начинающие разработчики считают, что хороший код — это суперконцентрированный код с кучей паттернов и хитрых приёмов.

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

Если бизнес-процессы не пересекаются, то связывать их искусственно с помощью кода — плохая идея.

🙁 Класс User c 20 полями. 10 полей используются в 1 сервисе, другие 10 - в другом, половина объекта всегда пустая
🙁 Универсальный метод с 7 параметрами под разные случаи
🙁 Сложная иерархия с кучей шаблонных методов

Единственный плюс — меньше кода. Зато
Плохая читаемость
Сложно писать тесты
Сильная связность. Функцию тяжело поменять или вынести в другой модуль

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

По статистике 2021 года около 10% проектов уже используют архитектуру Serverless.

Новые технологии — это классно, но важно понять, в чём вообще проблема и как она УЖЕ решается текущими средствами. И зачем решать её по-новому. Нужен чёткий ответ:

— Зачем переходить с микросервисов на Serverless? Что мы от этого получим?

Для ответа немного углубимся в историю инфраструктуры.

1️⃣ Всё своё

До 2006 года в каждой IT компании была особая комната — серверная. Системные администраторы настраивали сервера, следили за обновлениями, безопасностью, блоками питания и решали тысячу других вопросов.

Много расходов на оборудование и персонал
Железки надо закупать, подключать и нельзя сдать обратно. Сложно наращивать мощности или оптимизировать нагрузку

2️⃣ IasS — Infrastructure as a Service

В 2006 появился Amazon EC2 и начался тренд на IaaS: код запускается не на собственных серверах, а на арендованных.

Это самый простой вариант облачной инфраструктуры. Сейчас подобную услугу предлагают AWS, Google, DigitalOcean, Microsoft, IBM, SAP, для патриотов есть Яндекс и Сбер.

Легко добавлять и сокращать ресурсы
Меньше расходы на персонал
Не надо беспокоиться об отключении электроэнергии и потопах

Инфраструктура всё ещё требует внимания — на арендованной виртуалке надо установить ОС, JVM, все службы и обновления, настроить компоненты и развернуть сервисы.

Большая часть этих операций почти у всех одинакова. Так что дальше расходимся на две ветки:

🅰️ PaaS — Platform as a Service

PaaS = IaaS + ОС + базовый мониторинг + легко подключаемые компоненты

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

Примеры PaaS: Heroku, AWS Elastic Beanstalk и тд

Много готовых компонентов — можно быстро настроить работающую систему
Набор компонентов большой, но всё же ограниченный. Если использовать что-то непопулярное, то придётся искать обходные пути
Сильная привязка к вендору. Выбрал PaaS от амазона — скорее всего файловое хранилище, очереди и БД тоже будут амазон.

🅱️ Докер

Популярная альтернатива PaaS — докер-контейнеры в предоставленных виртуалках. В каждом контейнере свой runtime и все нужные для сервиса файлы.

Для управления контейнерами есть куча инструментов — Kubernetes, Mesos, Google Container Engine. Amazon ECR и Google CR помогают с хранением докер-образов, AWS Fargate — с масштабированием.

Супер гибкость. Можно собрать любые компоненты с любыми настройками и связать их как угодно
Cложно выбирать и долго настраивать

Заметили, что не было ни единого слова про архитектуру? PaaS и Docker только упрощают сборку инфраструктуры. Пока что нет разницы, что запускать внутри — гигантский монолит или сервис из трёх файлов.

В следующем посте перейдём уже к Serverless. Поговорим, почему это называется архитектурой и что ещё готовы взять на себя облачные провайдеры.
Что такое Serverless, часть 2

В прошлом посте рассмотрели, как инфраструктура понемногу переходила на аутсорс. Serverless — следующий этап такого перехода.

AWS Lambda — самая первая и популярная платформа для Serverless, поэтому дальше буду говорить про неё.

Важно! Есть ещё термин Lambda Architecture — это вообще про другое.

Итак, в чём суть.

Любое приложение — это набор функций. Допустим, в интернет-магазине три функции:
▫️ Добавить товары
▫️ Сделать заказ
▫️ Вывести список текущих заказов

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

Serverless абстрагируется от этой проблемы. Мы не распределяем функциональность по артефактам и работаем просто с функциями. Проектируем не сборник фич, а отдельные маленькие компоненты.

Поэтому Serverless считается архитектурой. Или отдельной моделью проектирования, если термин "архитектура" кажется вам неподходящим.

Платформы, на которых запускаются Serverless приложения, называют FaaS — Functions as a Service.

В Петербурге Serverless используют около 10 компаний, что не очень много. Но тренд растёт, даже Сбер уже сделал свою FaaS платформу.

Как это работает?

Каждая функция состоит из:
▫️ Исполняемого кода
▫️ Списка зависимостей
▫️ Списка событий-триггеров
▫️ Конфигурации — количество памяти, необходимые права, время жизни функции и тд

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

Станет ли код проще?

Точно нет. Проектировать систему с изолированными функциями гораздо сложнее, чем слепить всё в монолит или десяток сервисов🙂

У Serverless свой деплой, тестирование и кодовая база. Даже если перенести только некоторые части приложения, общая схема заметно усложнится.

Зачем переходить на Serverless?

Если микросервисы в PaaS или докере нормально справляются, то должна быть веская причина что-то менять. Таких причин может быть две:

1️⃣ Функции развёртываются по необходимости. Облака это удобно, но иногда очень дорого. FaaS модель неплохо снижает стоимость при переменной нагрузке.

2️⃣ Масштабирование по умолчанию. В AWS Lambda автоматически масштабируются и сервисы, и остальные компоненты, например, БД. В докере и PaaS для этого нужно прилагать немало усилий.

Сколько стоит?

Допустим бэкенд мобильного приложения принимает за месяц 3 миллиона запросов, среднее время — 120мс. Выделим для одной функции 1536 МБ и процессор х86.
Плата за месяц в таком случае будет 2,7 доллара.

Это только AWS Lambda, остальные компоненты оплачиваются отдельно.

Что ещё

Никаких Ansible, Docker, bash скриптов
Базовый мониторинг и аналитика из коробки
Spring Cloud и плагин IDEA AWS Toolkit сильно облегчают разработку

😐 Нужны отличные навыки проектирования
😐 Своеобразное тестирование

Сложно адаптировать уже существующие приложения
Больше задержек, response time может увеличиться

В целом Serverless — очевидный тренд, который набирает обороты. Жду в ближайшие два года тонну докладов на конференциях на эту тему, как когда-то было про микросервисы и реактивное программирование🙂
Сколько объектов Number[] будет создано в этом коде?
Сколько объектов Number[] будет создано в этом коде?
Anonymous Poll
20%
0
16%
1
9%
2
44%
3
11%
6
Enum и метод values

Если вы новичок и мало знаете про enum, то лучше начать с этого лонгрида. Сегодня обсудим итерацию через метод values и как его оптимизировать.

Итак, enums — это синтаксический сахар, который при компиляции превращается в обычный класс. Класс из примера выше превратится в

public final class Number extends Enum<Number
>

Элементы енума станут статическими полями:

public static final Number ONE;
public static final Number TWO;
public static final Number THREE;

Внутри нового класса появится массив:

Number[] VALUES = { ONE, TWO, THREE};

И его копия будет возвращаться в методе values:

return VALUES.clone();

При каждом вызове values возвращается новая копия массива. Дело в том, что массивы — это изменяемый объект. Если возвращать ссылку на VALUES напрямую, любой желающий сможет поменять исходный массив:

Number.values()[2] = ONE;

Это небезопасно, поэтому каждый раз возвращается копия.

Если цикл с values используется в высоконагруженном коде, то разумно сохранить массив в отдельную переменную и переиспользовать её:

static Number[] numbers = Number.values();

for (Number n : numbers) {…}

Если код вызывается редко, то смысла в отдельной переменной нет.

Пример из жизни

В Spring Web 5.2 в классе HttpStatus есть такой код:

for (HttpStatus status : values()) {
if (status.value == statusCode) {
return status;
}
}

Этот цикл вызывается почти в каждом запросе, но только в этом году завели баг. К описанию прилагался бенчмарк: при нагрузке 600 запросов/сек код производил мегабайт мусора каждую секунду.

Теперь код выглядит так:

private static final HttpStatus[] VALUES;

static {
VALUES = values();
}

for (HttpStatus status : VALUES) {
if (status.value == statusCode) {
return status;
}
}

Ответ на вопрос перед постом

Будет создано 3 массива: один внутри класса Number и два клона при вызове values()
Потихоньку появляются доклады с Joker 2021. Конференции - это классно, но часто бывает, что:

▪️ Название доклада не отражает содержание
▪️ Тема интересная, но слушать тяжело
▪️ Доклад слишком простой или слишком сложный

Поделюсь с вами summary двух самых популярных докладов с последнего джокера.

Выделила основные идеи, полезные детали и сгруппировала всё, что получилось. Если тема заинтересует — посмотрите целиком. Если нет — сэкономите 2 часа жизни🙂

P.S. Все вопросы по содержанию не ко мне, а к спикерам
Два доклада с Joker 2021

1️⃣ Алексей Нестеров, Олег Докука — Что нового в Spring Framework 6 (1:10:58)

Оба спикера никак не связаны со спрингом, просто обсуждают слухи о нововведениях.

Spring 6 использует java 17 и не будет работать на версиях ниже.
Дата выхода - октябрь 2022.
Кодовая база Spring 6 использует модули (фича java 9). Можно скомпилировать только нужные модули, и итоговый проект займёт меньше места.

Spring Native переедет в Spring Boot 3.
Spring будет связывать некоторые бины на этапе сборки, а не в рантайме. Это ускорит запуск приложений и потребление памяти.

Поменяются имена некоторых модулей. Например, пакет javax.* станет jakarta.*
В Spring WebFlux добавится поддержка HTTPClient из JDK 11.

Что удалится: Hessian, Http Invoker, JMS Invoker, JAX-WS, SecurityManager.

Что удалится, но менее вероятно: поддержка Joda Time, RxJava 1/2, FactoryBean, Autowired через сеттеры, конфигурация через XML.

Зачем смотреть: незачем. Информация недостоверная, много догадок, мало деталей.

2️⃣ Иван Крылов — От 11 к 17 версии Java (55:07)

Это доклад НЕ про новые фичи языка. Про sealed классы, records и switch спикер отсылает на доклад Тагира Валеева Java 17 для тех, кто в танке.

Два подхода к обновлению версии java в проекте:

1) Каждые полгода переходить на новую версию

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

Плюс — всегда новая и поддерживаемая версия java
Минус — раз в полгода тратить несколько спринтов на переход

2) Переходить только на LTS версию

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

Переезд на java 17 — это необходимость. Дело не в новых фичах, а в том, что поддержка старых версий ограничена и периодически прекращается. Миграция с 8 до 17 сложная, с 11 до 17 попроще.

С чем могут быть проблемы при переходе

▫️ Удалены финалайзеры из FileOutputStream, FileInputStream, zip.Deflater, zip.Inflater, zip.ZipFile, color.ICC_Profile. Это связано с предстоящим удалением финалайзеров в java 18.
▫️ Удалён конструктор URLDecoder.
▫️ Многие классы в AWT и Swing поменяли видимость.
▫️ Удалён Applet API.

Изменения виртуальной машины

▪️ GraalVM больше не связан с OpenJDK. Причина — административные проблемы и сложности с версионированием относительно OpenJDK.
▪️ Удалена оптимизация biased locking из-за плохой совместимости с Project Loom.
▪️ Добавлены CDS архивы для уменьшения времени старта VM.
▪️ Гибкий metaspace.

Изменения в сборщиках мусора

▫️ Удалён CMS.
▫️ ZGC готов к использованию в продакшене с 15 версии, в ранних версиях очень много багов.
▫️ G1 и Shenandoah возвращают неиспользуемую память ОС. Это уменьшает потребляемую память сервиса, и сервисы в облачной среде обойдутся дешевле.

Изменения в JDK

▪️ Классы по работе с TCP и UDP работают быстрее — DatagramSocketAPI, SocketAPI.
▪️ NullPointerException показывают больше информации.
▪️ Новый генератор случайных чисел.

Изменения Security

▫️ Добавлены Root сертификаты с разным сроком действия
▫️ Удалён SecurityManager
▫️ Удалены модули java.security, java.rmi.activation

Платформы

▪️ Добавилась поддержка Linux Alpine. ОС полезна для маленьких контейнеров в облаках. В ОС меньше команд, поэтому она более безопасна. Если маленькие контейнеры не нужны, то лучше выбрать другую ОС.
▪️ Полная поддержка ARM: унифицированный Linux порт, Windows, macOS

Какие проекты не успели войти в java 17

▫️ Loom: легковесные потоки в JVM
▫️ Panama: работа с нативным кодом, альтернатива JNI
▫️ Valhalla: новые типы данных value types — функциональные как классы и компактные как примитивы

Зачем смотреть: незачем. Большую часть видео перечисляются неязыковые фичи между 11 и 17 версией. О них говорят редко, так что для кого-то информация окажется новой. Но с тем же успехом можно посмотреть полный список фич и поискать слова, которые актуальны для вашего проекта или интересны лично вам.
State of spring 2021

Поделюсь статистикой State of spring 2021 от VMWare. Данные релевантны для энтерпрайзных проектов из Европы и США.

Сама статистика довольно скучная, поэтому дам небольшое овервью по некоторым модулям.

🔸 79% используют Spring Security в рабочих проектах

🔸 79% Spring Data

Простые интерфейсы для работы с данными из разных БД. Обычно используется в связке с:
▫️ 78% JPA
▫️ 74% JDBC
▫️ 46% MongoDB
▫️ 37% Redis
▫️ 31% ElasticSearch

🔸 73% WebMVC
🔸 61% Boot
🔸 39% Kafka

🔸 38% Batch — фоновая обработка большого количества данных

🔸 37% Cloud

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

Берёт на себя типовые задачи в микросервисной архитектуре:
▫️ Service Discovery — получить адрес другого сервиса
▫️ Добавить в логи информацию про конкретный сервис

🔸 35% WebFlux — поддержка реактивных библиотек

🔸 32% Integration

Ещё один уровень абстракции и набор готовых компонентов, которые соединяются через Enterprise Integration Patterns. Через конфигурацию можно описать несложную логику вроде "прочитай XML, преврати в JSON, отправь по HTTP".

🔸 3% Native

Компиляция и запуск на GraalVM. Уменьшает время старта и потребление памяти, хорошо работает с контейнерами.
Большинство опрошенных в восторге от Native, но пока не торопятся внедрять в рабочий проект. Потому что Native пока в стадии бета и GraalVM слишком незрелая технология. Но 65% планируют использовать Native в будущем.
Мой вариант — последний😊 Чищу мандаринки, смотрю статистику канала и радуюсь.

Спасибо, что читаете нудные посты без картинок, помогаете найти ошибки и задаёте интересные вопросы. Благодаря вам канал живёт и развивается!

Продолжим этот движ в 2022🚀