diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..ef592cf --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,6 @@ +{ + "MD013": false, + "MD024": { + "siblings_only": true + } +} diff --git a/README.md b/README.md index bdf1ec9..4cb91f9 100644 --- a/README.md +++ b/README.md @@ -13,40 +13,12 @@ Proof of Concept for a RESTful Web Service built with **Spring Boot 4** targeting **JDK 25 (LTS)**. This project demonstrates best practices for building a layered, testable, and maintainable API implementing CRUD operations for a Players resource (Argentina 2022 FIFA World Cup squad). -## Table of Contents - -- [Features](#features) -- [Tech Stack](#tech-stack) -- [Project Structure](#project-structure) -- [Architecture](#architecture) -- [API Reference](#api-reference) -- [Prerequisites](#prerequisites) -- [Quick Start](#quick-start) - - [Clone](#clone) - - [Build](#build) - - [Run](#run) - - [Access](#access) -- [Testing](#testing) -- [Containers](#containers) - - [Build and Start](#build-and-start) - - [Stop](#stop) - - [Reset Database](#reset-database) -- [Environment Variables](#environment-variables) -- [Command Summary](#command-summary) -- [Releases](#releases) -- [Contributing](#contributing) -- [Legal](#legal) - ## Features -- ๐Ÿ”Œ **RESTful API** - Full CRUD operations for Players resource - ๐Ÿ“š **Clean Architecture** - Layered design with clear separation of concerns +- ๐Ÿ“ **Interactive Documentation** - Live API exploration and testing interface - ๐Ÿšฆ **Input Validation** - Bean Validation (JSR-380) constraints - โšก **Performance Caching** - Optimized data retrieval with cache annotations -- ๐Ÿ” **Advanced Search** - League search with JPQL and squad number lookup with derived queries -- ๐Ÿ“ **Interactive Documentation** - Live API exploration and testing interface -- ๐Ÿฉบ **Health Monitoring** - Application health and metrics endpoints -- โœ… **Comprehensive Testing** - High code coverage with automated reporting - ๐Ÿณ **Containerized Deployment** - Multi-stage builds with pre-seeded database - ๐Ÿ”„ **Automated Pipeline** - Continuous integration with automated testing and builds @@ -68,31 +40,6 @@ Proof of Concept for a RESTful Web Service built with **Spring Boot 4** targetin > ๐Ÿ’ก **Note:** Maven wrapper (`./mvnw`) is included, so Maven installation is optional. -## Project Structure - -```tree -src/main/java/ar/com/nanotaboada/java/samples/spring/boot/ -โ”œโ”€โ”€ Application.java # Main entry point, @SpringBootApplication -โ”œโ”€โ”€ controllers/ # REST endpoints (@RestController) -โ”‚ โ””โ”€โ”€ PlayersController.java -โ”œโ”€โ”€ services/ # Business logic (@Service, caching) -โ”‚ โ””โ”€โ”€ PlayersService.java -โ”œโ”€โ”€ repositories/ # Data access (@Repository, Spring Data JPA) -โ”‚ โ””โ”€โ”€ PlayersRepository.java -โ”œโ”€โ”€ models/ # Domain entities & DTOs -โ”‚ โ”œโ”€โ”€ Player.java # JPA entity -โ”‚ โ””โ”€โ”€ PlayerDTO.java # Data Transfer Object with validation -โ””โ”€โ”€ converters/ # Infrastructure converters - โ””โ”€โ”€ IsoDateConverter.java # JPA converter for ISO-8601 dates - -src/test/java/.../test/ -โ”œโ”€โ”€ controllers/ # Controller tests (@WebMvcTest) -โ”œโ”€โ”€ services/ # Service layer tests -โ”œโ”€โ”€ repositories/ # Repository tests (@DataJpaTest) -โ”œโ”€โ”€ PlayerFakes.java # Test data factory for Player entities -โ””โ”€โ”€ PlayerDTOFakes.java # Test data factory for PlayerDTO -``` - ## Architecture Layered architecture with dependency injection via Spring Boot's IoC container and constructor injection using Lombok's `@RequiredArgsConstructor`. @@ -187,44 +134,26 @@ graph RL class tests test ``` -*Simplified, conceptual view โ€” not all components or dependencies are shown.* - -### Arrow Semantics - -Arrows follow the injection direction: `A --> B` means A is injected into B. Solid arrows (`-->`) represent active Spring dependencies โ€” beans wired by the IoC container and invoked at runtime. Dotted arrows (`-.->`) represent test dependencies โ€” test classes reference the types they exercise but are not injected into them. - -### Composition Root Pattern - -`Application` is the composition root: `@SpringBootApplication` triggers component scanning that discovers and registers all beans, `@EnableCaching` activates the caching infrastructure, and `@Bean ModelMapper` declares the mapping dependency explicitly. Constructor injection is enforced throughout via Lombok's `@RequiredArgsConstructor` on `final` fields. - -### Layered Architecture - -Four layers: Initialization (`Application`), HTTP (`controllers`), Business (`services`), and Data (`repositories`). - -Spring and third-party packages are placed inside the subgraph of the layer that uses them โ€” co-residency communicates the relationship without extra arrows: `Spring Boot` and `SpringDoc` in Initialization, `Spring Validation` in HTTP, `Spring Cache` and `ModelMapper` in Business, `Spring Data JPA` in Data. - -`models` and `converters` are cross-cutting: `models` defines the JPA entity and DTOs shared across all layers; `converters` holds the `AttributeConverter` that handles ISO-8601 date serialization for `models`. `Jakarta Persistence` and `Lombok` are their respective dependencies. `Lombok` is also used in `services` and `controllers` via `@RequiredArgsConstructor` and `@Slf4j`, though those arrows are omitted for clarity. - -### Color Coding - -Blue = core application packages, yellow = Spring ecosystem, red = third-party libraries, green = tests. +> *Arrows follow the injection direction (A โ†’ B means A is injected into B). Solid = runtime dependency, dotted = structural. Blue = core domain, red = third-party, green = tests.* ## API Reference Interactive API documentation is available via Swagger UI at `http://localhost:9000/swagger/index.html` when the server is running. -**Quick Reference:** +| Method | Endpoint | Description | Status | +| ------ | -------- | ----------- | ------ | +| `GET` | `/players` | List all players | `200 OK` | +| `GET` | `/players/{id}` | Get player by ID | `200 OK` | +| `GET` | `/players/search/league/{league}` | Search players by league | `200 OK` | +| `GET` | `/players/squadnumber/{squadNumber}` | Get player by squad number | `200 OK` | +| `POST` | `/players` | Create new player | `201 Created` | +| `PUT` | `/players/{id}` | Update player by ID | `200 OK` | +| `DELETE` | `/players/{id}` | Remove player by ID | `204 No Content` | +| `GET` | `/actuator/health` | Health check | `200 OK` | -- `GET /players` - List all players -- `GET /players/{id}` - Get player by ID -- `GET /players/search/league/{league}` - Search players by league -- `GET /players/squadnumber/{squadNumber}` - Get player by squad number -- `POST /players` - Create new player -- `PUT /players/{id}` - Update existing player -- `DELETE /players/{id}` - Remove player -- `GET /actuator/health` - Health check +Error codes: `400 Bad Request` (validation failed) ยท `404 Not Found` (player not found) ยท `409 Conflict` (duplicate squad number on `POST`) -For complete endpoint documentation with request/response schemas, explore the [interactive Swagger UI](http://localhost:9000/swagger/index.html). You can also access the OpenAPI JSON specification at `http://localhost:9000/v3/api-docs`. +For complete endpoint documentation with request/response schemas, explore the [interactive Swagger UI](http://localhost:9000/swagger/index.html). You can also access the OpenAPI JSON specification at `http://localhost:9000/docs`. ## Prerequisites @@ -267,42 +196,9 @@ Once the application is running, you can access: - **API Server**: `http://localhost:9000` - **Swagger UI**: `http://localhost:9000/swagger/index.html` -- **OpenAPI Spec**: `http://localhost:9000/v3/api-docs` +- **OpenAPI Spec**: `http://localhost:9000/docs` - **Health Check**: `http://localhost:9001/actuator/health` -## Testing - -Run the full test suite with coverage: - -```bash -./mvnw verify -``` - -**View Coverage Report:** - -```bash -open target/site/jacoco/index.html -``` - -**Test Structure:** - -- **Unit Tests** - `@WebMvcTest`, `@DataJpaTest` for isolated layer testing (with `@AutoConfigureCache` for caching support) -- **Test Database** - SQLite in-memory (jdbc:sqlite::memory:) for fast, isolated test execution -- **Mocking** - Mockito with `@MockitoBean` for dependency mocking -- **Assertions** - AssertJ fluent assertions -- **Naming Convention** - `givenX_whenY_thenZ` BDD pattern: - - `givenPlayersExist_whenGetAll_thenReturnsOkWithAllPlayers()` - - `givenSquadNumberExists_whenPost_thenReturnsConflict()` - - `givenPlayerExists_whenFindById_thenReturnsPlayer()` - -**Coverage Targets:** - -- Controllers: 100% -- Services: 100% -- Repositories: Custom query methods (interfaces excluded by JaCoCo design) - -> ๐Ÿ’ก **Note:** Dates are stored as ISO-8601 strings for SQLite compatibility. A JPA `AttributeConverter` handles LocalDate โ†” ISO-8601 string conversion transparently. Tests use SQLite in-memory database (jdbc:sqlite::memory:) - the converter works seamlessly with both file-based and in-memory SQLite. - ## Containers ### Build and Start @@ -335,44 +231,72 @@ docker compose down -v # Remove volumes docker compose up # Fresh start with seed data ``` +### Pull Docker images + +Each release publishes multiple tags for flexibility: + +```bash +# By semantic version (recommended for production) +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:1.0.0 + +# By club name (memorable alternative) +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:arsenal + +# Latest release +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:latest +``` + ## Environment Variables -### Development (Local) +### Development -Configuration in `src/main/resources/application.properties`: +Configured in `src/main/resources/application.properties`: ```properties -# Server Configuration server.port=9000 management.server.port=9001 - -# Database Configuration (SQLite) spring.datasource.url=jdbc:sqlite:storage/players-sqlite3.db -spring.datasource.driver-class-name=org.sqlite.JDBC -spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect -spring.jpa.hibernate.ddl-auto=none - -# Caching -spring.cache.type=simple - -# OpenAPI Documentation -springdoc.api-docs.path=/v3/api-docs springdoc.swagger-ui.path=/swagger/index.html +springdoc.api-docs.path=/docs ``` -### Testing (Local) +### Testing -Configuration in `src/test/resources/application.properties`: +Configured in `src/test/resources/application.properties`: ```properties -# Test Database (SQLite in-memory) spring.datasource.url=jdbc:sqlite::memory: -spring.datasource.driver-class-name=org.sqlite.JDBC -spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect spring.jpa.hibernate.ddl-auto=create-drop ``` -> ๐Ÿ’ก **Note:** Tests use SQLite in-memory database (jdbc:sqlite::memory:) for fast, isolated execution. The ISO-8601 date converter works identically with both file-based and in-memory SQLite. +## Contributing + +Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on: + +- Code of Conduct +- Development workflow and best practices +- Commit message conventions (Conventional Commits) +- Pull request process and requirements + +**Key guidelines:** + +- Follow [Conventional Commits](https://www.conventionalcommits.org/) for commit messages +- Ensure all tests pass (`./mvnw verify`) +- Always use Maven wrapper (`./mvnw`), never system Maven +- Keep changes small and focused +- Review `.github/copilot-instructions.md` for architectural patterns + +**Testing:** + +Run the test suite with JUnit 5 + JaCoCo: + +```bash +# Run tests with coverage report +./mvnw verify + +# View coverage report +open target/site/jacoco/index.html +``` ## Command Summary @@ -390,112 +314,12 @@ spring.jpa.hibernate.ddl-auto=create-drop | `docker compose down` | Stop and remove containers | | `docker compose down -v` | Stop and remove containers with volumes | | `docker compose logs -f` | View container logs | +| **AI Commands** | | +| `/pre-commit` | Runs linting, tests, and quality checks before committing | +| `/pre-release` | Runs pre-release validation workflow | > ๐Ÿ’ก **Note:** Always use the Maven wrapper (`./mvnw`) instead of system Maven to ensure consistent builds. -## Releases - -This project uses **historic football clubs** as release codenames ๐Ÿ† (inspired by Ubuntu, Android, and macOS naming conventions). - -### Release Naming Convention - -Releases follow the pattern: `v{SEMVER}-{CLUB}` (e.g., `v1.0.0-arsenal`) - -- **Semantic Version**: Standard versioning (MAJOR.MINOR.PATCH) -- **Club Name**: Alphabetically ordered codename from the [historic club list](CHANGELOG.md) - -### Create a Release - -To create a new release, follow this workflow: - -#### 1. Create a Release Branch - -Branch protection prevents direct pushes to `master`, so all release prep goes through a PR: - -```bash -git checkout master && git pull -git checkout -b release/v1.0.0-arsenal -``` - -#### 2. Update CHANGELOG.md - -Move items from `[Unreleased]` to a new release section in [CHANGELOG.md](CHANGELOG.md), then commit and push the branch: - -```bash -# Move items from [Unreleased] to new release section -# Example: [1.0.0 - Arsenal] - 2026-XX-XX -git add CHANGELOG.md -git commit -m "docs(changelog): prepare release notes for v1.0.0-arsenal" -git push origin release/v1.0.0-arsenal -``` - -#### 3. Merge the Release PR - -Open a pull request from `release/v1.0.0-arsenal` into `master` and merge it. The tag must be created **after** the merge so it points to the correct commit on `master`. - -#### 4. Create and Push Tag - -After the PR is merged, pull `master` and create the annotated tag: - -```bash -git checkout master && git pull -git tag -a v1.0.0-arsenal -m "Release 1.0.0 - Arsenal" -git push origin v1.0.0-arsenal -``` - -#### 5. Automated CD Workflow - -This triggers the CD workflow which automatically: - -1. Validates the club name -2. Builds and tests the project with Maven -3. Publishes Docker images to GitHub Container Registry with three tags -4. Creates a GitHub Release with auto-generated changelog from commits - -#### Pre-Release Checklist - -- [ ] Release branch created from `master` -- [ ] `CHANGELOG.md` updated with release notes -- [ ] Changes committed and pushed on the release branch -- [ ] Release PR merged into `master` -- [ ] Tag created with correct format: `vX.Y.Z-club` -- [ ] Club name is valid (A-Z from the [historic club list](CHANGELOG.md)) -- [ ] Tag pushed to trigger CD workflow - -### Pull Docker Images - -Each release publishes multiple tags for flexibility: - -```bash -# By semantic version (recommended for production) -docker pull ghcr.io/nanotaboada/java-samples-spring-boot:1.0.0 - -# By club name (memorable alternative) -docker pull ghcr.io/nanotaboada/java-samples-spring-boot:arsenal - -# Latest release -docker pull ghcr.io/nanotaboada/java-samples-spring-boot:latest -``` - -> ๐Ÿ’ก See [CHANGELOG.md](CHANGELOG.md) for the complete club list (A-Z) and release history. - -## Contributing - -Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on: - -- Code of Conduct -- Development workflow and best practices -- Commit message conventions (Conventional Commits) -- Pull request process and requirements - -**Key guidelines:** - -- Follow [Conventional Commits](https://www.conventionalcommits.org/) for commit messages -- Ensure all tests pass (`./mvnw verify`) -- Always use Maven wrapper (`./mvnw`), never system Maven -- Keep changes small and focused -- Review `.github/copilot-instructions.md` for architectural patterns - ## Legal This project is provided for educational and demonstration purposes and may be used in production at your own discretion. All trademarks, service marks, product names, company names, and logos referenced herein are the property of their respective owners and are used solely for identification or illustrative purposes. diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000..0d54751 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,74 @@ +# Releases + +Releases follow the pattern `v{SEMVER}-{CLUB}` (e.g., `v1.0.0-arsenal`). Codenames are drawn alphabetically from the [historic club list](CHANGELOG.md) of famous football clubs. + +## Workflow + +### 1. Create a Release Branch + +Branch protection prevents direct pushes to `master`, so all release prep goes through a PR: + +```bash +git checkout master && git pull +git checkout -b release/v1.0.0-arsenal +``` + +### 2. Update CHANGELOG.md + +Move items from `[Unreleased]` to a new release section in [CHANGELOG.md](CHANGELOG.md), then commit and push the branch: + +```bash +# Move items from [Unreleased] to new release section +# Example: [1.0.0 - Arsenal] - 2026-XX-XX +git add CHANGELOG.md +git commit -m "docs(changelog): prepare release notes for v1.0.0-arsenal" +git push origin release/v1.0.0-arsenal +``` + +### 3. Merge the Release PR + +Open a pull request from `release/v1.0.0-arsenal` into `master` and merge it. The tag must be created **after** the merge so it points to the correct commit on `master`. + +### Pre-release Checklist + +Before creating the tag, verify all of the following: + +- [ ] `CHANGELOG.md` `[Unreleased]` section is moved to a new versioned release entry +- [ ] Release PR is merged into `master` +- [ ] `./mvnw clean install` passes +- [ ] Club name is valid and follows alphabetical order (see [historic club list](CHANGELOG.md)) +- [ ] All CI checks on `master` are green + +### 4. Create and Push Tag + +After the PR is merged, pull `master` and create the annotated tag: + +```bash +git checkout master && git pull +git tag -a v1.0.0-arsenal -m "Release 1.0.0 - Arsenal" +git push origin v1.0.0-arsenal +``` + +### 5. Automated CD Workflow + +Pushing the tag triggers the CD workflow which automatically: + +1. Validates tag format (semver and club name) +2. Builds and tests the project with Maven +3. Publishes Docker images to GitHub Container Registry with three tags +4. Creates a GitHub Release with auto-generated changelog from commits + +## Docker Pull + +Each release publishes multiple tags for flexibility: + +```bash +# By semantic version (recommended for production) +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:1.0.0 + +# By club name (memorable alternative) +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:arsenal + +# Latest release +docker pull ghcr.io/nanotaboada/java-samples-spring-boot:latest +```