|
1 | | -# GitHub Copilot Instructions |
| 1 | +# Copilot Instructions |
2 | 2 |
|
3 | | -> **Token Efficiency Note**: This is a minimal pointer file (~500 tokens, auto-loaded by Copilot). |
4 | | -> For complete operational details, reference: `#file:AGENTS.md` (~2,500 tokens, loaded on-demand) |
5 | | -> For specialized knowledge, use: `#file:SKILLS/<skill-name>/SKILL.md` (loaded on-demand when needed) |
| 3 | +## Project Overview |
6 | 4 |
|
7 | | -## Quick Context |
| 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). |
8 | 6 |
|
9 | | -**Project**: Spring Boot REST API demonstrating modern Java patterns |
10 | | -**Stack**: Java 25 (LTS) • Spring Boot 4 • JPA/Hibernate • SQLite • Maven • Docker |
11 | | -**Pattern**: Controller → Service → Repository → JPA (layered architecture) |
12 | | -**Philosophy**: Learning-focused PoC emphasizing Spring Boot best practices |
| 7 | +## Stack |
13 | 8 |
|
14 | | -## Core Conventions |
| 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) |
15 | 18 |
|
16 | | -- **Naming**: camelCase (methods/variables), PascalCase (classes) |
17 | | -- **Annotations**: Use Spring stereotypes (@RestController, @Service, @Repository) |
18 | | -- **Lombok**: Reduce boilerplate (@Data, @Builder, @AllArgsConstructor) |
19 | | -- **Dependency Injection**: Constructor injection (Lombok @RequiredArgsConstructor) |
20 | | -- **Testing**: JUnit 5 + AssertJ for fluent assertions |
21 | | -- **Build**: Use `./mvnw` wrapper, NOT system Maven |
22 | | -- **Commit Messages**: Follow Conventional Commits with issue number suffix |
23 | | - - Format: `type(scope): description (#issue)` (max 80 chars) |
24 | | - - Example: `docs: optimize AI agent instructions for token efficiency (#259)` |
25 | | - - Types: `feat`, `fix`, `chore`, `docs`, `test`, `refactor` |
| 19 | +## Project Patterns |
26 | 20 |
|
27 | | -## Test Naming Convention |
| 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 |
28 | 29 |
|
29 | | -**Pattern**: `method_scenario_outcome` |
| 30 | +## Code Conventions |
30 | 31 |
|
31 | | -- **method**: The method being tested (e.g., `post`, `findById`, `create`) |
32 | | -- **scenario**: The context or condition (e.g., `playerExists`, `invalidData`, `noMatches`) |
33 | | -- **outcome**: The expected result (e.g., `returnsPlayer`, `returnsConflict`, `returnsEmpty`) |
| 32 | +### Naming |
| 33 | + |
| 34 | +- **Classes**: PascalCase (e.g., `PlayersController`, `PlayerDTO`) |
| 35 | +- **Methods/Variables**: camelCase (e.g., `findById`, `squadNumber`) |
| 36 | +- **Test methods**: `method_scenario_outcome` (e.g., `post_squadNumberExists_returnsConflict`) |
| 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 |
| 45 | + |
| 46 | +### Lombok usage |
| 47 | + |
| 48 | +- Reduce boilerplate with @Data, @Builder, @AllArgsConstructor |
| 49 | +- Use @RequiredArgsConstructor for constructor injection |
| 50 | +- Never use field injection |
| 51 | + |
| 52 | +### REST conventions |
| 53 | + |
| 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 |
| 57 | + |
| 58 | +### Logging |
| 59 | + |
| 60 | +- Use SLF4J (injected via Lombok @Slf4j) |
| 61 | +- Never use System.out.println |
| 62 | + |
| 63 | +### Configuration |
| 64 | + |
| 65 | +- Externalize via @Value or application.yml |
| 66 | +- Never hardcode values in code |
| 67 | + |
| 68 | +## Testing |
| 69 | + |
| 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 |
34 | 121 |
|
35 | | -**Examples**: |
36 | 122 | ```java |
37 | | -// Controller: post_squadNumberExists_returnsConflict() |
38 | | -// Service: create_noConflict_returnsPlayerDTO() |
39 | | -// Repository: findById_playerExists_returnsPlayer() |
| 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 |
40 | 130 | ``` |
41 | 131 |
|
42 | | -**JavaDoc**: Use proper BDD (Given/When/Then) structure in comments: |
| 132 | +### Test structure |
| 133 | + |
| 134 | +Use BDD (Given/When/Then) consistently in JavaDoc comments, method names, and code sections: |
| 135 | + |
43 | 136 | ```java |
44 | 137 | /** |
45 | | - * Given a player with squad number 5 already exists in the database |
46 | | - * When POST /players is called with a new player using squad number 5 |
47 | | - * Then response status is 409 Conflict |
| 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 |
48 | 141 | */ |
49 | 142 | @Test |
50 | | -void post_squadNumberExists_returnsConflict() { ... } |
| 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 | +} |
51 | 152 | ``` |
52 | 153 |
|
53 | | -**Benefits**: Concise method names for IDE test runners, full BDD context in JavaDoc for code readability. |
54 | | - |
55 | | -## Architecture at a Glance |
56 | | - |
| 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()`) |
| 161 | + |
| 162 | +## Avoid |
| 163 | + |
| 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 |
57 | 186 | ``` |
58 | | -Controller → Service → Repository → JPA → Database |
59 | | - ↓ ↓ ↓ |
60 | | -Validation Cache Query Methods |
61 | | -``` |
62 | | - |
63 | | -- **Controllers**: REST endpoints with @RestController |
64 | | -- **Services**: Business logic with @Service + caching |
65 | | -- **Repositories**: JpaRepository with derived queries |
66 | | -- **DTOs**: ModelMapper for entity ↔ DTO transformations |
67 | | -- **Cache**: Spring Cache abstraction (1-hour TTL) |
68 | | - |
69 | | -## Copilot Should |
70 | | - |
71 | | -- Generate idiomatic Spring Boot code with proper annotations |
72 | | -- Use JPA repository patterns (derived queries, @Query) |
73 | | -- Follow REST conventions with ResponseEntity<T> |
74 | | -- Write tests with @SpringBootTest and MockMvc |
75 | | -- Apply Lombok annotations to reduce boilerplate |
76 | | -- Use ModelMapper for DTO transformations |
77 | | -- Implement proper exception handling with @ControllerAdvice |
78 | 187 |
|
79 | | -## Copilot Should Avoid |
80 | | - |
81 | | -- Field injection (use constructor injection) |
82 | | -- Using `new` for services (breaks DI) |
83 | | -- Missing @Transactional on service methods |
84 | | -- Exposing entities directly in controllers (use DTOs) |
85 | | -- System.out.println (use SLF4J logging) |
86 | | -- Hardcoded configuration (use @Value or application.yml) |
87 | | - |
88 | | -## Quick Commands |
| 188 | +## Quick Reference |
89 | 189 |
|
90 | 190 | ```bash |
91 | | -# Run with hot reload |
92 | | -./mvnw spring-boot:run |
93 | | - |
94 | | -# Test with coverage |
95 | | -./mvnw clean test jacoco:report |
| 191 | +# Development |
| 192 | +./mvnw spring-boot:run # Run with hot reload |
| 193 | +./mvnw clean test jacoco:report # Test with coverage |
96 | 194 |
|
97 | 195 | # Docker |
98 | | -docker compose up |
| 196 | +docker compose up # Start in container |
99 | 197 |
|
100 | | -# Swagger: http://localhost:9000/swagger-ui/index.html |
101 | | -# Actuator: http://localhost:9001/actuator/health |
| 198 | +# Documentation |
| 199 | +http://localhost:8080/swagger-ui.html # API docs |
| 200 | +http://localhost:8080/actuator/health # Health check |
102 | 201 | ``` |
103 | 202 |
|
104 | | -## Need More Detail? |
| 203 | +## Commit Messages |
| 204 | + |
| 205 | +Follow Conventional Commits with issue number suffix: |
105 | 206 |
|
106 | | -**For operational procedures**: Load `#file:AGENTS.md` |
107 | | -**For Docker expertise**: *(Planned)* `#file:SKILLS/docker-containerization/SKILL.md` |
108 | | -**For testing patterns**: *(Planned)* `#file:SKILLS/testing-patterns/SKILL.md` |
| 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)` |
109 | 210 |
|
110 | | ---- |
| 211 | +## Additional Context |
111 | 212 |
|
112 | | -**Why this structure?** Copilot auto-loads this file on every chat (~500 tokens). Loading `AGENTS.md` or `SKILLS/` explicitly gives you deep context only when needed, saving 80% of your token budget! |
| 213 | +For detailed operational procedures, workflows, and troubleshooting, see `AGENTS.md`. |
0 commit comments