Например, ситуация 15 летней давности. Сейчас, возможно, что-то изменилось. Сбытовая компания закупает на свободном рынке электроэнергию в МВт, а перепродаёт её потребителям уже в кВт. Так вот для потребителя тариф получался простым делением на 1000. Т.о. для потребителя тариф на электроэнергию по свободной цене был с точностью до 5 знаков.
Не меняется, но их может стать несколько. Да, это неправильно, и остаться должен только один, но важен сам факт: теоретически номер СНИЛСа может измениться.
После появления встроенной функции uuidv7() в PostgreSQL не осталось ни одного разумного аргумента в пользу UUIDv4.
Есть такой аргумент. Функция появилась в последней версии слоника, а миграция, во-первых, не всегда возможна в принципе, во-вторых, не всегда возможна в адекватные сроки. В-третьих, работает - не трогай.
Использование COALESCE в запросе позволяет нам безопасно проверять статус токена: если записи нет в базе - мы по умолчанию считаем такую сессию отозванной.
Вы бы, прежде, чем делать подобные заявления хоть попробовали запросы повыполнять в БД. COALESCE конкретно в данном запросе - ненужная хрень, т.к. поле nullable = false. Если записи, удовлетворяющей условию отбора нет, то будет возвращён пустой набор.
Как в этом случае поведёт себя spring-data-репозиторий, я, честно, не помню, но по JPA специфицикации getSingleResult() при отсутствующей записи бросит NoResultException. Spring-data, скорее всего, как-то обрабатывает этот момент, но вряд ли на пустом наборе он вернёт что-либо, отличающееся от значения по умолчанию для результирующего типа. В данном случае, boolean, а, стало быть, по умолчанию вернётся false, т.о. метод isTokenRevoked() вернёт абсолютно противоположное ожидаемому значение.
JwtAuthenticationFilter extends OncePerRequestFilter ... извлекает токен из заголовка Authorization и "представляется" системе Spring Security
Раз уж затащил этого ~монстра~ Spring Security, то, емнип, там уже всё "из коробки" готово для работы jwt, надо только законфигурить, без какого-либо написания своих компонентов вообще.
на стороне order-service процесс никогда не перейдёт ни в APPROVED, ни в CANCELLED
Чтобы это исправить достаточно раз в t1 с/м/ч/etc. запускать фоновую задачу, которая просто переводит все PENDING в CANCELLED, если со времени создания прошло t2. Лучше, чтобы t1 < t2.
Выбор localDateTime обусловлен тем, что на старте разработки важнее наглядность данных в БД
Как одно связано с другим? В БД тип колонки что для LocalDateTime, что для Instant будет одинаковым.
упрощение отладки логики, завязанной на локальное время.
У java.time.Instant вполне задокументированный человекочитаемый toString(). Т.ч. что в sout, что в лог, что просто под дебагером понять какая там именно дата можно без проблем.
Наличие конструкторов по умолчанию и сеттеров это необходимый компромисс
Это не компромисс, а вполне себе требование стандарта JPA. Который, кстати, хибер вполне себе допускает нарушать.
Проектирование это всегда баланс между академической чистотой и прагматизмом.
Как раз с точки зрения прогматизма equals() и hashCode() вообще не надо реализовывать, пока не упёрся в проблему, решить которую без реализации этих методов вообще не получается. У вас же буквально первая сущность UserEntity уже имеет натуральный идентификатор (unique = true) и вот как раз с точки зрения бизнес-логики равенство должно бы проверяться по этому значению.
ведет к оверинжинирингу
Это именно то, что мы наблюдаем на описываемом проекте. Берём простую задачу: найти NicknameOldEntity по идентификатору. Казалось бы, чего проще, и проблем быть не должно, но вместо тупейшего select * from nickname_old where id = ? у нас появится монстр с двумя join-ами на ровном месте.
Какая разработка высоконагруженной системы? Вам бы для начала "How to..." прочесть, посмотреть на аналогичные решения, доступные в сети, обдумать используемые там решения, а потом уже, если останется желание, можно и статью на Хабр написать, которая будет реально полезной.
Уж сколько раз эта темаобсасывалась со всех сторон. В том числе и на Хабре. Сущность мутабельна, согласно спецификации должна иметь конструктор по умолчанию, соответственно, её можно создать в неконсистентном состояннии. Её внутренней состояние может измениться в течение жизни.
Определять equals()/hashCode() в 145% случаев не требуется. А где требуется надо быть очень осторожным и завязывать реализацию этих методов только на идентификатор неправильно. А уж ваша реализация equals() вообще неправильная, т.к. не учитывает прокси.
private LocalDateTime createdAt;
Время создания - это точка на временной оси, какой-то конкретный момент, при чём тут локальное время? А если учесть, что это время меняется одним параметром запуска jvm, то это вообще полный звиздец. Для таких полей надо использовать java.time.Instant.
А развести потоки никак? Это ж классика: клиент принимает данные и складывает их в буфер, а паралллельный поток этот буфер читает и что-то с прочитанным делает.
Да, немного по-другому названо просто.
Хороший комп за полторашку при нынешних ценах на оперативу и видяхи не соберёшь :).
Чтобы баланс сходился, нужно использовать специальное бухгалтерское округление. Но этим редко кто заморачивается.
В какой-то степени, так и случилось. :)
Деньги считают не только в банках.
Например, ситуация 15 летней давности. Сейчас, возможно, что-то изменилось. Сбытовая компания закупает на свободном рынке электроэнергию в МВт, а перепродаёт её потребителям уже в кВт. Так вот для потребителя тариф получался простым делением на 1000. Т.о. для потребителя тариф на электроэнергию по свободной цене был с точностью до 5 знаков.
Не меняется, но их может стать несколько. Да, это неправильно, и остаться должен только один, но важен сам факт: теоретически номер СНИЛСа может измениться.
Разрешите поинтересоваться в целях повышения образования. (ц)
А можно пример несуррогатного ключа, который на дистанции хотя бы в 10-20 лет 146% не изменится?
TL/DR Soft delete - хрень.
Есть такой аргумент. Функция появилась в последней версии слоника, а миграция, во-первых, не всегда возможна в принципе, во-вторых, не всегда возможна в адекватные сроки. В-третьих, работает - не трогай.
Т.ч. не всё так однозначно.
Я просто оставлю это здесь.
Возможно, вы правы.
Действительно, зачем делать сразу нормально? А так можно ещё пару недель на рефакторинг списать. Код крутится, лавеха мутится.
А зачем тогда нужен весь Spring Security?
TL/DR. Новички, не делайте так никогда. Автор, мягко говоря, некомпетентен в вопросах, о которых рассуждает.
@ConfigurationProperties.Вы бы, прежде, чем делать подобные заявления хоть попробовали запросы повыполнять в БД.
COALESCEконкретно в данном запросе - ненужная хрень, т.к. полеnullable = false. Если записи, удовлетворяющей условию отбора нет, то будет возвращён пустой набор.Как в этом случае поведёт себя spring-data-репозиторий, я, честно, не помню, но по JPA специфицикации
getSingleResult()при отсутствующей записи броситNoResultException. Spring-data, скорее всего, как-то обрабатывает этот момент, но вряд ли на пустом наборе он вернёт что-либо, отличающееся от значения по умолчанию для результирующего типа. В данном случае,boolean, а, стало быть, по умолчанию вернётсяfalse, т.о. методisTokenRevoked()вернёт абсолютно противоположное ожидаемому значение.Раз уж затащил этого ~монстра~ Spring Security, то, емнип, там уже всё "из коробки" готово для работы jwt, надо только законфигурить, без какого-либо написания своих компонентов вообще.
И начните уже писать тесты.
При переводе в заказа в CANCELLED в bank-service отправляется отмена платежа, а сервис уже смотрит провёл он его реально и надо ли отменять.
Чтобы это исправить достаточно раз в t1 с/м/ч/etc. запускать фоновую задачу, которая просто переводит все PENDING в CANCELLED, если со времени создания прошло t2. Лучше, чтобы t1 < t2.
Как одно связано с другим? В БД тип колонки что для
LocalDateTime, что дляInstantбудет одинаковым.У
java.time.Instantвполне задокументированный человекочитаемый toString(). Т.ч. что в sout, что в лог, что просто под дебагером понять какая там именно дата можно без проблем.Это не компромисс, а вполне себе требование стандарта JPA. Который, кстати, хибер вполне себе допускает нарушать.
Как раз с точки зрения прогматизма
equals()иhashCode()вообще не надо реализовывать, пока не упёрся в проблему, решить которую без реализации этих методов вообще не получается. У вас же буквально первая сущностьUserEntityуже имеет натуральный идентификатор (unique = true) и вот как раз с точки зрения бизнес-логики равенство должно бы проверяться по этому значению.Это именно то, что мы наблюдаем на описываемом проекте. Берём простую задачу: найти
NicknameOldEntityпо идентификатору. Казалось бы, чего проще, и проблем быть не должно, но вместо тупейшегоselect * from nickname_old where id = ?у нас появится монстр с двумя join-ами на ровном месте.Какая разработка высоконагруженной системы? Вам бы для начала "How to..." прочесть, посмотреть на аналогичные решения, доступные в сети, обдумать используемые там решения, а потом уже, если останется желание, можно и статью на Хабр написать, которая будет реально полезной.
Уж сколько раз эта тема обсасывалась со всех сторон. В том числе и на Хабре. Сущность мутабельна, согласно спецификации должна иметь конструктор по умолчанию, соответственно, её можно создать в неконсистентном состояннии. Её внутренней состояние может измениться в течение жизни.
Определять
equals()/hashCode()в 145% случаев не требуется. А где требуется надо быть очень осторожным и завязывать реализацию этих методов только на идентификатор неправильно. А уж ваша реализацияequals()вообще неправильная, т.к. не учитывает прокси.Время создания - это точка на временной оси, какой-то конкретный момент, при чём тут локальное время? А если учесть, что это время меняется одним параметром запуска jvm, то это вообще полный звиздец. Для таких полей надо использовать
java.time.Instant.А куда делся главный?
Не будешь же ты всегда таскать калькулятор с собой. (ц) Учитель математики
А развести потоки никак? Это ж классика: клиент принимает данные и складывает их в буфер, а паралллельный поток этот буфер читает и что-то с прочитанным делает.