LifeSports Application(ReactNative & Nest.js) - 12. map-service
#1 map-service
map-service는 디바이스에서 사용될 맵 관련 서비스입니다. 예를 들어 유저가 풋살장을 검색할 경우 map-service -> wayfinding-service 통신 -> 풋살장 데이터 호출 식의 흐름을 가질 수 있습니다. 그림으로 간략히 살펴보겠습니다.
그림처럼 1) map data 요청 -> 2) map-service와 wayfinding-service 통신 -> 3) 데이터 탐색 -> 4) 결과 반환 -> 5) client에게 결과 보여주기 순으로 진행됩니다.
그러면 map-service를 만들어 보도록 하겠습니다.
#2 map-service project
nest new map-service
cd map-service
nest generate module maps
nest generate service maps
map-service와 wayfinding-service는 TCP기반의 통신을 진행합니다. rabbitmq라는 메시지 브로커가 존재하지만 다양한 방법으로 시도한 결과 큐 하나만으로 request와 response가 동시에 이루어지지 않아 TCP기반의 통신을 채택했습니다.
우선 map-service부터 구현을 진행하겠습니다.
- ./src/app.controller.ts
import { Controller, Get, HttpStatus, Param, Query } from "@nestjs/common";
import { statusConstants } from "./constants/status.constant";
import { MapsService } from "./maps/maps.service";
@Controller("map-service")
export class AppController {
constructor(private readonly mapsService: MapsService) {}
@Get('/')
public async getAll(): Promise<any> {
try {
const result: any = await this.mapsService.getAll();
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: "Get list of map"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@Get('map/:_id')
public async getOne(@Param('_id') _id: string): Promise<any> {
try {
const result: any = await this.mapsService.getOne(_id);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Error message: " + result.message,
});
}
return await Object.assign({
status: HttpStatus.OK,
payload: Builder(ResponseMaps)._id(result.payload._id)
.ycode(result.payload.ycode)
.type_nm(result.payload.type_nm)
.gu_nm(result.payload.gu_nm)
.parking_lot(result.payload.parking_lot)
.bigo(result.payload.bigo)
.xcode(result.payload.xcode)
.tel(result.payload.tel)
.addr(result.payload.addr)
.in_out(result.payload.in_out)
.home_page(result.payload.home_page)
.edu_yn(result.payload.edu_yn)
.nm(result.payload.nm)
.build(),
message: "Get data by _id"
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Error Message: " + err
});
}
}
@Get('maps/list-type-nm/:type_nm')
public async getListByTypeNm(@Param('type_nm') type_nm: string): Promise<any> {
try {
const result: any = await this.mapsService.getListByTypeNm(type_nm);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: "Get list of map"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@Get('maps/list-gu-nm/:gu_nm')
public async getListByGuNm(@Param('gu_nm') gu_nm: string) : Promise<any> {
try {
const result: any = await this.mapsService.getListByGuNm(gu_nm);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: "Get list of map"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@Get('maps/list-gu-type')
public async getListGuNmAndTypeNm(@Query() query): Promise<any> {
try {
const result: any = await this.mapsService.getListGuNmAndTypeNm(query.gu_nm, query.type_nm);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: "Get list of map"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
}
컨트롤러의 오류 코드들을 제거해나가면서 컨트롤러를 완성시키도록 하겠습니다.
npm install --save class-validator class-transformer builder-pattern
- ./src/dto/map.dto.ts
import { IsNumber, IsString } from "class-validator";
export class MapsDto {
@IsString()
_id: string;
@IsNumber()
ycode: Number;
@IsString()
type_nm: string;
@IsString()
gu_nm: string;
@IsString()
parking_lot: string;
@IsString()
bigo: string;
@IsNumber()
xcode: Number;
@IsString()
tel: string;
@IsString()
addr: string;
@IsString()
in_out: string;
@IsString()
home_page: string;
@IsString()
edu_yn: string;
@IsString()
nm: string;
}
- ./src/vo/response.maps.ts
export class ResponseMaps {
_id: string;
ycode: Number;
type_nm: string;
gu_nm: string;
parking_lot: string;
bigo: string;
xcode: Number;
tel: string;
addr: string;
in_out: string;
home_page: string;
edu_yn: string;
nm: string;
}
- ./src/constants/status.contants.ts
export const statusConstants = {
SUCCESS: "SUCCESS",
ERROR: "ERROR",
};
컨트롤러에서 대부분의 오류 코드들을 제거했으니 wayfinding-service와 rabbitmq를 이용하여 통신을 진행하도록 하겠습니다.
#3 TCP 통신
map-service, wayfinding-service에 다음의 패키지를 설치하도록 하겠습니다.
npm i --save @nestjs/microservices
map-service부터 설정을 하도록 하겠습니다.
- ./src/maps/maps.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { MapsService } from './maps.service';
@Module({
imports: [
ClientsModule.register([{
name: 'wayfinding-service',
transport: Transport.TCP
}])],
providers: [MapsService],
exports: [MapsService],
})
export class MapsModule {}
wayfinding-service도 map-service에 등록하여 양 서비스 간의 통신을 하도록 하겠습니다.
- ./src/main.ts
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const microservice = app.connectMicroservice({
transport: Transport.TCP
});
app.enableCors();
await app.startAllMicroservices();
await app.listen(7900);
}
bootstrap();
nestjs에서 request-response 메시지 통신은 client.send 메서드를 활용해 메시지를 발행하고, @MessagePattern이라는 데코레이터를 이용해 메시지를 전달받습니다. 그러면 maps.service.ts 클래스에서 메시지를 발행하고 wayfinding-service에서 메시지를 전달받도록 하겠습니다.
- ./src/app.controller.ts
import { Controller, Get, HttpStatus, Param, Query } from "@nestjs/common";
import { Builder } from "builder-pattern";
import { statusConstants } from "./constants/status.constant";
import { MapsService } from "./maps/maps.service";
import { ResponseMaps } from "./vo/response.maps";
@Controller("map-service")
export class AppController {
constructor(private readonly mapsService: MapsService) {}
@Get('/')
public getAll(): any {
try {
const result: any = this.mapsService.getAll();
if(result.status !== HttpStatus.OK) {
return result;
}
return result;
} catch(err) {
return Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@Get('map/:_id')
public async getOne(@Param('_id') _id: string): Promise<any> {
try {
const result: any = await this.mapsService.getOne(_id);
if(result.status !== HttpStatus.OK) {
return result;
}
return await Object.assign({
status: HttpStatus.OK,
payload: Builder(ResponseMaps)._id(result.payload._id)
.ycode(result.payload.ycode)
.type_nm(result.payload.type_nm)
.gu_nm(result.payload.gu_nm)
.parking_lot(result.payload.parking_lot)
.bigo(result.payload.bigo)
.xcode(result.payload.xcode)
.tel(result.payload.tel)
.addr(result.payload.addr)
.in_out(result.payload.in_out)
.home_page(result.payload.home_page)
.edu_yn(result.payload.edu_yn)
.nm(result.payload.nm)
.build(),
message: "Get data by _id"
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Error Message: " + err
});
}
}
@Get('maps/list-type-nm/:type_nm')
public async getListByTypeNm(@Param('type_nm') type_nm: string): Promise<any> {
try {
const result: any = await this.mapsService.getListByTypeNm(type_nm);
if(result.status !== HttpStatus.OK) {
return result;
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: "Get list of map"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@Get('maps/list-gu-nm/:gu_nm')
public async getListByGuNm(@Param('gu_nm') gu_nm: string) : Promise<any> {
try {
console.log(gu_nm);
const result: any = await this.mapsService.getListByGuNm(gu_nm);
if(result.status !== HttpStatus.OK) {
return result;
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: "Get list of map"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@Get('maps/list-gu-type')
public async getListGuNmAndTypeNm(@Query() query): Promise<any> {
try {
const result: any = await this.mapsService.getListGuNmAndTypeNm(query.gu_nm, query.type_nm);
if(result.status !== HttpStatus.OK) {
return result;
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: "Get list of map"
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
}
- ./src/maps/maps.service.ts
import { HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Observable } from 'rxjs';
import { statusConstants } from 'src/constants/status.constant';
@Injectable()
export class MapsService {
constructor(@Inject('wayfinding-service') private readonly client: ClientProxy) {}
public getAll(): Observable<any> {
try {
return this.client.send({ cmd: 'GET_ALL' }, '');
} catch(err) {
return Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Map-service: " + err
});
}
}
public async getOne(_id: string): Promise<any> {
try {
return this.client.send({ cmd: 'GET_ONE' }, _id);
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Map-service: " + err
});
}
}
public async getListByTypeNm(type_nm: string): Promise<any> {
try {
return this.client.send({ cmd: 'GET_LIST_TYPE' }, type_nm);
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Map-service: " + err
});
}
}
public async getListByGuNm(gu_nm: string): Promise<any> {
try {
return this.client.send({ cmd: 'GET_LIST_GU' }, gu_nm);
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "map-service: " + err
});
}
}
public async getListGuNmAndTypeNm(gu_nm: string, type_nm: string): Promise<any> {
try {
return this.client.send({ cmd: 'GET_LIST_GU_TYPE' }, [gu_nm, type_nm]);
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "map-service: " + err
});
}
}
}
메서드마다 전반적인 코드는 비슷합니다. 마지막 getListGuNmAndTypeNm을 기준으로 코드를 설명하겠습니다. this.client.send 첫 번째 인자는 메시지명이고, 두 번째 인자는 데이터입니다. 만약 client.send과정에서 오류가 발생한다면 try~catch문을 이용하여 유저에게 map-service의 에러메시지를 반환하도록 합니다.
이어서 wayfinding-service컨트롤러 부분에서 이 메시지를 받아보도록 하겠습니다.
- ./src/app.controller.ts
import { Controller, HttpStatus } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { Builder } from 'builder-pattern';
import { statusConstants } from './constants/status.constant';
import { ResponseMaps } from './vo/response.maps';
import { WayfindingService } from './wayfinding/wayfinding.service';
@Controller("wayfinding-service")
export class AppController {
constructor(private readonly wayfindingService: WayfindingService) {}
@MessagePattern({ cmd: 'GET_ALL' })
public async getAll(data: any): Promise<any> {
try {
const result: any = await this.wayfindingService.getAll();
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: result.message
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@MessagePattern({ cmd: 'GET_ONE' })
public async getOne(data: any): Promise<any> {
try {
const result: any = await this.wayfindingService.getOne(data);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Error message: " + result.message,
});
}
return await Object.assign({
status: HttpStatus.OK,
payload: Builder(ResponseMaps)._id(result.payload._id)
.ycode(result.payload.ycode)
.type_nm(result.payload.type_nm)
.gu_nm(result.payload.gu_nm)
.parking_lot(result.payload.parking_lot)
.bigo(result.payload.bigo)
.xcode(result.payload.xcode)
.tel(result.payload.tel)
.addr(result.payload.addr)
.in_out(result.payload.in_out)
.home_page(result.payload.home_page)
.edu_yn(result.payload.edu_yn)
.nm(result.payload.nm)
.build(),
message: result.message
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Error Message: " + err
});
}
}
@MessagePattern({ cmd: 'GET_LIST_TYPE' })
public async getListByTypeNm(data: any): Promise<any> {
try {
const result: any = await this.wayfindingService.getListByTypeNm(data);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: result.message,
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err,
});
}
}
@MessagePattern({ cmd: 'GET_LIST_GU' })
public async getListByGuNm(data: any) : Promise<any> {
try {
const result: any = await this.wayfindingService.getListByGuNm(data);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: result.message,
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
@MessagePattern({ cmd: 'GET_LIST_GU_TYPE' })
public async getListGuNmAndTypeNm(data: any): Promise<any> {
try {
console.log(data);
const result: any = await this.wayfindingService.getListGuNmAndTypeNm(data[0], data[1]);
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: HttpStatus.INTERNAL_SERVER_ERROR,
payload: null,
message: "Error message: " + result.message
});
}
const responseMaps: Array<ResponseMaps> = [];
for(const el of result.payload) {
responseMaps.push(el);
}
return await Object.assign({
status: HttpStatus.OK,
payload: responseMaps,
message: result.message,
});
} catch(err) {
return await Object.assign({
status: HttpStatus.BAD_REQUEST,
payload: null,
message: "Error message: " + err
});
}
}
}
- ./src/wayfinding/wayfinding.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Builder } from 'builder-pattern';
import { Model } from 'mongoose';
import { statusConstants } from 'src/constants/status.constant';
import { MapsDto } from 'src/dto/maps.dto';
import { Maps, MapsDocument } from 'src/schema/maps.schema';
@Injectable()
export class WayfindingService {
constructor(@InjectModel(Maps.name) private mapsModel: Model<MapsDocument>,) {}
public async getAll(): Promise<any> {
try {
const maps = await this.mapsModel.find();
if(!maps) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: Not data!"
});
}
const result: Array<MapsDto> = [];
for(const element of maps) {
result.push(element);
}
return await Object.assign({
status: statusConstants.SUCCESS,
payload: result,
message: "Success transaction"
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: " + err
});
}
}
public async getOne(_id: string): Promise<any> {
try {
const map = await this.mapsModel.findById(_id);
if(!map) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: Not data!"
});
}
return await Object.assign({
status: statusConstants.SUCCESS,
payload: Builder(MapsDto)._id(String(map._id))
.ycode(map.ycode)
.type_nm(map.type_nm)
.gu_nm(map.gu_nm)
.parking_lot(map.parking_lot)
.bigo(map.bigo)
.xcode(map.xcode)
.tel(map.tel)
.addr(map.addr)
.in_out(map.in_out)
.home_page(map.home_page)
.edu_yn(map.edu_yn)
.nm(map.nm)
.build(),
message: "Success transaction"
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: " + err
});
}
}
public async getListByTypeNm(type_nm: string): Promise<any> {
try {
const maps = await this.mapsModel.find({ type_nm: type_nm });
if(!maps) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: Not data!"
});
}
const result: Array<MapsDto> = [];
for(const element of maps) {
result.push(element);
}
return await Object.assign({
status: statusConstants.SUCCESS,
payload: result,
message: "Success transaction"
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: " + err
});
}
}
public async getListByGuNm(gu_nm: string): Promise<any> {
try {
const maps = await this.mapsModel.find({ gu_nm: gu_nm });
if(!maps) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: Not data!"
});
}
const result: Array<MapsDto> = [];
for(const element of maps) {
result.push(element);
}
return await Object.assign({
status: statusConstants.SUCCESS,
payload: result,
message: "Success transaction"
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: " + err
});
}
}
public async getListGuNmAndTypeNm(gu_nm: string, type_nm: string): Promise<any> {
try {
const maps = await this.mapsModel.find({
gu_nm: gu_nm,
type_nm: type_nm
});
if(!maps) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: Not data!"
});
}
const result: Array<MapsDto> = [];
for(const element of maps) {
result.push(element);
}
return await Object.assign({
status: statusConstants.SUCCESS,
payload: result,
message: "Success transaction"
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Wayfinding-service: " + err
});
}
}
}
map-service와 wayfinding-service 간 통신을 위한 코드를 완료했습니다. 그러면 postman을 이용하여 데이터를 잘 받아오는지 테스트를 진행하겠습니다.
#4 테스트
1) GET /map-service/
2) GET /map-service/map/:_id
3) GET /map-service/maps/list-type-nm/:type_nm
4) GET /map-service/maps/list-gu-nm/:gu_nm
5) GET /map-service/maps/list-gu-type?gu_nm=:gu_nm&type_nm=:type_nm
5개의 endpoint 통신 결과 데이터가 응답이 잘 되는 모습을 확인할 수 있습니다.
다음 포스트에서는 kong을 이용하여 apigateway를 만들어 지금까지 만든 서비스들을 하나의 포트로 묶어 보도록 하겠습니다.
Author And Source
이 문제에 관하여(LifeSports Application(ReactNative & Nest.js) - 12. map-service), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@biuea/LifeSports-ApplicationReactNative-Nest.js-12.-map-service저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)