국비학원 44일차 : Spring Boot_6, API 명세서_2

Digeut·2023년 5월 1일
0

국비학원

목록 보기
38/44

Board 프로젝트

프로젝트 기본 폴더 구조

⭐기본 폴더구조

  • controller
  • config
  • service
    -implement
  • repository
  • dto
    -request
    -response
  • entity

Entity: 데이터베이스에서 사용되는 객체 또는 테이블을 의미합니다. 엔티티는 데이터베이스와 직접적으로 상호작용하는 객체이며, 데이터베이스 테이블과 일치하는 속성을 가집니다.

DTO(Data Transfer Object): 데이터 전송 객체로, 서로 다른 계층간 데이터를 전송할 때 사용됩니다. DTO는 일반적으로 비즈니스 로직에서 사용하는 Entity와는 다르게 단순한 데이터 구조체로 구성되어 있습니다. DTO는 데이터베이스에서 읽은 Entity 데이터를 서비스, 컨트롤러 또는 클라이언트로 전달하는 데 사용됩니다.

Repository: 엔티티를 검색, 저장 및 업데이트하는 인터페이스를 제공하는 객체입니다. Repository는 데이터베이스와의 상호작용을 캡슐화합니다.

Service: 비즈니스 로직을 구현하는 객체입니다. Service는 Repository에서 엔티티를 가져와 비즈니스 규칙에 따라 처리한 후, Controller나 다른 Service에 결과를 반환합니다.

Controller: 클라이언트의 요청을 받아 해당 요청을 처리하는 객체입니다. Controller는 클라이언트로부터 요청을 받아 Service에 전달하고, Service에서 처리한 결과를 클라이언트에 반환합니다.

기본 클래스들 정의

application.proterties

server.port = 4040

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/연결할SQL_DB
					?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root

BoardApplication

package com.⭐.board;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BoardApplication {

	public static void main(String[] args) {
		SpringApplication.run(BoardApplication.class, args);
	}

}

Controller - UserController

package com.⭐.board.controller;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

}

Controller - BoardController

package com.⭐.board.controller;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class BoardController {

}

@RestController

@RestController 어노테이션을 사용하면 HTTP 요청에 대한 응답으로 JSON, XML 등의 데이터 형식을 반환하는 RESTful API를 쉽게 개발할 수 있습니다. @RestController 어노테이션이 붙은 클래스에서는 HTTP 요청을 처리하기 위한 다양한 어노테이션들이 사용됩니다. 예를 들어, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 어노테이션 등이 있습니다.

@RestController 어노테이션은 @Controller 어노테이션과 @ResponseBody 어노테이션의 결합체로 생각할 수 있습니다. @Controller 어노테이션은 스프링 MVC에서 사용되는 어노테이션으로, HTTP 요청을 처리하기 위한 컨트롤러 역할을 합니다. 하지만 @Controller 어노테이션은 메서드에 @ResponseBody 어노테이션을 추가해야만 해당 메서드의 반환값이 HTTP 응답의 바디(body)에 포함되어 전송됩니다.

Service - UserService

package com.⭐.board.service;

public ✔️interface UserService {

}

Service - BoardService

package com.⭐.board.service;

public ✔️interface BoardService {

}

Repository와 Service는 interface로 구현하는 이유

유연성: 인터페이스를 사용하면 다양한 구현체를 만들 수 있습니다. 이를 통해 코드 유연성과 확장성을 높일 수 있습니다. 예를 들어, 한 개의 인터페이스를 여러 개의 구현체로 만들어 다른 데이터베이스에서 데이터를 가져오는 등의 작업을 할 수 있습니다.

의존성 주입(Dependency Injection): 인터페이스를 사용하면 의존성 주입(DI)을 쉽게 구현할 수 있습니다. DI를 사용하면 객체 간의 의존성을 외부에서 설정할 수 있습니다. 따라서 인터페이스를 구현한 구현체를 DI할 수 있습니다.

테스트 용이성: 인터페이스를 사용하면 테스트 코드 작성이 용이해집니다. 테스트 코드에서 인터페이스를 구현하는 Mock 객체를 만들어 테스트할 수 있기 때문입니다. 이를 통해 테스트 코드의 독립성과 안정성을 높일 수 있습니다.

코드 가독성: 인터페이스를 사용하면 코드의 가독성이 높아집니다. 인터페이스를 구현하는 클래스는 해당 인터페이스가 제공하는 메서드를 반드시 구현해야 하기 때문입니다. 따라서 인터페이스를 사용하면 어떤 클래스가 어떤 메서드를 제공하는지 쉽게 파악할 수 있습니다.

프로그래밍 패턴 적용: 인터페이스는 객체지향 프로그래밍의 다양한 패턴을 적용하기 용이합니다. 예를 들어, 인터페이스를 사용하면 전략 패턴, 팩토리 메서드 패턴, 빌더 패턴 등의 다양한 패턴을 적용할 수 있습니다.

User API

1. 사용자 등록

URL : POST http://localhost:4040/api/v1/user
UserController

@RestController
@RequestMapping("/api/v1/user") //v1= version1
public class UserController {


    @PostMapping("")
    public ResponseEntity<?> postUser(){

    }
}

@RequestMapping

클래스 레벨에서 사용되는 경우, 해당 클래스의 모든 요청 URL의 공통 경로를 지정합니다.
메서드 레벨에서 사용되는 경우, 해당 메서드가 처리하는 요청 URL을 지정합니다.

예를 들어, @RequestMapping("/users") 어노테이션이 UserController 클래스에 지정된 경우, 해당 클래스의 모든 메서드는 "/users" 경로를 기준으로 매핑됩니다. 즉, "/users/create", "/users/delete", "/users/update" 등의 URL이 가능해집니다.

  • @GetMapping =
    @RequestMapping(value="/user", method=RequestMethod.GET)
  • @PostMapping =
    @RequestMapping(value="/user", method=RequestMethod.POST)
  • @PatchMapping =
    @RequestMapping(value="/user", method=RequestMethod.PATCH)
  • @DeletdMapping =
    @RequestMapping(value="/user", method=RequestMethod.DELETE)

API의 Request Parameter작성

dto - requset - user - PostUserRequestDto

@Data
@NoArgsConstructor
public class PostUserRequestDto {
    @NotBlank
    @Email
    private String userEmail;
    @NotBlank
    private String userPassword;
    @NotBlank
    private String userNickname;
    @NotBlank
    @Pattern(regexp = "^\\d{3}-\\d{3,4}-\\d{4}$") 
    		//전화번호에 대한 패턴 커스텀 작업
    private String userPhoneNumber;
    @NotBlank
    private String userAddress;
    private String userProfileImageUrl;
}

⭐검증을 할 위치를 잘 구분해야한다. DTO 또는 Service에서 검증을 할게 뭔지 구분을 해야한다.

  • 사용자 닉네임, 빈 문자열이거나 공백으로만 구성될 수 없고 → 입력관련은 dto검증

  • 사용중인 닉네임이 아니어야 합니다 → DB에서 받아서 해야하므로 Service에서 검증

  • 사용자 전화번호, 빈 문자열이거나 공백으로만 구성될 수 없고
    전화번호 형식이어야 하며 → 입력 관련 Dto에서 검증

  • 사용중인 전화번호가 아니어야 합니다. → DB에서 받아야하므로 Service에서 검증

@NoArgsConstructor

클래스에 기본 생성자를 자동으로 생성해주는 기능을 제공합니다.

자바 클래스에는 기본적으로 파라미터를 받지 않는 디폴트 생성자가 있습니다. 그러나 클래스에 많은 수의 필드가 있거나 생성자의 파라미터 수가 많아질 경우, 생성자를 작성하는 것은 번거롭고 지루한 일이 될 수 있습니다. 이 때, @NoArgsConstructor를 사용하면 이러한 생성자를 자동으로 생성해주기 때문에, 코드를 간결하게 작성할 수 있습니다.

⭐ResponseDto
포스트맨에서 url적용시에 반환되는 오류들을 코드로 정해서 반환되게 해주는 dto.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseDto {
    private String code;
    private String message;
}

Controller - UserController

@RestController
@RequestMapping("/api/v1/user") //모듈설정..
public class UserController {

    @PostMapping("")
    public ResponseEntity<⭐ResponseDto> postUser(
        @Valid @RequestBody PostUserRequestDto requestBody
    ){
        return null;
    }
}

@Valid

객체의 유효성을 검사하는 기능을 수행

Validation 예외 처리 방법

Validation 예외 처리를 위해 여러 가지 방법을 제공합니다. 가장 일반적인 방법은 try-catch 블록을 사용하는 것입니다. 유효성 검사를 수행하는 메서드에서 검사에 실패할 경우, 예외를 던지고 이를 try-catch 블록에서 처리합니다. 예를 들어, @Valid 어노테이션을 사용하여 유효성 검사를 수행할 때, 검사에 실패하면 MethodArgumentNotValidException 예외가 발생합니다. 이 예외를 try-catch 블록에서 처리하여 적절한 응답을 생성할 수 있습니다.

또한, Spring Framework에서는 @ExceptionHandler 어노테이션을 사용하여 예외 처리를 간편하게 할 수 있습니다. 이 어노테이션을 사용하면, Controller에서 발생한 예외를 특정한 메서드에서 처리할 수 있습니다. 이를 통해 Controller에서 예외 처리 코드를 분리하여 가독성과 유지 보수성을 높일 수 있습니다.

또한, Spring Framework에서는 Validator 인터페이스를 구현하여 사용자 정의 유효성 검사 로직을 만들 수 있습니다. 이를 통해 개발자는 자신이 만든 클래스나 DTO에 대한 유효성 검사를 수행할 수 있으며, 이때 Validator 인터페이스에서 발생한 예외를 try-catch 블록이나 @ExceptionHandler 어노테이션을 사용하여 처리할 수 있습니다.

마지막으로, Spring Framework에서는 BindingResult 인터페이스를 사용하여 검사 결과를 처리할 수 있습니다. BindingResult 인터페이스를 사용하면 검사 결과에 대한 정보를 확인하고, 이를 기반으로 적절한 응답을 생성할 수 있습니다.

예외에 대한 처리를 위해서 CustomExceptionHandler생성
exception - CustomExceptionHandler

@RestControllerAdvice
public class CustomExceptionHandler {
    
    @ExceptionHandler(HttpMessageNotReadableException.class) 
    //특정한 예외 발생시 밑에 메서드 실행되게 해준다.
    public ResponseEntity<ResponseDto> handlerHttpMessageNotReadableException(
    HttpMessageNotReadableException exception){
        ResponseDto responseBody = 
        new ResponseDto("VF", "Request Parameter Validation Failed");
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
        .body(responseBody);
    }
}

오류의 예외처리 하지 않은 경우 400으로 오류 반환된다.

예외처리 되지 않은 오류 이름.

지정한 오류 이름으로 반환시킬수 있다.

@RestControllerAdvice

@RestControllerAdvice는 Spring Framework에서 제공하는 어노테이션 중 하나로, RESTful 웹 서비스에서 발생하는 예외를 처리하기 위해 사용됩니다.

@RestControllerAdvice 어노테이션을 사용하면, @ControllerAdvice 어노테이션과 유사하게, 모든 @RestController 클래스에서 발생하는 예외를 처리하는 공통 처리기를 정의할 수 있습니다. 예외 처리기는 @ExceptionHandler 어노테이션을 사용하여 특정 예외를 처리할 수 있습니다.

@RestControllerAdvice 어노테이션을 사용하여 전역 예외 처리기를 만들면, 코드 중복을 줄일 수 있고, 예외 처리 로직을 중앙 집중화하여 유지 보수성과 가독성을 높일 수 있습니다. 예를 들어, RESTful 웹 서비스에서 사용자 요청에 대한 예외 처리를 담당하는 클래스를 만들고, 이를 @RestControllerAdvice 어노테이션으로 등록하면, 모든 @RestController 클래스에서 이 예외 처리기를 사용할 수 있습니다.

@RestControllerAdvice 어노테이션을 사용하여 처리할 수 있는 예외 종류는 다양합니다. 일반적으로, @ExceptionHandler 어노테이션을 사용하여 처리하는 예외는 RuntimeException, ServletException, IOException 등이 있습니다. 또한, Spring Framework에서 제공하는 예외 중에서도, MethodArgumentNotValidException, HttpMessageNotReadableException 등 RESTful 웹 서비스에서 흔히 발생하는 예외를 처리할 수 있습니다.

예외처리에 대한 동작

Spring Framework에서는 예외 처리가 여러 레이어에서 일어날 수 있습니다. 예를 들어, 데이터 전송 객체(DTO) 검증을 하는 Service 레이어에서 예외가 발생했을 경우, 이 예외는 해당 레이어에서 처리될 수 있습니다. 만약, 이 예외를 해당 레이어에서 처리하지 않았다면, 이 예외는 컨트롤러(Controller) 레이어로 전달됩니다.

컨트롤러 레이어에서는, 이 예외를 @ExceptionHandler 어노테이션을 사용하여 처리할 수 있습니다. 이 경우, 해당 예외를 처리할 메서드를 컨트롤러 클래스에 정의하고, @ExceptionHandler 어노테이션을 이 메서드에 적용합니다. 이렇게하면, 해당 예외가 발생했을 때, 이 메서드가 호출되어 예외 처리를 수행하고, 처리 결과를 클라이언트에게 반환할 수 있습니다.

이렇게 처리된 예외는 클라이언트에게 HTTP 응답으로 전달됩니다. 이 때, HTTP 응답 코드와 함께 예외 메시지도 함께 전달됩니다. 이러한 예외 처리 방식을 통해, 클라이언트는 요청한 작업이 성공적으로 수행되지 않은 경우에 대한 처리 결과를 받을 수 있습니다.

Controller - UserController

@RestController
@RequestMapping("/api/v1/user") //모듈설정..
public class UserController {

	private UserService userService;

    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }

    @PostMapping("")
    public ResponseEntity<ResponseDto> postUser(
        @Valid @RequestBody PostUserRequestDto requestBody
    ){
        return null;
        ResponseEntity<ResponseDto> response = 
        userService.postUser(requestBody);
        return response;
    }
}

entity - UserEntity

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "User")
@Table(name = "User") //DB랑 맵핑시켜줄때 사용
public class UserEntity {
    @Id
    private String email;
    private String password;
    private String nickname;
    private String phoneNumber;
    private String address;
    private boolean consentPersonalInformation;
    private String profileImageUrl;

    public UserEntity(PostUserRequestDto dto){
        this.email = dto.getUserEmail();
        this.password = dto.getUserPassword();
        this.nickname = dto.getUserNickname();
        this.phoneNumber = dto.getUserPhoneNumber();
        this.address = dto.getUserAddress();
        this.consentPersonalInformation = true;
        this.profileImageUrl = dto.getUserProfileImageUrl();
    }
}

repository - UserRepository

public interface UserRepository extends JpaRepository<UserEntity, String>{
													/*해당 엔터티 PK의 타입*/

   public boolean existsByEmail(String email);
    public boolean existsByNickname(String nickname);
    public boolean existsByPhoneNumber(String phoneNumber);  
    
    public UserEntity findByEmail(String email);
}

service - UserService

public interface UserService {
    
    public ResponseEntity<ResponseDto> postUser(PostUserRequestDto dto);
}

service - implements - UserServiceImplement

@Service
public class UserServiceImplement implements UserService {
    
    private UserRepository userRepository;

    @Autowired
    public UserServiceImplement(UserRepository userRepository){
        this.userRepository = userRepository;
    }

    @Override //잘입력했는지 확인하는 용도
    public ResponseEntity<ResponseDto> postUser(PostUserRequestDto dto){
       
        // ResponseEntity<ResponseDto> result = null;
        ResponseDto responseBody = null;

        String eamil = dto.getUserEmail();
        String nickname = dto.getUserNickname();
        String phoneNumber = dto.getUserPhoneNumber();

        try{

        // 1. 이메일 중복 반환 : 
        //받아올 이메일을 알아야한다. 
        //DB 유저테이블에 해당 이메일이 존재하는지 찾아와야한다.
        boolean hasEmail = userRepository.existsByEmail(eamil); 
        //존재하면 true, 아니면 false
        if(hasEmail){
                responseBody = new ResponseDto("EU", "Existent User Email");
                return ResponseEntity.
                status(HttpStatus.BAD_REQUEST).
                body(responseBody);
            }
        // 2. 닉네임 중복 반환 DB 유저테이블에 해당 값이 존재하는지 찾아와야한다.
        boolean hasNickname = userRepository.existsByNickname(nickname);
        if(hasNickname){
            responseBody = new ResponseDto("EN", "Existent User Nickname");
            return ResponseEntity.
            status(HttpStatus.BAD_REQUEST).
            body(responseBody);
        }

        // 3. 휴대전화 번호 중복 반환 
        boolean hasPhoneNumber =
        		userRepository.existsByPhoneNumber(phoneNumber);
        if(hasPhoneNumber) {
            responseBody = new ResponseDto("EP", "Existent User Phone Number");
            return ResponseEntity.
            status(HttpStatus.BAD_REQUEST).
            body(responseBody);
        }

        //삽입시에는 엔터티에 저장하고 레포에 세이브..?
        //유저 레코드 삽입
        UserEntity userEntity = new UserEntity(dto);
        userRepository.save(userEntity);

        responseBody = new ResponseDto("SU", "Success");
        

        } catch(Exception exception){

            // 데이터 베이스 오류 반환
            exception.printStackTrace();
            responseBody = new ResponseDto("DE", "Database Error");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).
            body(responseBody);
        }

        // 성공 반환 
        return ResponseEntity.status(HttpStatus.OK).body(responseBody);


        
    }

}
profile
개발자가 될 거야!

0개의 댓글