Imported from if25b190/SWEN3-Paperless (
AGENTS.md). Install upstream withnpx skills add if25b190/SWEN3-Paperless. Copyright stays with the author.
Backend — Spring Boot
Standards: docs/java-style.md · docs/annotations.md ·
docs/layered-architecture.md · docs/controllers.md ·
docs/mappers.md · docs/exceptions.md ·
docs/testing.md · docs/logging.md
Stack
Java 25 · Spring Boot 4.x · Maven · Spring Data JPA · Lombok · JUnit 6 + Mockito + AssertJ.
Feature package layout
One package per feature, split into layer subpackages:
at/fhtw/swen3/paperless/<feature>/
controller/ <Feature>Controller @RestController @RequestMapping("/<feature>")
service/ <Feature>Service interface — the feature's public surface
<Feature>ServiceImpl @Service, class-level @Transactional — the one impl
repository/ <Feature>Repository interface extends JpaRepository<<Feature>Entity, Id>
model/ <Feature>, enums plain POJO (Lombok), no jakarta.persistence imports
entity/ <Feature>Entity @Entity only, no logic
dto/ Create/Update/Response records
mapper/ <Feature>Mapper static — DTO ↔ model
<Feature>EntityMapper static — model ↔ entity
Not every feature needs every file — a read-only feature has no Create<Feature>DTO. Add a
layer only when it actually carries weight.
Gotchas
- The service is an interface
<Feature>Serviceplus one@Serviceimplementation<Feature>ServiceImpl(both inservice/). Inject and mock the interface;@InjectMocksin the service's own unit test targets<Feature>ServiceImpl— Mockito can't instantiate an interface. - The repository is a plain Spring Data interface, nothing hand-written. "Find or 404" is a
service concern:
repository.findById(id).orElseThrow(() -> new AppException(AppErrorMessage.<X>_NOT_FOUND)). model/holds plain POJOs (no JPA imports);entity/holds@Entityclasses only, no logic.- No
finalon method parameters or local variables, anywhere. Production code, controllers and tests all agree on this;finalsurvives only on Lombok constructor-injected fields, because@RequiredArgsConstructorneeds it. varin controllers and tests; explicit types in services and mappers.- No existence checks or business logic in controllers — that belongs in the service, which should throw when something isn't there.
@Transactionalat class level, service classes only (readOnly = truefor read paths).