Обновить
33
Антон Куранов@Throwable

Пользователь

0,1
Рейтинг
13
Подписчики
Отправить сообщение
По поводу первого примера: начиная с Java 9 есть удобный метод ifPresentOrElse.

Это костыль, и хуже на порядок, чем обычное ветвление. Кроме того возникнут проблемы, чтобы вернуть что-то из лямбды:


Person person = findPerson();
if (person == null) {
   person = new Persion();
   createPerson(person);
} else {
   person.setXXX();
   updatePerson(person);
}
doSomethingWith(person);

Если же возвращается просто какой-то тип, то не всегда понятно, может ли быть здесь null, или нет.

В том-то и дело, что в общем случае у нас есть 3 типа контрактов:


  • Nullable
  • Гарантированно не null
  • Х.з. (на усмотрение юзера)

Вы можете лично у себя в коде использовать какое-то соглашение, избавившись от последнего варианта (напр. везде вместо null возвращаем Optional), но при интеграции со сторонними API и с той же java rtl возникнут проблемы — придется для всего писать обертки Optional.ofNullable(), либо ставить проверки Objects.requireNonNull(). И в этом случае вы также можете забыть сделать соответствующее преобразование.


Кроме того, очень часто nullability бывает контекстно-зависимым, и это нельзя выразить никаким статичным контрактом. Пример из JPA:


@Entity
public class MyJPAEntity {
    @Id @GeneratedValue
    private Long id;
}

Поле id может быть null только при создании объекта, пока он еще не persistent. В 99.9% случаев объект будет вытащен из базы, и поле всегда будет иметь значение. Возвращать для этого поля Optional, отдавая себе отчет, что оно тут ну точно не может быть null, но каждый раз делать лишние приседания по преобразованию, будет сильно отдавать шизофренией и загрязнять код.
И вот к чему приводит подобная практика:


public static Optional<String> toString(Object obj) {
    return Optional.ofNullable(obj).map(Object::toString);
}
...
System.out.println(toString(1).orElseThrow(() -> new RuntimeException("WTF!")))
System.out.println(toString("Privet").orElseThrow(() -> new RuntimeException("WTF!")))
System.out.println(toString(new Persion()).orElseThrow(() -> new RuntimeException("WTF!")))

Это вообще касается всех контрактов (checked exceptions, class cast, etc...) — большинство из них невычислимо в статике. Для этого нужно не бояться ставить проверки и писать тесты вместо параноидального желания все прибить гвоздями.


Если интересно, посмотрите давнее видео Андрея Бреслава, как они в Котлине тщетно пытались жестко прикрутить nullable-контракты и какие проблемы возникли с интеграцией с кодом на джаве. В итоге пришлось внутренне на уровне компилятора ввести третий тип с неопределенной nullability (String!!), который приводится к одному из первых двух при первой соответствующей декларации.

Проблема Optional в том, что он пришел из функциональных языков, где сплошь и рядом используется композиция, но он плохо ложится на императивное программирование. Особенно в случае, где необходимо не просто заменить empty дефолтным значением или трансформировать результат в одно действие, а осуществить полноценное ветвление:


Optional<Person> personOpt = findPerson();
if (personOpt.isPresent()) {
    Person person = personOpt.get();
    person.setXXX();
    updatePerson(person);
} else {
    createPerson();
}

Тут у нас появляется одна дополнительная переменная (personOpt) и одно дополнительное действие (personOpt.get()).
Поэтому программирая на Java, практически всегда лучше использовать слабые контракты типа @Nullable/@Nonnull:


@Nullable Person findPerson() {...}
...
Person person = findPerson();
if (person == null) {
   person.setXXX();
   updatePerson(person);
} else {
   createPerson();
}

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


Другое преимущество nullable-аннотаций в том, что они не требуют сквозного перелопачивания всего API, как в случае с Optional. Умный компилятор (Kotlin) или IDE (Idea) умеют выводить nullability-контракт транзитивно из исходников, при этом не требуя какого-либо изменения в коде. Кроме того, если использовать только парадигму Optional, то должен быть некий и аналог @Nonnull: что-то типа Required<T>. Однако такой практики нигде не существует. В итоге nullable-аннотациями можно указывать более сильные и гибкие контракты, нежели при помощи Optional, и делать это в более простой форме.


Ну и вообще, как вы подметили в статье, исторически Java экосистема не приспособлена для Optional. Его использование оправдано только как отсутствие результата вычисления функции. Использовать его как контейнер для данных крайне нелепо.


Например, JPA Specification возвращает Optional вместо null.

Я что-то пропустил??? Или вы не различаете Spring Data и JPA?


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

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

Если происходит IndexOutOfBoundException, DivisionByZeroException, то их очевидно здесь ловить не надо. Это ошибка программирования, если они происходят, а значит продолжать в принципе небезопасно

Мой пример именно из серии, если бы язык требовал строгое соблюдение контракта, а все исключения были бы проверяемыми. Контракт с List.get() и a/b может кидать исключения, соответственно, следуя вашей идее, вы обязаны его перехватить, даже если по логике он никогда не возникает (List-то об этом не знает!). И в этом случае кинуть какой-нибудь WTFException, который тоже должен являться частью контракта. Ну либо определить IndexOutOfBoundException как возможную ситуацию в контракте метода, выставляя наружу детали реализации. Более того, любой доступ к полям объекта может выдать NPE, поэтому и их тоже по идее нужно заворачивать в exception или пользоваться Optional вместо значения. И это мы еще не коснулись вычислимости типов, где у Java не такие большие возможности, и при каждом кастинге ставить catch ClassCastException. В итоге это все закончится паранойей и шизофренией, которая совершенно не нужна для реальных бизнес задач. Ваш контракт будет нагружен кучей ненужного дерьма, которое не имеет отношения к решаемой задаче и никогда никем не будет испльзоваться.


Кстати, по поводу сервиса и его переезда на RPC: сделайте корректную обработку ошибок, если коннект валится — то всё, это как нуллпойнтер в локальной версии.

Так и сделано. Кидается специальное RuntimeException, которое перехватывается каким-то там верхним обработчиком. А бизнес коду абсолютно наплевать — это именно исключительная ситуация, которая не должна происходить в работающей системе. Бизнес код не знает что делать в этом случае и поэтому не должен ставить никаких обработчиков на этот счет. Поэтому и нагружать контракт различными деталями реализации не есть хорошо.

По поводу коллекций — так исторически сложилось, а исправить уже нет возможности. В некоторых API immutable collection заменяет Enumeration, но особо не прижилось.


А теперь по поводу checked exceptions вопрос, вы реально топите за то, чтобы все exceptions были checked и пришлось бы писать подобный код?


Map<String,Double> calculateAmount(List<MyBean> mybean) throws ElementDoesNotExistException, CannotCalculateAmount, CouldNotCollectAmountPct, NullPointerException
{
    Map<String,Double> hashMap = new HashMap<>();
    for (int i = 0; i < arrayList.size(); i++) {
        MyBean mybean;
        try {
            mybean = arrayList.get(i);
        } catch (IndexOutOfBoundException e) {
            throw new ElementDoesNotExistException(e);
        }
        double amountPct;
        try {
            amountPct = mybean.getAmount()/arrayList.size();
        } catch (DivisionByZeroException e) {
            throw new CannotCalculateAmount(e);
        }
        try {
            hashMap.put(mybean.getId(), amountPct);
        } catch (UnsupportedOperationException | ClassCastException | NullPointerException | 
IllegalArgumentException e) {
            throw new CouldNotCollectAmountPct(e);
    }
    return hashmap;
}

Это полный контракт данной операции, разве что отсутствует OutOfMemoryError и StackOverflowError.


Если надо расширить контракт — расширяйте, в смысле берите новый расширенный контракт и реализуйте. Потому что от вашей новой реализации на старом контракте никто не хочет неожиданностей.

Боюсь, с таким ригидным подходом до сих пор бы не существовало бы ни Spring, ни JavaEE, ни других фреймворков и технологий в экосистеме. Более слабая и "оптимистичная" парадигма наиболее продуктивна — контракт никогда не отражает всех нюансов, поэтому нужно быть готовым ко всему, но особо на этом не зацикливаться.

Извините, но ваши рассуждения слишком наивны и идеалистичны. Что делать в случае, если ваш контракт изначально не определял возможного исключения, но при при смене провайдера это стало нужно? Например есть интерфейс с методом, который всю жизнь вызывался локально, но затем его реализовали через RPC? Ни клиента ни контракт уже не поменять.


RuntimeException — это ошибка в программе (например NPE), когда надо просто падать.

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


Итак, ошибки, ожидаемые от сервиса, известны, они объявлены.

Ну да ну да. Всевозможные RuntimeException-ы особенно объявлены...


а непроверяемые — это всегда должен быть только результат системного сбоя / ошибки программирования.

NumberFormatException при Integer.parseInt, когда пользователь ввел строку вместо числа — это ошибка программирования или системный сбой? В самой Java API использование checked/unchecked много где неконсистентно. Почему парсинг URL выдает checked MalFormedURLException, а парсинг числа unchecked NumberFormatException?

Когда код что-то вызывает, он определенно имеет отношение к тому, что вызвал. И к ошибкам этого в том числе.

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


public class DataOutputStream extends FilterOutputStream implements DataOutput {
    public final void writeInt(int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
        incCount(4);
    }
}

Здесь метод writeInt() вызывает OutputStream.write() аж 4 раза, при этом ни нет одного обработчика! Таким же образом любой код вправе сохранить "оптимистичную" модель программирования, не заботясь о завершении каждой "небезопасной" операции, и не замусоривая код обработчиками и логикой, которая не относится к этому методу. Здесь обязуют декларировать checked IOException, однако если бы тут был RuntimeException декларация отсутствовала бы. И такой код вполне себе имеет право на существование.


Сбой операции — один из возможных исходов операции и он входит в контракт этой операции. И корректное обращение со всеми исходами — это часть ответственности вызывающего.

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


Во-вторых, вы никогда не сможете обработать все ошибочные исходы, ибо их множество в общем случае вычислимо, но никогда не определено ни одним контрактом. Я имею ввиду всевозможные RuntimeException, которые могут возникнуть в вызываемом коде, особенно если имеете дело со сторонней библиотекой/фреймворком, где под капотом подмешано большое количество различных технологий. И это множество зависит от многих факторов, многие из которых определяются не кодом, а runtime-конфигурацией системы: для одного и того же интерфейса может быть куча разных подключаемых провайдеров, код может работать в транзакции, вызывать remote-методы, делать RPC, стучаться в базу — и везде есть чему вальнуться. Поскольку конфигурация динамическая, на этапе проектирования нет возможности определить все возможные варианты сбоев.


В-третьих, сильно завязывать бизнес логику на исключения — это заведомо плохой подход. Exception — это именно исключительная ситуация, которая говорит о невозможности выполнения операции и требует раннего завершения всей цепочки вызовов. Если аварийное завершение — это часть вашего контракта и предусмотрена бизнес логикой, то и возвращайте его как обычное значение. Например вместо NotFoundException или NullPonterException возвращайте обычный Optional или на худой конец null.

А чем не устраивает например http://www.cfg4j.org/ или http://owner.aeonbits.org/ ??? Тот самый фатальный недостаток?


В вашей библиотеке, насколько я могу судить, отсутствуют следующие важные вещи:


  • Зачастую конфиги имеют иерархическую структуру, поэтому нужно иметь возможность, чтобы пропертями могли быть другие Config-классы.
  • А как быть со пропертями-списками?
  • А что насчет других типов пропертей? Например LocalDate, URL, Duration, etc… Поэтому нужны дефолтные и кастомные конвертеры.
  • Если уж вы упомянули возможность горячего изменения пропертей, то необходим механизм, позволяющий "слушать" эти изменения. Например singleton-бины должны корректно апдейтнуть конфигурацию, если изменился релевантный параметр, а сервисы — еще и рестартнуться на горячую.

Круто! То что нужно! Собирался в свободное время написать что-то похожее, но вы меня сильно опередили. На проде есть кластер из 3 нодов, пока проходит сертификация и rollout приложения нужно часто копаться в логах. А использовать SSH+консоль контрпродуктивно. А ELK и прочаие сторонние тулзы не предусмотрены контрактом.


Есть пожелание по поводу настройки работы с rolled-файлами. Допустим роллинг файлов сконфигурирован по паттерну: service.${hostname}_%d{yyyy-MM-dd}.%i.log, то есть текущий лог записывается на каждой ноде в файл service.nodeX.log, который потом заворачивается в напр. service-nodeX_2021-01-20.0.log по дате и лимиту в МБ. Насколько я понял, для того, чтобы сработал мёрж нужно указать все файлы на всех нодах вручную, а мы точно не знаем сколько их. Поэтому в шорткатах будет очень удобно иметь возможность использовать URL-параметры и wildcards|regexp для задания фильтра на файлы, а также указывать имя хоста:


log-paths = {
    daily = {
        file = [
            ${HOME}"/my-app/logs/service.node-cn-01_${date}.*.log@node-cn-01",
            ${HOME}"/my-app/logs/service.node-cn-02_${date}.*.log@node-cn-02", 
            ${HOME}"/my-app/logs/service.node-cn-03_${date}.*.log@node-cn-03"
        ]
    }
}

И использовать в URL типа http://localhost:8111/log?log=daily&date=2020-01-20.

(*) Подожду Вальгаллу, чтобы уж наверняка!

Тут вообще поднимается вопрос цифрового права конечного потребителя. До сих пор все законодательное регулирование было сосредоточено на введении различного рода ограничений на различные действия компаний и конечных пользователей в цифровой среде. Однако нет ни одного закона, гарантирующего хоть какое-нибудь право финального пользователя. По сути, принимая лицензию, тебя ставят перед выбором: либо ты принимаешь все наши условия, либо не пользуешься продуктом. Такая практика может иметь смысл в среде с множеством конкурентных провайдеров. Но сейчас по-сути весь цифровой рынок поделен между несколькими коммерческими компаниями-гигантами, и идти тебе вобщем-то некуда. Ты фактически не имеешь право ни на что: твой бизнес аккаунт могут заблокировать в любой момент без объяснения причин, в один момент ты можешь лишиться всех своих данных, доменов, способов оплаты, etc. При необходимости всегда могут заткнуть тебя, выкинув со всех публичных площадок. В недалеком будущем стать цифровым изгоем будет соизмеримо с участью бомжа — ни наняться на работу, ни совершить оплату, ни даже тупо идентифицировать свою личность.


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

Неправильная оценка времени (основная тема этой статьи).

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


время выполнения задачи = объем задачи / скорость выполнения


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


Украшательства: слишком много внимания уделяется деталям, не относящимся к сути проекта.

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


Недостаточно времени уделено этапу исследований и проектирования архитектуры. Или наоборот — уделено слишком много времени.

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


Недооценка потенциальных проблем интеграции со сторонними системами.

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


Я бы еще добавил:


  • Фетишизм и фричество (девопс, тдд и т.д.)

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

Формально слабый антропный принцип сразу же сводится к сильной его формулировке, где подразумевается бесконечное пространство возможных вселенных со всеми возможными параметрами и конфигурациями. Тем не менее, даже в сильном антропном принципе если ваша модель кажется слишком "экзотичной" и "искусственной" (как современная теория струн), то скорей всего она является лишь частным ограниченным описанием некоторых явлений этого мира. В хорошей модели не должно быть никаких привилегированных констант, измерений, подпространств, и других сущностей.

(2.1) Почему даже в 11-мерном пространстве время остается выделенным измерением?


(4) Почему именно три-брана? Ведь она может являться частью объектов более высоких размерностей, эффекты наличия которых также должны присутствовать в наблюдаемой вселенной.

Классическое голивудское путешествие во времени эффектно, но парадоксально. По-сути это телепортация, когда с т.з. 4D-пространства-времени мировая линия человека или объекта перестает быть непрерывной, и возникают особые точки там, где их быть не должно. На сегодняшний день такие особые точки существуют только внутри черных дыр, и то лишь в результате релятивистского приближения.


Гораздо интересней случай, когда пространство-время топологически неодносвязно и содержит "кротовые норы", предсказываемые многими физическими теориями. В таких топологиях существуют траектории, где мировая линия объекта может пересекать сама себя, и действительно возможны перемещения во времени, что неминуемо приводит к парадоксу. Пока что хорошего объяснения этому нет. Но большинство ученых априори принимают, что Вселенная непарадоксальна (иначе любая сразу теория теряет свою предсказательную силу), а значит должны существовать механизмы, запрещающие подобные траектории.


Интересна также нолановская концепция реверсивности времени в фильме "Довод". Даром что инвертированный объект должен был бы состоять из антиматерии и эффектно проаннигилировать с окружающей средой. Однако там верно указана причина обратного времени: инвертированная энтропия объекта. Вопрос в том, будет ли объект из антиматерии проявлять свойства убывания энтропии (правильный ответ нет) и что для этого нужно.

Собственно все как я и предполагал. А теперь почему я никогда НЕ выберу JEE для своего проекта.


  • Каждый девелопер в команде должен пройти дебильную ручную процедуру установки и конфигурации сервера локально на своей тачке. То же самое нужно сделать для каждого environment, сервера CI/CD, etc. Конфиги сервера всегда хранятся отдельно от проекта бог знает где и нередко недоступны ввиде файлов. Поэтому правильно делают те, кто пакуют сервер с приложением в докер.
  • Вам повезло, что ваш сервер OpenSource. Коммерческие решения как правило не так приветливы к программеру (по секрету скажу, что вендорам насрать на девелоперов: их целевая аудитория — шефы) и как правило не умеют пускаться и деплоиться локально из IDE. Поэтому прощай нормальный дебаг и IDE и здравствуй коммандная строка и System.out.println().
  • Все JEE решения сильно завязаны на тулинг (коммерческие IDE, плагины, коннекторы). Через пару лет тулзы апдейтнутся, и ваш проект тупо перестанет собираться или запускаться. Так у меня было практически с любым JEE проектом — через пару лет так просто поправить плевый баг или фичу без геморроя уже не удастся.
  • JEE вообще не предназначен для юнит-тестирования. Когда он задумывался, еще даже не было такого модного слова. Во-первых в общем случае JEE не умеет пускаться из IDE локально в embedded режиме и цеплять приложение из workspace (в случае с Glassfish для вас это делает умный плагин к IDE). Во-вторых, не позволяет управлять сборкой контекста: то есть тупо нельзя динамически указать какие бины для теста нужно задеплоить, а какие нет, ведь весь bean-дискавери завязан на текущий classpath. Евангелисты JEE предлагали иметь на каждый тест свой maven-профайл, где указывается свой classpath с ресурсами, специфичными для теста, и пускать тесты вручную из мавена. В-третьих, нельзя программно установить конфигурацию сервера для конкретного теста, например когда ваш тестовый сервис должен послать месседж в тестовую очередь или законнектиться к другой базе данных. Особо продвинутые умудряются отделить всю логику от ресурсов сервера, обвешаться со всех сторон моками и тестировать локально без контейнера. Что конкретно они при этом тестируют — они не понимают сами. Конечно есть технологии типа TomEE и Arquillian, но это велосипеды, призванные криво обойти одно из важнейших ограничений JEE.
  • Application-specific parameters вообще отсутствует как таковой. То есть тупо указать вебсервису, что тут на данной машине он должен вызывать этот URL, а на другой тот, будет связано с глубоким копанием JAX-WS и велосипедами ввиде отдельно лежащего файла .properties, достижимого по абсолютному путю, либо environment variables. С другой стороны, конфигурация приложения — это то, что из коробки предоставляет вообще любой фреймворк.
  • Конкретно что такое дает ваш JEE, чего нет в библиотеках и фреймворках? Правильный ответ — JTA. Это то единственное, ради чего его используют в энтерпрайзе. Однако JTA — это один большой закамуфлированный архитектурный антипаттерн, который вы никогда не поймете до конца и никода не сможете настроить нормально. Все остальное делается проще, лучше и изящнее, нежели в JEE.

Чтобы разделить business и web tier не нужен JEE сервер — для джавы есть 100500 легких технологий ремоутинга. Помнится мы в свое время (лет 15 назад) использовали RMI. Сейчас это модно делать при помощи Rest API. Запускаются локально два процесса: business и web, по мере необходимости рестартится любой из них.


Теперь по поводу вашего проекта. Вангую:


  • Функционал проекта не выходит за рамки базы данных и веба
  • Вы единственный разработчик
  • У вас нет ни одного автоматического теста
  • Конфигурация сервера и ресурсов лежит отдельно от кода
  • Вы используете Eclipse и ваш проект сильно завязан на его тулинг, отсутствуют средства автоматического деплоя
  • Вы используете какой-нибудь opensource сервер, типа Glassfish или TomEE
  • Отсутствует более чем полностью параметризация приложения
  • Не используется CI/CD

А по поводу CDI — это во многом повторение EJB 3 версии, без их преимуществ. Попробуйте в CDI организовать транзакцию.

Скорей без их рудиментов. С JEE7 бины умеют транзакцию.

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

Один вопрос — нафига? Модуль — логическая единица, а не физическая. В одном процессе и контексте могут сожительствовать сколь угодно много модулей. Сделать инъекцию бина и локальный вызов метода гораздо проще, чем городить API, усложнять архитектуру и искать себе проблемы там, где их нет. Какие конкретно насущные задачи решит разбивка на разные deployable units?


пытающиеся превратить Спринг то в наносервисы, то наоборот в интегрированное решение наподобие Java EE, уже не помогут

Это все-равно что превращать трамвай в паровоз. Spring стал популярным именно как замена JEE, сначала предоставляя более простой слой интеграции с ней, а затем как независимое решение. И уже JEE в надежде удержать юзеров стал таскать идеи из спринга, например CDI.

Монолит — слово-вирус, придуманное и разрекламированное PR-менеджерами для продвижения клауд решений. Поэтому не стоит бездумно изпользовать его везде как штамп. Не существует ни монолитов ни микросервисов. Существует приложение и функциональные модули, и особняком стоит архитектура решения как способ взаимодействия модулей. И спринг как бы здесь ни при чем.


Я поневоле использую JavaEE с 2005 с открытыми и коммерческими серверами вкупе с кучей проприетарного барахла, которое шло поверх. Причем, сколько использую, столько плююсь — платформа реально мешает выполнять задачу, ограничивает и усложняет многие вещи, которые без нее сделать в разы проще.

Ну, во-первых, меня до сих пор интересует зачем вообще делать деплой, если мы не на продакшне. Это бесполезная операция. Функция public static void main(String… args) до сих пор не deprecated, а весь функционал сервера приложений доступен ввиде библиотек или фреймворков.


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


Хорошее решение сделано в Spring Boot Devtools: там разделены контексты класслоадеров на статические — которые грузят из джаров (которые в свою очередь редко меняются) и динамические — которые грузят из .class вокрспейса. При изменении в файле класса спринг перезагружает контекст, при этом оставляя статические класслоадеры, которые уже отрезольвили все необходимые зависимости. В итоге приложение апдейтится за секунду.

Вы слышали когда-нибудь о вирусах на Яве?

Лет 15 назад каждый второй вирус распространялся через JVM Browser Plugin, хотя аплеты вроде как работают в изолированной песочнице. Из-за чего юзеры начали массовый выпил джавы со своих компов.


Итак Ява-машина — это ОС. Даже круче чем ОС местами.

Нет, это именно виртуальная машина, не более. А вот была попытка написания настоящей ОС на 100% джаве: https://www.jnode.org/


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

Она не может создавать изолированные контексты памяти (разные кучи) внутри одной JVM. Поэтому любой тред легко может навернуть всю JVM с OutOfMemory. Плюс все контроли за ресурсами устанавливаются на уровне процесса, а не контекста, поэтому никто не запретит треду открыть миллион файлов или создать еще миллион тредов.

Информация

В рейтинге
3 857-й
Откуда
Madrid, Испания
Зарегистрирован
Активность