스프링

 

 

중급자를 위해 준비한
[백엔드] 강의입니다.

다양한 스프링 기술을 사용하여 Self-Descriptive Message와 HATEOAS(Hypermedia as the engine of application state)를 만족하는 REST API를 개발하는 강의입니다.

✍️
이런 걸
배워요!

Self-Describtive Message와 HATEOAS를 만족하는 REST API를 이해

다양한 스프링 기술을 활용하여 REST API 개발

스프링 HATEOAS와 스프링 REST Docs 프로젝트 활용

테스트 주도 개발(TDD)

스프링으로 REST를 따르는 API를 만들어보자!
백기선의 스프링 기반 REST API 개발

스프링 기반 REST API 개발

이 강의에서는 다양한 스프링 기술을 사용하여 Self-Descriptive Message와 HATEOAS(Hypermedia as the engine of application state)를 만족하는 REST API를 개발합니다.

그런 REST API로 괜찮은가

2017년 네이버가 주관한 개발자 컨퍼런스 Deview에서 그런 REST API로 괜찮은가라는 이응준님의 발표가 있었습니다. 현재 REST API로 불리는 대부분의 API가 실제로는 로이 필딩이 정의한 REST를 따르고 있지 않으며, 그 중에서도 특히 Self-Descriptive Message와 HATEOAS가 지켜지지 않음을 지적했고, 그에 대한 대안을 제시되었습니다.

이번 강의는 해당 발표에 영감을 얻어 만들어졌습니다. 2018년 11월에 KSUG에서 동일한 이름으로 세미나를 진행한 경험이 있습니다. 4시간이라는 짧지 않은 발표였지만, 빠르게 진행하느라 충분히 설명하지 못하고 넘어갔던 부분이 있었습니다. 내용을 더 보충하고, 또 해결하려는 문제에 대한 여러 선택지를 제공하는 것이 좋을 것 같아 이 강의를 만들게 되었습니다.
또한 이 강의에서는 제가 주로 사용하는 IntelliJ 단축키도 함께 설명하고 있습니다.

 

인프런 :

 

강의 :  https://www.inflearn.com/course/spring_rest-api#

 

 

강의 자료 :  https://docs.google.com/document/d/1GFo3W6XxqhxDVVqxiSEtqkuVCX93Tdb3xzINRtTIx10/edit

 

 

강의 소스 :

 

https://gitlab.com/whiteship/natural

 

https://github.com/keesun/study

ksug201811restapi

 

 

 

이번 강좌에서는 다음의 다양한 스프링 기술을 사용하여 REST API를 개발합니다.

  • 스프링 프레임워크

  • 스프링 부트

  • 스프링 데이터 JPA

  • 스프링 HATEOAS

  • 스프링 REST Docs

  • 스프링 시큐리티 OAuth2

 

또한 개발은 테스트 주도 개발(TDD)로 진행하기 때문에 평소 테스트 또는 TDD에 관심있던 개발자에게도 이번 강좌가 도움이 될 것으로 기대합니다.

 

사전 학습

  • 스프링 프레임워크 핵심 기술 (필수)

  • 스프링 부트 개념과 활용 (필수)

  • 스프링 데이터 JPA (선택)

 

학습 목표

  • Self-Describtive Message와 HATEOAS를 만족하는 REST API를 이해합니다.

  • 다양한 스프링 기술을 활용하여 REST API를 개발할 수 있습니다.

  • 스프링 HATEOAS와 스프링 REST Docs 프로젝트를 활용할 수 있습니다.

  • 테스트 주도 개발(TDD)에 익숙해 집니다.

 

 

REST API 는 다음 두가지를 만족해야 한다.

1) Self-Describtive Message

2) HATEOAS

 

 

 

 

 

 

 

17. 비즈니스 로직 적용

 

강의 :   

 

https://www.inflearn.com/course/lecture?courseSlug=spring_rest-api&unitId=16423&tab=curriculum

 

테스트 할 것
비즈니스 로직 적용 됐는지 응답 메시지 확인
offline과 free 값 확인

 

EventController

 

@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE)
@RequiredArgsConstructor
@Log4j2
public class EventController {




   @PostMapping
    public ResponseEntity createEvent(@RequestBody @Valid  EventDto eventDto, Errors errors){
        if(errors.hasErrors()){
            log.info("첫번째 Bad Request 처리 {}", errors.getFieldErrors());
            //return ResponseEntity.badRequest().build();
            return ResponseEntity.badRequest().body(errors);
        }

        //커스텀 validate 검사
        eventValidator.validate(eventDto, errors);
        if(errors.hasErrors()){
            log.info("두번째 Bad Request 처리");
           // return ResponseEntity.badRequest().build();
            //errors 의 경우 기본적으로 Serialize 처리가 안되어 있어 에러 발생
            //다음괴 같이 ErrorsSerializer 클래스를 만들어 처리해 준다.
            log.info(" errors  : {}", errors);
            return ResponseEntity.badRequest().body(errors);
        }

        //modelMapper 오류
        //Event event=modelMapper.map(eventDto, Event.class);
        Event event = eventDto.toEvent();
        //유료인지 무료인지 변경처리
        event.update();
        Event newEvent=this.eventRepository.save(event);//저장

        
        URI createdUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
        return ResponseEntity.created(createdUri).body(event);
    }




}


 

 

EventTest

@SpringBootTest
@Transactional
class EventTest {

   @Autowired
    private  ModelMapper modelMapper;



    @Test
    public void testFree(){
        //Given
        Event event=Event.builder()
                .basePrice(0)
                .maxPrice(0)
                .build();

        //When
        event.update();


        //Then
        Assertions.assertThat(event.isFree()).isTrue();


        //Given
        event=Event.builder()
                .basePrice(100)
                .maxPrice(100)
                .build();

        //When
        event.update();

        //Then
        Assertions.assertThat(event.isFree()).isFalse();
    }



    @Test
    public void testOffline(){
        //Given
        Event event =Event.builder()
                .location("강남역 네이버 D2 스타텁 팩토리")
                .build();

        //When
        event.update();

        //Then
        Assertions.assertThat(event.isOffline()).isTrue();


        //Given
        event =Event.builder()
                .build();

        //When
        event.update();

        //Then
        Assertions.assertThat(event.isOffline()).isFalse();
    }



}




 

 

 

Event

import jakarta.persistence.*;
import lombok.*;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;


@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
//@Setter
@EqualsAndHashCode(of="id")
@Entity
public class Event {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private String description;
    private LocalDateTime beginEnrollmentDateTime;
    private LocalDateTime closeEnrollmentDateTime;
    private LocalDateTime beginEventDateTime;
    private LocalDateTime endEventDateTime;
    private String location; // (optional) 이게 없으면 온라인 모임
    private int basePrice; // (optional)
    private int maxPrice; // (optional)-
    private int limitOfEnrollment;
    private boolean offline;
    private boolean free;

    @Enumerated(EnumType.STRING)
    private EventStatus eventStatus;
    
    public void update() {
        //Update Free
        if(this.basePrice==0 && this.maxPrice==0){
            this.free=true;
        }else{
            this.free=false;
        }

        //Update offline
        if(StringUtils.hasText(this.location)){
            this.offline=true;
        }else {
            this.offline=false;
        }
    }

}

 

 

 

 

 

 

 


 

18. 매개변수를 이용한 테스트

 

JUnit 5 Parameterized Tests

 

 

강의 :   

 

https://www.inflearn.com/course/lecture?courseSlug=spring_rest-api&unitId=16424&tab=curriculum

 

		<!-- https://mvnrepository.com/artifact/pl.pragmatists/JUnitParams -->
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-params</artifactId>
			<version>5.9.2</version>
			<scope>test</scope>
		</dependency>

 

 

 사용법

참조 : https://reflectoring.io/tutorial-junit5-parameterized-tests/

 

    @ParameterizedTest
   // @CsvSource({"0,0,true", "100,0,false"})
   // @MethodSource("paramsForTestFree")
    @MethodSource
    public void testFree(int basePrice, int maxPrice, boolean isFree){
        //Given
        Event event=Event.builder()
                .basePrice(basePrice)
                .maxPrice(maxPrice)
                .build();

        //When
        event.update();

        //Then
        Assertions.assertThat(event.isFree()).isEqualTo(isFree);
    }

    static  Object[] testFree(){
        return new Object[]{
                new Object[] {0, 0,true},
                new Object[]{100,0, false},
                new Object[]{0,100, false},
                new Object[]{100, 200, false}
        };
    }

 

 

    @ParameterizedTest
    @MethodSource
    public void testOffline(String location, boolean isOffline){
        //Given
        Event event =Event.builder()
                .location(location)

                .build();
        //When
        event.update();

        //Then
        Assertions.assertThat(event.isOffline()).isEqualTo((isOffline));
    }


    static Object[] testOffline(){
      return  new Object[]{
                  new Object[]{"강남", true},
                  new Object[]{null, false},
                  new Object[]{"  ", false}
        };
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

돌로 치면 돌로 치고 떡으로 치면 떡으로 친다 , 원수는 원수로 갚고, 은혜는 은혜로 갚는다는 말.

댓글 ( 4)

댓글 남기기

작성