|
2 | 2 |
|
3 | 3 | ## Project Overview |
4 | 4 |
|
5 | | -This is a Spring Boot REST API for managing football players, demonstrating modern Java patterns and clean architecture. It's a learning-focused proof-of-concept that follows Spring Boot best practices with layered architecture (Controller → Service → Repository). |
| 5 | +REST API for managing football players built with Java 25 and Spring Boot 4. Demonstrates layered architecture (Controller → Service → Repository), Spring Data JPA with SQLite, comprehensive validation, caching, and Swagger documentation. |
6 | 6 |
|
7 | | -## Stack |
8 | | - |
9 | | -- **Java**: 25 (LTS, required for consistency) |
10 | | -- **Spring Boot**: 4.0.0 (with Spring MVC) |
11 | | -- **ORM**: Spring Data JPA with Hibernate |
12 | | -- **Database**: SQLite (file-based for runtime, in-memory for tests) |
13 | | -- **Build Tool**: Maven 3 (use `./mvnw` wrapper, NOT system Maven) |
14 | | -- **Containers**: Docker with Docker Compose |
15 | | -- **Logging**: SLF4J with Logback |
16 | | -- **Monitoring**: Spring Boot Actuator |
17 | | -- **API Docs**: SpringDoc OpenAPI 3 (Swagger UI) |
18 | | - |
19 | | -## Project Patterns |
20 | | - |
21 | | -- **Layered architecture**: Controller → Service → Repository → Database |
22 | | -- **Dependency injection**: Constructor injection via Lombok's @RequiredArgsConstructor |
23 | | -- **DTO pattern**: Never expose entities in controllers, use DTOs with ModelMapper |
24 | | -- **Caching**: Spring Cache abstraction (@Cacheable) with 1-hour TTL |
25 | | -- **Error handling**: @ControllerAdvice for global exception handling |
26 | | -- **Validation**: Bean Validation (JSR-380) in DTOs |
27 | | -- **Repository queries**: Mix of derived queries (findBySquadNumber) and custom JPQL (@Query) |
28 | | -- **Date handling**: ISO-8601 strings with custom JPA converter for SQLite compatibility |
29 | | - |
30 | | -## Code Conventions |
31 | | - |
32 | | -### Naming |
| 7 | +## Quick Start |
33 | 8 |
|
34 | | -- **Classes**: PascalCase (e.g., `PlayersController`, `PlayerDTO`) |
35 | | -- **Methods/Variables**: camelCase (e.g., `findById`, `squadNumber`) |
36 | | -- **Test methods**: `givenX_whenY_thenZ` (e.g., `givenSquadNumberExists_whenPost_thenReturnsConflict`) |
37 | | - |
38 | | -### Annotations |
39 | | - |
40 | | -- **Controllers**: @RestController (never @Controller for REST APIs) |
41 | | -- **DTOs**: Lombok @Data, @Builder, @AllArgsConstructor |
42 | | -- **Services**: @Service + @Transactional on mutating methods |
43 | | -- **Repositories**: @Repository (extend JpaRepository) |
44 | | -- **Entities**: @Entity with @Table, @Id, @GeneratedValue |
| 9 | +```bash |
| 10 | +# Development |
| 11 | +./mvnw spring-boot:run # Run with hot reload (port 9000) |
| 12 | +./mvnw clean test # Run tests |
| 13 | +./mvnw clean test jacoco:report # Test with coverage |
45 | 14 |
|
46 | | -### Lombok usage |
| 15 | +# Docker |
| 16 | +docker compose up # Start in container |
| 17 | +docker compose down -v # Reset database |
47 | 18 |
|
48 | | -- Reduce boilerplate with @Data, @Builder, @AllArgsConstructor |
49 | | -- Use @RequiredArgsConstructor for constructor injection |
50 | | -- Never use field injection |
| 19 | +# Documentation |
| 20 | +http://localhost:9000/swagger/index.html # API docs |
| 21 | +http://localhost:9001/actuator/health # Health check |
| 22 | +``` |
51 | 23 |
|
52 | | -### REST conventions |
| 24 | +## Stack |
53 | 25 |
|
54 | | -- Return ResponseEntity<T> from controllers |
55 | | -- Use proper HTTP status codes (201 Created, 204 No Content, 409 Conflict) |
56 | | -- OpenAPI annotations required on all endpoints |
| 26 | +- Java 25 (LTS, required) |
| 27 | +- Spring Boot 4.0.0 (Spring MVC) |
| 28 | +- Spring Data JPA + Hibernate |
| 29 | +- SQLite (file-based runtime, in-memory tests) |
| 30 | +- Maven 3 (use `./mvnw` wrapper) |
| 31 | +- Bean Validation (JSR-380) |
| 32 | +- Spring Cache (1-hour TTL) |
| 33 | +- JUnit 5 + AssertJ + MockMvc |
| 34 | +- JaCoCo (coverage) |
| 35 | +- SpringDoc OpenAPI 3 (Swagger) |
| 36 | +- Lombok (boilerplate reduction) |
57 | 37 |
|
58 | | -### Logging |
| 38 | +## Project Patterns |
59 | 39 |
|
60 | | -- Use SLF4J (injected via Lombok @Slf4j) |
61 | | -- Never use System.out.println |
| 40 | +- **Architecture**: Layered (Controller → Service → Repository → JPA) |
| 41 | +- **Dependency Injection**: Constructor injection via Lombok `@RequiredArgsConstructor` |
| 42 | +- **Error Handling**: `@ControllerAdvice` for global exception handling |
| 43 | +- **Validation**: Bean Validation annotations in DTOs (`@NotNull`, `@Min`, etc.) |
| 44 | +- **Caching**: Spring `@Cacheable` on service layer (1-hour TTL) |
| 45 | +- **DTO Pattern**: ModelMapper for entity ↔ DTO transformations |
| 46 | +- **Repository**: Spring Data JPA with derived queries and custom JPQL |
62 | 47 |
|
63 | | -### Configuration |
| 48 | +## Code Conventions |
64 | 49 |
|
65 | | -- Externalize via @Value or application.yml |
66 | | -- Never hardcode values in code |
| 50 | +- **Naming**: camelCase (methods/variables), PascalCase (classes), UPPER_SNAKE_CASE (constants) |
| 51 | +- **Files**: Class name matches file name (e.g., `PlayersController.java`) |
| 52 | +- **Package Structure**: |
| 53 | + - `controllers/` - REST endpoints (`@RestController`) |
| 54 | + - `services/` - Business logic (`@Service`) |
| 55 | + - `repositories/` - Data access (`@Repository`, extends `JpaRepository`) |
| 56 | + - `models/` - Domain entities (`@Entity`) and DTOs |
| 57 | + - `converters/` - JPA attribute converters |
| 58 | +- **Annotations**: |
| 59 | + - Controllers: `@RestController`, `@RequestMapping`, `@Operation` (OpenAPI) |
| 60 | + - Services: `@Service`, `@Transactional`, `@Cacheable` |
| 61 | + - Repositories: `@Repository` (Spring Data JPA) |
| 62 | + - DTOs: `@NotNull`, `@Min`, `@Max` (Bean Validation) |
| 63 | + - Entities: `@Entity`, `@Table`, `@Id`, `@GeneratedValue` |
| 64 | +- **Lombok**: `@Data`, `@Builder`, `@AllArgsConstructor`, `@RequiredArgsConstructor` |
| 65 | +- **Logging**: SLF4J (never `System.out.println`) |
67 | 66 |
|
68 | 67 | ## Testing |
69 | 68 |
|
70 | | -### Framework |
71 | | - |
72 | | -- **Unit tests**: JUnit 5 with AssertJ for fluent assertions |
73 | | -- **Integration tests**: @SpringBootTest with MockMvc |
74 | | -- **Coverage**: JaCoCo reports (must maintain high coverage) |
75 | | - |
76 | | -### Test naming |
77 | | - |
78 | | -**Pattern**: `givenX_whenY_thenZ()` (BDD Given-When-Then) |
79 | | - |
80 | | -**Naming philosophy:** |
81 | | - |
82 | | -Use **semantic naming** that describes the business/domain state and behavior, not technical implementation details: |
83 | | - |
84 | | -- **Given** (precondition): Describe the state of the system or data |
85 | | - - `givenPlayerExists` - A player is present in the system |
86 | | - - `givenNoExistingPlayer` - No player matching criteria |
87 | | - - `givenInvalidPlayer` - Invalid data provided |
88 | | - - `givenRaceCondition` - Concurrent modification scenario |
89 | | - - `givenNullId` - Missing required identifier |
90 | | -- **When** (action): The method/operation being tested |
91 | | - - `whenCreate` - Creating a new entity |
92 | | - - `whenRetrieveById` - Fetching by identifier |
93 | | - - `whenUpdate` - Updating existing entity |
94 | | - - `whenDelete` - Removing an entity |
95 | | - - `whenSearchByLeague` - Searching/filtering operation |
96 | | -- **Then** (outcome): Expected result or behavior |
97 | | - - `thenReturnsPlayerDTO` - Returns the DTO object |
98 | | - - `thenReturnsNull` - Returns null (not found/conflict) |
99 | | - - `thenReturnsTrue` / `thenReturnsFalse` - Boolean success indicator |
100 | | - - `thenReturns26Players` - Specific count of results |
101 | | - - `thenReturnsEmptyList` - No results found |
102 | | - - `thenReturnsOk` - HTTP 200 response |
103 | | - - `thenReturnsNotFound` - HTTP 404 response |
104 | | - - `thenReturnsCreated` - HTTP 201 response |
105 | | - |
106 | | -**Code conventions:** |
107 | | - |
108 | | -- Comments: Use **Given/When/Then** (BDD pattern) |
109 | | -- Assertions: Use `then()` from AssertJ BDDAssertions |
110 | | -- Variables: |
111 | | - - Use `actual` for operation results |
112 | | - - Use `expected` for comparison values when verifying equality |
113 | | - - Use `existing` for pre-saved entities in database |
114 | | - |
115 | | -**Why BDD Given-When-Then?** |
116 | | - |
117 | | -- ✅ **Readable**: Natural language flow, accessible to all stakeholders |
118 | | -- ✅ **Behavior-focused**: Tests business logic, not implementation details |
119 | | -- ✅ **Self-documenting**: Method name clearly states test scenario |
120 | | -- ✅ **Framework-aligned**: Matches Cucumber/SpecFlow patterns |
121 | | - |
122 | | -```java |
123 | | -// Examples across layers: |
124 | | -givenNoExistingPlayer_whenCreate_thenReturnsPlayerDTO() // Service: success case |
125 | | -givenPlayerAlreadyExists_whenCreate_thenReturnsNull() // Service: conflict case |
126 | | -givenPlayerExists_whenFindById_thenReturnsPlayer() // Repository: found |
127 | | -givenPlayerDoesNotExist_whenFindById_thenReturnsEmpty() // Repository: not found |
128 | | -givenValidPlayer_whenPost_thenReturnsCreated() // Controller: HTTP 201 |
129 | | -givenPlayerDoesNotExist_whenGetById_thenReturnsNotFound() // Controller: HTTP 404 |
130 | | -``` |
131 | | - |
132 | | -### Test structure |
133 | | - |
134 | | -Use BDD (Given/When/Then) consistently in JavaDoc comments, method names, and code sections: |
135 | | - |
136 | | -```java |
137 | | -/** |
138 | | - * Given no existing player with the same squad number |
139 | | - * When create() is called with valid player data |
140 | | - * Then the player is saved and a PlayerDTO is returned |
141 | | - */ |
142 | | -@Test |
143 | | -void givenNoExistingPlayer_whenCreate_thenReturnsPlayerDTO() { |
144 | | - // Given |
145 | | - Player player = PlayerFakes.createOneValid(); |
146 | | - PlayerDTO expected = PlayerDTOFakes.createOneValid(); |
147 | | - // When |
148 | | - PlayerDTO actual = playersService.create(expected); |
149 | | - // Then |
150 | | - then(actual).isEqualTo(expected); |
151 | | -} |
152 | | -``` |
153 | | - |
154 | | -### Test requirements |
155 | | - |
156 | | -- Unit tests required for all service and repository methods |
157 | | -- Integration tests required for all controller endpoints |
158 | | -- Tests use in-memory SQLite (jdbc:sqlite::memory:) |
159 | | -- All tests must pass before PR (`./mvnw clean install`) |
160 | | -- **Assertion quality**: Verify actual behavior, not just counts (e.g., verify league content, not just `hasSize()`) |
| 69 | +- **Structure**: `*Tests.java` in `src/test/java/` (mirrors main package structure) |
| 70 | +- **Naming Pattern**: `method_scenario_outcome` |
| 71 | + - `method`: Method under test (e.g., `post`, `findById`, `create`) |
| 72 | + - `scenario`: Context (e.g., `playerExists`, `invalidData`, `noMatches`) |
| 73 | + - `outcome`: Expected result (e.g., `returnsPlayer`, `returnsConflict`, `returnsEmpty`) |
| 74 | +- **Examples**: |
| 75 | + |
| 76 | + ```java |
| 77 | + // Controller |
| 78 | + void post_squadNumberExists_returnsConflict() |
| 79 | + |
| 80 | + // Service |
| 81 | + void create_noConflict_returnsPlayerDTO() |
| 82 | + |
| 83 | + // Repository |
| 84 | + void findById_playerExists_returnsPlayer() |
| 85 | + ``` |
| 86 | + |
| 87 | +- **JavaDoc**: BDD Given/When/Then structure in test comments |
| 88 | + |
| 89 | + ```java |
| 90 | + /** |
| 91 | + * Given a player with squad number 5 already exists in the database |
| 92 | + * When POST /players is called with a new player using squad number 5 |
| 93 | + * Then response status is 409 Conflict |
| 94 | + */ |
| 95 | + @Test |
| 96 | + void post_squadNumberExists_returnsConflict() { ... } |
| 97 | + ``` |
| 98 | + |
| 99 | +- **Annotations**: `@SpringBootTest`, `@AutoConfigureMockMvc`, `@Test` |
| 100 | +- **Assertions**: AssertJ (fluent, e.g., `assertThat(result).isNotNull()`) |
| 101 | +- **Mocking**: Mockito for service layer tests |
| 102 | +- **Database**: Tests use in-memory SQLite (auto-cleared after each test) |
| 103 | +- **Coverage**: Target high coverage (JaCoCo reports in `target/site/jacoco/`) |
161 | 104 |
|
162 | 105 | ## Avoid |
163 | 106 |
|
164 | | -- **Field injection** - Always use constructor injection |
165 | | -- **Using `new` for Spring beans** - Breaks dependency injection |
166 | | -- **Missing @Transactional** - Required on service methods that modify data |
167 | | -- **Exposing entities in controllers** - Always use DTOs |
168 | | -- **System.out.println** - Use SLF4J logging |
169 | | -- **Hardcoded config** - Use @Value or application.yml |
170 | | -- **System Maven** - Always use `./mvnw` wrapper for consistency |
171 | | -- **SQLite in production** - This is a demo/development-only setup |
172 | | - |
173 | | -## Folder Structure |
174 | | - |
175 | | -```tree |
176 | | -src/main/java/ # Application code |
177 | | - ├── Application.java # @SpringBootApplication entry point |
178 | | - ├── controllers/ # REST endpoints |
179 | | - ├── services/ # Business logic + caching |
180 | | - ├── repositories/ # Data access (Spring Data JPA) |
181 | | - ├── models/ # Entities (Player) and DTOs (PlayerDTO) |
182 | | - └── converters/ # JPA converters (ISO date handling) |
183 | | -src/test/java/ # Test classes (mirrors main structure) |
184 | | -storage/ # SQLite database file (runtime) |
185 | | -scripts/ # Docker entrypoint and healthcheck |
186 | | -``` |
187 | | - |
188 | | -## Quick Reference |
| 107 | +- Field injection (use constructor injection) |
| 108 | +- Using `new` for Spring beans (breaks DI) |
| 109 | +- Missing `@Transactional` on service methods that modify data |
| 110 | +- Exposing entities directly in controllers (use DTOs) |
| 111 | +- `System.out.println` (use SLF4J logging) |
| 112 | +- Hardcoded configuration (use `@Value` or `application.properties`) |
| 113 | +- Ignoring exceptions (always handle or propagate) |
| 114 | +- Testing implementation details (test behavior, not internals) |
189 | 115 |
|
190 | | -```bash |
191 | | -# Development |
192 | | -./mvnw spring-boot:run # Run with hot reload |
193 | | -./mvnw clean test jacoco:report # Test with coverage |
| 116 | +## Commit Messages |
194 | 117 |
|
195 | | -# Docker |
196 | | -docker compose up # Start in container |
| 118 | +Follow Conventional Commits format (enforced by commitlint in CI): |
197 | 119 |
|
198 | | -# Documentation |
199 | | -http://localhost:8080/swagger-ui.html # API docs |
200 | | -http://localhost:8080/actuator/health # Health check |
201 | | -``` |
| 120 | +**Format**: `type(scope): description (#issue)` (max 80 chars) |
202 | 121 |
|
203 | | -## Commit Messages |
| 122 | +**Types**: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `ci`, `perf`, `style`, `build` |
204 | 123 |
|
205 | | -Follow Conventional Commits with issue number suffix: |
| 124 | +**Examples**: |
206 | 125 |
|
207 | | -- **Format**: `type(scope): description (#issue)` (max 80 chars) |
208 | | -- **Types**: feat, fix, chore, docs, test, refactor |
209 | | -- **Example**: `feat(api): add squad number search endpoint (#123)` |
| 126 | +- `feat(api): add player stats endpoint (#42)` |
| 127 | +- `fix(service): resolve cache invalidation bug (#88)` |
| 128 | +- `test: adopt BDD Given-When-Then pattern across all tests (#266)` |
210 | 129 |
|
211 | | -## Additional Context |
| 130 | +--- |
212 | 131 |
|
213 | | -For detailed operational procedures, workflows, and troubleshooting, see `AGENTS.md`. |
| 132 | +For detailed workflows, troubleshooting, and CI/CD setup, load `#file:AGENTS.md`. |
0 commit comments