블로그에 쓸 기능들 정리한 깃헙입니다
https://github.com/YuSunjo/spring-boot-example
GitHub - YuSunjo/spring-boot-example: spring boot 간단한 예제 모음(블로그 용도) - s3이미지 업로드, nfs를 통
spring boot 간단한 예제 모음(블로그 용도) - s3이미지 업로드, nfs를 통한 이미지 업로드, FCM, Feign-Client, GraphQl - GitHub - YuSunjo/spring-boot-example: spring boot 간단한 예제 모음(블로그 용도) - s3이미지 업로
github.com
먼저 graphql을 사용하기에 앞서 무엇이고 왜 사용해야하는지 알아보겠습니다.
GraphQl이란?
GraphQL은 페이스북에서 개발한 API를 위한 쿼리 언어로 기존에 서버와 클라이언트 간 데이터 전달을 위해 많이 사용되는 Rest API의 단점들을 보완해줄 수 있는 기술입니다.
Rest Api의 단점
- over-fetching
- 클라이언트에서 실제로 사용되는 데이터만 불러오지 않고 사용되지 않는 데이터도 함께 불러옴으로 써 리소스의 낭비를 발생시키는 것을 의미
- under-fetching
- 클라이언트에서 데이터를 활용하기 위해 API를 요청할 때 EndPoint마다 Response 되는 값들이 정해져 있어서 필요한 데이터들을 모두 불러오기 위해 여러 개의 API를 요청하는 것을 의미
graphQL의 장점
# over-fetching 해결
query {
user(id: 1) {
# 원하는 정보만 가져온다.
id
name
age
}
}
# under-fetching 해결
query {
user(id: 1) {
# 원하는 정보만 가져온다.
id
name
age
}
board(id: 1) {
title,
content
}
}
graphQL의 단점
- 사용되는 GraphQL을 위한 스키마를 추가 작성을 해줘야 합니다.
- 자바로 구현되는 클래스 + 스키마 ⇒ 관리할게 많음
- 항상 고정된 Response만 있을 경우 불필요
GraphQl 사전지식
https://nomadcoders.co/graphql-for-beginners
node js로 되어있는 강의지만 원리는 같아서 도움이 됩니다.
- 공식문서 : https://docs.spring.io/spring-graphql/docs/1.0.0-SNAPSHOT/reference/html/
- GraphQL을 위한 스키마를 작성할 때 Rest API에 GET, POST, PUT, DELETE가 있는 것처럼 GraphQL에는 Query(GET), Mutation(POST, PUT, DELETE)이 존재합니다.
- resolver을 작성해야합니다.
- spring boot에서는 어노테이션으로 Type, query, mutation을 지정할 수 있습니다.
- 그래서 직접 스키마를 지정하는 버전, 스키마를 지정하지 않고 어노테이션으로 하는 버전 2개를 하겠습니다.
- 밑에 예제는 jpa + querydsl로 하는데 이 설정은 따로 하지 않습니다.
dependency 추가 (gradle)
// graphql
implementation 'com.graphql-java:graphql-spring-boot-starter:5.0.2'
implementation 'com.graphql-java:graphql-java-tools:5.2.4'
// graphql gui
implementation 'io.leangen.graphql:graphql-spqr-spring-boot-starter:0.0.4'
application.yml
graphql:
servlet:
enabled: true
mapping: /graphql
# 위에 spqr 추가해주면 설정 가능
spqr:
gui:
enabled: true
스키마 작성하는 예제
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class User extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String age;
user.graphqls
type User {
id: Int!,
name: String!,
age: String!
}
type Query {
getUser(id: Int!): User!
}
type Mutation {
createUser(createUserRequest: CreateUserRequest!): User!,
}
input CreateUserRequest {
name: String!,
age: String!
}
- 스키마로 인식되는 파일은 .graphqls에 해당하는 확장자 ⇒ user.graphqls, board.graphqls …
- 컬럼명 : 타입 으로 작성
- ! 는 null 값을 허용하는지? 체크해줍니다.
@Component
@RequiredArgsConstructor
public class UserMutation implements GraphQLMutationResolver {
private final UserRepository userRepository;
public UserInfoResponse createUser(CreateUserRequest request) {
User user = userRepository.save(request.toEntity());
return UserInfoResponse.of(user);
}
}
@Component
@RequiredArgsConstructor
public class UserQuery implements GraphQLQueryResolver {
private final UserRepository userRepository;
public UserInfoResponse getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("존재하지 않는 유저입니다."));
return UserInfoResponse.of(user);
}
}
- GraphQLQueryResolver, GraphQLMutationResolver을 통해서 resolver을 작성해줍니다.
직접 통신을 해서 결과값 받아오기 (.http 사용, gui 사용)
graphql.http (.http를 사용해서 api 통신을 할 수 있습니다.)
###
POST {{backend_api}}/graphql
Content-Type: application/graphql
mutation {
createUser(createUserRequest: {
name: "hello",
age: "10"
}
) {
id
name
age
}
}
### 결과값
{
"data": {
"createUser": {
"id": 1,
"name": "hello",
"age": "10"
}
}
}
POST {{backend_api}}/graphql
Content-Type: application/graphql
query {
getUser(id: 1) {
id
name
age
}
}
### 결과값
{
"data": {
"getUser": {
"id": 1,
"name": "hello",
"age": "10"
}
}
}
gui 사용
{{주소}}/gui에 들어가면
gui 화면이 나옵니다.
결과값
스키마 작성하지 않고 작성하는 예제
- graphQLType으로 타입을 지정
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@GraphQLType
public class User extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String age;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private final List<Board> boardList = new ArrayList<>();
public User(String name, String age) {
this.name = name;
this.age = age;
}
}
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@GraphQLType
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
public Board(String title, String content, User user) {
this.title = title;
this.content = content;
this.user = user;
}
}
- @GraphQlApi을 명시해주고 @GraphQLQuery, @GraphQLMutation 으로 query, mutation을 명시해줍니다.
- CreateUserReqeust, UserInfoResponse 이런것도 따로 명시안해줘도 됩니다.
@Service
@RequiredArgsConstructor
@GraphQLApi
@Transactional
public class GraphQlBoardService {
private final UserRepository userRepository;
private final BoardRepository boardRepository;
@GraphQLQuery
public BoardInfoResponse getBoard(Long id) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new NotFoundException("존재하지 않는 유저입니다."));
return BoardInfoResponse.of(board);
}
@GraphQLQuery
public List<BoardInfoResponse> findAllBoard() {
return boardRepository.findBoardAll().stream().map(BoardInfoResponse::of).collect(Collectors.toList());
// return boardRepository.findAll().stream().map(BoardInfoResponse::of).collect(Collectors.toList());
}
@GraphQLMutation
public BoardInfoResponse createBoard(CreateBoardRequest request) {
User user = userRepository.findById(request.getUserId())
.orElseThrow(() -> new NotFoundException("존재하지 않는 유저입니다."));
Board board = boardRepository.save(request.toEntity(user));
return BoardInfoResponse.of(board);
}
}
@Service
@RequiredArgsConstructor
@GraphQLApi
@Transactional
public class GraphQLUserService {
private final UserRepository userRepository;
@GraphQLQuery
public UserInfoResponse getUser(Long id) {
User user = userRepository.findUserById(id)
.orElseThrow(() -> new NotFoundException("존재하지 않는 유저입니다."));
return UserInfoResponse.of(user);
}
@GraphQLQuery
public List<UserInfoResponse> findAllUser() {
return userRepository.findAllUser().stream().map(UserInfoResponse::of).collect(Collectors.toList());
}
@GraphQLMutation
public UserInfoResponse createUser(CreateUserRequest request) {
User user = userRepository.save(request.toEntity());
return UserInfoResponse.of(user);
}
}
gui 결과
메서드 이름은 getBoard 인데 docs는 왜 board 라고 뜨는지 아시는 분은 좀 알려주세요 ㅠㅠ
- board 생성하기
- user의 board들 가져오기
- 모든 유저 불러오기
'spring boot > 기술 적용' 카테고리의 다른 글
converter를 만들지 않고 jpa mysql에서 json 사용하기 (0) | 2022.06.28 |
---|---|
fetchJoin과 pagination을 같이 사용할 때 (0) | 2022.06.24 |
MultipleBagFetchException 발생시 - fetchJoin 여러개 썼을 때 (0) | 2022.06.24 |
@Transactional의 정의, 같은 클래스에서 호출, 예외처리 (0) | 2022.06.22 |
JPA - pageable을 이용한 paging (0) | 2021.10.31 |