← назад к методологии · уровень 1 из 3

Почти каждый Spring-проект начинается одинаково: контроллер вызывает сервис, сервис идёт в репозиторий. Эту схему называют слоёной архитектурой. Она простая, понятная и в большинстве случаев работает отлично — до тех пор, пока сервис не разрастается до полусотни методов.

Что такое слоёная архитектура

Три слоя, выстроенных в цепочку:

HTTP-запрос
    ↓
Controller  — принимает запрос, зовёт сервис, возвращает ответ
    ↓
Service     — бизнес-логика, проверки, оркестрация
    ↓
Repository  — запросы в базу данных

Каждый слой отвечает за своё. Контроллер не лезет в базу — это дело репозитория. Репозиторий не принимает HTTP — это дело контроллера. Сервис в середине держит логику.

Код выглядит примерно так:

@RestController
class OrderController {
    private final OrderService service;

    @PostMapping("/orders")
    OrderResponse create(@RequestBody CreateOrderRequest req) {
        return service.createOrder(req);
    }
}

@Service
class OrderService {
    private final OrderRepository repo;

    @Transactional
    OrderResponse createOrder(CreateOrderRequest req) {
        // проверки, логика, сохранение
        Order order = new Order(req.getCustomerId(), req.getItems());
        repo.save(order);
        return OrderResponse.from(order);
    }
}

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

Когда этого достаточно

Слоёная архитектура отлично подходит для:

  • Настоящего CRUD. Справочники, словари, простые административные панели. Операции — создать, прочитать, изменить, удалить. Никакой сложной логики внутри.
  • Прототипа или короткоживущего сервиса. Если сервис проживёт год-два и потом будет переписан или выключен — вкладываться в сложную структуру не имеет смысла.
  • Маленькой команды с понятным доменом. Когда домен укладывается в голове за полчаса, а инвариантов почти нет, дополнительные абстракции только мешают.
  • Тонкого прокси. Сервис принимает запрос и перекладывает его в другой сервис или внешнее API — бизнес-логики минимум.

Уровень 1 — не «недоделанный» уровень. Для части сервисов он остаётся достаточным навсегда. Добавлять сложность ради сложности — ошибка.

Что должно быть на этом уровне

Тонкие контроллеры. Контроллер делает ровно одно: принимает запрос, зовёт сервис, возвращает ответ. Никакой логики внутри контроллера.

Сервисы по предметным областям. OrderService, CustomerService — каждый отвечает за свою тему. Логика и обращения к репозиторию живут внутри сервиса.

Транзакция на уровне метода. Одна операция — один @Transactional метод. Либо всё сохраняется, либо ничего.

Одна модель данных. То, что приходит из API, и то, что лежит в базе, связано простым маппингом. Отдельных доменных моделей и объектов-значений на этом уровне нет — они появятся выше.

Где это начинает мешать

Слоёная модель хороша до тех пор, пока сервис остаётся небольшим. Проблемы начинаются, когда он растёт:

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

Одна операция вызывается из нескольких мест с отличиями. Если одну операцию вызывают из трёх контроллеров с чуть разными проверками, эти проверки со временем расходятся — появляются незаметные ошибки.

Нет единой точки контроля. Метрики на операцию, аудит, единый способ обработки ошибок — всё это приходится добавлять в каждый метод руками. Легко пропустить, сложно поддерживать.

Сложно тестировать операцию изолированно. Тест бизнес-операции тянет за собой весь сервис-класс со всеми зависимостями. Нет способа запустить только одну операцию без остальных.

Это не «плохой код» — это предел слоёной модели. У неё просто нет механизма явно выделять отдельные операции.

Что фиксируется в спецификации на этом уровне

Если проект использует Use Case спецификацию, на уровне 1 она минимальна: глоссарий, роли, схема данных и список операций. Агрегатов и доменных событий нет — они помечаются «не применимо на Уровне 1». Центральная сущность всё равно выносится в отдельный файл — формат раскладки единый для всех уровней.

Признаки, что пора на уровень 2

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

Если хотя бы одно из этого актуально — пора к следующему шагу: Уровень 2: Use Case Pattern. Там каждая бизнес-операция выделяется в отдельный UseCase + Handler.

Коротко

  • Слоёная архитектура — три слоя: Controller, Service, Repository.
  • Подходит для CRUD, прототипов, маленьких сервисов с простым доменом.
  • Контроллер тонкий, логика в сервисе, транзакция на уровне метода.
  • Ломается, когда сервис разрастается: теряется явность операций, метрики и аудит не масштабируются.
  • Уровень 1 — осознанный выбор, а не недоделка. Для части сервисов он достаточен навсегда.

Что почитать дальше

  • Use Case Pattern — все три уровня — общая картина и когда переходить между уровнями.
  • Уровень 2: Use Case Pattern — явное выделение каждой операции в UseCase + Handler.
  • REST API Style Guide — контракты для внешнего API.