[rest api 프로젝트 구축] - 필수 처리 요소

geun kim·2023년 3월 24일
0

REST API 구축

목록 보기
2/20

* 요청,응답 전문(JSON)을 DTO 객체 생성 (Map으로 받는 것은 여러 모로 별루다.....)

json to dto 자동 생성 참조링크

  • desirialization : json -> 객체로 변환
  • serialization : 객체 -> json으로 변환

Data 관련 util

ObjectMapper가 json 스트링을 변환 할 때 Composite 객체가 존재 할 경우
변수명을 json Object명에 사용 한다.
아래 예시

public Class Resource{
    private User user;
    private String source;
}
{
	"user" : {
  			"name" : "geun",
              "age" : 12
  		},
  "source" : "source"        
}

@JsonUnWrapped : Composite 객체에 해당 어노테이션에 선언하면
아래와 같이 json Object 명을 사용하지 않는다.

   public Class Resource{
        @JsonUnWrapped
        private User user;
        private String source;
   }
{
	{
  	"name" : "geun",
      "age" : 12
  },
  	"source" : "source"        
}                

ObjectMapper : 객체 <->json 변환하는 라이브러리

ex)  String json = objectMapper.writeValueAsString(eventDto)  //객체를 json string으로 변환
     Event event = objectMapper.readValue(json,Event.class);  // json 을 객체로 변환

modelmapper: dto <-> domain 객체에 값 복사 해 주는 라이브러리
@ 중요: ModelMapper는 요청시 받은 DTO를 Entity객체에 binding 해 줄 때
조회 요청 시 Entity 객체를 DTO로 변환 시 필요 할 듯
물론 Entity를 안 거치고 DTO를 바로 사용 할 경우에는 필요하지 않겠지만......

ex) Event event = modelMapper.map(eventDto,Event.class);
EventDto eventDto1 = modelMapper.map(event,EventDto.class);
    <!-- DTO <-> 도메인 간 값 복사 할 용도로 사용 -->
		<dependency>
			<groupId>org.modelmapper</groupId>
			<artifactId>modelmapper</artifactId>
			<version>2.3.1</version>
		</dependency>

1.에러처리 (bad request)

<!-- DTO에서 입력값 체크 용도로 활용 , javax.validation-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
			<version>2.5.4</version>
		</dependency>
  • validation 설정

    EventValidator : 입력값 검증 후 error 정보를 Erros객체에 상세정보 세팅
    ErrorsSerializer : ObjectMapper class에 ErrorSerializer 클래스를 등록하여 Error 클래스를 serialization
    할 수 있도록 한다.

2.페이징 처리 (페이징 처리 다양한 예제 분석 필요)

Pageable : page,size,sort등 paging에 관련된 정보를 담고 있다.
PagedResourcesAssembler : 페이지 링크 정보를 생성 해 준다.

@GetMapping
    public ResponseEntity queryEvents(Pageable pageable){
        return ResponseEntity.ok().body(eventRepository.findAll((pageable)));
    }

응답값

{
    "content": [
        {
            "id": 29,
            "name": "event26",
            "description": "test event26",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 28,
            "name": "event25",
            "description": "test event25",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 27,
            "name": "event24",
            "description": "test event24",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 26,
            "name": "event23",
            "description": "test event23",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 25,
            "name": "event22",
            "description": "test event22",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 24,
            "name": "event21",
            "description": "test event21",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 23,
            "name": "event20",
            "description": "test event20",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 5,
            "name": "event2",
            "description": "test event2",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 22,
            "name": "event19",
            "description": "test event19",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        },
        {
            "id": 21,
            "name": "event18",
            "description": "test event18",
            "beginEnrollmentDateTime": null,
            "closeEnrollmentDateTime": null,
            "beginEventDateTime": null,
            "endEventDateTime": null,
            "location": null,
            "basePrice": 0,
            "maxPrice": 0,
            "limitOfEnrollment": 0,
            "offline": false,
            "free": false,
            "eventStatus": null
        }
    ],
    "pageable": {
        "sort": {
            "sorted": true,
            "unsorted": false,
            "empty": false
        },
        "offset": 10,
        "pageNumber": 1,
        "pageSize": 10,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalElements": 30,
    "totalPages": 3,
    "size": 10,
    "number": 1,
    "sort": {
        "sorted": true,
        "unsorted": false,
        "empty": false
    },
    "numberOfElements": 10,
    "first": false,
    "empty": false
}
@GetMapping
    public ResponseEntity queryEvents(Pageable pageable, PagedResourcesAssembler<Event> pagedResourcesAssembler){
        Page<Event> eventPage = eventRepository.findAll((pageable));
        PagedModel<EventResource> eventResources = pagedResourcesAssembler.toModel(eventPage, e -> new EventResource(e));
        eventResources.add(new Link("/docs/index.html#resources-events-list").withRel("profile"));
        return ResponseEntity.ok().body(eventResources);
    }

응답값

{
    "_embedded": {
        "eventList": [
            {
                "id": 29,
                "name": "event26",
                "description": "test event26",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/29"
                    }
                }
            },
            {
                "id": 28,
                "name": "event25",
                "description": "test event25",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/28"
                    }
                }
            },
            {
                "id": 27,
                "name": "event24",
                "description": "test event24",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/27"
                    }
                }
            },
            {
                "id": 26,
                "name": "event23",
                "description": "test event23",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/26"
                    }
                }
            },
            {
                "id": 25,
                "name": "event22",
                "description": "test event22",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/25"
                    }
                }
            },
            {
                "id": 24,
                "name": "event21",
                "description": "test event21",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/24"
                    }
                }
            },
            {
                "id": 23,
                "name": "event20",
                "description": "test event20",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/23"
                    }
                }
            },
            {
                "id": 5,
                "name": "event2",
                "description": "test event2",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/5"
                    }
                }
            },
            {
                "id": 22,
                "name": "event19",
                "description": "test event19",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/22"
                    }
                }
            },
            {
                "id": 21,
                "name": "event18",
                "description": "test event18",
                "beginEnrollmentDateTime": null,
                "closeEnrollmentDateTime": null,
                "beginEventDateTime": null,
                "endEventDateTime": null,
                "location": null,
                "basePrice": 0,
                "maxPrice": 0,
                "limitOfEnrollment": 0,
                "offline": false,
                "free": false,
                "eventStatus": null,
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/events/21"
                    }
                }
            }
        ]
    },
    "_links": {
        "first": {
            "href": "http://localhost:8080/api/events?page=0&size=10&sort=name,desc"
        },
        "prev": {
            "href": "http://localhost:8080/api/events?page=0&size=10&sort=name,desc"
        },
        "self": {
            "href": "http://localhost:8080/api/events?page=1&size=10&sort=name,desc"
        },
        "next": {
            "href": "http://localhost:8080/api/events?page=2&size=10&sort=name,desc"
        },
        "last": {
            "href": "http://localhost:8080/api/events?page=2&size=10&sort=name,desc"
        }
    },
    "page": {
        "size": 10,
        "totalElements": 30,
        "totalPages": 3,
        "number": 1
    }
}

test code

@Test
    @TestDescription("30개의 이벤트를 10개씩 두번째 페이지 조회하기")
    public void queryEvents() throws  Exception{
        //given
        IntStream.range(0,30).forEach(i -> this.generateEvent(i));

        //when
        this.mockMvc.perform(get("/api/events")
                        .param("page","1")
                        .param("size","10")
                        .param("sort", "name,DESC")
                )
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("page").exists())
                .andDo(document("query-events"));
        //then

    }

    private void generateEvent(int i) {
        Event event = Event.builder()
                .name("event"+i)
                .description("test event"+i)
                .build();
        eventRepository.save(event);
    }

3.응답 시 응답 본문으로 사용 할 객체 구조화

  • 서버측 에러 발생 시 처리
    Contoller에서 try~catch로 잡아서 응답전문 생성
    service 객체에서 throws 해서 controller에서 예외처리 한다.

응답 객체 생성 예제 링크

* 어떻게 구성 할지 생각 해 보자...................

Response<Error,T> 생성 시 JavaBean 스펙을 준수 해야만 해야 serialization 할 수 있다.
Response<Error,T> 객체 구조
   -- result : N(실패 : 1111 ~ 9999)
       		-- Error Class 활용 (에러 정보)
   -- result : y(성공 : 0000)
   			-- 응답 전문

4.restdocs or swagger

rest docs 만들기

5.문자열을 외부설정으로 빼내기

에러코드를 Propertie 파일로 빼내서 처리한다.
구체적인 부분은 정리 해 보자...
<<<< 학습 필요!!!!! >>>>
외부 설정 : application.properties 다른 설정 파일 읽어와서 bean으로 만들 순 없을까???

외부설정

6.인증

oauth 인증서버 설정

[숙제..............]
ClientDetailsServiceConfigurer clients 구성을 inmemory 화 되어 있는데 일반적인 구성 확인 필요

리소스 서버 설정

7.api 연동 테스트 (swagger or postman)

포스트맨 연동

profile
Devops Load Map

0개의 댓글