JavaRush /Курсы /Spring Data JPA /Практический walkthrough mini-shop

Практический walkthrough mini-shop

Spring Data JPA
1 уровень , 4 лекция
Открыта

1. Жёсткие границы проекта 🤯

Есть две крайности. Первая — взять слишком игрушечный проект, где у вас две сущности и одна таблица. Тогда курс получается чисто демонстрационным и не дотягивает до реальной backend-жизни. Вторая — попытаться построить «полный e-commerce», добавить пользователей, платежи, доставку, уведомления, безопасность, интеграции и случайно превратить data-course в бесконечную энциклопедию.

Mini-shop хорош тем, что он достаточно живой, но ещё не расползается во все стороны. У нас есть каталог товаров, учёт остатков и заказы. Этого уже достаточно, чтобы честно поговорить и про чтения, и про записи, и про транзакции, и про согласованность данных.

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

2. Три зоны, из которых собирается один data-layer

Внутри проекта есть три предметные зоны: catalog, inventory и ordering. Они не случайны и не симметричны «для красоты». Каждая отвечает на свой набор вопросов о данных.

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

Inventory отвечает на вопрос: сколько этого можно продать прямо сейчас. Здесь живут остатки и резервы. Это уже не просто справочник. Здесь состояние меняется чаще и чувствительнее к ошибкам. Если каталог можно представить как более-менее стабильную витрину, то inventory — это пульсирующая часть системы.

Ordering отвечает на вопрос: что именно заказал клиент и как это зафиксировано исторически. Здесь важны номер заказа, позиции, количество, цена на момент покупки, итоговая сумма, адрес доставки. Эта зона особенно ценна для обучения, потому что быстро вытаскивает на поверхность transaction thinking.

Коротко это можно держать так:

Зона Главный вопрос Почему она полезна для курса
catalog Что продаём? Даёт entity-модель, querying и read-side сценарии
inventory Сколько доступно? Даёт consistency, конкуренцию и изменения состояния
ordering Что заказал клиент? Даёт многосущностные операции и транзакционную логику

Уже по одной этой таблице видно, что курс не про «случайные сущности». Он про разные классы задач внутри одного data-layer.

3. Сценарий чтения: показать карточку товара

Возьмём простой сценарий глазами клиента или каталога: нужно показать карточку товара. На словах это звучит безобидно. Но если чуть присмотреться, внутри уже прячется несколько вопросов.

Что именно нужно показать? Название, цену, категорию, описание, возможно производителя, возможно наличие на складе. И тут сразу видно, что даже «обычное чтение» редко сводится к одной голой таблице. Где-то понадобится связь с категорией. Где-то — подробности товара. Где-то — остаток. А дальше уже встаёт вопрос: всё ли это должно читаться как один объект, как несколько связанных сущностей или как отдельная read-model?

То есть ещё до первой аннотации видно, что data-layer — это не только «уметь сохранять». Это ещё и умение читать данные под конкретный use case. Карточка товара — не то же самое, что строка каталога. Админский список — не то же самое, что деталка товара. Здесь уже начинает рождаться тема read-side и того, как проектировать querying под разные сценарии.

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

4. placeOrder() как сердцевина курса 💥

Если нужен один сценарий, который собирает почти весь будущий курс в одну точку, это placeOrder(). Внешне он выглядит естественно: клиент оформляет заказ. Но внутри это уже серьёзная бизнес-операция.

Система должна понять, какие товары входят в заказ, проверить количество, сопоставить товары с остатками, зафиксировать сам заказ, создать позиции заказа, посчитать итоговую сумму и изменить состояние остатков. Это не один вызов «сохранить объект». Это серия связанных изменений, которые должны образовать один согласованный результат 🧵

Упрощённо такая операция может мыслиться так:

class OrderService {

    String placeOrder(String customerEmail, List<OrderLineRequest> lines) {
        // Входные данные: email клиента и список позиций (что и в каком количестве хотят купить)

        // 1. Загрузить товары и остатки
        // 2. Проверить доступность количества
        // 3. Создать CustomerOrder
        // 4. Создать OrderItem для каждой позиции
        // 5. Изменить StockItem
        // 6. Сохранить изменения как один согласованный результат
      
        // В ответ обычно возвращаем внешний идентификатор заказа (номер/код), а не внутренний DB id
        return "ORD-2025-0001";
    }
}

И вот здесь становится видно, почему курс спроектирован именно так. Из одного placeOrder() естественно вырастают почти все большие темы:

  • как выглядит entity-модель и связи;
  • где проходит граница репозитория;
  • где начинается transaction boundary;
  • как ORM формирует SQL;
  • как не потерять согласованность остатков;
  • что делать с конкурентными изменениями;
  • как тестировать такой сценарий.

Это и есть ключевая мысль первого дня: один обычный use case уже тянет за собой почти весь курс, и не искусственно, а по инженерной логике 🤯

5. Сценарий выборки

Теперь добавим другой тип задачи. Не клиентский read-сценарий, а внутренний админский. Например, нужно найти товары по части имени, категории и статусу. Или построить отчёт по товарам с низким остатком. Эти задачи обычно не сводятся к «загрузить сущность по id».

Здесь уже становятся естественными вопросы: когда хватает derived query, когда удобнее JPQL, когда нужна проекция, а когда оправдан точечный native-запрос. Причём всё это уже заложено в домене, а не навязано искусственно. Low-stock report не берётся из воздуха. Он вырастает из того, что у нас есть inventory и бизнесу действительно важно видеть товары, которые скоро закончатся.

Это очень сильный сигнал про цели и ценность курса. Мы не будем изучать querying как коллекцию случайных трюков. Мы будем связывать форму запроса с формой use case. И тогда методы репозитория перестают быть «магическими именами», а становятся конкретными ответами на конкретные потребности системы.

6. Где проходят границы модели

Теперь можно мягко очертить будущую модель, не превращая лекцию в инвентаризацию классов. В каталоге будут жить Category и Product. Это базовые факты о том, что продаётся. Подробности товара вроде описания или производителя логично держать отдельно как ProductDetails, потому что не каждое чтение каталога должно тащить на себе все детальные поля.

В inventory будет жить StockItem, потому что остаток — это отдельный вид состояния. Это не то же самое, что товар. У товара есть имя и цена. У остатка есть доступное и, возможно позже, зарезервированное количество. Смешивать эти данные в один «всё-объект» обычно неудобно и для модели, и для чтений.

В ordering будут CustomerOrder и OrderItem. Это тоже принципиально разные вещи. Заказ — корневой факт бизнес-операции. Позиции заказа — его состав. Цена в OrderItem фиксируется как часть истории, потому что заказ должен переживать будущие изменения цены в каталоге. Адрес доставки в нашем проекте — часть заказа, а не отдельная самостоятельная подсистема.

На уровне структуры проекта полезно сразу думать по границам фич, а не «одной большой папкой entity на всё приложение»:

shop-data-jpa/          # Корень проекта/модуля с data-layer
├── catalog/            # Всё про "что продаём": категории, товары, описания, чтения под каталог
├── inventory/          # Всё про "сколько доступно": остатки, резервы, конкуренция за сток
└── ordering/           # Всё про "как оформляем": заказы, позиции, транзакционные сценарии

Это небольшой, но важный архитектурный жест. Он помогает не терять смысловые зоны проекта с самого начала.

7. Таблица дня: mini-shop persistence map

Ниже — главный переносимый артефакт первого дня. Сохраните его. Это не просто таблица «что будет в курсе», а рабочая карта: из какого сценария какой persistence-вопрос вырастает. По ней потом удобно проверять, не превращается ли JPA в магию.

Сценарий Какие данные должны стать устойчивым состоянием Какой persistence-вопрос внутри Где курс это закроет
Показать карточку товара Product, Category, ProductDetails, иногда StockItem Как читать под конкретный use case, не тянуть лишнее, понимать связи и JOIN Entity mapping, querying, fetch planning
Создать или изменить товар Product, связь с Category, ограничения по полям Как проектировать entity и repository без свалки методов Entity model, basic repositories, constraints
Оформить заказ (placeOrder()) CustomerOrder, OrderItem, изменение StockItem Где transaction boundary, как держать согласованность и историю Transactions, persistence context, locking
Найти товары по фильтрам Данные каталога и статусы Как выражать read-side сценарии разной сложности Derived queries, JPQL, projections, specifications
Построить low-stock report Актуальные остатки и связанные товары Когда нужна проекция, а когда оправдан SQL-ориентированный отчёт Querying, projections, точечные native queries
Эволюционировать схему Таблицы, ключи, ограничения, индексы Кто источник правды для схемы и как не жить на ручных правках Flyway, DB constraints, schema discipline
Проверить всё это тестами Тестовые данные, репозитории, сервисные сценарии Как тестировать data-layer отдельно и осмысленно @DataJpaTest, service integration tests

Вот здесь и появляется ощущение, что курс спроектирован как одна инженерная история, а не как набор «сегодня аннотация, завтра ещё аннотация» 🌟 На одном проекте естественно появляются mapping, querying, transactions, consistency, migrations и testing. Не потому, что автору захотелось охватить всё, а потому, что сам домен этого требует.

8. Самоограничение

Важно ещё и то, чего в проекте сознательно нет. Мы не тащим сюда security, платежи, внешние интеграции, messaging, кэширование, микросервисы и Kubernetes. Не потому, что это «неважно», а потому, что сейчас они бы размыли главный фокус: слой данных внутри одного Spring Boot-приложения с одной PostgreSQL-базой.

И точно так же курс не будет притворяться полным deep-dive в Hibernate. Мы будем говорить о Hibernate ровно настолько, насколько это нужно, чтобы понимать поведение ORM в обычной прикладной разработке. Глубокая runtime-механика, тонкая производительность и диагностика internals — это уже следующий уровень и отдельный курс 🔬

Такая честная граница — не ограничение ради ограничений. Это способ сделать курс максимально полезным. Когда рамки прозрачны, студент понимает, зачем здесь каждая тема и почему рядом нет десяти лишних веток. Доверие к курсу очень часто строится именно на этом: автор знает, где остановиться, а не только где добавить ещё одну модную технологию 👍

9. Следующий шаг

После такого walkthrough уже хорошо видно, что следующий шаг курса должен быть ориентирован именно на SQL. Если карточка товара, остатки и заказ всё равно стоят на таблицах, ключах и JOIN, значит без реляционного фундамента дальше будет только иллюзия понимания. Поэтому следующий естественный вопрос звучит не «какую аннотацию ставить первой», а «как вообще мыслятся таблицы, связи, ограничения и операции чтения/записи под эту модель».

А дальше маршрут тоже выглядит логично. Сначала SQL-фундамент и инфраструктура. Потом entity-модель и базовые репозитории. Потом querying. Потом transaction thinking. Потом lazy loading, fetch planning, consistency, Flyway и тесты. То есть курс не набрасывает темы. Он последовательно отвечает на вопросы, которые уже видны из mini-shop.

И есть ещё одна полезная линия позиционирования. Тут можно чуть-чуть похвастаться 😅 Когда этот курс закончится, у вас будет не просто «знание нескольких аннотаций», а собранный слой данных. Поэтому следующими естественными шагами станут Spring Test и Hibernate deep-dive. Первый усилит проверяемость системы, второй — понимание runtime-поведения ORM. Но сначала нужен фундамент текущего курса, и первый уровень как раз должен был показать почему.

10. Типичные ошибки 🚫

Ошибка №1: воспринимать проект как список сущностей, а не как набор сценариев.
Как только вы перестаёте видеть use case, модель быстро становится случайной. А вместе с ней случайными становятся и репозитории.

Ошибка №2: пытаться превратить mini-shop в «полный e-commerce».
Это сразу убивает фокус. Курс сильнее именно потому, что проект ограничен и заставляет глубоко понять data-layer, а не поверхностно пробежать всё подряд.

Ошибка №3: думать, что placeOrder() — это «ну потом, когда дойдём до транзакций».
Наоборот, уже сейчас полезно видеть, что один такой сценарий и есть причина существования половины тем курса.

Ошибка №4: считать проектный walkthrough чем-то вторичным по сравнению с теорией.
На самом деле это и есть проверка теории на жизнеспособность. Если идея не выдерживает обычного доменного сценария, значит её объяснили слишком абстрактно.

1
Задача
Spring Data JPA, 1 уровень, 4 лекция
Недоступна
JSON-карта предметных зон mini-shop
JSON-карта предметных зон mini-shop
1
Задача
Spring Data JPA, 1 уровень, 4 лекция
Недоступна
Пакеты и классы доменной карты
Пакеты и классы доменной карты
1
Опрос
Слой данных, 1 уровень, 4 лекция
Недоступен
Слой данных
Persistence в приложении
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ