GraphQL과 Apollo 열 여섯번째 이야기 - Kotlin + Spring Boot에서 GraphQL 사용해 보기 - 실습 환경 구성

2023. 12. 17. 01:08개념 정리 작업실/Kotlin

728x90
반응형

 

 

 

카카오페이 | 마음 놓고 금융하다

여기를 눌러 링크를 확인하세요.

qr.kakaopay.com

 

 

 

 

 

 

코틀린 완벽 가이드

COUPANG

www.coupang.com

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

 




🗂 목차

✅ GraphQL과 Apollo 첫번째 이야기 - 개념 익히기
 GraphQL과 Apollo 두번째 이야기 - REST API란?
 GraphQL과 Apollo 세번째 이야기 - GraphQL의 정보 주고 받는 방식

 GraphQL과 Apollo 네번째 이야기 - Apollo란?
✅ GraphQL과 Apollo 다섯번째 이야기 - GraphQL을 간단하게 구현해 보아요 😀
 GraphQL과 Apollo 여섯번째 이야기 - GraphQL Module화에 대해 알아보아요 😀
 GraphQL과 Apollo 일곱번째 이야기 - GraphQL Data Type에 대해 알아보아요 😀
 GraphQL과 Apollo 여덟번째 이야기 - GraphQL Union과 Interface 그리고 인자와 인풋 타입에 대해 알아보아요 😀
 GraphQL과 Apollo 아홉번째 이야기 - Java + Spring Boot에서 GraphQL 사용해 보기 - 실습 환경 구성
 GraphQL과 Apollo 열번째 이야기 - Java + Spring Boot에서 GraphQL 사용해 보기 - 실습 해보기
✅ GraphQL과 Apollo 열 한번째 이야기 - TypeScript + Nest.js에서 GraphQL 사용해 보기 - 실습 환경 구성
✅ 
GraphQL과 Apollo 열 두번째 이야기 - TypeScript + Nest.js에서 GraphQL 사용해 보기 - 실습 환경 테스트
 GraphQL과 Apollo 열 세번째 이야기 - TypeScript + Nest에서 GraphQL 사용해 보기 - 실습 해보기
 
GraphQL과 Apollo 열 네번째 이야기 - React와 Apollo Client
 GraphQL과 Apollo 열다섯번째 이야기 - React와 Apollo Client - Query와 Mutation 사용하여 웹 페이지 만들기
 GraphQL과 Apollo 열 여섯번째 이야기 - Kotlin + Spring Boot에서 GraphQL 사용해 보기 - 실습 환경 구성
 GraphQL과 Apollo 열 일곱번째 이야기 - Kotlin + Spring Boot에서 GraphQL 사용해 보기 - 실습 해보기


🤔 내가 만난 문제

⚠️ [Nest.js] TypeORM Table 관계가 맺어졌을 때, Seeding (feat. Migration)
⚠️ [Spring Boot 3.0] Could not resolve org.springframework.boot:spring-boot-gradle-plugin
⚠️ [Spring Boot 3] Spring Doc(Swagger) White Label Error


📋 부록

🔍 [Nest.js] 초기 환경 구성 (feat. TypeORM, QueryBuilder, GraphQL, Apollo)
🔍 [SOLID][Nest.js][Java + Spring] Interface를 활용한 결합도 분리 (Interface를 이용한 Dependency Injection - DI)

 

 

 

 

GitHub - junyharang-coding-study/GraphQL-Study: GraphQL을 공부하고, 실습한 코드에요 😀

GraphQL을 공부하고, 실습한 코드에요 😀. Contribute to junyharang-coding-study/GraphQL-Study development by creating an account on GitHub.

github.com

 

 

 

🚀 GraphQL과 Apollo 열 여섯번째 이야기

    🔽  Kotlin + Spring Boot 에서 GraphQL 사용해 보기 - 실습 환경 구성

        📦 개요

이번 글에서는 자프링(Java + Spring Boot)으로 개발한 GraphQL 실습 코드를 코프링(Kotlin + Spring Boot)로 바꿔서 실습하는 시간을 가져보려고 해요.

 

 

 

 

 

 

    🔽  환경 구성

        📦 Kotlin + Spring Boot (자프링)

이번 글을 이해하기 위해하기 위해선 Kotlin(코틀린) 문법을 어느정도 알아야 한다고 생각해요.
이 곳에 해당 내용 정리를 해 두었으니 도움이 되시길 바랄게요.

 

[Kotlin(코틀린)] 문법 총 정리

코틀린 완벽 가이드 COUPANG www.coupang.com "이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다." 🚀 Kotlin 문법 총 정리 🔽 들어가기 📦 소개 안녕하세요? Andlo

junyharang.tistory.com

 

이번에도 자프링과 마찬가지로 Spring for GrpahQL을 사용해서 실습 해 보려고 해요.


🔍 graphql-java / graphql-java-spring
🔍 graphql-java-kickstart / graphql-spring-boot
🔍 Netflix / dgs-framwork

스프링 진영에서는 위와 같이 GraphQL을 이용하기 위한 3가지 대표적 Library(라이브러리) / 프레임워크(framwork)가 준비 되어 있고, 이 중 하나를 선택하여 스프링 측에서 GraphQL을 사용할 수 있도록 구성한 것으로 보여요.

뿌리가 되는 프로젝트는 graphql-java / graphql-java-spring 이 프로젝트로 모든 프로젝트가 이걸 기반으로 개발 되었다고 해요. 또한, Spring for GraphQL은 공식적으로 graphql-java 후속 프로젝트라고 소개하고 있어요.

위에 있는 프로젝트들을 이용하여 GraphQL을 개발하면 이 전까지 공부했듯이 Resolver를 개발하고, 별도의 설정 등을 해주어야 했지만, Spring for GraphQL graphql-java의 단순 후속 프로젝트 뿐 아니라, graphql-java 개발팀이 개발을 하였기 때문에 스프링 측에서 추구하는 방향답게 추가 코드 없이 MVC 개발하듯 개발할 수 있게 만들어 두었어요.

 

 

 

 

        📦 실습 환경

🔍 JDK 17(Java 17) with Kotlin 1.3.X
🔍 Spring Boot 3.1.6 (Spring for GraphQL은 Spring Boot 2.7.0 이상 지원)
🔍 Embedded H2 DBMS(In-Memory)
🔍 JPA & QueryDsl
🔍 InteliJ (2023.2.5 Ultimate)



 

        📦 초기 구성


최초 인텔리제이에서 위와 같이 프로젝트 탭에서 새 프로젝트를 눌러 프로젝트를 만들어 줄게요.

 

그리고 Spring Initializr에서 위와 같이 프로젝트를 구성해 주었어요.
참고로 Comunity 버전을 쓰시는 분은 위 내용이 없을 거에요.

https://start.spring.io/

위 사이트에서 프로젝트를 구성하고, 내려 받으면 됩니다.

참고로 자프링 실습때는 스프링 버전을 2.7.X 대역으로 만들었는데,
이제 Spring 진영에서 2.7 버전을 공식적으로 EOL 시켰기 때문에 Spring Initializr에서 생성할 수가 없어요.

그래서 3.1.6 버전으로 프로젝트 시작해요.



초기 종속성은 위의 것들을 사용해서 실습해 보려고 해요.
참고로 Lombok의 경우 Kotlin(코틀린)에서는 필요 없어요.

이제 생성을 눌러 프로젝트를 만들어 줄게요.


위와 같이 만들었을 때, build.gradle 상에 문제가 발생할 수 있어요.
해당 내용은 이 곳에 정리해 두었어요.

 

[Spring Boot 3.0] Could not resolve org.springframework.boot:spring-boot-gradle-plugin

스프링 부트 3 : 자바 백엔드 개발 입문 COUPANG www.coupang.com "이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다." 👷‍♂️ 작업 중인 내용 Spring Boot(스프링

junyharang.tistory.com

 

build.bradle

반응형
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-graphql'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework:spring-webflux'
    testImplementation 'org.springframework.graphql:spring-graphql-test'
}



Spring for GraphQLJPA 환경에서 쉽게 사용할 수 있도록 지원해주고 있다고 해요.
그리고, WebFlux 환경에서도 사용할 수 있어요.

주니는 Web MVC 기반으로 실습해 볼거에요.

최초 프로젝트 Root(최상위) Directory(디렉터리) 안에 아래와 같이 파일을 만들어 주어야 해요.

 

 

.graphqlconfig

{
  "name": "Graphql-Example",
  "schemaPath": "src/main/resources/graphql/*.graphqls",
  "extensions": {}
}

 

위와 같이 GraphQL 설정 파일을 만들어 주었어요.
이 파일은 이 전에 공부했었던 GraphQL Schema(스키마) 및 관련 설정 정보를 정의하는데 사용하는 파일이에요.

즉, GraphQL 스키마의 위치 및 프로젝트에 대한 추가적인 설정을 하는데 이용되는 것이에요.

그럼 속성들을 알아볼게요.

∙ name: GraphQL 설정 이름으로 위에서는 "Graphql-Example"로 지정. 이름은 일반적으로 프로젝트 또는 Application(애플리케이션)의 이름과 관련.

∙ schemaPath: GraphQL 스키마 파일 경로 지정. 이 경로는 프로젝트 안에서 GraphQL 스키마 파일 위치 지정.

∙ extensions: 추가적 설정 지정을 위한 속성이며, 필요에 따라 추가적인 GraphQL 설정을 포함시킬 수 있음.

 

이번에는 데이터베이스를 구성해 볼건데, 주니는 임베이드 H2 DBIn-Memory Mode를 사용해보려고 해요.
종속성은 프로젝트 구성 시 Spring Initalizr에서 추가했어요.

 

application.yml


위와 같이 application.yml에 등록해 주면 H2 DB를 굳이 설치하지 않아도 테스트 용으로 알맞게 사용할 수 있어요.

 

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:{DB 이름}
    username: sa
    password:


만약 application.yml 을 사용한다면 위와 같이 작성해주면 되고,

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:{DB 이름}
spring.datasource.username=sa
spring.datasource.password=


properties는 위와 같이 작성해 주면 돼요.


일단 여기까지 설정하고, 서버를 기동 시켜 보면



위와 같이 정상적으로 기동 되는 걸 확인할 수 있어요.

 

http://localhost:8080/h2-console


그럼 위와 같이 H2-console에 접근 할 수 있어요.




위와 같이 설정하고, 연결을 하면 Embedded H2 DB를 In-memory Mode로 사용할 수 있어요.

 

 

/src/main/resources/init/database


Mock Data(데이터)를 만들기 위해 위와 같이 Directory(디렉터리)를 만들어 주었어요.

/src/main/resources/init/database


그리고 위와 같이 data.sql과 schema.sql을 만들어 주었어요.


schema.sql은 말 그대로 미리 DB와 Table 구조를 정의하는 곳이고,
data.sql은 Table에 미리 넣어둘 데이터를 정의하는 곳이에요.

얄코님의 강의에서 사용된 csv File(파일) 기반으로 구조와 데이터를 넣어둘게요.

 

equipments.csv

 

schema.sql

 

data.sql




teams.csv



schema.sql



data.sql








peoples.csv

 

schema.sql

 

data.sql







roles.csv

 

schema.sql

 

data.sql







softwares.csv

 

schema.sql

 

data.sql







supplies.csv

 

schema.sql

 

data.sql

 

위와 같이 schema.sql과 data.sql을 만든 뒤 서버를 구동 시키면

select * from equipment

 

select * from people


위와 같이 H2 DBMS에 Mock 데이터가 잘 입력된 걸 확인할 수 있어요.

 

 

이제 다시 GraphQL 사용을 위한 설정을 해 볼게요.

application.yml

728x90
  graphql:
    graphiql:
      enabled: true
      printer:
        enable: true


위와 같이 내용을 추가해 주었어요.

위 내용을 분석해 보면 graphipql enable을 허용하면 localhost:8080/h2-console과 같이 localhost:8080/graphiql을 통해 Query Test가 가능한 서비스를 이용할 수 있어요.

이 전에 공부했던 Apollo의 Playground를 이용하기 위한 설정이라고 생각하면 좋을 거 같아요.

또한, 이 방법 말고, 인텔리제이의 GraphQL Plugin(플러그인)을 설치해서 인텔리제이에서도 직접 테스트가 가능하고, Postman을 이용하는 방법도 있어요.

printer enable을 허용해준 것은 jpa의 show-sql이 출력될 때, GraphQL Query 내용도 출력되게 하기 위함이에요.

 

 

 

 

🚀 코드 구현

    🔽 People

        📦 Entity

실습을 위한 Entity를 만들어 볼게요.

얄코님의 강의를 들으면서 공부했을 때, Equipment, people, team, supply를 주로 사용했는데,
주니도 이것에 초점을 맞춰 실습을 해보려 해요.

참고로 자프링 실습 편에서는 Equipment를 통해 정리를 했는데, People이 연관 관계도 있고, 좀 더 로직이 들어가야 해서 이번 코프링 편에서는 People을 가지고, 정리해 볼게요.

글에서는 People만 작성하고, 나머지는 Git Hub에서 소스코드를 봐주시면 좋을 거 같아요.

https://github.com/junyharang-coding-study/GraphQL-Study/tree/master/graphql-test

 

People.java


자바에서 만들었던 Entity는 위와 같아요.

 

 

People.kt

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/entity/People.kt


코틀린자바와 다르게 클래스 이름 뒤에 () 안에 Property(프로퍼티) 즉, 클래스의 주요 속성을 정의하는 방식으로 요소들을 입력해 주어야 해요. 이렇게 해 주면 주 생성자와 요소를 같이 만들어 주는 효과가 있기 때문이에요.

주 생성자는 클래스의 객체 생성 시 사용되는 생성자이고, 클래스의 주요 속성 즉, 프로퍼티를 정의하는 역할을 하고 있어요.

코틀린프로퍼티는 클래스의 필드 또는 멤버 변수를 의미해요.
이는 클래스 내부의 데이터를 나타내는 변수로 클래스의 State(상태)를 저장해요.
주 생성자를 통해 정의된 프로퍼티들은 클래스의 객체가 생성될 때, 초기화 되고, 이를 통해 객체가 어떤 상태를 갖는지 알 수 있어요.

참고로 자바와 다르게 변수명 앞에 var라는 키워드를 붙혔는데, 이 것은 변경 가능(Mutable) 프로퍼티라는 뜻이에요.
val로 정의해 주면 읽기 전용(read-only) 프로퍼티변경이 불 가능한 상수 변수로 선언한다는 의미가 돼요.

Entity 속성들을 var로 한 이유는 RequestDTO 내용을 추후 toEntity()를 이용하여 Entity로 바꿔주야 하는 등 변경이
일어나야 하기 때문이에요.

또 하나 참고해야 할 것은 peopleId 자료형 뒤에 ? 표가 붙었다는 점이에요.
이 의미는 해당 변수에 Null이 들어올 수도 있다는 이야기인데, 이렇게 해 준 이유는 밑에 DTO에서 또 언급하겠지만, PeopleCreateRequestDto 객체에서 toEntity()를 만들어 요청 객체를 Entity로 변환해주려고 할 때, peopleId는 Auth Increment를 적용했기 때문에 Null 값을 전달해 줘야 해요.

그런데, ?를 적용하지 않으면 Null을 넣어줄수가 없기 때문에 ?를 붙혀준거에요.

그리고, Sex, BooldType, Role은 EnumType으로 자료형을 만들어 주었어요.

BloodType.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/common/enumtype/BloodType.kt

 

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/entity/People.kt
kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/entity/People.kt


위 부분은 추후 Update(수정) 로직 구현 시 각각의 필드에 대해 수정할 메서드를 만들어 최대한 Entity의 Immutability
(불변성)을 확보하려고 만든 메서드에요.

 

 

 

 

 

 

        📦 Resolver

이번에는 Resolver(리졸버) Code를 만들어 볼게요.
Resolver(리졸버) REST API로 구현했다면 Controller라고 명명했을거에요.



PeopleResolver.java


먼저 위 자바 코드와 같이 저장 Handle(핸들) 메서드를 만들어 볼게요.

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt


현재 DTO와 Service 자료형이 빨간 이유는 아직 만들어 주지 않았기 때문이에요.

@MutaionMapping에 노란 줄이 가 있는 것은 해당 핸들 메서드와 Mapping(매핑) 되는 GraphQL Schma(스키마)가 없기 때문이에요.

 

PeopleResolver.java


이번에는 목록 조회를 할 수 있는 로직을 만들어 볼게요.
참고로 자바 코드에서는 RequestDTO로 받지 않고, 요소들을 각각 받아 보았는데,
코틀린에서는 RequestDTO로 모두 모아 받아보려고 해요.

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt


위와 같이 매개 변수로 두 객체를 받는데, 하나는 팀원 정보를 입력 받아 검색 처리를 위해 사용될 DTO 객체이고,
하나는 Paging 요청을 받을 DTO 객체를 받으려고 해요. 자료형 뒤에 ?는 Null이 들어올 수도 있다는 의미에요.



PeopleResolver.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt


상세 조회는 해당 인원의 ID를 받아 하나의 정보를 조회할 수 있도록 만들거에요.

 

 

PeopleResolver.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt


한 팀에는 여러 Member(멤버)가 속해 있기 때문에 team ID를 통해 소속 팀원 조회를 위한 로직도 만들어 보려고 해요.
여기서도 자프링과 다른 것은 Paging 처리를 위한 요청을 코프링에서는 DTO로 묶어서 받는다는 것이고,
Client(클라이언트)에서 해당 내용은 조회를 안해도 될 수 있게 ?로 Null이 들어올 수 있음을 정의해 주었어요.




PeopleResolver.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt


자프링에서는 RequestDTO에 peopleId를 함께 담아 전달하게 해 주었는데,
코프링에서는 둘이 분리 시켜 전달하도록 해 주었어요.

 

 

 

PeopleResolver.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt


삭제 로직은 위와 같이 만들어 주었어요.
팀원의 Id를 받아 Database(데이터베이스)에 해당 인원이 존재하는지 찾고, 있으면 삭제하고, 없으면 Error를 내보내 줄거에요.

이제 리졸버를 다 만들었으니 빨간 색으로 Error가 나는 부분들을 해결해 볼게요.

 

 

 

 

 

 

 

        📦 DTO

 

 

PeopleRequestDto.java
kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/dto/request/PeopleCreateRequestDto.kt


자프링과 다르게 코프링에서는 위와 같이 toEntity()를 CreateRequestDto에 만들어 주었어요.
위 Entity를 만들 때 언급했던 것처럼 toEntity()DTO를 Entity로 변환해주는 메서드로 위와 같이 People 객체를 생성자를 통해 만들 때, peopleId를 무조건 전달해 주어야 하는데, Auth Increment를 적용했기 때문에 Null을 전달해 준 것이에요. 근데 Entity 프로퍼티에 ?가 안 붙어 있음 Null을 넣어줄 수 없기 때문에 위와 같이 한 것이에요.

그리고, RequestDto는 var 대신 val을 입력하여 수정을 불가하게 만들었는데, 클라이언트에서 요청이 올 때, 딱 한번 값이 입력되고, 이 Dto 내에 어떤 것들도 값이 변경되어야 할 이유가 없기 때문이에요.

 

 

PeopleSearchRequestDto.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/dto/request/PeopleSearchRequestDto.kt


검색을 위한 Dto는 위와 같이 검색이 가능할 수 있도록 선언해 주었어요.
자프링에서는 멤버 Id와 팀 Id도 받았었는데, 리졸버에서 팀 조회는 따로 뺐고, 멤버 Id는 상세 조회를 통해 할 수 있기 때문에 위와 같이 하기로 하였어요.

그리고 모두 ? 표가 붙어 있는걸 볼 수 있어요.
검색을 위해 모든 값을 다 입력할 필요가 없고, 어떤 걸 검색할지 알 수 없기 때문이에요.

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/common/constant/PageRequestDto.kt


위 DTO는 클라이언트가 페이징 처리를 원할 때, 보낼 DTO 객체에요.

프로퍼티를 먼저 보면 currentPage는 현재 페이지를 나타내는 정수 값 또는 Null을 받을 변수에요.
perPagesize는 페이지 당 요소의 개수를 나태는 정수 값 또는 Null을 담을 변수에요.
odrderBy는 ID를 기준으로 내림 차순할 것인지에 대한 여부를 담을 변수인데, ture가 담기면 내림 차순으로 정렬하고, False나, Null이 담기면 정렬 하지 않아요.

8 ~ 14번째 줄 getOffset()은 현재 페이지와 페이지당 항목 수를 이용하여 페이지의 시작 위치(offset)을 계산하여 반환하는 메서드에요.
만약 currentPage 또는 perPageSize가 Null이면 0을 반환해요. 그렇지 않으면 현재 페이지 위치 값에 1을 빼고, 그 결과를 한 페이지 당 출력할 요소 개수와 곱한 값을 반환해요.

이렇게 하는 이유는 페이징 처리에서 현재 페이지의 시작 위치를 계산하기 위한 연산인데, 데이터베이스의 페이징 Query에서 특정 페이지의 결과를 가져오기 위한 계산법이에요.


페이징 처리에서 각 페이지는 일정한 개수의 항목을 포함하고 있어요.
현재 페이지의 시작 위치는 (현재 페이지 번호 - 1) * 페이지당 항목 수로 계산 돼요. 이 계산을 통해 해당 페이지의 첫 번째 항목의 위치를 알 수 있기 때문이에요.

예를 들어 페이지당 10개의 항목이 있다고 했을 때,

1페이지의 시작 위치는 (1 - 1) * 10 = 0 이에요.
2페이지의 시작 위치는 (2 - 1) * 10 = 10 이에요.
3페이지의 시작 위치는 (3 - 1) * 10 = 20 이에요.

이런 식으로 각 페이지의 시작 위치를 계싼하여 특정 페이지의 결과를 가져오기 위한 공식이에요.
페이지 당 항목 수와 현재 페이지 번호가 주어졌을 때, 시작 위치를 계산하는 연산은 페이징 처리에서 일반적으로 많이 사용하고 있어요.

16 ~ 22번째 줄 getLimist()은 페이지 당 표시할 요소의 개수를 반환하는 메서드에요.
perPageSize가 Null이면 0을 반환해요. 그렇지 않으면 PerPageSize 값을 그대로 반환해요.

24 ~ 35번째 줄 getOrderBy()는 정렬 방식을 반환하는 메서드로 Null이거나, False이면 False를 반환하고, true일 때만 true를 반환해요.


 

 

PeopleUpdateRequestDto.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/dto/request/PeopleUpdateRequestDto.kt


UpdateRequestDto도 자프링과 다르게 peopleId를 받지 않는걸로 수정했는데, 그 이유는 ID 값은 변경되지 않는 로직으로 처리하고 싶었기 때문이에요.

물론 성, 이름, 성별, 혈액형, 출신지는 변하지 않는 불변 정보겠지만, 연습을 위해 수정이 가능하게 로직을 짜려고 해요.

수정 역시 어떤 값을 수정하고자 클라이언트가 값을 보낼지 모르기 때문에 모든 프로퍼티에 ?를 붙혀주었어요.

 

PeopleResponseDto.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/dto/response/PeopleResponseDto.kt


자프링에서 Entity를 ResponseDTO를 바꿀 때, Builder Pattern을 사용하여 바꾸기 위해 @Builder를 사용했지만,
코틀린에서는 Lombok을 사용할 수 없기 때문peopleToDto()만들어서 처리해 주려고 해요.

ResponseDTO에서는 프로퍼티에 private을 붙혀놨는데, 이는 외부에서 읽을 수는 있으나, 수정할 수 없게 만들기 위함이에요.

ResponseDTO는 Entity를 ResponseDTO로 값을 넣는 행위 외에는 변경되는 일이 없기 때문이고, 내부 메서드 peopleToDto를 통해서는 변경이 가능하게 만들어 준 것이에요.

그리고, companion object라는 키워드가 있고, 이 부분에 peopleToDto()가 묶여 있는 걸 볼 수 있어요.
companion object{}코틀린에서 클래스 내부에 선언되어 클래스와 연결된 Static(정적) 멤버를 나타내기 위해 사용해요.
따라서 이 객체에 속한 멤버는 클래스 인스턴스 없이도 호출하게 하기 위해 사용한 것이고, 자바로 치면 아래와 같이 만든거에요.

public static PeopleResponseDto peopleToDto(People people) {}


그리고 peopleToDto() 안에 프로퍼티 중 !!가 붙은 프로퍼티가 있는 걸 알 수 있어요.
이는 nullable Type의 변수를 non-nullable로 강제 Casting(캐스팅)하는 연산자에요.
이 연산자는 변수가 Null이 아님을 개발자가 확신할 때만 사용해야 해요. 만약 peopleId, teamId가 Null이 될 가능성이 있다면 안전한 Null Check나, 기타 방법을 고려해야 해요.

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/model/entity/People.kt

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/team/model/entity/Team.kt


Entity의 두 프로퍼티는 ?가 붙어있는 걸 알 수 있어요.
이유는 Entity 부분에서 이야기 한 것과 동일한 이유에요.

이렇게 두 프로퍼티에 ?가 붙어있기 때문에 DTO에서 그냥 선언할 수는 없어요.
그런데 ResponseDTO는 Service Layer에서 데이터 베이스에서 값을 찾은 뒤 Null Check를 하고, 정상이면 값을 넣어 반환하는 단계를 거치기 때문에 Null이 아님을 개발자인 주니는 확신할 수 있어요.

그렇기 때문에 위와 같이 !!를 붙혀준 것이에요.

위 ResponseDTO는 Entity 특정 필드들을 선택적으로 노출하고, companion objectFactory Method를 사용하여 Entity를 Dto로 바꾸는 역할 등을 수행하기 위한 클래스에요.

 

 

 

 

 

        📦 Repository (JPA)

PeopleRepository.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/repository/PeopleRepository.kt


JPA Repository는 위와 같이 만들어 주었어요.

 

 

 

 

 

        📦 Repository (QueryDSL)

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/repository/PeopleQueryDslRepository.kt

 

 

PeopleQueryDslRepository.java


자프링에서는 위와 같이 검색과 페이징 처리를 할 수 있는 Dynamic Query 처리를 위한 QueryDSL을 사용하였는데,
코프링에서는 보다 정확한 페이징 처리를 위해 Custom 하게 변경한 부분이 있어요.

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/repository/PeopleQueryDslRepositoryImpl.kt


참고로 QueryDSL 사용 시 15번째 줄에 people이 인식이 안 되는 걸 알 수 있는데,
이는 일반 Entity객체가 아닌 Q Entity 객체를 인식 시켜줘야 하는데, 인식하지 못하기 때문이에요.



이럴 때는 위와 같이 Gradle를 이용해 Compiler를 돌려 Compile 해 주면 Q Entity가 생기게 할 수 있어요.

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/repository/PeopleQueryDslRepositoryImpl.kt


이제 정삭적으로 인식 되었고, 6번째 줄에 QEntity가 import 된걸 확인할 수 있어요.

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/repository/PeopleQueryDslRepositoryImpl.kt


19 ~ 25번째 줄 내용은 아래에서 이야기 해 보도록 하고, 일단 위 내용을 분석해 볼게요.


최초 @Repository로 Spring에서 해당 클래스를 데이터 접근 계층의 빈으로 등록해 주며, 이것은 Repository 객체야 라고 알려준 것이에요.

jpaQueryFactory는 QueryDSL을 사용하여 JPA Query를 작성하기 위한 Factory에요.
이를 사용하여 Query를 작성할 수 있어요.

27 ~ 29번째 줄에 pageRequestDTO에 orderBy 프로퍼티 값이 False이 아닌지 확인하고, True라면 peopleID를 기준으로 내림차순으로 정렬될 수 있게 처리한 부분이에요.

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/common/QueryDslSupportUtil.kt


QueryDslRepository에서 사용한 queryDslPagingProcessing()은 위와 같이 정의 되어 있어요.

@ComponentSpring에서 해당 클래스를 Component Scan을 통해 Bean으로 등록하게 하기 위해 선언해 주었어요.

queryDslPagingProcessing()는 JPAQuery와 PageRequestDto를 매개 변수로 받아 페이징 처리를 하고, 반환하는 메서드에요.

13번째 줄에서 jpaQuery.stream().count()의 값이 2보다 크거나, 같은지를 확인하는데, 2보다 작다면 페이징 처리할 이유가 없기 때문이에요.

크거나 같다면 offset과 limit을 적용하여 데이터베이스에서 페이징된 결과를 조회할 수 있도록 처리해 주었어요.

만약 작다면 첫번째 단일 항목만 리스트에 담아 반환되도록 처리해준 부분이에요.


 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/repository/PeopleQueryDslRepositoryImpl.kt

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/repository/PeopleQueryDslRepositoryImpl.kt


24 ~ 30번째 내용은 공통 되는 부분이 많아 대표적으로 다른 25, 26번째 내용에 대해 분석해 볼게요.

최초 eqFirstName()은 QueryDSL에서 사용되는 BooleanExpression 객체를 반환하는데, 매개 변수로 받은 firstName 문자열이 비어있거나, Null이면 Null을 반환하고, 그렇지 않으면 people.firstName.eq(firstName) 의 처리 결과를 반환하게 돼요.

좀 더 자세히 이야기 해 보자면 51번째 줄에서 firstName 문자열이 비어있거나, Null이 아닌지를 확인하는 StringUtils.hasText(Spring Framework Utility Method)를 사용하여 확인하고, Null이 아니라면 people.firstName.eq(firstName)을 통해 인자로 전달된 문자열 firstName과 서로 같은지를 확인한 결과 BooleanExpresstion 객체를 반환하는 것이에요.

6번째 줄에는 StringUtils.hasText를 통해 확인하지 않았는데, 이는 Enum Type이라 문자열 처럼 처리할 수 없었기 때문이에요.

 

 

 

 

 

        📦 Service

PeopleService.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleService.kt

 

 

 

PeopleServiceImpl.java
kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


코프링에선 자프링보다 코드 수를 엄청 줄인걸 알 수 있어요.
최초 22번째 줄에서 입력된 Team이 존재하는지 알기 위해 DTO에 담긴 teamID를 통해 데이터베이스를 조회하고, 그 결과 값이 없으면 존재 하지 않는다고 응답하게 해 두었어요.

해당 팀이 존재한다면 소속할 팀이 있다는 것이니 데이터베이스에 저장해주고, 저장한 뒤 반환된 ID 값을 saveAsPeopleId 변수에 담아 주었어요.

그 값이 Null이면 저장이 제대로 안된 것이니 저장이 안 되었다고 응답을 보내고, Null이 아니면 제대로 저장이 된 것이기 때문에 저장이 완료된 결과를 응답으로 보내게 처리해 주었어요.


PeopleServiceImpl.java

 

PeopleServiceImpl.java

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


역시 자프링보다 코드를 많이 줄인걸 알 수 있는데, 그 비법은 바로 따로 메서드를 빼주려고 하기 때문이에요.

여기선 코프링만의 when 절을 사용해 보았어요.
39번째 줄에 pageRequestDto의 currentPage와 perPageSize가 Null인지를 확인하고, Null이 아니면 40번째 when 줄을 다시 타게 돼요.

이 때는 peopleSearchRequestDto가 Null인지 확인하는데, 아니라면 processingParameterNotNull()로 두 객체를 모두 매개 변수로 보내게 처리해 주었고, Null이라면 processingParameterPagingNotNull()로 PageRequestDto 객체만 보내도록 처리했어요.

이렇게 한 것은 SeacrchRequestDto가 Null이 아니면 검색 로직을 타게 하기 위함이고, 해당 객체가 Null인데, PageRequestDto가 Null이 아닌 경우는 전체 조회를 하고, 페이징 처리만 하게 하기 위함이에요.

만약 이 두 객체 모두 Null이라면 46번째 줄을 타게 되고, processingParameterNull()을 호출하여 연산된 결과를 통해 해당 결과가 비어 있는지 확인하고, 비어 있다면 값을 찾지 못했다는 응답을 보내게 돼고, 아니라면 정상 처리 되었다는 내용을 반환하게 해 주었어요.


kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


processingParameterNotNull()은 이렇게 두 개의 매개 변수를 QueryDsl RepositoryfindBySearchAndPaging()으로 보내 연산을 처리하고, 찾아온 요소를 담은 List 변수를 가지고, 비어 있다면 값을 못 찾은 것이니 값을 못 찾았다는 내용을 응답해 주게 해 주고, 비어 있지 않다면 정상 처리 된 것이니 해당 내용을 ResponseDTO로 바꿔 응답하게 해 주었어요.

그리고, Pagination 생성자의 요소의 개수와 해당 테이블의 모든 요소 개수 그리고, 정렬 여부를 반환하는 내용을 담은 객체를 반환하게 해 주었어요.

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


processingParameterPagingNotNull()은 검색은 하지 않고, 페이징 처리만 하고자 할 때, 처리를 위한 메서드에요.
위와 처리 로직은 동일하지만, QueryDsl Repository findBySearchAndPaging()에 검색 관련 Dto는 Null을 보내는 걸 알 수 있어요.

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


processingParameterNull()은 People에 대해 전체 조회를 하고는 싶은데 그 어떤 값도 전달하지 않았을 때 처리하기 위한 메서드에요.

JPA RepositoryfindAll()을 호출하여 전체 요소를 다 조회한 뒤 mapNotNull로 반복하면서 Null이 없는지를 확인하고, Null이 아닌 요소를 ResponseDTO로 변환하여 List에 다시 저장하는 과정을 수행하고, 그 List를 반환하게 해 주었어요.


kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


위의 로직은 teamId로 멤버를 검색하기 위한 로직이에요.
위와 동일한 로직인데, 위의 내용을 처리하기 위해 ProcessingParameterNotNull()을 수정해 주었어요.

해당 내용은 GitHub에서 주니가 작성한 코드를 확인해 주시면 좋을 거 같아요.

 

GitHub - junyharang-coding-study/GraphQL-Study: GraphQL을 공부하고, 실습한 코드에요 😀

GraphQL을 공부하고, 실습한 코드에요 😀. Contribute to junyharang-coding-study/GraphQL-Study development by creating an account on GitHub.

github.com

 

PeopleServiceImpl.java
kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


위 코드는 상세 조회 로직으로 peopleId를 받게 되면 JPA를 이용하여 해당 ID를 이용해 데이터베이스에서 검색을 하고,
값이 있으면 정상 처리했다고 응답하고, 아니면 값을 못 찾았다고 에러를 응답하게 처리해 준 부분이에요.





kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


위 코드는 수정을 위한 로직이에요.
최초 멤버 ID를 이용해 데이터베이스에 값을 조회하고, 해당 ID를 가진 개체의 요소를 가져오게 돼요.

그 개체가 비어 있다면 수정할 대상을 찾지 못했다는 에러 응답을 보내주고, 비어 있지 않다면 chekUpdateRequest()에 DTO와 데이터베이스에서 찾은 개체가 Optional로 감싸져 있기 때문에 get()을 통해 Entity 객체로 변경해서 매개 변수로 전달해 주었어요.

그런 뒤 정상 처리가 되었다는 응답을 주면서 해당 인원의 ID값을 함께 반환해 주었어요.

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


checkUpdateRequest()는 요청 DTO에 담겨 있는 값들만 변경하기 위해 Null 여부를 확인하고, Null이 아닌 값만 각각의 update()를 이용해서 Entity 프로퍼티에 값을 담아 주도록 했어요.

이렇게 하면 JPA 영속성 컨텍스트의 1차 캐시값과 값이 다르기 때문에 Update Query가 데이터베이스로 날아가게 될 거에요.

참고로 people.apply{}는 코틀린에서 제공하는 확장 함수 apply를 사용한 코드 블록이에요.
apply()는 수신 객체를 변경하거나, 초기화 하는데 사용되고, 수신 객체 자체를 반환하는 특징을 가지고 있어요. 즉, 코드 블록 내에서 수신 객체에 대한 작업을 수행하기 위해 이용해요.

좀 더 자세히 이야기 해보자면 people.apply{}는 people 객체에 대한 일련의 변경 작업을 수행하기 위해 선언한 것이에요.
people 객체의 상태를 peopleUpdateRequestDto에서 가져온 값으로 수정하기 위해 사용한 것이에요.

apply()를 사용하면 불 필요한 변수 할당을 피하면서 코드를 간결하게 유지할 수 있다는 장점이 있어요.

💡 참고 사항
apply란?

1. 수신 객체 설정: apply()의 수신 객체는 this 연산자로 참조.
2. 객체 변경 또는 초기화: apply() 내부에서는 수신 객체에 대한 변경 작업 수행. when 절에서는 peopleUpdateRequestDto에서 해당하는 필드가 Null이 아닌 경우에만 해당 필드 업데이트 진행.
3. 수신 객체 반환: apply()는 마지막에 수신 객체 반환. 따라서 return people.apply{}는 변경된 people 객체 반환.

 

 

 

PeopleServiceImpl.java
kotlin/com/junyharangstudy/kotlingraphqltest/api/people/service/PeopleServiceImpl.kt


위 코드는 삭제 로직 처리를 위한 코드에요.
peopleId를 인자로 받아 JPA findById()를 통해 해당 ID에 해당하는 멤버의 요소를 찾게 되고, 그 요소가 비어 있다면 삭제 대상을 못 찾았다는 에러를 반환하게 되고, 비어 있지 않다면 127번째 줄을 통해 해당 요소의 ID를 이용하여 삭제를 해 주고, 정상적으로 삭제 되었다는 것을 반환하게 처리해 주었어요.

 

 

 

 

    🔽 graphqls(Schema)

        📦 query.graphqls

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt

 

resources/graphql/graphql/query.graphqls




        📦 mutaion.graphqls

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt

 

kotlin/com/junyharangstudy/kotlingraphqltest/api/people/resolver/PeopleResolver.kt

 

resources/graphql/graphql/mutaion.graphqls





 

        📦 people.graphqls

resources/graphql/graphql/people/people.graphqls

 

resources/graphql/graphql/people/people.graphqls

 

resources/graphql/graphql/people/people.graphqls



위와 같이 
GraphQL 스키마를 구성해 주었어요.

 

 

 

    🔽 잘 만들었나? 🤔

        📦 People CRUD Test

만들어진 로직을 이용하여 CRUD Test를 진행해 볼게요.

http://localhost:8080/graphiql


위와 같이 Apollo Playgroud와 같은 서비스를 Spring For GraphQL도 제공하는데 이름이 Graphiql 같아요.
참고로 이 친구는 로직이 구현되어야 올라와요.

로직이 구현되지 않은 상태에서 application.yml에 설정만 해 주었다고 해서 URL을 입력하면 White Error만 나올거에요.
주니는 그래서 굉장히 당황했거든요.

 

http://localhost:8080/graphiql?path=/graphql

 

query findBygetPeopleListSeachAndPaging {
  getPeopleList(
    peopleSearchRequestDto: {
      # lastName: "",
      # firstName: "",
      # sex: "",
      # bloodType: "",
      # serveYears: "",
      # role: "",
      hometown: "California",
    },
    pageRequestDto: {
      currentPage: 1,
      perPageSize: 10,
      orderBy: true
    }) {
    statusCode,
    message,
    data {
      peopleId,
      teamId,
      lastName,
      firstName,
      sex,
      bloodType,
      serveYears,
      role,
      hometown
    },
    pagination {
      perPageSize,
      totalElementCount,
      totalPage,
      orderBy
    }
  }
}

 

위와 같이 페이징 처리와 검색 모두 잘 되는 걸 알 수 있어요.

http://localhost:8080/graphiql?path=/graphql


아무 조건을 주지 않아도 잘 동작하는 걸 알 수 있어요.

 

http://localhost:8080/graphiql?path=/graphql

 

query getPeopleByPeopleId {
  getPeopleByPeopleId(peopleId: 1) {
    statusCode,
    message,
    data {
      peopleId,
      teamId,
      lastName,
      firstName,
      sex,
      bloodType,
      serveYears,
      role,
      hometown
    }
  }
}


peopleId를 이용한 상세 조회도 정상적으로 동작하는 걸 확인할 수 있어요.




http://localhost:8080/graphiql?path=/graphql

query getPeopleListByTeamId {
  getPeopleListByTeamId(
    teamId: 1,
    pageRequestDto: {
      currentPage: 1,
      perPageSize: 10,
      orderBy: true
  }) {
    statusCode,
    message,
    data {
      peopleId,
      teamId,
      lastName,
      firstName,
      sex,
      bloodType,
      serveYears,
      role,
      hometown
    },
    pagination {
      perPageSize,
      totalElementCount,
      totalPage,
      orderBy
    }
  }
}


teamId를 이용한 목록 조회도 가능하게 처리해 놓았어요.


http://localhost:8080/graphiql?path=/graphql

mutation savePeople {
  savePeople(input: {
    teamId: 1,
    lastName: "H",
    firstName: "Juny",
    sex: "male",
    bloodType: "B",
    serveYears: 10,
    role: "developer",
    hometown: "Seoul"
  }) {
    statusCode,
    message,
    data
  }
}


삽입도 잘 되는 걸 알 수 있어요.



http://localhost:8080/graphiql?path=/graphql

mutation updatePeople {
  updatePeople(
    peopleId: 50,
    input: {
    teamId: 5,
    lastName: "H"
    firstName: "Junyss",
    serveYears: 20,
  }) {
    statusCode,
    message,
    data
  }
}


수정 처리도 잘 진행 되는 걸 알 수 있어요.



http://localhost:8080/graphiql?path=/graphql

 

mutation deletePeople {
  deletePeople(peopleId: 50) {
    statusCode,
    message,
    data
  }
}


삭제 까지 깔끔하게 진행 되었어요.


이렇게 모두 정상 처리 되는 걸 확인할 수 있어요.


People 외에 다른 것들은 Git Hub 소스 코드를 참고해 주세요.
참고로 각 도메인 별로 여러가지 테스트를 위해 로직이 조금 다를 수 있으니 이 점 참고해 주세요!

https://github.com/junyharang-coding-study/GraphQL-Study/tree/master/graphql-test


이제 강의에서 배웠던 실습을 진행해 볼게요.



 

 

 

 

토비의 스프링 3.1 Vol 1: 스프링의 이해와 원리

COUPANG

www.coupang.com

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

 

 

 

 

🧐 참고 자료

 

[코프링] Java 서버를 Kotlin으로

👏 Java를 Kotlin으로! 지난 시간에는 코틀린에 익숙해지기 위한 테스트 코드 작성법을 따라 적어봤다. 이제 Java 프로젝트를 Kotlin으로 변경해보는 시간을 가져보자! 📗 Domain 리팩토링 📄 Book 먼저

velog.io

 

 

 

GitHub - junyharang-coding-study/GraphQL-Study: GraphQL을 공부하고, 실습한 코드에요 😀

GraphQL을 공부하고, 실습한 코드에요 😀. Contribute to junyharang-coding-study/GraphQL-Study development by creating an account on GitHub.

github.com

 

 

 

 

카카오페이 | 마음 놓고 금융하다

여기를 눌러 링크를 확인하세요.

qr.kakaopay.com

 

 

 

728x90
반응형