일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- graphql
- 자바
- TCP
- Scanner
- Mongoose
- 프로그래머스
- nodejs
- 조건문
- bufferdreader
- redis
- Spring
- Apollo
- 스프링부트
- java11
- 스프링
- ai
- 서버
- mongodb
- HTTP
- puppeteer
- restapi
- Android
- eof
- 백준알고리즘
- Docker
- LangChain
- mysql
- k8s
- MapReduce
- java
- Today
- Total
자라나라 개발머리
2. GraphQL 정리 / 스키마 분석 / resolvers 구현 본문
지난 시간엔 node.js(express)와 mongoDB를 통해 서버를 구축하고, DB와 연결을 했다.
이를 기반으로, GrapeQL API를 구현하려고 한다.
개요 게시글에서 말했듯이, GrapeQL은 API 포맷 종류 중 하나고,
REST API와 양극단의 장단점을 가지고 있다.
GraphQL(요청 복잡, 데이터 단순) <-> REST API(요청 단순, 데이터 복잡)
그래서 GraphQL은 요청이 일정하지 않고 필요한 데이터 구성이 많을수록 이점이 있는 API포맷이다.
오늘은 GraphQL를 더 세세하게 정리해보고, 주어진 스키마를 분석하고, resolvers 까지 구현해볼 것이다.
GrapeQL 정리
GraphQL의 장점
- 데이터 전송량 감소
원하는 데이터만 받아올 수 있기 때문에 효율적으로 데이터 이동 비용을 줄일 수 있다.
- 요청량 감소
URL의 Depth가 다르더라도 한 번에 요청이 가능하기 때문에, 요청횟수를 줄일 수 있다.
- 하나의 URL에서 POST로 모든 요청 가능
내 서버 URL주소/graphql 로 모든 요청을 통일한다. 메소드도 POST로 통일한다.
GraphQL 구조
크게는 스키마, 리졸버로 나뉜다.
스키마는 API 구조를 정의하는 역할을 하고,
리졸버는 그 API를 구현하는 역할을 한다.
따라서 스키마만 보면 API가 어떻게 구성되어있는지 바로 확인이 가능하고,
사용자는 스키마 구조에 맞춰서 요청을 보내야 응답을 받을 수 있다.
스키마 분석
오늘 내가 분석할 스키마의 구조는 아래와 같이 생겼다.
type Query {
getExchangeRate(src:String!, tgt:String!): ExchangeInfo
}
type Mutation {
postExchangeRate(info: InputUpdateExchangeInfo): ExchangeInfo
deleteExchangeRate(info: InputDeleteExchangeInfo): ExchangeInfo
}
input InputUpdateExchangeInfo {
...
}
input InputDeleteExchangeInfo {
...
}
type ExchangeInfo @key(fields: "src, tgt") {
...
}
1) Query, Mutation
스키마에 필수로 있어야하는 요소다.
쿼리와 뮤테이션은 사용자가 쓰게 될 기능을 이름,매개변수,반환값으로 정의한다.
쿼리와 뮤테이션의 차이는, 쿼리엔 REST의 GET과 같은 기능을 구현하고 뮤테이션은 POST, DELETE, PUT 기능을 구현한다.
쿼리와 뮤테이션은 동작 방식에 차이가 있다. 쿼리는 병렬적으로, 뮤테이션은 직렬적으로 처리된다.
데이터의 변경이 없는 GET은 여러개가 동시에 처리되도 괜찮지만, 다른 메소드들은 데이터의 변경이 있기 때문에 직렬로 처리해야하는 것이 아닐까 생각한다.
2) 객체 타입
GraphQL에서는 Int, Float, String, Boolean, ID 자료형을 제공한다. 이를 이용해 사용자가 필요한 객체를 생성한다.
이를 객체 타입이라고 하고 코드에서는 input으로 시작하는 두 개의 객체와 type ExchangeInfo가 있다.
둘 다 똑같은 객체인데 type와 input이 나뉘는 이유는,type으로 시작하는 객체는 출력 객체, input은 입력 객체이다.
객체를 나눈 이유에 대한 설명은 아래 블로그에 자세하게 나와있다!
https://velog.io/@cadenzah/graphql-input-type
GraphQL에서 `input` 타입을 왜 사용할까?
GraphQL의 'Object' 타입과 'input' 타입의 차이
velog.io
따라서, 위 스키마는 하나의 출력 객체와 두 개의 입력 객체를 가지고 있다.
또, 위 구조에서 특이해 보이는 점이 하나 있는데, 바로 아래 코드다.
type ExchangeInfo @key(fields: "src, tgt") {
...
}
@key(fields: "src, tgt") 가 적혀있다.
@key directive는 entity(여기선 fields)의 primary key를 정의한다.
@key는 federation 을 사용할 때 쓰이는데, federation이란 GraphQL 여러 작은 서비스를 하나로 통합해주는 것을 말한다. 이때, @key는 SQL 처럼 primary key와 스키마 간의 관계를 표현할 때 사용한다.
스키마의 구조에 대해 설명했고, 이제 상세 코드를 보겠다.
type Query {
"환율조회"
getExchangeRate(src:String!, tgt:String!): ExchangeInfo
}
type Mutation {
"환율등록, src, tgt, date에 대해서 upsert"
postExchangeRate(info: InputUpdateExchangeInfo): ExchangeInfo
"환율삭제, 해당일자의 해당 통화간 환율을 삭제"
deleteExchangeRate(info: InputDeleteExchangeInfo): ExchangeInfo
}
"환율업데이트정보 Input"
input InputUpdateExchangeInfo {
"소스통화, krw, usd"
src: String!
"타겟통화"
tgt: String!
"환율"
rate: Float!
"기준일, 값이 없으면, 최신일자로 등록"
date: String
}
"환율삭제 Input"
input InputDeleteExchangeInfo {
"소스통화"
src: String!
"타겟통화"
tgt: String!
"기준일"
date: String!
}
"환율정보"
type ExchangeInfo @key(fields: "src, tgt") {
"소스통화"
src: String!
"타겟통화"
tgt: String!
"환율"
rate: Float!
"기준일, 값이 없으면, 최신일자의 환율을 응답"
date: String!
}
ExchangeInfo
src, tgt, rate, date 4개의 필드, 모두 null값 허용X (!가 이를 뜻한다)
InputUpdateExchangeInfo, InputDeleteExcahngeInfo도 이를 공유한다.
resolvers 구현
위 스키마를 토대로 resolvers를 구현한다.구현하기 전, GraphQL을 구현한 프레임워크들이 다양하게 있는데, 이는 GraphQL 공식홈페이지에서 확인할 수 있다.아래 링크는 javascript 코드로 GraphQL을 제공해주는 툴/프레임워크를 모아놓은 사이트다.
https://graphql.org/code/#javascript
GraphQL Code Libraries, Tools and Services
Typetta is an open-source ORM written in TypeScript that aims to allow seamless access to data in a typed fashion to all main SQL databases (MySQL, PostgreSQL, Microsoft SQL Server, SQLLite3, CockroachDB, MariaDB, Oracle & Amazon Redshift) and also to the
graphql.org
무작정 찾아서 구현하다, Graph-yoga와 Apollo server를 혼동해서 여러 혼란을 겪으며.. 시간을 소모했다.
둘 다 experss를 내장하고 있다.
구현과정에서 이리저리 시도하다 apollo를 사용하게 되었다.
아래 링크를 주로 참고해서 구현하였다.
https://velog.io/@wjd489898/graphQL-MongoDB-%EC%8B%A4%EC%8A%B5%ED%95%98%EA%B8%B0
graphQL - MongoDB 실습하기 (feat. Apollo-server)
Yalco님의 강의를 기반으로 실습을 진행했습니다.models/index.js먼저 mongoDB에 가입하여 자신의 클러스터를 만들고 데이터베이스를 만든다. index.js만약 db가 잘 연결되었다면 MongoDB Connected 라는 메시
velog.io
시행착오
1. Error: Unknown directive "@key".
처음 코드를 실행했을 때 부터 이 오류가 있었다. @apollo/federation을 쓰면 된다길래, apollo 서버를 사용했는데 해결되지 않았다. 사실 아직도 해결하지 못했다 ..
federation에 대해서 찾아보니, GraphQL 마이크로 서비스들을 하나의 서비스로 통합해주는 기능을 한다고 한다. 거기서 겹치는 스키마와 필드들을 여러 directive로 관계를 설정해주며 사용하는 것 같다. 지금은 간단한 예제를 구현하는거라, federation을 사용하진 않는 것 같다.
여러 많은 방법을 시도해봤지만, 위 오류를 해결하지 못했다.
하지만 프로젝트 완성은 해야하니, 생각해 낸 결과가 @key를 지우는거다.
federation과 프로젝트 크기를 봤을 때 @key가 지금 당장은 필요한 부분은 아니라고 생각해서 빼게 되었다.
이 부분은 좀 더 찾아보고 포함해서 구현할 수 있게 시도해봐야겠다.
2. 리졸버 안 구현코드를 작성하면서, mongoDB, mongoose, apollo 등 여러 공식 사이트의 문서를 많이 참고했다.
이제까지 구글링 할 땐, 참고할 자료들이 많고 국내 개발자분들이 쉽게 설명해주신 자료가 많아서 공식문서를 굳이 참고하지 않았는데, 이번 프로젝트 진행하면서 참고 할 자료가 많지 않아 공식문서를 자주 보게됐다.
이런 개발 습관을 들여놓으면, 더 정확하고 효율적인 코드 개발이 가능할 것 같다.
최종코드
//index.js
import { gql } from "graphql-tag"
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import mongoose from 'mongoose';
import moment from 'moment';
// ---------- mongoDB
mongoose
.connect( 'mongodb+srv://{id}:<password>@exchangeratecluster.1uwsu64.mongodb.net/?retryWrites=true&w=majority');
.then(() => console.log('MongoDB conected'))
.catch((err) => {
console.log(err);
});
// ExchangeRate 모델 정의
const exchangeRateSchema = new mongoose.Schema({
src: String,
tgt: String,
rate: Number,
date: String
});
const ExchangeRate = mongoose.model('ExchangeRate', exchangeRateSchema);
// ---------- GraphQL
const typeDefs = gql`
type Query {
"환율조회"
getExchangeRate(src:String!, tgt:String!): ExchangeInfo
}
type Mutation {
"환율등록, src, tgt, date에 대해서 upsert"
postExchangeRate(info: InputUpdateExchangeInfo): ExchangeInfo
"환율삭제, 해당일자의 해당 통화간 환율을 삭제"
deleteExchangeRate(info: InputDeleteExchangeInfo): ExchangeInfo
}
"환율업데이트정보 Input"
input InputUpdateExchangeInfo {
"소스통화, krw, usd"
src: String!
"타겟통화"
tgt: String!
"환율"
rate: Float!
"기준일, 값이 없으면, 최신일자로 등록"
date: String
}
"환율삭제 Input"
input InputDeleteExchangeInfo {
"소스통화"
src: String!
"타겟통화"
tgt: String!
"기준일"
date: String!
}
"환율정보"
type ExchangeInfo {
"소스통화"
src: String!
"타겟통화"
tgt: String!
"환율"
rate: Float!
"기준일, 값이 없으면, 최신일자의 환율을 응답"
date: String!
}
`;
const resolvers = {
Query: {
async getExchangeRate(_, args) {
const { src, tgt } = args
const date = moment().format('YYYY-MM-DD');
const filter = { src: src, tgt: tgt, date: date };
try {
const exchangeRate = await ExchangeRate.findOne(filter);
return exchangeRate;
} catch (err) {
console.error('환율 조회 오류:', err);
}
}
},
Mutation: {
async postExchangeRate(_, args) {
const { info } = args;
const filter = { src: info.src, tgt: info.tgt, date: info.date };
const update = { rate: info.src == info.tgt ? 1.0 : info.rate };
const options = { upsert: true, new: true };
try {
const exchangeRate = await ExchangeRate.findOneAndUpdate(filter, update, options);
return exchangeRate;
} catch (err) {
console.error('환율 업데이트 오류:', err);
}
},
async deleteExchangeRate(_, args) {
const { info } = args;
const filter = { src: info.src, tgt: info.tgt, date: info.date };
try {
const deletedExchangeRate = await ExchangeRate.findOne(filter);
await ExchangeRate.deleteOne(filter);
return deletedExchangeRate;
} catch (err) {
console.error('환율 삭제 오류:', err);
}
},
}
};
const server = new ApolloServer({typeDefs, resolvers})
const { url } = await startStandaloneServer(server, {
listen: { port: 5110 },
});
server.listen().then(({url}) => {
console.log(`🚀 Server ready at ${url}`)
})
참고:
GraphQL 관련
https://docs.aws.amazon.com/ko_kr/appsync/latest/devguide/graphql-overview.html
https://www.youtube.com/watch?v=9BIXcXHsj0A&t=4267s
https://blog.doctor-cha.com/integrating-graphql-services-with-graphql-federation#key-directive
https://blog.doctor-cha.com/integrating-graphql-services-with-graphql-federation
아폴로 관련
https://www.apollographql.com/docs/federation/
https://www.apollographql.com/docs/apollo-server/schema/schema
https://github.com/apollographql/apollo-server
https://www.apollographql.com/docs/federation/entities/
https://www.youtube.com/watch?v=9BIXcXHsj0A&t=1820s
https://velog.io/@wjd489898/graphQL-MongoDB-%EC%8B%A4%EC%8A%B5%ED%95%98%EA%B8%B0
MongoDB CRUD 관련 참고자료 (mongoose)
https://www.mongodb.com/docs/manual/crud/
https://www.mongodb.com/docs/manual/tutorial/insert-documents/
'프로젝트 > 개인' 카테고리의 다른 글
3. 환경변수 세팅 / 코드 모듈화 / 테스트 (완) (0) | 2023.06.05 |
---|---|
1. node.js - MongoDB 개발 환경 세팅 / DB 연동 (0) | 2023.06.05 |
0. Node.js - MongoDB GraphQL API 서버 구현 개요 / 리서치 (0) | 2023.06.05 |
3. 스프링 부트 - 안드로이드 스튜디오 연결/Retrofit2 (완) (0) | 2023.02.25 |
2. 스프링 부트 - MySQL 연결 (JPA 사용) (0) | 2023.01.28 |