Как структурировать проекты на Spring/Spring Boot?

В данном тексте мы попробуем разобраться в самой спорной и холиварной теме – как разбивать на модули и как расположить файлы в проекте на Java и Spring.

Представим, что мы будем структурировать большой проект, который уже существует. Так как такие большие проекты обычно пишутся большой командой и защищены NDA, то за неимением такого мы возьмём небольшой проект и будем представлять, что он очень большой!

Итак, теперь мы имеем следующую структуру папок и файлов:

.
├── application/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/
│   │   │   │   └── ru/example/
│   │   │   │       ├── controller/
│   │   │   │       │   ├── PersonController.java
│   │   │   │       │   └── OrderController.java
│   │   │   │       └── Main.java
│   │   │   └── resources/
│   │   │       └── application.properties
│   │   └── test/
│   │       └── ...
│   └── pom.xml
├── database/
│   ├── src/
│   │   └── main/
│   │       └── java/
│   │           └── ru/example/dao/
│   │               └── impl/
│   │                   ├── PersonDaoJdbc.java
│   │                   ├── OrderDaoJdbc.java
│   │                   ├── PersonDao.java
│   │                   └── OrderDao.java
│   └── pom.xml
├── model/
│   ├── src/
│   │   └── main/
│   │       └── java/
│   │           └── ru/example/model/
│   │               ├── Person.java
│   │               └── Order.java
│   └── pom.xml
├── service/
│   ├── src/
│   │   ├── main/
│   │   │   └── java/
│   │   │       └── ru/example/service/
│   │   │           ├── impl/
│   │   │           │   ├── PersonServiceImpl.java
│   │   │           │   └── OrderService.java
│   │   │           ├── PersonService.java    
│   │   │           └── OrderService.java
│   │   └── test/
│   │       └── ...
│   └── pom.xml
└── pom.xml

Возможно, вы узнали структуру вашего проекта!

Итак, начнём

Давайте попробуем упростить структуру с точки зрения современных практик и возможностей Spring Boot. Для начала посмотрим на разбиение на Maven-модули по слоям приложения. Существует такое понятие, как связность компонента (cohesion, не путать со связАнностью компонент — coupling).

Связности бывают разные: • случайная связность; • коммуникационная связность; • функциональная связность; • логическая связность; • процедурная связность; • последовательностная связность; • временнАя связность.

На самое деле, это — не лучшее деление для большого проекта. Чуть попозже мы поговорим про функциональную связность.

Существует несколько аргументов «за» такого деления: • можно на уровне зависимости Maven-модулей разделить архитектурные слои; • можно очень просто контролировать зависимости каждого слоя и используемые фреймворки.

Тем не менее существует несколько недостатков данного разделения: • собственно, функциональная связанность компонент; • большая сложность в дальнейшем разделить на микросервисы (они требуют функциональной связанности); • если вы используете Spring или Spring Boot, то начинаются проблемы с автоконфигурацией (Main -класс в одном модуле, а тестируемый класс – в другом); • да, и самое ужасное – application.properties находится в одном модуле, а использоваться могут в другом.

Объединим в один модуль. Итак, теперь мы имеем следующую структуру папок и файлов:

.
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── ru/example/
│   │   │       ├── controller/
│   │   │       │   ├── PersonController.java
│   │   │       │   └── OrderController.java
│   │   │       ├── service/
│   │   │       │   ├── impl/
│   │   │       │   │   ├── PersonServiceImpl.java
│   │   │       │   │   └── OrderService.java
│   │   │       │   ├── PersonService.java    
│   │   │       │   └── OrderService.java   
│   │   │       ├── model/
│   │   │       │   ├── Person.java
│   │   │       │   └── Order.java      
│   │   │       ├── dao/
│   │   │       │   ├── impl/
│   │   │       │   │   ├── PersonDaoJdbc.java
│   │   │       │   │   └── OrderDaoJdbc.java
│   │   │       │   ├── PersonDao.java
│   │   │       │   └── OrderDao.java
│   │   │       └── Application.java
│   │   └── resources/
│   │       └── application.properties
│   └── test/
│       └── ...
└── pom.xml

Обратим внимание, что каждый сервис покрыт отдельным интерфейсом.

Существует несколько аргументов «за» покрытие интерфейсами всех сервисов. Попробуем отойти от некоторых принципов в пользу более сжатого кода: • задать «контракт» для сервиса и соблюсти Dependency Inversion Principle. Собственно, с этим никак нельзя поспорить)). Но в современных средах разработки выделение интерфейса состоит из нажатия комбинации клавиш на клавиатуре. Эту операцию можно успеть сделать всегда, притом, что большинство интерфейсов всегда будут иметь только одного наследника; • добавить возможность создания JDK-proxy вместо CGLib-proxy при использовании AOP или некоторых проектов Spring. С этим тоже не поспоришь)). По скорости CGLib-proxy не хуже JDK dynamic proxy, правда, создаются чуть медленнее. Скорость поднятия контекста приложения – сомнительный параметр для оптимизации; • иметь возможность мокирования интерфейсов для написания Unit-тестов. А вот это не аргумент! C Mockito можно создать моки всех методов без наличия интерфейса.

Уберём ненужные интерфейсы.

.
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── ru/example/
│   │   │       ├── controller/
│   │   │       │   ├── PersonController.java
│   │   │       │   └── OrderController.java
│   │   │       ├── service/
│   │   │       │   ├── PersonService.java    
│   │   │       │   └── OrderService.java   
│   │   │       ├── model/
│   │   │       │   ├── Person.java
│   │   │       │   └── Order.java      
│   │   │       ├── dao/
│   │   │       │   ├── PersonDao.java
│   │   │       │   └── OrderDao.java
│   │   │       └── Application.java
│   │   └── resources/
│   │       └── application.properties
│   └── test/
│       └── ...
└── pom.xml

И, наконец, переструктурируем в функциональную связность:

 .
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── ru/example/
│   │   │       ├── person/
│   │   │       │   ├── Person.java
│   │   │       │   ├── PersonDao.java
│   │   │       │   ├── PersonController.java
│   │   │       │   └── PersonService.java    
│   │   │       ├── service/
│   │   │       │   ├── Order.java      
│   │   │       │   ├── OrderDao.java
│   │   │       │   ├── OrderController.java
│   │   │       │   └── OrderService.java   
│   │   │       └── Main.java
│   │   └── resources/
│   │       └── application.properties
│   └── test/
│       └── ...
└── pom.xml

Вот и всё. Сравните теперь с исходным проектом!