1. Выбираем проект курса
Deep-dive курс по Hibernate очень легко испортить из лучших побуждений. Достаточно начать объяснять каждый новый эффект на отдельной игрушечной модели: здесь одна сущность для merge(), там другая для N+1, дальше третья для locking, потом четвёртая для bulk update. Формально вы всё ещё говорите про Hibernate. По факту студент запоминает не механику, а набор случайных кусочков домена. Через пару дней начинает казаться, что ORM — это магия, которая каждый раз ведёт себя по-новому просто потому, что пример снова другой.
Именно поэтому в курсе есть один сквозной проект — Commerce Persistence Lab. Не “потому что так солиднее”, а потому что runtime-поведение Hibernate лучше всего видно на повторяющемся домене и в повторяющейся среде. Когда сущности знакомы, таблицы знакомы, use case знаком, вы перестаёте тратить внимание на расшифровку очередной новой предметной области и можете смотреть в то место, которое нам действительно важно: что ORM делает между кодом и SQL.
Это важный методический выбор. Проект здесь не декорация и не маркетинговая витрина. Он делает очень конкретную работу: удерживает курс в одном контексте, накапливает лабораторные сценарии и позволяет сравнивать разные решения на одном и том же материале. Если сегодня вы видели Product и PurchaseOrder, то через неделю это уже не “новые имена”, а знакомые точки наблюдения для fetching, merge(), locking, batch-сценариев и диагностики.
2. Почему выбран именно commerce backoffice
Домен проекта — это не полноценный интернет-магазин и не попытка симулировать весь e-commerce мир. Это сознательно ограниченный backoffice-контур: каталог, клиенты, заказы, остатки, немного истории изменений и несколько связанных read/write сценариев. Такой выбор очень практичен. Он достаточно простой, чтобы не утопить курс в бизнес-сложности, и достаточно богатый, чтобы на нём естественно проявлялись почти все типовые ORM-вопросы.
Backoffice хорош ещё и тем, что он честно даёт и чтения, и записи. У вас есть список товаров, карточка товара, список заказов, редактирование заказа, работа с остатками, аудит изменений. Это не искусственные примеры “ради аннотации”, а нормальные backend-use cases, где Hibernate либо помогает, либо создаёт скрытую цену. Когда такой домен выбран аккуратно, студенту проще доверять курсу: видно, что проект служит пониманию persistence layer, а не пытается впечатлить лишним функционалом.
Ещё одна причина в том, что commerce-домен отлично выдерживает рост сложности. В начале достаточно обычного Product и чтения каталога. Дальше появляются OrderItem, история изменений цены, связи товар–категория, отдельный контур остатков, конфликтующие обновления, bulk-операции, projections, read-model split. То есть проект естественно наращивает материал от фундаментальных тем к продвинутым, не требуя каждый раз менять предметную область.
3. Три среза проекта, на которых и будет видно Hibernate
Чтобы проект не превращался в список сущностей, лучше смотреть на него не как на инвентаризацию классов, а через три устойчивых предметных среза. Именно они чаще всего будут возвращаться по ходу курса.
| Срез | Типичный use case | Какие ORM-вопросы здесь естественно возникают |
|---|---|---|
| Каталог | список товаров, карточка товара, классификация по категориям | lazy loading, N+1, projections, JOIN FETCH, read-model vs entity-loading |
| Заказы | создание заказа, редактирование заказа, работа с позициями и снимками данных | states, dirty checking, flush, merge(), cascade, lifecycle графа |
| Остатки и резервирование | проверка доступности товара, резерв, конкурентные обновления | transaction boundary, optimistic/pessimistic locking, bulk side effects, consistency |
Эта таблица полезна не только как обзор проекта. Она сразу показывает, почему домен действительно подходит deep-dive курсу. Каталог даёт богатый материал для зрелого чтения данных. Заказы дают живой граф сущностей и write-сценарии, где видно поведение контекста. Остатки и резервирование вытаскивают на поверхность разговор про конкурентность, точность обновлений и цену транзакционных решений.
4. Каталог: удобное место, чтобы увидеть цену чтений
Каталожный контур проекта строится вокруг Product, ProductDetails, Category и явной связи товар–категория через ProductCategoryAssignment. Уже в этих объектах заложено очень много будущих наблюдений.
Список товаров и карточка товара — два похожих с точки зрения бизнеса сценария, но с точки зрения ORM они часто требуют разных решений. Для списка вам обычно не нужен весь граф и все детали. Для карточки, наоборот, может понадобиться больше связанных данных. Именно на таких различиях потом очень наглядно проявляется цена entity-loading по умолчанию и польза осознанных read-model решений.
Здесь же хорошо видна ценность честной модели связи. Вместо наивного ManyToMany проект с самого начала закладывает отдельную сущность ProductCategoryAssignment. Это помогает курсу не только методически, но и инженерно. Как только в связи появляются порядок, дата назначения, soft delete или любые правила жизненного цикла, отдельная сущность перестаёт быть “избыточной сложностью” и становится более честной моделью данных. На таком примере потом легко обсуждать owning side, constraints и управляемость SQL-поведения.
Ещё один важный слой каталога — ProductDetails. Это очень удобный материал для разговора о том, почему “карточка” и “список” не должны читаться одинаково. Пока объект один и локальная база маленькая, разницу почти не видно. Но как только use case становится реалистичным, выясняется, что граница между “нужны детали сейчас” и “нужно просто короткое summary” — это не косметика, а основа зрелого fetch-дизайна.
5. Заказы: место, где ORM перестаёт быть игрушкой
Заказной контур построен вокруг PurchaseOrder, OrderItem, Customer и связанных данных клиента. Это отличный полигон для deep-dive, потому что заказ — почти всегда агрегат, а агрегат быстро вытягивает наружу реальное поведение ORM: жизненный цикл дочерних объектов, изменение managed-графа, flush ordering, merge-ловушки, cascade, orphan removal и то, как transaction boundary склеивает всё это в одну операцию.
Важная деталь проекта в том, что заказная часть не сводится к “ссылка на товар и количество”. В нормальной backend-системе заказ часто хранит snapshot части данных на момент покупки: артикул, название, цену, адрес доставки в виде снимка, а не только внешние ссылки на текущие справочники. Это делает модель честнее и одновременно полезнее для курса. Мы сможем различать ситуации, где нужен именно managed graph сущностей, и ситуации, где правильнее держать отдельное значение или snapshot, а не полагаться на живую связь со справочником.
Именно в этом контуре особенно хорошо видно, почему merge() так часто понимают неправильно. Detached-flow, редактирование заказа из внешнего DTO, повторное чтение текущего состояния, каскады на детей — всё это возникает в заказной части приложения совершенно естественно. Поэтому проект не выдумывает искусственные кейсы ради темы. Он просто использует домен, в котором такие кейсы действительно ожидаемы.
6. Остатки и резервирование: где всплывает цена корректности
Если каталожный контур в первую очередь учит вас читать данные, а заказной — управлять графом сущностей, то контур остатков и резервирования добавляет ещё один взрослый слой: конкурирующие обновления.
Здесь в центре обычно находится InventoryItem, а рядом могут появляться InventorySnapshot и другие сервисные сущности для исторических или batch-сценариев. Именно на таком материале логично обсуждать optimistic и pessimistic locking, lost update, короткие критические секции и то, как transaction boundary влияет на реальную корректность данных.
Это тоже важный методический выбор. Курс не хочет обсуждать locking в вакууме. Нам нужен прикладной конфликт, где ошибка действительно дорого стоит: два потока резервируют один и тот же товар, массовое изменение остатков сталкивается со stale persistence context, read-only логика случайно превращается в write-side effect. В таком домене и optimistic conflict, и bulk side effect, и stale data ощущаются не как “академические случаи”, а как нормальная backend-боль.
Отсюда и одно из сильных качеств всего проекта: каждая следующая тема курса появляется не потому, что “так в Hibernate принято”, а потому что сквозной домен естественно поднимает новый инженерный вопрос. Это очень помогает держать курс живым и предметным.
7. baseline жёстко фиксирован
Теперь к технической части. У Commerce Persistence Lab есть жёсткий baseline, и это сделано специально. Для deep-dive курса воспроизводимость — часть обучения. Если у одного студента Hibernate ведёт себя на одной версии, у другого — на другой, один запускает H2, другой PostgreSQL, а третий позволяет ORM тихо менять схему через ddl-auto=update, то дальше обсуждать generated SQL и runtime-поведение бессмысленно.
Вот тот baseline, на котором строится курс:
| Компонент | Baseline |
|---|---|
| Java | 25 |
| Gradle | 9.4 |
| Spring Boot | 4.0.3 |
| Spring Data JPA | 4.0.3 |
| Hibernate ORM | 7.2.4.Final |
| Hibernate Envers | 7.2.4.Final |
| База данных | PostgreSQL локально через Docker Compose |
| Управление схемой | Flyway |
| Правило работы с web-границей | open-in-view=false |
| Режим анализа | SQL log + bind values + Hibernate statistics |
Важно не просто знать цифры, а понимать, зачем они зафиксированы. PostgreSQL нужен потому, что курс разбирает реальное ORM-поведение, а не сглаженную in-memory картинку. Flyway нужен потому, что схема должна быть явной и версионированной, а не “как-нибудь созданной Hibernate на старте”. open-in-view=false нужен потому, что мы не хотим растягивать жизнь persistence context туда, где он только маскирует архитектурную проблему. А SQL log и statistics нужны потому, что курс держится на наблюдаемости, а не на вере в красивый код. 🧪
Если сказать совсем коротко, baseline фиксируется не ради “дисциплины ради дисциплины”. Он фиксируется, чтобы мы могли спорить о поведении, а не о случайных различиях среды. И это сильная сторона курса: студент с первого дня видит, что здесь не будет прыжков между несовместимыми стеками и “почти такими же” примерами.
8. Как организован код и где проходят границы проекта
Кодовая база проекта разбита по фичам, а не по слоям. Это означает, что вместо огромных общих пакетов entity, repository и service на весь проект вы видите более естественные контуры: catalog, orders, inventory, customer, promotion, audit, labsupport. Такой подход делает проект удобнее не только для чтения, но и для обучения. Вы смотрите на Hibernate не в абстрактном “слое сущностей вообще”, а в конкретном предметном контексте.
Условно корневая структура выглядит так:
# Базовый package приложения: от него начинаются все импорты в проекте
com.example.commerce
├─ catalog
# Контур каталога: чтения, карточка товара, категории, fetching и N+1
├─ orders
# Контур заказов: агрегат, граф сущностей, каскады, merge/flush/dirty checking
├─ inventory
# Контур остатков: конкурентные обновления, locking, корректность транзакций
├─ customer
# Контур клиентов: данные клиента, связи с заказами, возможные read/write сценарии
├─ audit
# Контур аудита: историчность, Envers/логирование изменений и наблюдаемость
└─ labsupport
# Вспомогательный контур для учебных вещей: генерация данных, smoke-тесты, диагностика
labsupport здесь тоже не случайность. В production-коде вы не всегда захотите держать вспомогательные вещи для подсчёта statistics, smoke-сценариев или генерации данных рядом с основным кодом. В учебной лаборатории это очень полезно. Такой пакет помогает не загрязнять бизнес-фичи вспомогательной диагностикой и при этом не терять удобные инструменты наблюдения.
Границы проекта тоже зафиксированы довольно жёстко. Здесь нет UI, нет полноценного checkout-flow, нет security как центральной темы, нет внешних интеграций ради интеграций. Это не потому, что такие темы не важны. Это потому, что в рамках этого курса они создавали бы шум и размывали фокус. Проект должен быть достаточно интересным, чтобы раскрыть Hibernate, и достаточно ограниченным, чтобы не превратиться в маленькую продуктовую вселенную.
9. Типичные ошибки при знакомстве со сквозным проектом 🚧
Ошибка №1: воспринимать проект как “недоделанный интернет-магазин”.
Если смотреть на него с этой позиции, почти всё начнёт казаться неполным. Но проект не обязан быть продуктом. Он обязан быть хорошей persistence-лабораторией.
Ошибка №2: видеть только список сущностей и не видеть три предметных среза.
Тогда домен действительно начинает казаться каталогом классов. Но стоит переключиться на use case-уровень — каталог, заказ, резервирование, — и проект сразу собирается в понятную карту.
Ошибка №3: недооценивать baseline.
Кажется, что PostgreSQL, Flyway и open-in-view=false — это просто “технические детали”. На самом деле это часть учебной честности. Без них deep-dive быстро превращается в хаос.
Ошибка №4: пытаться добавить в проект соседние темы, чтобы он выглядел “реалистичнее”.
Обычно это ухудшает учебную ценность. Реализм здесь достигается не количеством технологий, а качеством наблюдаемого поведения persistence layer.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ