[Sunny Braille] Swagger로 더러워진 내 컨트롤러 코드 인공호흡: 인터페이스로 스웨거 코드 분리하는 방법

2024. 3. 4. 11:00· PROJECT/Sunny Braille
목차
  1. 💋 Swagger 적용으로 복잡해진 Controller,,,
  2. 💋 Swagger 관련 코드를 인터페이스로 분리!
  3. 💋 참고자료
반응형
반응형

 

 

 

시각장애인을 위한 빠르고 정확한 수학교재 인공지능 점자 변환 서비스, Sunny Braille

어떤 교재든 텍스트 뿐만 아니라 수식도 점자로 변환해주는 교육용 AI 점역 소프트웨어

 

 

 

해바라기 팀 소개 영상 ⇒ https://www.youtube.com/watch?v=cuYIEpQx2po&t=8s

 

Sunny Braille 소스 코드 ⇒ https://github.com/sunnybraille

 

시각장애인을 위한 빠르고 정확한 수학교재 인공지능 점자 변환 서비스, Sunny Braille

어떤 교재든 텍스트 뿐만 아니라 수식도 점자로 변환해주는 교육용 AI 점역 소프트웨어. 시각장애인을 위한 빠르고 정확한 수학교재 인공지능 점자 변환 서비스, Sunny Braille has 4 repositories available

github.com

 

 

관련 PR ⇒ https://github.com/Sunflower-yonsei/sunflower-server/pull/78

 

refactor: Swagger 코드를 인터페이스로 분리 by gitchannn · Pull Request #78 · Sunflower-yonsei/sunflower-server

주요 변경사항 관련 이슈 closes #63 ✔️ Squash & Merge 방식으로 병합해 주세요!

github.com

 

💋 Swagger 적용으로 복잡해진 Controller,,,

 

프로젝트 도중 클라이언트와 API를 문서화하기 위해서 스웨거를 적용했다. 그랬더니 컨트롤러가 너무 복잡해서 읽기 어려워졌다.

Swagger와 관련된 코드와 웹 endpoint, request parameter, path variable을 정의하는 코드가 모두 섞여있어서 정말 보기 안좋다

 

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import sunflower.server.api.response.BrfFileQueryResponse;
import sunflower.server.api.response.PdfRegisterResponse;
import sunflower.server.api.response.TranslationStatusResponse;
import sunflower.server.application.TranslationService;
import sunflower.server.application.dto.TranslationStatusDto;
import sunflower.server.exception.FileEmptyException;

import java.net.URI;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

@RequiredArgsConstructor
@RestController
@RequestMapping("/translations")
@Tag(name = "점자 번역 API", description = "Sunny Braille 핵심 기능으로, 점역 작업을 담당합니다.")
public class TranslationApiController {

	private final TranslationService translationService;

	@Operation(summary = "PDF 점자 번역 요청 API", description = "점역 작업은 서버에서 비동기적으로 처리되며, id만 즉시 반환됩니다.")
	@ApiResponse(responseCode = "201", description = "PDF가 성공적으로 업로드되었습니다.",
			headers =
			@io.swagger.v3.oas.annotations.headers.Header(
					name = "Location",
					description = "Translations id(해바라기에서 제공하는 id)",
					schema = @Schema(type = "long"),
					required = true
			))
	@PostMapping(consumes = MULTIPART_FORM_DATA_VALUE)
	public ResponseEntity<PdfRegisterResponse> registerPdf(
			@Parameter(name = "file", description = "점역할 pdf 파일", required = true,
					example = "쎈_3124번.pdf", schema = @Schema(type = "file"))
			@RequestPart("file") MultipartFile file
	) {
		if (file.isEmpty()) {
			throw new FileEmptyException(1, "빈 파일입니다.");
		}

		final Long id = translationService.register(file);

		return ResponseEntity
				.created(URI.create("/translations/" + id))
				.body(PdfRegisterResponse.from(file.getOriginalFilename()));
	}

	@Operation(summary = "점역 상황 체크 API", description = "id와 함께 요청하면 점역 진행 상황을 반환하며, 클라이언트의 progress bar 표시를 위해 사용해 주세요.")
	@ApiResponse(responseCode = "200", description = "점역 진행 상황을 반환합니다.",
			content = {
					@Content(mediaType = "application/json", schema = @Schema(implementation = TranslationStatusResponse.class))
			})
	@GetMapping("/{id}/status")
	public ResponseEntity<TranslationStatusResponse> checkStatus(
			@Parameter(description = "Translations id", required = true) @PathVariable("id") Long id) {
		final TranslationStatusDto dto = translationService.status(id);
		return ResponseEntity.ok(TranslationStatusResponse.from(dto));
	}

	@Operation(summary = "점역 결과 BRF 파일 반환 API", description = "id와 함께 요청하면 점역 결과를 반환합니다.")
	@ApiResponse(responseCode = "200", description = "점역된 결과를 반환합니다..",
			content = {
					@Content(mediaType = "application/json", schema = @Schema(implementation = BrfFileQueryResponse.class))
			})
	@GetMapping("/{id}")
	public ResponseEntity<BrfFileQueryResponse> queryBrfFile(
			@Parameter(description = "Translations id", required = true) @PathVariable("id") Long id) {
		final String brfContent = translationService.findBrfFileById(id);
		final BrfFileQueryResponse response = BrfFileQueryResponse.from(id, brfContent);
		return ResponseEntity.ok(response);
	}
}

 

 

💋 Swagger 관련 코드를 인터페이스로 분리!

 

인터페이스로 분리해도 여전히 어노테이션은 유지하면서 컨트롤러의 코드를 깔끔하게 수정할 수 있다.

 

TranslationApiControllerDocs 인터페이스

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import sunflower.server.api.response.BrfFileQueryResponse;
import sunflower.server.api.response.PdfRegisterResponse;
import sunflower.server.api.response.TranslationStatusResponse;

@Tag(name = "점자 번역 API", description = "Sunny Braille 핵심 기능으로, 점역 작업을 담당합니다.")
public interface TranslationApiControllerDocs {

	@Operation(summary = "PDF 점자 번역 요청 API", description = "점역 작업은 서버에서 비동기적으로 처리되며, id만 즉시 반환됩니다.")
	@ApiResponse(responseCode = "201", description = "PDF가 성공적으로 업로드되었습니다.",
			headers =
			@io.swagger.v3.oas.annotations.headers.Header(
					name = "Location",
					description = "Translations id(해바라기에서 제공하는 id)",
					schema = @Schema(type = "long"),
					required = true
			))
	ResponseEntity<PdfRegisterResponse> registerPdf(
			@Parameter(name = "file", description = "점역할 pdf 파일", required = true,
					example = "쎈_3124번.pdf", schema = @Schema(type = "file"))
			@RequestPart("file") MultipartFile file
	);

	@Operation(summary = "점역 상황 체크 API", description = "id와 함께 요청하면 점역 진행 상황을 반환하며, 클라이언트의 progress bar 표시를 위해 사용해 주세요.")
	@ApiResponse(responseCode = "200", description = "점역 진행 상황을 반환합니다.",
			content = {
					@Content(mediaType = "application/json", schema = @Schema(implementation = TranslationStatusResponse.class))
			})
	ResponseEntity<TranslationStatusResponse> checkStatus(
			@Parameter(description = "Translations id", required = true) @PathVariable("id") Long id);

	@Operation(summary = "점역 결과 BRF 파일 반환 API", description = "id와 함께 요청하면 점역 결과를 반환합니다.")
	@ApiResponse(responseCode = "200", description = "점역된 결과를 반환합니다..",
			content = {
					@Content(mediaType = "application/json", schema = @Schema(implementation = BrfFileQueryResponse.class))
			})
	ResponseEntity<BrfFileQueryResponse> queryBrfFile(
			@Parameter(description = "Translations id", required = true) @PathVariable("id") Long id);
}

 

 

TranslationApiController

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import sunflower.server.api.response.BrfFileQueryResponse;
import sunflower.server.api.response.PdfRegisterResponse;
import sunflower.server.api.response.TranslationStatusResponse;
import sunflower.server.application.TranslationService;
import sunflower.server.application.dto.TranslationStatusDto;
import sunflower.server.exception.FileEmptyException;

import java.net.URI;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

@RequiredArgsConstructor
@RestController
@RequestMapping("/translations")
public class TranslationApiController implements TranslationApiControllerDocs {

	private final TranslationService translationService;

	@PostMapping(consumes = MULTIPART_FORM_DATA_VALUE)
	public ResponseEntity<PdfRegisterResponse> registerPdf(@RequestPart("file") MultipartFile file) {
		if (file.isEmpty()) {
			throw new FileEmptyException(1, "빈 파일입니다.");
		}

		final Long id = translationService.register(file);

		return ResponseEntity
				.created(URI.create("/translations/" + id))
				.body(PdfRegisterResponse.from(file.getOriginalFilename()));
	}

	@GetMapping("/{id}/status")
	public ResponseEntity<TranslationStatusResponse> checkStatus(@PathVariable("id") Long id) {
		final TranslationStatusDto dto = translationService.status(id);
		return ResponseEntity.ok(TranslationStatusResponse.from(dto));
	}

	@GetMapping("/{id}")
	public ResponseEntity<BrfFileQueryResponse> queryBrfFile(@PathVariable("id") Long id) {
		final String brfContent = translationService.findBrfFileById(id);
		final BrfFileQueryResponse response = BrfFileQueryResponse.from(id, brfContent);
		return ResponseEntity.ok(response);
	}
}

 

💋 참고자료

  • https://github.com/Sunflower-yonsei/sunflower-server/pull/78
  • https://github.com/Busan-Dinosaur/Foodbowl-Backend/tree/develop/src/main/java/org/dinosaur/foodbowl/domain/member/presentation

 

반응형

'PROJECT > Sunny Braille' 카테고리의 다른 글

[Sunny Braille] 교내 세션 발표인줄 알았는데 반기문 옆자리인 건에 대하여 (GEEF 2024 초청받음)  (23) 2024.03.14
[Sunny Braille] 외부 API 호출, 비동기적으로 처리하기! (ft. Spring Event): 트랜잭션은 어떻게 해야할까,,?  (0) 2024.03.07
[Sunny Braille] 팀 합류와 동시에 타이거 팀이 되다..! 장고 코드를 스프링 코드로 바꿔주세요!: Mathpix API 사용해서 수학문제 사진에서 OCR 처리하기 (수식 인식하기)  (2) 2024.02.14
[Sunny Braille] 시각장애인을 위한 수학교재 인공지능 점자변환 서비스, 해바라기 팀에 합류하다  (3) 2024.02.07
  1. 💋 Swagger 적용으로 복잡해진 Controller,,,
  2. 💋 Swagger 관련 코드를 인터페이스로 분리!
  3. 💋 참고자료
'PROJECT/Sunny Braille' 카테고리의 다른 글
  • [Sunny Braille] 교내 세션 발표인줄 알았는데 반기문 옆자리인 건에 대하여 (GEEF 2024 초청받음)
  • [Sunny Braille] 외부 API 호출, 비동기적으로 처리하기! (ft. Spring Event): 트랜잭션은 어떻게 해야할까,,?
  • [Sunny Braille] 팀 합류와 동시에 타이거 팀이 되다..! 장고 코드를 스프링 코드로 바꿔주세요!: Mathpix API 사용해서 수학문제 사진에서 OCR 처리하기 (수식 인식하기)
  • [Sunny Braille] 시각장애인을 위한 수학교재 인공지능 점자변환 서비스, 해바라기 팀에 합류하다
깃짱
깃짱
연새데학교 컴퓨터과학과 & 우아한테크코스 5기 백엔드 스타라이토 깃짱
반응형
깃짱
깃짱코딩
깃짱
전체
오늘
어제
  • 분류 전체보기
    • About. 깃짱
    • Weekly Momentum
      • 2024
    • PROJECT
      • AIGOYA LABS
      • Stamp Crush
      • Sunny Braille
    • 우아한테크코스5기
    • 회고+후기
    • Computer Science
      • Operating System
      • Computer Architecture
      • Network
      • Data Structure
      • Database
      • Algorithm
      • Automata
      • Data Privacy
      • Graphics
      • ETC
    • WEB
      • HTTP
      • Application
    • C, C++
    • JAVA
    • Spring
      • JPA
      • MVC
    • AI
    • MySQL
    • PostgreSQL
    • DevOps
      • AWS
      • 대규모 시스템 설계
    • frontend
      • HTML+CSS
    • NextJS
    • TEST
    • Industrial Engineering
    • Soft Skill
    • TIL
      • 2023
      • 2024
    • Linux
    • Git
    • IntelliJ
    • ETC
      • 日本語

블로그 메뉴

  • 홈
  • 깃허브

인기 글

최근 글

태그

  • 상속
  • 우아한테크코스
  • 상속과조합
  • 조합
  • 우테코
  • 예외
  • Java
  • Composition
  • 람다와스트림
  • 스트림
  • 함수형프로그래밍
  • 컴포지션
  • 우아한테크코스5기
  • OOP
  • TDD
  • 레벨로그
  • 람다
  • lamda
  • 우테코5기
  • Stream
hELLO · Designed By 정상우.v4.2.0
깃짱
[Sunny Braille] Swagger로 더러워진 내 컨트롤러 코드 인공호흡: 인터페이스로 스웨거 코드 분리하는 방법
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.