Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 67 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ It demonstrates:

- REST endpoints for a small process-related resource
- layered backend structure with controller, service and repository
- request validation
- status-based filtering
- request validation and matching database constraints
- status-based filtering and pagination
- explicit HTTP success and error behavior
- basic persistence with Spring Data JPA
- restrained parameterized logging for write operations
- an H2 in-memory database for local development and tests
- automated API integration tests with MockMvc
- a reproducible Maven Wrapper workflow
Expand Down Expand Up @@ -52,10 +53,12 @@ It complements my main Data/BI portfolio projects around SQL, Python, Power BI,
| Language | Java 21 | Main implementation language |
| Framework | Spring Boot 4.1 | REST API application framework |
| API layer | Spring Web MVC | HTTP endpoints and JSON responses |
| Pagination | Spring Data `Pageable` and `PagedModel` | Bounded list responses with stable page metadata |
| Persistence | Spring Data JPA | Repository abstraction and entity persistence |
| Database | H2 | In-memory local development and test database |
| Validation | Jakarta Validation | Validation for incoming request data |
| Error format | Spring `ProblemDetail` | Consistent `application/problem+json` responses |
| Logging | SLF4J | Structured create, update and delete messages |
| Tests | JUnit 5, Spring Boot Test, MockMvc | API integration and persistence verification |
| Build tool | Maven Wrapper | Reproducible builds on Windows, macOS and Linux |
| CI | GitHub Actions | Automated Java 21 Maven verification |
Expand Down Expand Up @@ -86,13 +89,21 @@ The API uses request and response records instead of exposing the JPA entity dir

| Method | Endpoint | Success | Purpose |
|---|---|---:|---|
| `GET` | `/api/process-checks` | `200 OK` | Return all process-check records |
| `GET` | `/api/process-checks?status=OK` | `200 OK` | Filter records by `OK`, `WARNING` or `CRITICAL` |
| `GET` | `/api/process-checks?page=0&size=20` | `200 OK` | Return one page of process-check records |
| `GET` | `/api/process-checks?status=OK&page=0&size=20` | `200 OK` | Filter and page records by status |
| `GET` | `/api/process-checks/{id}` | `200 OK` | Return one process-check record by ID |
| `POST` | `/api/process-checks` | `201 Created` | Create a record and return its URI in `Location` |
| `PUT` | `/api/process-checks/{id}` | `200 OK` | Replace the editable values of an existing record |
| `DELETE` | `/api/process-checks/{id}` | `204 No Content` | Delete an existing record |

List endpoints accept the standard Spring Data parameters:

- `page`: zero-based page number, default `0`
- `size`: requested page size, default `20`, capped at `100`
- `sort`: field and direction, for example `sort=processName,asc`

The default list order is `lastCheckedAt,desc`.

Typical client errors:

| Situation | Result |
Expand All @@ -103,27 +114,39 @@ Typical client errors:

---

## Example Process-Check Record
## Example Paged Response

```json
{
"id": 1,
"processName": "Daily sales import",
"owner": "Data Operations",
"status": "OK",
"lastCheckedAt": "2026-07-10T00:25:00",
"slaMinutes": 60
"content": [
{
"id": 1,
"processName": "Daily sales import",
"owner": "Data Operations",
"status": "OK",
"lastCheckedAt": "2026-07-10T00:25:00",
"slaMinutes": 60
}
],
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
```

Request validation requires:

- a non-blank `processName`
- a non-blank `owner`
- a non-blank `processName` with at most 120 characters
- a non-blank `owner` with at most 120 characters
- a valid status: `OK`, `WARNING` or `CRITICAL`
- a non-null ISO local date-time value
- `slaMinutes` of at least `1`

The entity mirrors the non-null and maximum-length constraints so the API and database schema enforce the same basic rules.

---

## Error Response Example
Expand Down Expand Up @@ -164,10 +187,18 @@ Then open:
http://localhost:8080/api/process-checks
```

At first startup, the API returns an empty JSON array because the H2 database is empty:
At first startup, the H2 database is empty, so the list endpoint returns an empty page:

```json
[]
{
"content": [],
"page": {
"size": 20,
"totalElements": 0,
"totalPages": 0,
"number": 0
}
}
```

---
Expand All @@ -189,13 +220,14 @@ At first startup, the API returns an empty JSON array because the H2 database is
The automated suite verifies:

- application context startup
- default and requested pagination
- unfiltered and status-filtered list requests
- empty filter results
- invalid status handling
- lookup by ID
- `404` Problem Detail responses
- successful creation with `201 Created` and `Location`
- request validation failures
- blank, invalid and oversized request values
- update behavior and persisted values
- successful deletion with `204 No Content`
- update and delete behavior for unknown IDs
Expand Down Expand Up @@ -223,11 +255,12 @@ The workflow has read-only repository permissions and cancels superseded runs fo
The full CRUD flow can also be exercised manually with curl, an API client or an IDE HTTP client:

```text
POST /api/process-checks create a process-check record
GET /api/process-checks list all process-check records
GET /api/process-checks/1 read one process-check record
PUT /api/process-checks/1 update one process-check record
DELETE /api/process-checks/1 delete one process-check record
POST /api/process-checks
GET /api/process-checks?page=0&size=20
GET /api/process-checks?status=OK&page=0&size=20
GET /api/process-checks/1
PUT /api/process-checks/1
DELETE /api/process-checks/1
```

Example test data:
Expand All @@ -242,6 +275,14 @@ slaMinutes: 60

---

## Logging

Create, update and delete operations write one parameterized application log entry containing the record ID and, where useful, its status.

Read requests and complete request bodies are not logged. This keeps the example useful for troubleshooting without producing noisy logs or copying input data unnecessarily.

---

## H2 Database Note

This project uses an **H2 in-memory database** for local development and API testing.
Expand Down Expand Up @@ -314,12 +355,14 @@ This repository demonstrates a small but realistic backend foundation:
- Spring Boot application structure
- REST endpoint and HTTP-status design
- JSON request and response handling
- CRUD operations and status filtering
- paginated list queries and status filtering
- layered backend organization
- request validation
- request validation aligned with persistence constraints
- standard Problem Detail error responses
- explicit transaction boundaries
- JPA dirty checking for managed updates
- persistence abstraction with Spring Data JPA
- restrained parameterized logging
- local development and testing with H2
- automated integration testing
- reproducible Maven builds
Expand All @@ -332,7 +375,7 @@ This repository demonstrates a small but realistic backend foundation:

This is a learning project.

It does not include production database configuration, Docker deployment, authentication and authorization, a frontend UI, cloud deployment, monitoring infrastructure, pagination or enterprise-scale operational error handling.
It does not include production database configuration, Docker deployment, authentication and authorization, a frontend UI, cloud deployment, metrics, tracing or enterprise-scale operational error handling.

These omissions are intentional. The current scope is limited to a clean, understandable and tested Spring Boot REST API baseline.

Expand Down
47 changes: 32 additions & 15 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Small Java 21 / Spring Boot REST API portfolio project

# Spring Boot Process API Basics

**Small Java 21 / Spring Boot REST API project exposing validated process-check data through a layered backend structure, automated tests and H2 persistence.**
**Small Java 21 / Spring Boot REST API project exposing validated and paginated process-check data through a layered backend structure, automated tests and H2 persistence.**

[View repository](https://github.com/DataTideHH/spring-boot-process-api-basics) · [Read the full README](https://github.com/DataTideHH/spring-boot-process-api-basics/blob/main/README.md) · [View CI](https://github.com/DataTideHH/spring-boot-process-api-basics/actions/workflows/ci.yml) · [DataTideHH portfolio](https://datatidehh.de/)

Expand All @@ -17,7 +17,7 @@ This project is a deliberately compact backend learning project.

It demonstrates how process-related records can be represented, validated, persisted and exposed through a small REST API using Spring Boot.

The goal is not to present a production service or an enterprise backend system. The goal is to document a clean first step from Java basics toward a small layered REST API with explicit HTTP behavior, persistence and automated verification.
The goal is not to present a production service or an enterprise backend system. The goal is to document a clean first step from Java basics toward a small layered REST API with explicit HTTP behavior, bounded list queries, persistence and automated verification.

---

Expand All @@ -37,11 +37,14 @@ It follows the [IPv4 Subnet Calculator Multilang](https://datatidehh.github.io/i
- controller, service and repository separation
- Spring Data JPA repository usage
- request and response records
- Jakarta Validation
- Jakarta Validation aligned with entity constraints
- H2 in-memory persistence
- CRUD endpoints for process-check data
- optional status filtering
- status filtering and pagination
- stable page metadata through Spring Data `PagedModel`
- standard `ProblemDetail` error responses
- explicit transaction boundaries and JPA dirty checking
- restrained parameterized logging for write operations
- integration tests with Spring Boot Test and MockMvc
- reproducible Maven Wrapper builds
- GitHub Actions verification on Java 21
Expand All @@ -52,30 +55,44 @@ It follows the [IPv4 Subnet Calculator Multilang](https://datatidehh.github.io/i

| Method | Endpoint | Result | Purpose |
|---|---|---:|---|
| `GET` | `/api/process-checks` | `200` | List all records |
| `GET` | `/api/process-checks?status=OK` | `200` | Filter by `OK`, `WARNING` or `CRITICAL` |
| `GET` | `/api/process-checks?page=0&size=20` | `200` | List one page of records |
| `GET` | `/api/process-checks?status=OK&page=0&size=20` | `200` | Filter and page by status |
| `GET` | `/api/process-checks/{id}` | `200` | Read one record |
| `POST` | `/api/process-checks` | `201` | Create a record and return `Location` |
| `PUT` | `/api/process-checks/{id}` | `200` | Update a record |
| `DELETE` | `/api/process-checks/{id}` | `204` | Delete a record |

The list endpoint defaults to 20 records, sorts by `lastCheckedAt` descending and caps requested page sizes at 100.

Invalid input returns `400 Bad Request`. Unknown record IDs return `404 Not Found` as `application/problem+json`.

---

## Example process-check record
## Example paged response

```json
{
"id": 1,
"processName": "Daily sales import",
"owner": "Data Operations",
"status": "OK",
"lastCheckedAt": "2026-07-10T00:25:00",
"slaMinutes": 60
"content": [
{
"id": 1,
"processName": "Daily sales import",
"owner": "Data Operations",
"status": "OK",
"lastCheckedAt": "2026-07-10T00:25:00",
"slaMinutes": 60
}
],
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
```

`processName` and `owner` are required and limited to 120 characters. The JPA entity mirrors these length and nullability rules.

---

## Local usage
Expand Down Expand Up @@ -104,7 +121,7 @@ http://localhost:8080/api/process-checks

## Verification

The integration suite covers list and filter behavior, lookup by ID, creation, validation failures, updates, deletion, persistence effects and `404` Problem Detail responses.
The integration suite covers pagination, sorting, status filtering, lookup by ID, creation, blank and oversized input, updates, deletion, persistence effects and `404` Problem Detail responses.

The GitHub Actions workflow runs `clean verify` with Eclipse Temurin Java 21 for pull requests and pushes to `main`.

Expand Down Expand Up @@ -136,4 +153,4 @@ The project uses an H2 in-memory database. Data is reset when the application st

The sample data is synthetic and does not contain personal, customer or production data.

This is a learning project with a deliberately limited scope. It does not claim production deployment, authentication, cloud operation or enterprise-scale infrastructure.
This is a learning project with a deliberately limited scope. It does not claim production deployment, authentication, cloud operation, monitoring infrastructure or enterprise-scale operation.
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package de.datatidehh.processapi.processcheck;

import jakarta.persistence.*;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import java.time.LocalDateTime;

@Entity
Expand All @@ -10,13 +17,20 @@ public class ProcessCheck {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 120)
private String processName;

@Column(nullable = false, length = 120)
private String owner;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private ProcessStatus status;

@Column(nullable = false)
private LocalDateTime lastCheckedAt;

@Column(nullable = false)
private Integer slaMinutes;

protected ProcessCheck() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package de.datatidehh.processapi.processcheck;

import jakarta.validation.Valid;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedModel;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -14,7 +18,6 @@
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

@RestController
@RequestMapping("/api/process-checks")
Expand All @@ -27,10 +30,15 @@ public ProcessCheckController(ProcessCheckService service) {
}

@GetMapping
public List<ProcessCheckResponse> findAll(
@RequestParam(required = false) ProcessStatus status
public PagedModel<ProcessCheckResponse> findAll(
@RequestParam(required = false) ProcessStatus status,
@PageableDefault(
size = 20,
sort = "lastCheckedAt",
direction = Sort.Direction.DESC
) Pageable pageable
) {
return service.findAll(status);
return new PagedModel<>(service.findAll(status, pageable));
}

@GetMapping("/{id}")
Expand Down Expand Up @@ -70,4 +78,4 @@ private URI buildLocation(Long id) {
.buildAndExpand(id)
.toUri();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package de.datatidehh.processapi.processcheck;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ProcessCheckRepository extends JpaRepository<ProcessCheck, Long> {

List<ProcessCheck> findAllByStatus(ProcessStatus status);
Page<ProcessCheck> findAllByStatus(ProcessStatus status, Pageable pageable);
}
Loading