Tutorial
여기 따라하면 기본적인건 해볼 수 있다.
설치
서버사이드(Java/Maven)
maven의존성
springframework에서 지원하는 java버전이 있고
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
expediagroup에서 지원하는 kotlin버전이 있다.
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-spring-server</artifactId>
<version>7.0.1</version>
</dependency>
후자가 Resolver(리졸버)만 구현하면 스키마를 자동생성해주고 kotlin 타입과 연동되는등 kotlin을 사용한다면 더 편리한 측면이 있지만, 여기서는 전자를 사용한 기준으로 서술한다.
expediagroup 아티펙트의 경우, 내 경우엔 /graphql 404문제가 해결이 안돼서 springframework를 쓰기로 했다.
추가로 조사해보니 webmvc대신 webflux로 교체해야 호환되는 이슈도 있었다(여기)
spring-boot-starter-web 대신 spring-boot-starter-webflux의존성으로 바꿔야 하는데 WebConfig.kt등 여러곳에서 코딩방식을 바꿔야 하는 걸로 보인다.
클라이언트 사이드(React/Next.js)
관련 library설치
npm install @apollo/client graphql
아폴로?
Apollo Client는 JavaScript 어플리케이션에서 GraphQL API와 통신할 수 있게 해주는 라이브러리입니다.
Facebook에서는 GraphQL을 발명했으며, Relay라는 GraphQL 클라이언트를 만들어 공개했습니다. 그러나 Apollo가 Relay보다 더 널리 사용되는 이유는
사용자 친화적: Apollo는 사용자 친화적이고, 초보자에게 친숙하며, 설정이 상대적으로 간단합니다. 문서화도 잘 되어 있어, 개발자들이 쉽게 접근하고 사용할 수 있습니다.
커뮤니티 지원: Apollo는 강력한 커뮤니티 지원을 받고 있으며, 다양한 추가 기능과 툴이 개발되고 있습니다. 또한 꾸준한 업데이트와 개선이 이루어지고 있어, 더 많은 개발자들이 Apollo를 선호하게 되었습니다.
코드
이번 문서 에서는 특정문제에 대해서 제출된 횟수를 리턴하는 예제를 해보기로 하자.
프로젝트 구성에 대해서는 여기를 보고오자.
서버사이드
Query
1. GraphQL 스키마 정의
src/main/resources/graphql/schema.graphqls 파일에 쿼리를 추가
type Query {
submissionCountByProblem(problemId: ID!): Int
}
2. 서비스 수정
SubmissionService에 getSubmissionCountByProblem매서드를 추가하여 문제별 제출 수를 가져옴
@Service
class SubmissionService(private val submissionRepository: SubmissionRepository) {
// 아래는 기존에 존재하던 매서드
fun submitProblem(userId: Long, problemId: Int, code: String): Submission {
val submission = Submission(
userId = userId,
problemId = problemId,
code = code,
status = "PENDING" // 초기 상태
)
return submissionRepository.save(submission)
}
// 아래 매서드 추가
fun getSubmissionCountByProblem(problemId: Int): Int {
return submissionRepository.countByProblemId(problemId)
}
}
3. 컨트롤러에 로직 추가
컨트롤러에 @QueryMapping 어노테이션을 사용하여 submissionCountByProblem GraphQL쿼리를 처리하는 메서드를 추가(엔드포인트 추가는 아니지만 약간 유사)
package com.sevity.problemservice.controller
import ...
@RestController
class SubmissionController(private val submissionService: SubmissionService) {
// 기존 코드
// ...
@QueryMapping
fun submissionCountByProblem(@Argument problemId: Int): Int {
return submissionService.getSubmissionCountByProblem(problemId)
}
}
Mutation
//TBD
클라이언트사이드
본격적으로 클라이언트 사이드 코딩을 하기전에
https://localhost:9993/graphiql
위 url에 접속해서 아래 처럼 날려볼 수 있다.
query {submissionCountByProblem(problemId: 13)}
그럼 결과가 다음처럼 보일 것이다.
apollo library 사용을 위해 아래 코드를 기본적으로 넣어준다.
// src/apolloClient.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://sevity.com:9993/graphql', // GraphQL 서버 URL
cache: new InMemoryCache(),
});
export default client;
그다음 _app.js를 수정해서 각 페이지에서 해당 기능을 사용할 수 있게 해준다.
import ...
import { ApolloProvider } from '@apollo/client';
import client from '../apolloClient'; // Adjust the path if necessary
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
)
}
export default MyApp;
실제 조회하는 코드를 [id].js에 넣어준다.(제출 횟수 및 관련 부분)
...
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
// 아래 스트링 방식이 후진적으로 보일 수 있는데, 간접적으로 문법검사나, 자동생성 툴이 존재한다.
// 하지만, 결국 서버측에 문자열로 graphQL을 보내야한다. 현재 graphQL의 한계점.
const GET_PROBLEM_SUBMISSION_COUNT = gql`
query GetProblemSubmissionCount($problemId: Int!) {
submissionCountByProblem(problemId: $problemId)
}
`;
const Problem = () => {
...
const { data, loading, error } = useQuery(GET_PROBLEM_SUBMISSION_COUNT, {
variables: { problemId: Number(id) },
skip: !id // id가 없는 경우 쿼리를 건너뜁니다.
});
// JavaScript의 옵셔널 체이닝(Optional Chaining)사용('?.'부분들)
const submissionCount = data?.submissionCountByProblem;
if(data)
console.log('GraphQL response:', JSON.stringify(data, null, 2));
...
return (
<div className="container">
<div className="alert alert-success mt-3">username: {username} </div>
{problem ? (
<>
<h1>{problem.title}</h1>
<p>{problem.description}</p>
<p><strong>예제 입력:</strong> {problem.exampleInput}</p>
<p><strong>예제 출력:</strong> {problem.exampleOutput}</p>
<p><strong>실제 입력:</strong> {problem.realInput}</p>
<p><strong>실제 출력:</strong> {problem.realOutput}</p>
{submissionCount && (
<p><strong>제출 횟수:</strong> {submissionCount}</p>
)}
<textarea
className="form-control"
rows="10"
value={sourceCode}
onChange={(e) => setSourceCode(e.target.value)}
></textarea>
<button className="btn btn-primary" onClick={handleSubmit}>
제출
</button>
</>
) : (
<p>Loading...</p>
)}
</div>
);
};
export default Problem;
트러블슈팅
/graphql 404
src/main/resources/graphql 폴더와 그안에 anyname.graphqls 파일을 생성안하면 /graphql 경로접근시 404뜨는 문제가 있어서 반나절 이상 소모했다 ㅠ
여기에도 기록함
expediagroup 아티펙트의 경우, 위의 해결책을 적용해도 여전히 404가 떴고, 추가로 조사해보니 webmvc대신 webflux로 교체해야 하는 이슈가 있었다(여기)
spring-boot-starter-web 대신 spring-boot-starter-webflux의존성으로 바꿔야 하는데 WebConfig.kt등 여러곳에서 코딩방식을 바꿔야 하는 걸로 보인다.
'Programming > SpringBoot' 카테고리의 다른 글
Spring에서 gRPC연동하기 (1) | 2023.10.11 |
---|---|
maven dependency (0) | 2023.10.11 |
Spring Boot에서 endpoint접근을 http에서 https로 변경하기 (0) | 2023.10.09 |
Spring Boot에서의 세션 관리 (1) | 2023.10.08 |