3 마이크로서비스 코드 구현
각 기술 스택의 검증된 템플릿을 사용해 마이크로서비스를 빠르게 시작
. 항공편 마이크로서비스 ⇒ node.js 부트스트래퍼인 노드 부트스트랩(node bootstrap)을 사용해 구현
. 예약 마이크로시스템 ⇒ 파이썬 플라스크 보일러플레이트가 작성된 깃허브 저장소를 사용
3.1 항공편 마이크로서비스 코드
노드부트스트랩을 사용해 프로젝트를 생성(구성)하고, 노드부트스랩에서 제공하는 데이터 마이그레이션 기능을 이용해 MySQL 데이터 저장소를 구현
방법1. nodebootstrap 설치 후 ms-flights 프로젝트 생성
c:\msur> npm install -g nodebootstrap
c:\msur> nodebootstrap -h
Node/Express.js project bootstrapper loaded with best-practices
Usage:
nodebootstrap <project_name> [options]
Options:
-m, [--mode] build mode to use.
Possible values: "webapp", "api", "cli" or "microservice" (default).
-p, [--path=PATH] output to the specified folder. Default is: <project_name>.
You can also indicate "." to build directly inside the current folder.
-h, [--help] output usage information
-v, [-V], [--version] output the version number
Description:
The 'nodebootstrap ' command creates a new application with a default
structure and configuration at the path you specify.
Examples:
# create a skeleton of a containerized microservice:
> nodebootstrap ms-first
# create a skeleton of an express MVC webapp:
> nodebootstrap -m webapp nodeapp-first
# create a skeleton of a console or client Node application
> nodebootstrap -m cli client-first
See more information at: http://www.nodebootstrap.io
c:\msur> nodebootstrap ms-flights
Building 'ms-flights' in microservice mode.
ATTENTION: microservice mode requires properly set-up Docker environment.
Do you have proper Docker setup (y/n)? y
Starting installation…
Downloading zip archive to C:\Users\myanj\AppData\Local\Temp\1647301073018-nodebootstrap.zip
Zip archive downloaded
Extraction complete
Will launch on port: 7507
Setup.sh created
Error: Command failed: chmod u+x ms-flights/setup.sh
'chmod'��(��) ���� �Ǵ� �ܺ� ����, ������ �� �ִ� ���α�, �Ǵ�
��ġ ������ �ƴմϴ�.
방법2. 깃 템플릿 복제 방식으로 코드를 가져와서 사용
https://github.com/inadarei/nodebootstrap-microservice
로컬 레포지토리로 복사
c:\msur> git clone https://github.com/myanjini/ms-flights.git
Cloning into 'ms-flights'...
remote: Enumerating objects: 70, done.
remote: Counting objects: 100% (70/70), done.
remote: Compressing objects: 100% (56/56), done.
Receiving objects: 52% (37/70)sed 63 (delta 2), pack-reused 0
Receiving objects: 100% (70/70), 272.64 KiB | 1.38 MiB/s, done.
Resolving deltas: 100% (2/2), done.
프로젝트 이름 및 설정 변경
파일이름 | 변경 전 | 변경 후 |
C:\msur\ms-flights\docker-compose.yml | ms-nodebootstrap-example | ms-flights |
ms-nodebootstrap-example-db | ms-flights-db | |
5501:5501 | 7507:5501 | |
C:\msur\ms-flights\package.json | ms-nodebootstrap-example | ms-flights |
C:\msur\ms-flights\Makefile | ms-nodebootstrap-example | ms-flights |
C:\msur\ms-flights\database.env | ms-nodebootstrap-example-db | ms-flights-db |
C:\msur\ms-flights\node_modules | 디렉터리 삭제 |
항공편 마이크로서비스 OAS(OpenAPI Spec) 확인
노드부트스트랩에서 제공하는 기능을 이용해서 항공편 마이크로서비스 OAS 구성을 확인
OAS 업데이트
C:\msur\ms-flights\docs\api.yml 파일의 내용을 앞에서 설계한 내용(https://github.com/implementing-microservices/ms-flights/blob/master/docs/api.yml)으로 업데이트
redoc을 이용해서 OAS 문서를 읽어서 디플로이
c:\msur\ms-flights\docs> make start PWD=c:/msur/ms-flights/docs
docker run -d --rm --name ms-nb-docs -p 3939:80 -v c:/msur/ms-flights/docs/api.yml:/usr/share/nginx/html/swagger.yaml -e SPEC_URL=swagger.yaml redocly/redoc:v2.0.0-rc.8-1
6f55ab54754de5d5d999411fec146fa7c6791c3f86338e22fb4c6e23a9318068
"server started at: http://0.0.0.0:3939" ⇐ 0.0.0.0 대신 127.127.127.127로 접속해야 함
HTML 템플릿으로 렌더링된 결과 확인
http://127.127.127.127:3939
오류 원인 조치 후에도 오류가 발생하는 경우, swagger.yaml 파일에 대한 브라우저 캐시를 삭제해야 함
불필요한 모듈 삭제 및 마이크로서비스 모듈 추가
모듈 추가
C:\msur\ms-flights> npm install
불필요한 모듈 삭제 (기본적으로 제공되는 모듈의 디렉터리 삭제)
C:\msur\ms-flights\lib\users
C:\msur\ms-flights\lib\homdoc
flights 모듈 추가 (디렉터리 생성)
C:\msur\ms-flights\lib\flights
appConfig 설정 변경
C:\msur\ms-flights\appConfig.js
… (생략) …
// Add all routes and route-handlers for your service/app here:
function serviceRoutes(app) {
// Add advanced healthcheck middleware (incl. database check)
const check = healthcheck();
const AdvancedHealthcheckers = require('healthchecks-advanced');
const advCheckers = new AdvancedHealthcheckers();
// Database health check is cached for 10000ms = 10 seconds!
check.addCheck('db', 'usersQuery', advCheckers.dbUsersCheck, {
minCacheMs: 10000,
});
app.use(check.express());
/* eslint-disable global-require */
/*
app.use('/' , require('homedoc')); // attach to root route
app.use('/users', require('users')); // attach to sub-route
*/
app.use('/flights', require('flights'));
/* eslint-enable global-require */
}
… (생략) …
입력 유효성 검사 코드 추가 및 API 엔드포인트에 대해 호출할 마이크로서비스 actions 모듈의 함수를 지정
C:\msur\ms-flights\lib\flights\controllers\mappings.js
// https://www.npmjs.com/package/spieler
const { spieler, check, matchedData, sanitize } = require('spieler')();
const router = require('express').Router({
mergeParams: true,
});
const actions = require('./actions');
const log = require('metalogger')();
const flightNoValidation = check(
'flight_no',
'flight_no must be at least 3 chars long and contain letters and numbers',
)
.exists()
.isLength({
min: 3,
})
.matches(/[a-zA-Z]{1,4}\d+/);
const dateTimeValidation = check(
'departure_date_time',
'departure_date_time must be in YYYY-MM-ddThh:mm format',
)
.exists()
.matches(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/);
const flightsValidator = spieler([flightNoValidation, dateTimeValidation]);
const seatmapsValidator = spieler([flightNoValidation]);
router.get('/', flightsValidator, actions.getFlightInfo);
router.get('/:flight_no/seat_map', seatmapsValidator, actions.getSeatMap);
module.exports = router;
actions 모듈 구현
C:\msur\ms-flights\lib\flights\controllers\actions.js
/* jshint -W079 */
const Promise = require('bluebird'),
config = require('config'),
log = require('metalogger')(),
representor = require('kokua'),
_ = require('lodash');
const actions = {};
const responseMediaType = 'application/hal+json';
actions.getSeatMap = async function (req, res, next) {
const response = { status: 'ok' };
response.req = req.body;
res.status(200).json(response);
};
actions.getFlightInfo = async function (req, res, next) {
const response = {
flight_id: 'edcc03a4-7f4e-40d1-898d-bf84a266f1b9',
origin_code: 'LAX',
destination_code: 'DCA',
};
//response.req = req.body;
res.status(200).json(response);
};
module.exports = actions;
mapping 모듈 추가
C:\msur\ms-flights\lib\flights\index.js
module.exports = require('./controllers/mappings');
데이터베이스 마이그레이션
노드 부트스트랩은 데이터베이스 수정을 코드화하여 모든 환경에 적용할 수 있도록 데이터베이스 마이그레이션을 위한 간단한 솔루션을 제공
데이터 마이그레이션을 위한 코드 템플릿 생성
C:\msur\ms-flights> make migration-create name=seat-maps
docker-compose -p nb-demo up -d
Creating ms-nodebootstrap-example-db ... done
Creating ms-nodebootstrap-example ... done
docker-compose -p nb-demo exec ms-nodebootstrap-example node_modules/db-migrate/bin/db-migrate create seat-maps --sql-file
[INFO] Created migration at /opt/app/migrations/20220315000644-seat-maps.js
[INFO] Created migration up sql file at /opt/app/migrations/sqls/20220315000644-seat-maps-up.sql
[INFO] Created migration down sql file at /opt/app/migrations/sqls/20220315000644-seat-maps-down.sql
C:\msur\ms-flights> make migration-create name=flights
docker-compose -p nb-demo up -d
ms-nodebootstrap-example-db is up-to-date
ms-nodebootstrap-example is up-to-date
docker-compose -p nb-demo exec ms-nodebootstrap-example node_modules/db-migrate/bin/db-migrate create flights --sql-file
[INFO] Created migration at /opt/app/migrations/20220315000811-flights.js
[INFO] Created migration up sql file at /opt/app/migrations/sqls/20220315000811-flights-up.sql
[INFO] Created migration down sql file at /opt/app/migrations/sqls/20220315000811-flights-down.sql
C:\msur\ms-flights> make migration-create name=sample-data
docker-compose -p nb-demo up -d
ms-nodebootstrap-example-db is up-to-date
ms-nodebootstrap-example is up-to-date
docker-compose -p nb-demo exec ms-nodebootstrap-example node_modules/db-migrate/bin/db-migrate create sample-data --sql-file
[INFO] Created migration at /opt/app/migrations/20220315000858-sample-data.js
[INFO] Created migration up sql file at /opt/app/migrations/sqls/20220315000858-sample-data-up.sql
[INFO] Created migration down sql file at /opt/app/migrations/sqls/20220315000858-sample-data-down.sql
데이터 마이그레이션 코드 추가
C:\msur\ms-flights\migrations\sqls\DATETIME-seat-maps-up.sql
CREATE TABLE IF NOT EXISTS `seat_maps` (
`flight_no` varchar(10) NOT NULL,
`seat_map` json NOT NULL,
`origin_code` varchar(10) NOT NULL,
`destination_code` varchar(10) NOT NULL,
PRIMARY KEY (`flight_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
C:\msur\ms-flights\migrations\sqls\DATETIME-seat-maps-down.sql
DROP TABLE `seat_maps`;
C:\msur\ms-flights\migrations\sqls\DATETIME-flights-up.sql
CREATE TABLE IF NOT EXISTS `flights` (
`flight_id` varchar(36) NOT NULL,
`flight_no` varchar(10) NOT NULL,
`flight_date` datetime(0) NULL,
PRIMARY KEY (`flight_id`),
FOREIGN KEY(`flight_no`)
REFERENCES seat_maps(`flight_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
C:\msur\ms-flights\migrations\sqls\DATETIME-flights-down.sql
INSERT INTO `seat_maps`
VALUES ('AA2532', '{"Cabin": [{"Row": [{"Seat": [{"Number": "A",
"Facilities": [{"Detail": {"content": "LegSpaceSeat"}}],
"exitRowInd": false, "premiumInd": false, "noInfantInd": false,
"restrictedReclineInd": false}], "RowNumber": 8}],
"Wing": {"lastRow": 22, "firstRow": 14},
"Column": [{"Column": "A", "Characteristics": ["Window"]}],
"lastRow": 23, "firstRow": 8,
"CabinClass": {"CabinType": "Economy"}}]}', 'LAX', 'DCA')
ON DUPLICATE KEY UPDATE
flight_no = 'AA2532' ;
C:\msur\ms-flights\migrations\sqls\DATETIME-sample-data-down.sql
TRUNCATE TABLE `seat_maps`;
데이터 마이그레이션
make restart로 프로젝트를 다시 시작해 마이그레이션이 자동으로 적용되도록 하거나, make migrate로 명시적으로 마이그레이션 작업을 수행
C:\msur\ms-flights> make migrate
API 테스트 ⇒ DB 연동이 되지 않은 상태이므로 mapping에 정의된 검증 로직과 action에 정의된 데이터를 반환하는지를 확인
c:\msur\ms-flights> make start
http://127.127.127.127:7507/flights?flight_no=AA34&departure_date_time=2020-05-17T13:20
http://127.127.127.127:7507/flights/AA2532/seat_map
헬스 체크
컨테이너로 배포되는 앱의 수명 주기를 관리하기 위해 컨테이너의 상태를 확인할 수 있는 엔드포인트 서비스가 필요
쿠버네티스의 경우 활성 프로브(liveness probe)와 준비성 프로브(readiness probe)를 위한 엔드포인트를 제공해야 함
헬스 체크 코드 수정
C:\msur\ms-flights\appConfig.js
function serviceRoutes(app) {
// 활성 프로브(Liveness Probe) - 컨테이너가 동작하는지 확인
const livenessCheck = healthcheck({ path: '/ping' });
app.use(livenessCheck.express());
// 준비성 프로브(Readiness Probe) - 데이터베이스와 앱이 실제로 동작할 준비가 되었는지 확인
const check = healthcheck();
const AdvancedHealthcheckers = require('healthchecks-advanced');
const advCheckers = new AdvancedHealthcheckers();
check.addCheck('db', 'dbQuery', advCheckers.dbCheck, {
minCacheMs: 10000, // 헬스 체크 엔드포인트가 자주 호출되더라도 10초 동안은 캐시 값을 제공하게 하여,
}); // 데이터베이스와 같은 다운스트림 시스템에 대한 부하를 줄임
app.use(check.express());
/* eslint-disable global-require */
/*
app.use('/', require('homedoc')); // attach to root route
app.use('/users', require('users')); // attach to sub-route
*/
app.use('/flights', require('flights'));
/* eslint-enable global-require */
}
헬스 체크 쿼리 수정
C:\msur\ms-flights\lib\healthchecks-advanced\index.js
const Duration = require('duration');
const db = require('datastore');
const log = require('metalogger')();
class Checks {
// async dbUsersCheck() {
// const start = new Date();
// const conn = await db.conn();
// const query = 'select `email`, `uuid`, `last_updated` from users LIMIT 1';
async dbCheck() {
const start = new Date();
const conn = await db.conn();
const query = 'select count(1) from seat_maps';
let errMsg = '';
const response = {};
try {
const users = await conn.query(query);
} catch (err) {
errMsg = err;
} finally {
const elapsed = new Duration(start, new Date());
const status = errMsg ? 'fail' : 'pass';
response.status = status;
response.metricValue = elapsed.milliseconds;
response.metricUnit = 'ms';
if (errMsg) {
response.output = errMsg;
}
}
return response;
}
}
module.exports = Checks;
헬스 체크 동작 확인
추가한 맵핑 정보가 동작(반영)하지 않는다면 make restart 명령으로 컨테이너를 새로 실행
http://127.127.127.127:7507/health
http://127.127.127.127:7507/ping
서비스 중지 및 변경 사항 깃허브 등록
c:\msur\ms-flights> make stop
c:\msur\ms-flights> git add .
c:\msur\ms-flights> git commit -m "항공편 마이크로서비스 구현"
[master 4ab094e] 항공편 마이크로서비스 구현
4 files changed, 12 insertions(+), 12 deletions(-)
c:\msur\ms-flights> git push origin master
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 659 bytes | 329.00 KiB/s, done.
Total 6 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), completed with 5 local objects.
To https://github.com/naanjini/ms-flights.git
2e5a6e8..4ab094e master -> master
댓글