3.2 예약 마이크로서비스 코드 구현 (P260)
파이썬 플라스크와 레디스 데이터 저장소를 사용해 구현
파이썬/플라스크 스택을 위한 ms-python-flask-template 템플릿을 사용
깃 템플릿 복제
https://github.com/inadarei/ms-python-flask-template
로컬 개발 환경으로 복제
c:\msur> git clone https://github.com/myanjini/ms-reservations.git
예약 마이크로서비스 OAS(OpenAPI Spec) 확인
OAS 업데이트
C:\msur\ms-reservations\docs\api.yml 파일의 내용을 앞에서 설계한 내용(https://github.com/implementing-microservices/ms-reservations/blob/master/docs/api.yml)으로 업데이트
redoc을 이용해서 OAS 문서를 읽어서 디플로이
c:\msur\ms-reservations\docs> make start PWD=c:/msur/ms-reservations/docs
docker run -d --rm --name ms-python-docs -p 3939:80 -v c:/msur/ms-reservations/docs/api.yml:/usr/share/nginx/html/swagger.yaml -e SPEC_URL=swagger.yaml redocly/redoc:v2.0.0-rc.8-1
b0f0428bfcc125f236c9e4fa0956b353959fcd3ea26e7300886d89870afa371c
"server started at: http://0.0.0.0:3939"
HTML 템플릿으로 렌더링된 결과 확인
http://127.127.127.127:3939/
마이크로서비스 구현 1. 예약
users 엔드포인트에 대한 매핑을 reservations 엔드포인트에 대한 매핑으로 변경
C:\msur\ms-reservations\service.py
… (생략) …
# # For more sophisticated forms in Flask, see:
# # https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iii-web-forms
# @app.route('/users', defaults={'user_id': ""}, methods=['POST'])
# @app.route('/users/<user_id>', methods=['POST'])
# def update_user(user_id):
# """Endpoint that creates or saves user in Redis database"""
# # Note 'force=True' ignores mime-type=app/json requirement default in Flask
# user = request.get_json(force=True)
# resp = handlers.save_user(user, user_id)
# return jsonify(resp)
@app.route('/reservations', methods=['PUT'])
def reserve():
"""Endpoint that reserves a seat for a customer"""
json_body = request.get_json(force=True)
resp = handlers.reserve(json_body)
if (resp.get("status") == "success"):
return jsonify(resp)
else:
return Response(
json.dumps(resp),
status=403,
mimetype='application/json'
)
… (생략) …
매핑 처리기 수정
C:\msur\ms-reservations\src\handlers.py
… (생략) …
# def save_user(user, user_id=""):
# """Save User route callback"""
# if user_id == "":
# user_id = str(uuid.uuid4())
# log.info("Saving USER_ID: %s", user_id)
# user = json.dumps(user)
# return model.save_user(user, user_id)
def reserve(json_body):
"""Save reservation callback"""
return model.save_reservation(json_body)
… (생략) …
모델 수정 - 데이터베이스에 실제 저장을 구현
레디스 CLI로 실행한 hsetnx 명령어를 파이썬으로 구현
C:\msur\ms-reservations\src\model.py
… (생략) …
# def save_user(user, user_id):
# """Saves user into redis database"""
# try:
# this.redis_conn.set(user_id, user)
# except redis.RedisError:
# response = {
# "completion" : {"status": "error"}
# }
# log.info("Error while saving in Redis", exc_info=True)
# else:
# response = {
# "completion": {
# "status": "success",
# "user_id" : user_id
# }
# }
# return response
def save_reservation(reservation):
"""Saves reservation into Redis database"""
seat_num = reservation['seat_num']
try:
result = this.redis_conn.hsetnx(
this.tblprefix + reservation['flight_id'],
seat_num,
reservation['customer_id'])
except redis.RedisError:
response = {
"error": f"Unexpected error reserving {seat_num}"
}
log.error(f"Unexpected error reserving {seat_num}", exc_info=True)
else:
if result == 1:
response = {
"status": "success",
}
else:
response = {
"error": f"Could not complete reservation for {seat_num}",
"description": "Seat already reserved. Cannot double-book"
}
return response
… (생략) …
테이블 수준 접두사를 선언
C:\msur\ms-reservations\src\model.py
… (생략) …
# this is a pointer to the module object instance itself.
# pylint: disable=invalid-name
this = sys.modules[__name__]
this.tblprefix = "flights:"
this.redis_conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT,
db=REDIS_DB, password=REDIS_PWD,
decode_responses=True)
… (생략) …
마이스크서비스 테스트 1. 예약
docker-compose.yaml 파일 수정
C:\msur\ms-reservations\docker-compose.yml
version: '3.4'
services:
ms-template-microservice:
container_name: ms-template-microservice
build:
context: .
restart: always
volumes:
- ./:/app
ports:
- 7701:5000
env_file:
- ./database-dev.env
environment:
FLASK_ENV: development
links:
- ms-redis
command: ./wait-for.sh -t 60 ms-redis:6379 -- gunicorn -b 0.0.0.0:5000 --reload -w 1 service:app
ms-redis:
container_name: ms-redis
image: redis:6-alpine
restart: always
command: redis-server /usr/local/etc/redis/redis.conf --requirepass 4n_ins3cure_P4ss
### you only need to host-map this port if you have an app (DB GUI Editor?)
### on host that needs access to the Redis DB. Otherwise, keep it commented.
#ports:
# - "6379:6379"
expose:
- 6379
volumes:
- local_redis_data:/data
# - $PWD/redis.conf:/usr/local/etc/redis/redis.conf
- ./redis.conf:/usr/local/etc/redis/redis.conf
environment:
- REDIS_REPLICATION_MODE=master
volumes:
local_redis_data:
쉘 스크립트 개행문자 변경
C:\msur\ms-reservations\wait-for.sh 파일의 개행문자를 LF로 변경
마이크로서비스 실행
c:\msur\ms-reservations> make start
docker-compose -p ms-workspace-demo up -d
Creating network "ms-workspace-demo_default" with the default driver
Creating ms-redis ... done
Creating ms-template-microservice ... done
예약 테스트
C:\msur\ms-reservations> curl --header "Content-Type:application/json" --request PUT --data "{\"seat_num\":\"12B\",\"flight_id\":\"werty\",\"customer_id\":\"dfdg\"}" http://127.127.127.127:7701/reservations
{
"status": "success"
}
중복 예약 시 오류 반환 확인
C:\msur\ms-reservations> curl --header "Content-Type:application/json" --request PUT --data "{\"seat_num\":\"12B\",\"flight_id\":\"werty\",\"customer_id\":\"dfdg\"}" http://127.127.127.127:7701/reservations
{"error": "Could not complete reservation for 12B", "description": "Seat already reserved. Cannot double-book"}
C:\msur\ms-reservations> curl --header "Content-Type:application/json" --request PUT --data "{\"seat_num\":\"12B\",\"flight_id\":\"werty\",\"customer_id\":\"jkfl\"}" http://127.127.127.127:7701/reservations
{"error": "Could not complete reservation for 12B", "description": "Seat already reserved. Cannot double-book"}
마이크로서비스 구현 2. 예약 검색
예약 검색 엔드포인트 구현
C:\msur\ms-reservations\service.py
… (생략) …
@app.route('/reservations', methods=['GET'])
def reservations():
""" Get Reservations Endpoint"""
flight_id = request.args.get('flight_id')
resp = handlers.get_reservations(flight_id)
return jsonify(resp)
… (생략) …
예약 검색 핸들러 구현
C:\msur\ms-reservations\src\handlers.py
… (생략) …
def get_reservations(flight_id):
"""Get reservations callback"""
return model.get_reservations(flight_id)
… (생략) …
예약 검색 모델 구현
C:\msur\ms-reservations\src\model.py
… (생략) …
def get_reservations(flight_id):
"""List of reservations for a flight, from Redis database"""
try:
key = this.tblprefix + flight_id
reservations = this.redis_conn.hgetall(key)
except redis.RedisError:
response = {
"error": "Cannot retrieve reservations"
}
log.error("Error retrieving reservations from Redis",
exc_info=True)
else:
response = reservations
return response
마이크로서비스 테스트 2. 예약 조회
C:\msur\ms-reservations> curl -v http://127.127.127.127:7701/reservations?flight_id=werty
* Trying 127.127.127.127:7701...
* Connected to 127.127.127.127 (127.127.127.127) port 7701 (#0)
> GET /reservations?flight_id=werty HTTP/1.1
> Host: 127.127.127.127:7701
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: gunicorn/20.0.4
< Date: Sat, 05 Mar 2022 23:14:17 GMT
< Connection: close
< Content-Type: application/json
< Content-Length: 20
<
{
"12B": "dfdg"
}
* Closing connection 0
서비스 중지 및 깃허브 등록
c:\msur\ms-reservations> make stop
c:\msur\ms-reservations> git add .
c:\msur\ms-reservations> git commit -m "예약 서비스 구현"
c:\msur\ms-reservations> git push
댓글