Information
- Rating
- Does not participate
- Location
- Краснодар, Краснодарский край, Россия
- Registered
- Activity
Specialization
Фулстек разработчик, Разработчик мобильных приложений
Ведущий
From 250,000 ₽
Git
PostgreSQL
Linux
REST
Java Spring Framework
Высоконагруженные системы
Java
Spring Boot
Kotlin
Разработка под Android
По поводу валидации - полностью согласен, практика полезная. На текущем этапе активного формирования схемы БД она не в приоритете, но при фиксации структуры это логичный шаг для контроля консистентности.
Что касается open-in-view, решение оставить его включенным осознанное. Для первой итерации и простых запросов в рамках обучения это упрощает работу со связями. Безусловно, при переходе к реальному Highload и тонкой настройке производительности эта настройка отключается в первую очередь вместе с переходом на Fetch Joins или Entity Graphs.
Просто не хотелось валить всё в одну кучу в первой же статье - настройки производительности и строгой валидации планировал вынести в отдельный блок конфигурации позже
По существу ваших замечаний:
Про Instant и логи: Согласен, toString() у Instant информативен. Однако выбор в пользу LocalDateTime на данном этапе сделан для упрощения визуального контроля данных в БД при ручных запросах в процессе прототипирования, чтобы банально было просто открыть БД и посмотреть когда и что создалось, а не высчитывать. Безусловно, Instant — более строгий стандарт для продакшена, и этот переход заложен в логику развития проекта.
Про реализацию equals/hashCode: Позиция "не реализовывать, пока нет проблемы" вполне жизнеспособна. Мой подход это создание базового контракта сущности сразу. Что касается использования бизнес-ключей (unique полей) в проверках это дискуссионный вопрос архитектурных паттернов, который заслуживает отдельного разбора.
Про join-ы и структуру: Вы абсолютно правы, такая иерархия связей увеличивает количество join-ов. Это осознанный архитектурный компромисс (trade-off). Я приношу производительность простых запросов в жертву строгой типизации и единообразной структуре отношений на уровне кода. На текущем объеме данных и в рамках задач первой итерации этот приоритет кажется мне более оправданным для поддержки проекта.
Что касается структуры наследования и выноса связей в отдельные классы:
Безопасность и типизация: Основная цель здесь это не просто экономия строк кода, а создание жесткого каркаса. Когда проект разрастается до десятков таблиц, наличие базовых классов, вроде UserOwnerOneToOne, гарантирует, что разработчик не забудет прописать нужные связи, индексы или правила удаления. Это минимизирует риск "накосячить" при создании новых сущностей, связанных с пользователем.
Использование коллекций внутри UserEntity, например, List<Post>, удобно, но в высоконагруженных системах это часто ведет к проблемам с производительностью: избыточные Select запросы, раздувание объекта пользователя. Подход с выделением связей в отдельные сущности позволяет работать с данными более точечно и изолированно.
Такая иерархия делает структуру БД и кода предсказуемой. Глядя на определение класса, сразу понятно, какую роль он играет в системе и какие базовые поля: ID, время создания в нем гарантированно есть.
Это, конечно, создает определенную многослойность, но для проекта, который планирует масштабироваться, такой фундамент кажется более надежным и защищенным от человеческого фактора в будущем
Код, разумеется, авторский. Что касается выбора аннотаций: @RequiredArgsConstructor из Lombok чаще всего используется для генерации конструктора под final поля. Например: при внедрении зависимостей в сервисах.
В JPA-сущностях поля не помечаются как final, так как их состояние управляется Hibernate. Кроме того, спецификация JPA требует наличия конструктора без параметров, поэтому здесь используются @NoArgsConstructor и @AllArgsConstructor. В данной ситуации я счел, что @Getter, @Setter и стандартных конструкторов более чем достаточно для текущих задач модели. Всегда стараюсь придерживаться принципа минимальной достаточности в использовании аннотаций, чтобы не перегружать код там, где в этом нет прямой необходимости
Спасибо за технический разбор. Критика принимается, однако стоит пояснить логику выбора данных решений в контексте текущего этапа проекта:
Использование insnanceof вместо getClass() это справедливое замечание для работы с Hibernate Proxy. В данном случае реализация намеренно оставлена лаконичной, так как на начальном этапе структура связей прозрачна и не использует сложные сценарии ленивой загрузки в коллекциях. При усложнении объектного графа рефакторинг этих методов под специфику проксирования это стандартный запланированный шаг.
Выбор localDateTime обусловлен тем, что на старте разработки важнее наглядность данных в БД и упрощение отладки логики, завязанной на локальное время. Переход на instant для обеспечения инвариантности относительно часовых поясов целесообразен при переходе к распределенной архитектуре. На текущем этапе выбранный тип полностью закрывает потребности системы, не создавая избыточной сложности при конвертации.
Наличие конструкторов по умолчанию и сеттеров это необходимый компромисс при использовании JPA/Hibernate. Обеспечение полной неизменяемости сущностей (Immutability) зачастую вступает в конфликт с требованиями фреймворков и производительностью маппинга.
Проектирование это всегда баланс между академической чистотой и прагматизмом. Стремление возводить частные практики в статус "единственно верных решений" часто ограничивает гибкость разработки и ведет к оверинжинирингу. Инженерная ценность заключается в том, чтобы понимать, когда инструмент необходим, а когда он становится избыточным усложнением. На текущем этапе выбранные решения полностью обоснованы задачами первой итерации проекта.
У меня high-load в контексте масштабируемости и надёжности, а не микросекундных задержек - для этого Java/Spring Boot (особенно с реактивным стеком или современным GC) более чем достаточно. Rust пока не пробовал, но держу в поле зрения.