Ugrás a fő tartalomhoz

Spring tanfolyam - 3. alkalom

A mostani alkalmon a ticketing alkalmazást fogunk befejezni.


4. Fejezet - API dokumentálása és a Swagger

Az előző alkalom végén az IDE-ben láthattuk és ki is próbálhattuk, hogy hogyan működik az API, ami jó volt egy gyors tesztelésre, de most megnézünkegy sokkal szebb megoldást, amit a Swagger UI kínál nekünk. Ahhoz viszont, hogy ezt az eszközt használni tudjuk módosításokat kell végeznünk a kódon.

Új függőség felvétele

Hogy a továbbiakban a Swaggert használni tudjuk, vegyük fel az alábbi függőséget a build.gradle.kts konfigurációs fájl dependencies blokkjába:

implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.1")
információ

Ne felejtsük el frissíteni a Gradle-t a kis elefántra való kattintással!

BoardController bővítése a HTTP válaszok dokumentációjával

információ

Az importok közül az alábbiakat válasszuk ki:

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses

Az @Operation annotáció egy egyetlen művelet leírására szolgál (művelet = egy HTTP metódus + egy path). A summary egy rövid összefoglaló, amit ajánlott megadni.

Az @ApiResponses annotáicó egy gyűjtő annotáicó, ami több @ApiResponse-t tartalmazhat. Az @ApiResponse egy konkrét státuszkódot ír le. A responseCode az egy HTTP státuszkód tárolja sztringként, a description egy rövid leírás, míg a content megmondja, hogy milyen típusú a válaszunk törzse.

Tehát a teljes osztály így fog kinézni:

@RestController
@RequestMapping("/board")
class BoardController(
private val boardService: BoardService
) {

@Operation(summary = "Create a new board")
@ApiResponses(
ApiResponse(
responseCode = "201",
description = "Board created",
content = [Content(schema = Schema(implementation = DetailedBoardDto::class))]
)
)
@PostMapping
fun createBoard(@RequestBody board: CreateBoardDto): ResponseEntity<DetailedBoardDto> {
val created = boardService.createBoard(board)
return ResponseEntity.status(HttpStatus.CREATED).body(created)
}


@Operation(summary = "List all boards")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Boards found",
content = [Content(schema = Schema(implementation = DetailedBoardDto::class))]
),
]
)
@GetMapping
fun getAllBoards(): ResponseEntity<List<DetailedBoardDto>> {
val boards = boardService.getAllBoards()
return ResponseEntity.ok(boards)
}


@Operation(summary = "Get a board by ID")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Board found",
content = [Content(schema = Schema(implementation = DetailedBoardDto::class))]
),
ApiResponse(responseCode = "404", description = "Board not found"),
]
)
@GetMapping("/{id}")
fun getBoard(@PathVariable id: Int): ResponseEntity<DetailedBoardDto> {
val board = boardService.getBoard(id)
return ResponseEntity.ok(board)
}


@Operation(summary = "Update a board")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Board updated",
content = [Content(schema = Schema(implementation = DetailedBoardDto::class))]
),
ApiResponse(
responseCode = "404",
description = "Board not found"
),
]
)
@PatchMapping("/{id}")
fun updateBoard(@PathVariable id: Int, @RequestBody board: UpdateBoardDto): ResponseEntity<DetailedBoardDto> {
val updated = boardService.updateBoard(id, board)
return ResponseEntity.ok(updated)
}


@Operation(summary = "Delete a board")
@ApiResponses(
ApiResponse(
responseCode = "204",
description = "Board deleted"
)
)
@DeleteMapping("/{id}")
fun deleteBoard(@PathVariable id: Int): ResponseEntity<Void> {
boardService.deleteBoard(id)
return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}

}
információ

Az ApiResponses annotációban a value egy tömböt vár, viszont a kotlin sajátosságai miatt, egyetlen ApiResponse esetén akár el is hagyható az egész tömb és csak egy ApiResponse-t adunk meg.

TicketController bővítése a HTTP válaszok dokumentációjával

Itt is hasonlóan járjunk el, mint a BoardController-nél.

@RestController
@RequestMapping("/ticket")
class TicketController(
private val ticketService: TicketService
) {

@Operation(summary = "Create a new ticket")
@ApiResponses(
value = [
ApiResponse(
responseCode = "201",
description = "Ticket created",
content = [Content(schema = Schema(implementation = DetailedTicketDto::class))]
),
ApiResponse(responseCode = "400", description = "Board or label not found"),
]
)
@PostMapping
fun createTicket(@RequestBody ticket: CreateTicketDto): ResponseEntity<DetailedTicketDto> {
val created = ticketService.createTicket(ticket)
return ResponseEntity.status(HttpStatus.CREATED).body(created)
}


@Operation(summary = "List all tickets")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Tickets found",
content = [Content(schema = Schema(implementation = DetailedTicketDto::class))]
)
]
)
@GetMapping
fun getAllTickets(): ResponseEntity<List<DetailedTicketDto>> {
val tickets = ticketService.getAllTickets()
return ResponseEntity.status(HttpStatus.OK).body(tickets)
}


@Operation(summary = "Get a ticket by ID")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Ticket found",
content = [Content(schema = Schema(implementation = DetailedTicketDto::class))]
),
ApiResponse(responseCode = "404", description = "Ticket not found"),
]
)
@GetMapping("/{id}")
fun getTicket(@PathVariable id: Int): ResponseEntity<DetailedTicketDto> {
val ticket = ticketService.getTicket(id)
return ResponseEntity.status(HttpStatus.OK).body(ticket)
}


@Operation(summary = "Update a ticket")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Ticket updated",
content = [Content(schema = Schema(implementation = DetailedTicketDto::class))]
),
ApiResponse(responseCode = "400", description = "Board or label not found"),
ApiResponse(responseCode = "404", description = "Ticket not found"),
]
)
@PatchMapping("/{id}")
fun updateTicket(@PathVariable id: Int, @RequestBody ticket: UpdateTicketDto): ResponseEntity<DetailedTicketDto> {
val updated = ticketService.updateTicket(id, ticket)
return ResponseEntity.status(HttpStatus.OK).body(updated)
}


@Operation(summary = "Delete a ticket")
@ApiResponses(
ApiResponse(
responseCode = "204",
description = "Ticket deleted",
),
)
@DeleteMapping("/{id}")
fun deleteTicket(@PathVariable id: Int): ResponseEntity<Void> {
ticketService.deleteTicket(id)
return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}

}

Swagger UI

Amennyiben mindent jól csináltunk az alkalmazás elindítása után a localhost:8080/swagger-ui/index.html URL-en megcsodálhatjuk az interaktív dokumentációt.


Feladatot készítette: Szabó Benedek, Leírást készítette: Bácsi Miklós