OpenAPI / Swagger
Цель урока
Сделать полноценный CRUD для задач с правильными HTTP-статусами (201/204/404), обработкой ошибок, и подготовить Task к подключению JPA.
Теория · для собеса
1 OpenAPI / Swagger — что это ▸
OpenAPI Specification (OAS) — стандарт описания REST API (бывший Swagger).
Swagger UI — интерактивная HTML-страница, где можно:
- посмотреть все endpoints
- увидеть схемы запросов/ответов
- попробовать вызвать прямо из браузера (try-it-out)
springdoc-openapi — Spring Boot интеграция, автогенерирует спецификацию из кода.
2 Что генерируется автоматически ▸
springdoc сканирует:
@RestControllerклассы → endpoints@RequestMapping/@GetMapping/... → URL + HTTP-метод@RequestBody/@PathVariable/@RequestParam→ параметры- DTO-классы → JSON Schema (для request/response)
- Javadoc на полях → описания
3 Что нужно добавлять вручную ▸
- Аннотации
@Operation,@ApiResponse,@Schemaдля тонкой настройки - OpenAPI-метаданные (title, version, contact, license)
---
Практика: улучшаем CRUD
-
1Зависимости
dependencies { implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") } -
2OpenAPI-конфигурация
package com.taskflow.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; @Configuration public class OpenApiConfig { @Bean public OpenAPI taskflowOpenAPI() { return new OpenAPI() .info(new Info() .title("TaskFlow API") .description("Production-like Spring Boot Task Management Service") .version("v0.1.0") .contact(new Contact() .name("Ivan") .email("ivan@taskflow.local") .url("https://taskflow.local")) .license(new License() .name("MIT") .url("https://opensource.org/licenses/MIT"))) .servers(List.of( new Server().url("http://localhost:8080").description("Local dev"), new Server().url("https://api.taskflow.local").description("Production") )); } } -
3Аннотируем Controller
@RestController @RequestMapping("/api/tasks") @Tag(name = "Tasks", description = "CRUD operations for tasks") public class TaskController { @Operation( summary = "Get all tasks", description = "Returns a list of all tasks, ordered by creation date (newest first)" ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "Tasks found", content = @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = TaskResponse.class)))) }) @GetMapping public ListfindAll() { return service.findAll(); } @Operation(summary = "Get task by id") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Task found", content = @Content(schema = @Schema(implementation = TaskResponse.class))), @ApiResponse(responseCode = "404", description = "Task not found", content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) @GetMapping("/{id}") public ResponseEntity findById( @Parameter(description = "Task ID", example = "1") @PathVariable Long id) { return service.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @Operation(summary = "Create a new task") @ApiResponses({ @ApiResponse(responseCode = "201", description = "Task created", content = @Content(schema = @Schema(implementation = TaskResponse.class))), @ApiResponse(responseCode = "400", description = "Validation failed", content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) @PostMapping public ResponseEntity create( @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "Task to create", required = true, content = @Content(schema = @Schema(implementation = CreateTaskRequest.class))) @Valid @RequestBody CreateTaskRequest req) { TaskResponse created = service.create(req); URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}") .buildAndExpand(created.id()).toUri(); return ResponseEntity.created(location).body(created); } @Operation(summary = "Update an existing task") @PutMapping("/{id}") public ResponseEntity update( @PathVariable Long id, @Valid @RequestBody UpdateTaskRequest req) { return service.update(id, req) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @Operation(summary = "Delete a task") @DeleteMapping("/{id}") public ResponseEntity delete(@PathVariable Long id) { try { service.delete(id); return ResponseEntity.noContent().build(); } catch (ResourceNotFoundException e) { return ResponseEntity.notFound().build(); } } } -
4Аннотируем DTO
@Schema(description = "Request to create a new task") public record CreateTaskRequest( @Schema(description = "Task title", example = "Read Spring Boot docs", requiredMode = RequiredMode.REQUIRED) @NotBlank @Size(min = 3, max = 200) String title, @Schema(description = "Optional description", example = "Chapter 5-7") @Size(max = 5000) String description ) {} -
5application.yml
springdoc: api-docs: path: /v3/api-docs swagger-ui: path: /swagger-ui.html operations-sorter: method # GET, POST, PUT, DELETE tags-sorter: alpha show-actuator: false -
6Запуск
./gradlew bootRunОткрываем:
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI JSON: http://localhost:8080/v3/api-docs
- OpenAPI YAML: http://localhost:8080/v3/api-docs.yaml
---
Зачем это на собесе
После урока ты должен уметь ответить на:
openapi.json генерируются TypeScript/Java/Python клиенты автоматически.5 вопросов на углубление
Раскрой вопрос и нажми «🤔 Хочу разобрать подробнее» — он попадёт в страницу ответов.
Разбор внутри: старое название, новый стандарт и возможности схемы.
Разбор внутри: openapi.json, generator CLI и типизированный клиент.
Разбор внутри: Swagger не видит 404 без явного ResponseEntity.
Разбор внутри: группировка по контроллерам и отдельные спецификации по путям.
Разбор внутри: примеры полей, requiredMode и польза для клиента API.
Готов идти дальше?
Выбери вопросы, которые тебе интересны, и изучи их. Потом — к следующему уроку.
🚀 Перейти к Уроку 10Сначала пройди все секции и выбери хотя бы 1 вопрос
⬅️ Назад к Уроку 8