테스트를 진행하기 위해 서버 코드, 설정을 작성하도록 하겠습니다.
require('dotenv').config();
import { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';
import { ApolloServer } from 'apollo-server-express';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { typeDefs } from './typeDefs';
import { resolvers } from './resolvers/resolver';
import cors from 'cors';
import express from 'express';
import http from 'http';
import { kafkaService } from './kafka/kafka.service';
import { mongoose } from './utils/mongoose.client';
const { PORT } = process.env;
class Server {
constructor() {}
public async start(typeDefs, resolvers) {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
schema: buildSubgraphSchema([{
typeDefs,
resolvers
}]),
context: ({ req }) => {
const token = req.headers.token ? req.headers.token : null;
return { token };
},
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer })
]
});
app.use(cors());
await server.start();
server.applyMiddleware({
app,
path: '/ride-service',
cors: false
});
await new Promise(resolve => httpServer.listen({
port: PORT
}, resolve));
await kafkaService();
mongoose;
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
}
}
const server = async (typeDefs, resolvers) => await new Server().start(typeDefs, resolvers);
server(typeDefs, resolvers);
PORT=6100
REDIS_PORT=6379
MONGO_URI="mongodb://localhost:27017/CARPOOL_RIDE"
REDIS_URI="127.0.0.1"
SECRET_KEY=asw8*@1ssmxa
ride-serivce 서버 코드와 설정 코드를 작성했고, 이어서 api-gateway에서 서비스를 등록하도록 하겠습니다.
...
const {
PORT,
AUTH_SERVICE_ENDPOINT,
RIDE_SERVICE_ENDPOINT
} = process.env;
class Server {
...
public async start() {
...
const server = new ApolloServer({
gateway: new ApolloGateway({
serviceList: [
{
name: "auth-service",
url: AUTH_SERVICE_ENDPOINT
},
{
name: "ride-service",
url: RIDE_SERVICE_ENDPOINT
}
],
buildService: ({
name,
url
}) => {
return new Authentication({ url });
}
}),
...
});
...
}
}
...
위와 같이 ride-service를 apollo gateway에 등록시켜줍니다.
import { RemoteGraphQLDataSource } from "@apollo/gateway";
const {
AUTH_SERVICE_ENDPOINT,
RIDE_SERVICE_ENDPOINT
} = process.env;
class Authentication extends RemoteGraphQLDataSource {
willSendRequest({
request,
context
}) {
if(request.http.url.includes(AUTH_SERVICE_ENDPOINT)) {
...
} else if(request.http.url.includes(RIDE_SERVICE_ENDPOINT)) {
if(
["carpoolRegister",
"reserve",
"arrival",
"getCarpools",
"loadCarpool",
"loadCarpoolsByDriver",
"detailCarpool",
"modifyCurrentLocation",
"start"].includes(request.query
.split('{')[1]
.split('(')[0])
) {
request.http.headers.set('token', context.token);
}
}
}
}
export { Authentication };
그리고 subgraph로 데이터를 보내기전에 토큰이 필요한 api를 지정하여 전송하기 전 헤더에 토큰을 실어줍니다.
api-gateway, ride-service의 index.js파일을 수정했으니 subscription 서버를 생성하도록 하겠습니다.
apollo federation을 이용한 gateway는 http서버로의 역할을 합니다. 즉, query라든지 mutation은 http의 요청을 수행하는 반면 subscription은 web socket 서버로써 작동을 하기 때문에 별도의 web socket서버가 필요하게 됩니다. 그래서 별도의 subscription 서버를 생성하도록 하겠습니다.
mkdir subscription-server
cd subscription-server
mkdir src
touch src/index.js
npm init -y
필요한 라이브러리를 설치하도록 하겠습니다.
npm install @graphql-tools/merge @graphql-tools/schema apollo-server-core apollo-server-express cors dotenv express graphql graphql-redis-subscriptions ioredis nodemon subscriptions-transport-ws tsc-watch
npm install -D typescript @types/ioredis @types/express
PORT=6901
REDIS_PORT=6379
REDIS_URI="127.0.0.1"
SECRET_KEY=asw8*@1ssmxa
라이브러리를 설치했으니 subscription메시지를 구독하기 위한 redis client를 작성하도록 하겠습니다.
import { RedisPubSub } from 'graphql-redis-subscriptions';
import Redis from 'ioredis';
const {
REDIS_PORT,
REDIS_URI
} = process.env;
const redis = new Redis(
parseInt(REDIS_PORT),
REDIS_URI
);
const pubsub = new RedisPubSub({
publisher: redis,
subscriber: redis
});
export { pubsub };
기본적인 query, mutation, type을 작성하고 redis client를 이용하여 메시지를 구독하는 subscription과 resolver를 작성하도록 하겠습니다.
import { gql } from 'apollo-server-express';
const typeDefs = gql`
type Location {
description: String!,
latitude: Float!,
longitude: Float!
}
`;
export default typeDefs;
import { gql } from 'apollo-server-express';
const typeDefs = gql`
type Query {
_empty: String
}
`;
export default typeDefs;
import { gql } from 'apollo-server-express';
const typeDefs = gql`
type Mutation {
_empty: String
}
`;
export default typeDefs;
import { gql } from 'apollo-server-express';
const typeDefs = gql`
type Subscription {
viewCurrentLocation: Location!
}
`;
export default typeDefs;
import { pubsub } from '../utils/redis.client';
const resolver = {
Subscription: {
viewCurrentLocation: {
subscribe: () => pubsub.asyncIterator("VIEW_CURRENT_LOCATION"),
},
}
};
export { resolver };
require('dotenv').config();
import http from 'http';
import express from 'express';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { ApolloServer } from 'apollo-server-express';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import cors from 'cors';
import {
execute,
subscribe,
} from 'graphql';
import { resolvers } from './resolvers/resolver';
import { typeDefs } from './typeDefs/index';
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
const { PORT } = process.env;
class Server {
constructor() {}
public async start(
typeDefs,
resolvers
) {
const app = express();
const httpServer = http.createServer(app);
const subscriptionServer = SubscriptionServer.create({
schema,
execute,
subscribe
}, {
server: httpServer,
path: "/"
});
const server = new ApolloServer({
schema,
plugins: [{
async serverWillStart() {
return {
async drainServer() {
subscriptionServer.close();
}
}
}
}]
});
app.use(cors());
await server.start();
server.applyMiddleware({
app,
path: "/",
cors: false
});
httpServer.listen({ port: PORT });
console.log(`🚀 Server ready at ws://localhost:${PORT}${server.graphqlPath}`);
}
}
const server = async (typeDefs, resolvers) => await new Server().start(typeDefs, resolvers);
server(typeDefs, resolvers);
subscription server를 완성했습니다. 그러면 테스트를 진행해보도록 하겠습니다.
1) carpoolRegister
{
"_id": {
"$oid": "61d3b3ac5f767ee3d472c459"
},
"ride_info_id": "IC-ihy1yaidm-z8oy1hp2yk",
"rider_id": "50dd7588-93bc-4d5b-8cd3-e7951ee27045",
"passengers": [],
"start_time": "12-01-22",
"start_location": {
"latitude": "127.00022",
"longitude": "122.2210"
},
"dest_location": {
"latitude": "127.00022",
"longitude": "122.2210"
},
"current_location": {
"latitude": null,
"longitude": null
},
"status": "PENDING",
"cost": 0,
"car": {
"car_name": "test_car1",
"car_number": "test_car_number1",
"car_size": "test_car_size1",
"_id": {
"$oid": "61d3b3ac5f767ee3d472c45a"
}
},
"__v": 0
}
2) loadCarpool
마이페이지에서 필요한 데이터만 지정하여 fetch를 했습니다.
3) detailCarpool
4) getCarpools
5) reserve
예약된 회원 아이디가 passengers에 들어가 있는 모습을 볼 수 있습니다.
6) start
탑승객이 한 명일 경우 다음과 같은 오류메시지가 출력됩니다.
탑승객을 한 명 더 추가하게 되면 다음과 같이 status값이 BEING으로 변경됩니다.
7) arrival
도착 후 arrival api를 호출하면 status가 FINSHED로 변경됩니다.
8) modifyCurrentLocation, viewCurrentLocation
subscription api를 호출하는 모습입니다. 6901번의 서버를 화면과 같이 listening상태로 실행시키고, 6900번의 서버에서 Mutation을 호출한 결과 바로 current_location을 불러오는 모습을 볼 수 있습니다.
이로써 최종적인 ride-service 테스트까지 완료를 했습니다. 그러면 이를 이용하여 react native와 연동을 진행해보도록 하겠습니다.