×Закрыть

Slim Docker image, или Как уменьшить вес Java-приложения

Приветствую, дорогой читатель DOU! Меня зовут Ростислав, я Java-разработчик в DGN Games, где работаю уже третий год. Это продуктовая международная компания, где большая команда занимается созданием онлайн-игр. Здесь я получил огромный опыт как в поддержке и доработке высоконагруженной системы, так и в построении микросервисной архитектуры приложения с нуля с использованием современного Spring Boot стека (включая всеми любимый Kubernetes).

В этой статье не будет информации о сборке кастомной ОС, сравнения существующих ОС, версий Java, документации по работе с Docker, так как подразумевается, что ты умеешь написать свой Dockerfile и собрать образ на его основе. Зато будет рассказ о том, как мне удалось построить Docker-образ весом всего ~100-200 MB, базирующийся на Debian Buster slim, с использованием Java (версия 13.0.2).

В чем проблема

Если ты читаешь эту статью, значит, так же, как и я, интересуешься новыми технологиями в мире Java. Сейчас существует много готовых решений для быстрого старта проектов любой сложности, приложения пишутся очень быстро, и бизнес хочет получать результат как можно скорее, чтобы целевая аудитория смогла воспользоваться фичей в кратчайшие сроки. А это влечет за собой частые деплои на prod, stage-энвайронменты и т. д.

Все Java-разработчики знают, что приложение может весить, например, 100 MB (вместе с зависимостями), но, чтобы оно успешно запустилось и выполняло свою задачу, ему нужен еще JRE (Java Runtime Environment), который весит в среднем 200 MB. Сюда же нужно добавить операционную систему (далее — ОС), где будет исполняться JRE, а это еще ~50-100 MB (если использовать slim-сборку). Итого получается 350-400 MB.

Размещая Docker-образы (images) в каком-нибудь удаленном хранилище (Container Registry), например ECR, можно значительно сократить время, потраченное на передачу (загрузку или скачивание) данных, чтобы размер образа занимал всего ~100-200 MB. Помимо времени, сокращается также и плата за тарификацию удаленного хранилища: например, ECR берет плату за объем хранилища и количество переданных данных (Data Transfer). Поэтому, экономя деньги компании и время на доставку образов, я написал эту статью, которая покажет, как уменьшить вес Java-приложения с использованием готовых инструментов JDK.

Напишем простое приложение

Я буду использовать Spring Boot версии 2.2.4.RELEASE, куда подключу базовые зависимости:

  • spring-boot-starter-web — для написания простого REST-эндпоинта и запуска приложения в embedded Tomcat.
  • spring-boot-starter-log4j2 — для работы с логированием от Apache Log4j 2.
  • jackson-dataformat-yaml — чтобы Spring смог проинициализировать конфигурацию логирования из YAML-файла.

Собирать приложение будет Maven, поэтому опишем для него POM-файл следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>slim-docker-image</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>13</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-websocket</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Само приложение не представляет из себя ничего интересного, поэтому не буду уделять ему много внимания, а просто покажу класс контроллера:

package com.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import static java.lang.String.format;

@Slf4j
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(@RequestParam(required = false) String name) {
        if (name == null) {
            name = "World!";
        }
        String response = format("Hello, %s", name);
        log.info("Created response '{}'", response);
        return response;
    }
}

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

После запуска вызовем эндпоинт GET запросом по адресу: localhost:8801/hello?name=Rostyslav

В итоге увидим в консоли примерно следующие логи:

Проанализировав содержимое, можем сделать вывод: приложение успешно выполняет свою задачу.

А что получается по размеру Jar?

Размер получился маленький (всего 17,43 MB), за счет того, что были подключены только базовые зависимости; ничего особенного и слишком тяжелого не используется. Но в любом другом большом проекте, где используется много различных библиотек и фреймворков, итоговый размер может достигать 100 MB и даже больше.

Сборка Docker-образа

Возьмем за основу openjdk:13.0.2-slim-buster. Его размер составляет 409 MB:

Напишем простой Dockerfile:

FROM openjdk:13.0.2-slim-buster
RUN mkdir -p /jar
COPY ./target/slim-docker-image.jar /jar/app.jar
ENTRYPOINT ["java","-jar","/jar/app.jar"]

и соберем образ. Итоговый размер получился 427 MB:

Теперь можно запустить контейнер на основе этого образа и убедиться, что приложение успешно запускается, как запускалось и без использования Docker:

Теперь проанализируем, сколько занимает установленный туда JDK 13.0.2:

Как видно на скриншоте, JDK занимает 316 из 409 MB размера всего образа.

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

Модули, jdeps и jlink

Для сборки кастомного slim JRE я буду использовать модульную систему, которая введена с 9-й версии Java. Полное ее название — Java Platform Module System, или JPMS. На помощь также придут инструменты jlink и jdeps, которые поставляются вместе с JDK.

При использовании jlink на выходе получается «урезанный» JRE, который будет включать только те модули, которые требует приложение и его зависимости, а все остальные модули просто не будут включены в сборку.

Прежде чем приступать к процессу сборки, нужно определиться, какие именно модули необходимы для запуска приложения. Для этого мы можем использовать инструмент jdeps. Он позволяет проанализировать JAR и вывести использующиеся модули. Но я бы хотел сразу прояснить некоторые моменты по этому инструменту и рассказать, почему я все же его не использую. Чтобы добиться правильного отображения использующихся модулей, надо потратить много времени, но каждый раз вылазит очередная ошибка, и ты пробуешь нагуглить решение. Например, возьмем следующую команду:

jdeps -cp "java -jar target/slim-docker-image.jar --thin.classpath" target/slim-docker-image.jar

Получается такой результат:

Как видим, есть модули not found, что не дает понимания, каких модулей в итоге не хватает. Изучая вопрос сборки JRE, я так и не смог добиться успешного вывода всех модулей из JAR, поэтому решил, что если даже после использования jdeps может броситься ClassNotFoundException, то почему бы самостоятельно не перебрать недостающие модули, запуская приложение на собранном JRE и анализируя stack trace? Тем более это не отнимает много времени, да и вообще, я уверен, что для других проектов не придется пересобирать новый JRE каждый день и даже каждый месяц. Это нужно будет сделать только в случае добавления новых зависимостей, да и то если они используют какой-то другой модуль, которого нет в сборке.

Итак, абсолютно для любого приложения требуется модуль java.base. Поэтому берем его и запускаем команду:

jlink --no-header-files --no-man-pages --compress=2 --strip-java-debug-attributes --add-modules java.base --output slim-jre

Подробнее обо всех параметрах можно почитать по ссылке, там все объяснено доступным языком.

Пробуем запустить приложение на собранном JRE:

Бросился ClassNotFoundException: java.beans.PropertyChangeEvent. Идем сюда, в строку поиска вводим java.beans.PropertyChangeEvent и смотрим на имя модуля:

Добавляем его к команде jlink:

jlink --no-header-files --no-man-pages --compress=2 --strip-java-debug-attributes --add-modules java.base,java.desktop --output slim-jre

В этот раз бросается ClassNotFoundException: javax.naming.NamingException.

Снова идем в поиск и ищем javax.naming.NamingException, добавляем его модуль java.naming. Повторяем манипуляции до тех пор, пока приложение не запустится.

Во время поиска модулей по какому-то из классов может быть так, что поиск не выдает результатов. Это значит, что ты пытаешься найти класс из модуля jdk.unsupported, поэтому берем и смело добавляем его к команде.

В итоге получился такой список модулей:

java.base,java.desktop,java.naming,java.management,java.security.jgss,java.instrument

Можем запустить приложение на собранном JRE и убедиться, что оно успешно работает, как и раньше:

Docker-образ с кастомным JRE

Берем за основу Dockerfile, который используется в openjdk, и модифицируем его таким образом, чтобы вместо установленного JDK использовался кастомный JRE. Ниже представлен уже готовый к использованию файл:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
FROM debian:buster-slim

RUN set -eux; \
	apt-get update; \
	apt-get install -y --no-install-recommends \
# utilities for keeping Debian and OpenJDK CA certificates in sync
		ca-certificates p11-kit \
	; \
	rm -rf /var/lib/apt/lists/*

# Default to UTF-8 file.encoding
ENV LANG C.UTF-8

ENV JAVA_HOME /usr/java/openjdk-13
ENV PATH $JAVA_HOME/bin:$PATH

# backwards compatibility shim
RUN { echo '#/bin/sh'; echo 'echo "$JAVA_HOME"'; } > /usr/local/bin/docker-java-home && chmod +x /usr/local/bin/docker-java-home && [ "$JAVA_HOME" = "$(docker-java-home)" ]

# https://jdk.java.net/
# > Java Development Kit builds, from Oracle
ENV JAVA_VERSION 13.0.2
ENV JAVA_URL https://download.java.net/java/GA/jdk13.0.2/d4173c853231432d94f001e99d882ca7/8/GPL/openjdk-13.0.2_linux-x64_bin.tar.gz
ENV JAVA_SHA256 acc7a6aabced44e62ec3b83e3b5959df2b1aa6b3d610d58ee45f0c21a7821a71

RUN set -eux; \
	\
	savedAptMark="$(apt-mark showmanual)"; \
	apt-get update; \
	apt-get install -y --no-install-recommends \
		wget \
	; \
	rm -rf /var/lib/apt/lists/*; \
	\
	wget -O openjdk.tgz "$JAVA_URL"; \
	echo "$JAVA_SHA256 */openjdk.tgz" | sha256sum -c -; \
	\
	mkdir -p "$JAVA_HOME"; \
	tar --extract \
		--file openjdk.tgz \
		--directory "$JAVA_HOME" \
		--strip-components 1 \
		--no-same-owner \
	; \
	rm openjdk.tgz; \
	\
	jlink --no-header-files \
          --no-man-pages \
          --compress=2 \
          --strip-java-debug-attributes \
          --add-modules java.base,java.desktop,java.naming,java.management,java.security.jgss,java.instrument \
          --output /usr/java/slim-jre \
    ; \
    rm -r $JAVA_HOME; \
    mv /usr/java/slim-jre $JAVA_HOME; \
	\
	apt-mark auto '.*' > /dev/null; \
	[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
	apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
	\
# update "cacerts" bundle to use Debian's CA certificates (and make sure it stays up-to-date with changes to Debian's store)
# see https://github.com/docker-library/openjdk/issues/327
#     http://rabexc.org/posts/certificates-not-working-java#comment-4099504075
#     https://salsa.debian.org/java-team/ca-certificates-java/blob/3e51a84e9104823319abeb31f880580e46f45a98/debian/jks-keystore.hook.in
#     https://git.alpinelinux.org/aports/tree/community/java-cacerts/APKBUILD?id=761af65f38b4570093461e6546dcf6b179d2b624#n29
	{ \
		echo '#!/usr/bin/env bash'; \
		echo 'set -Eeuo pipefail'; \
		echo 'if ! [ -d "$JAVA_HOME" ]; then echo >&2 "error: missing JAVA_HOME environment variable"; exit 1; fi'; \
# 8-jdk uses "$JAVA_HOME/jre/lib/security/cacerts" and 8-jre and 11+ uses "$JAVA_HOME/lib/security/cacerts" directly (no "jre" directory)
		echo 'cacertsFile=; for f in "$JAVA_HOME/lib/security/cacerts" "$JAVA_HOME/jre/lib/security/cacerts"; do if [ -e "$f" ]; then cacertsFile="$f"; break; fi; done'; \
		echo 'if [ -z "$cacertsFile" ] || ! [ -f "$cacertsFile" ]; then echo >&2 "error: failed to find cacerts file in $JAVA_HOME"; exit 1; fi'; \
		echo 'trust extract --overwrite --format=java-cacerts --filter=ca-anchors --purpose=server-auth "$cacertsFile"'; \
	} > /etc/ca-certificates/update.d/docker-openjdk; \
	chmod +x /etc/ca-certificates/update.d/docker-openjdk; \
	/etc/ca-certificates/update.d/docker-openjdk; \
	\
# https://github.com/docker-library/openjdk/issues/331#issuecomment-498834472
	find "$JAVA_HOME/lib" -name '*.so' -exec dirname '{}' ';' | sort -u > /etc/ld.so.conf.d/docker-openjdk.conf; \
	ldconfig; \
	\
# https://github.com/docker-library/openjdk/issues/212#issuecomment-420979840
# https://openjdk.java.net/jeps/341
	java -Xshare:dump; \
	\
# basic smoke test
#	javac --version; \
	java --version

# "jshell" is an interactive REPL for Java (see https://en.wikipedia.org/wiki/JShell)
CMD ["jshell"]

В строке 47 я добавил создание кастомного JRE, в строках 54 и 55 удалил установленный JDK и вместо него поместил собранный.

В строке 51 можно расширять модули, которые будут нужны для других проектов.

Теперь самое интересное. Собираем образ и смотрим на его размер:

Получилось 141 MB вместо 409 MB, что не может не радовать.

Теперь можно отправить его, например, в Docker Hub или любое другое хранилище и использовать как основу для образа приложения.

Модифицируем Dockerfile-приложения

1
2
3
4
FROM rostyslavm/slim-jre:1.0.0
RUN mkdir -p /jar
COPY ./target/slim-docker-image.jar /jar/app.jar
ENTRYPOINT ["java","-jar","/jar/app.jar"]

В строке 1 я использую образ, который отправил в Docker Hub.

После сборки образа, используя вышеуказанный файл, получаем slim-вариацию, которая весит 159 MB вместо 427 MB:

Создаем контейнер и убеждаемся, что приложение успешно работает:

Спасибо за внимание! Буду рад получить фидбэк, это будет мотивировать меня писать и на другие темы.

Если остались какие-то вопросы, можно писать в комментариях к статье и стучать мне в Telegram.

LinkedIn

35 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Спасибо за статью, но как тут уже ответили в комментариях, это решение нужно автоматизировать, чтобы стоило использовать его на production. Представьте, что вы написали новую строчку кода, которая требует модуль, отсутствующий в вашей сборке. У вас приложение падает при запуске в Docker. Вам нужно снова искать этот модуль, добавлять его и т.д.

если вас парит размер образа и скорость запуска, накуй спринг, используйте микронаут или spark (тот который не апач) . spring boot starters очень много за собой тащат неявных зависимостей

Ну во-первых не Spring, а Spring Boot, ну и все равно Spring Boot добавляет не больше 10-15 мегабайт, которые на фоне Docker образа не играют большой роли

Я тільки не зрозумів, а яка кінцева мета цієї економії? Зменшити розмір заради просто зменшити розмір?

Значит нужно прочитать статью ещё раз

Якщо потрібно ще раз читати статтю, щоб зрозуміти, яка її ідея, то, смію припустити, щось все таки не так із статтею. Читають ще раз, щоб розуміти деталі, якщо ідея незрозуміла зразу, то це інша історія
Ну, але то таке.

ECR берет плату за объем хранилища и количество переданных данных (Data Transfer). Поэтому, экономя деньги компании и время на доставку образов, я написал эту статью

Меньше ста баксов в месяц за терабайт ECR хранилища — это не те деньги имх, ради которых стоит так экономить, каждый раз пересобирая базовые образы jre. Понимаю в уменьшении размера есть смысл, когда разработчики часто запускают приложения локально и по каким либо причинам образы в офисную сеть качаются медленно, или когда размер образов сильно влияет на время выполнения CI джоб. Но и в таком случае кэшировать лэеры контейнеров в амишках/на локальных машинах выглядит проще, чем постоянно мучаться с зависимостями. Особенно в случае джавы.

Кстати, можно просто поставить Нексус, а там и Докер хранилище, и ещё много чего на все случаи жизни, что многим и так понадобится...

Не самое оптимальное решение. Я для создания базового образа использую сборочный контейнер, а сам базовый образ делаю на основе distroless base. Получается проще и меньше весом.

github.com/...​ge/blob/master/Dockerfile

А для сборки образа с приложением использую jib, он разбирает спринг бутовый fat jar на оптимальные слои.

всегда удивляли такие статьи
да не пофиг ли какой размер образа?
главное, что бы все приложения использовали одни и те же слои, тогда их будет кешировать
openjdk:13-jdk-alpine — например
и все N микросервисов его используют

причем всегда лепят uber jar на сотни мегабайт
хотя само приложение пару мегабайт + зависимости, которые редко меняются

maven-dependency-plugin — сложит все зависимости в /target/dependency/
а потом в докере

COPY ./target/dependency/*.jar /opt/app/lib/
COPY ./target/*.jar /opt/app/

каждый новый деплой будет грузить только новые джарки

Как уменьшить вес Java-приложения

Переписать на Go уже прелагали?

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

Ви, друже, ідіть у інші гілки трольте node.js розробників, щось мені здається що то go скоро почнуть усе оптом переписувати на щось інше.

Та мне и здесь неплохо тролится. Rust, кстати, тоже норм.

В этой статье не будет информации о сборке кастомной ОС, сравнения существующих ОС

Выбор ОС — тема отдельной статьи, эта статья не об этом.

В докере вы выбираете только пакетный менеджер, не ОС. Само приложения будет работать как раз на ОС хоста, с теми мудулями ядра и правами которые предоставит ОС хоста. То есть если у вас баг в ядре убунты-сервер из-за какого того хотфикса для 0day. То у вас завялятся все контейнеры на какой бы ос они не были (реальный случай).

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

Да, все верно. Автоматизировать можно, используя jdeps. Но вся загвоздка в том, что в проекте куча зависимостей и jdeps прихотливый для корректного вывода всех необходимых модулей. Но так как в проект зависимости не подключаются ежедневно, я принял решение искать модули вручную. Это довольно быстро делается, чем кажется ;)

Может, и быстро, но это не технологично и здесь нечем гордиться.

Та воно то так, взагалі тими модулями не дуже є чим пишатися, але шо маємо то маємо. jdeps може і працював би нормально, якщо б сам застосунок та усі його залежності також були модулями, але до цього також як до Європи, будь воно неладно :)

Размер получился маленький (всего 17,43 MB)

Дякую, посміявся.

Якщо треба мінімальний розмір — то вибір спрінга це помилка. Зараз є купа більш легких альтернатив (але це звісно залежить від задачі) — vert.x, play, etc.

quarkus дивились?

Всем этим альтернативам пока до спринга как Украине до Европы

Та будь який нетривіальний юзкейс, візьміть наприклад інтеграцію з хмарними провайдерами та апі для централізованої конфігурації, ресурс діскавері, чи діагностики, наприклад логи у cloudwatch або параметри з parameterstore.

Інтеграція не з такими стандартними базами даних, як Postgresql а, скажімо, хоч Redis чи Neo4j або Elasticsearch. Більш менш пристойне кешування. Але найбільше мені не вистачає якоїсь заміни Spring Securіty. Є якась підтримка jwt та й усе.

Спробуйте ще наприклад розробити щось на кшталт spring boot reusable modules, коли ви можете просто добавити залежність у pom та автоконфігурація усе підтягне. Конфігурація то взагалі окрема справа. Такої зручної та гнучкої конфігурації як у Spring boot дуже не вистачає.

наприклад логи у cloudwatch або параметри з parameterstore.

Берем авс лібу, пишем 15 строчок кода + конфіг. Все. Навіщо тут спрінг?

нтеграція не з такими стандартними базами даних, як Postgresql а, скажімо, хоч Redis чи Neo4j або Elasticsearch.

Берем потрібний драйвер. Все. Навіщо тут спрінг? Щоб реквестити дані через спрінг-дата? Ну так ви самі собі створили проблему.

Більш менш пристойне кешування.

Тут не знаю. Треба глянути. Впевнений що це можна рішити за пару годин, якщо немає з коробки.

Але найбільше мені не вистачає якоїсь заміни Spring Securіty. Є якась підтримка jwt та й усе.

Це не так — vertx.io/...​ication_and_authorisation. Розширені перми в верті рішаються не через анотації, а через методи при побудові хендлера. Ось і все.

spring boot reusable modules

Не зовсім ясно про що мова. Нагуглити не вийшо.

Берем авс лібу, пишем 15 строчок кода + конфіг. Все. Навіщо тут спрінг?

Ну якщо так міркувати, нащо тоді JPA наприклад.. Беремо JDBC та й пишемо.. Та й взагалі, нащо фреймворки, той же vertx.. Беремо netty (чи undertow) та й пишемо..

А відповідь тут така, що на нетривіальному проекті у такому випадку буде дописано пів-велосипеда, що не скоротить загальну кількість коду, але значно ускладнить підтримку.

Про vertx взагалі не розумію у чому хайп, однопоточність та реактивність з усіма їх проблемами.

Quarkus досить прогресивний фреймворк, як на мене, але дивлячись на те, як розробники кинули підтримку Wildfly/Thorntail, я б не поставив на них більше у серйозному проекті. Навряд чи вони протримаються ще років з 10 (а спрингу вже більше 15), щоб досягнути зрілості спрінга.

Взагалі то тенденція дуже здорова, все що можливо зробити ahead of time при компіляції та збірці, замість рантайму. Вона взагалі прослідковується і у фронтенд фреймворках також, наприклад stencil чи angular aot.

Але на мою думку розробники спрінгу теж не сидять на місці і працюють у цьому напрямку, і може навіть у них нормальний результат вийде швидше, бо в цілому у них ідеологія більше на це спрямована.

А головна ідеологія спрінга, як на мене, то декларативність. Замість програмування більшість коду містить конфігурації готових компонентів інфраструктури, сама доменна логіка, та невелика кількість кастомної інфраструктури, якщо треба. Тобто, така ідеологія дозволяє сконцентруватись на доменній логіці замість кодування інфраструктури.

І такий підхід досить ефективно працює з генераторами застосунків, як JHipster наприклад.

Якщо проблема з великим розміром залежностей у спрінга, може подивитись у іншу сторону, і пошукати, як їх зменшити? Ми наприклад колись proguard застосовували для оптимізації залежностей у великому апплеті, може і тут треба у цю сторону дивитись, на Андроїді то досить звична річ.

Проблеми з швидкістю старту також можна вирішувати, прибравши зайві компоненти, та використовуючи lazy ініціалізацію наприклад, де можливо.

нащо тоді JPA наприклад..

Суть JPA — мати апі, яке незалежне від вендора БД, щоб можна було переїхати якщо що. А по факту — зміна БД це виключення з правила, а не норма. Чи варто для цього юзати JPA? Питання риторичне.

Беремо netty (чи undertow) та й пишемо..

Ми так і зробили. Ні разу ще не пожалкував.

на нетривіальному проекті у такому випадку буде дописано пів-велосипеда

Навпаки. На тривіальному проекті — спрінг ідеальний вибір. На нетривіальному — спрінг краще оминати. Крок вліво, крок вправо від стандартних юз кейсів — і вам треба вже писати паки костилів.

Тобто, така ідеологія дозволяє сконцентруватись на доменній логіці замість кодування інфраструктури.

Тут погоджуюсь на всі 100.

Про vertx взагалі не розумію у чому хайп, однопоточність та реактивність з усіма їх проблемами.

Перформанс, простота, гнучкість.

Тут я думаю що може й нема про що сперечатися.

Я просто глянув на хабрі ваш профіль про вашу специфіку, там дійсно нетривіальна інфраструктура і кастомні бінарні протоколи, тож і спрінг там не дуже допоможе.

А у нас кривавий ентерпрайз, там основна нетривіальність у доменній логіці, тому й спрінг заходить нормально.

Тож просто може це різні кейси нетривіальності.

spring boot reusable modules
Не зовсім ясно про що мова. Нагуглити не вийшо.

Ну це про модульну архітектуру, наприклад цей лінк Modularizing a Spring Boot Application

Скажімо одному з ваших клієнтів потрібна підтримка однієї платіжної системи, а іншому — інша, чи взагалі не потрібна.

никаких проблем, используем elasticsearch, minio и прочее без стрингов

Можно взять уже готовый образ с jre, поддержкой которого занимаются люди с большим количеством времени.
Например, adoptopenjdk/13.0.2_8-jre-hotspot (и другие комбинации версии, сборки, архитектуры и тд)

Лучше пользовать готовый велосипед чем делать свой

Подписаться на комментарии