권상현 | 김석재 | 류성훈 | 정미정 |
---|---|---|---|
Github | Github | Github | Github |
💻 Team work | 📒 Project page |
공지사항, 컨벤션 공유 등 우리 팀을 위한 룰 |
요구사항 분석, 정보 공유 및 원할한 프로젝트를 위해 사용 |
-
임상시험정보를 수집하는 batch task를 작성하고, 주어진 조건에 맞는 데이터들을 보여주는 API를 만든다.
-
조건
-
최근 일주일내에 업데이트(변경사항이 있는) 된 임상정보 리스트
-
pagination 기능 (offset, limit 으로 구현)
-
문서화된 API명세
(❌ 주의사항)
- 개발에 사용된 key값이 레포지토리에 올라가서는 안됨
- 팀원간 동일한 Git branch전략, commit단위 사용
-
-
깃허브 repository에서 clone을 받습니다.
git clone https://github.com/wanted-pre-onboarding-2nd-BE-Team-D/003_Human_scape.git
-
MySQL에서 DB를 생성합니다.
CREATE DATABASE 003_Human_scape
-
루트폴더에
my_settings.py
를 생성하고 필요한 키값을 넣습니다.DATABASES = { 'default' : { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'database 이름' 'USER': 'mysql 아이디' 'PASSWORD': 'mysql 비밀번호' 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'charset': 'utf8mb4'} } } SECRET_KEY = 'Django Secret Key' SERVICE_KEY = 'Open API Key'
-
가상환경 생성 후 requirements.txt의 필요한 라이브러리를 다운로드 합니다.
Windows: 1. python -m venv venv 2. venv\Scripts\activate.bat 3. pip install -r requirements.txt Mac: 1. python -m venv venv 2. source venv\bin\activate 3. pip install -r requirements.txt
-
migrate를 합니다.
python manage.py migrate
-
로컬서버를 실행시킵니다.
python manage.py runserver
구현한 방법과 이유 어려웠던 점 , 간략한 내용(개인) 서술
권상현
초기 계획
-
초기 DB 설계 시 오픈 API에서 받아 온 임상시험 정보(이하 정보)를 토대로 생성한 각각의 레코드(로우, 행)에 생성 시각을 나타내는
created_at
, 최신화 시각을 나타내는updated_at
을 추가하고 이를 통해 이력관리를 계획함. -
이후 매일 자정마다 진행될
batch_task
는 다음과 같이 구성
a. 오픈 API를 통해 받아온 데이터들 중 `과제번호` 가 제일 고유성이 있는 항목일 것이라 판단, `task_id` 컬럼으로 지정하고 primary_key로 선언
b. `과제번호`를 토대로 새로 입력해야 할 정보인지 DB에 이미 존재하고 있는 정보인지 구분
- DB에 존재하지 않는 정보는 레코드를 새로 생성
- 이미 DB에 입력한 정보일 경우 받아온 값과 비교해 변경사항이 있다면 최신화
c. 이 과정은 `Django`에서 제공하는 `update_or_create` 메소드를 사용
- 정보를 불러와 조회할 때
created_at
,updated_at
항목을 바탕으로 최신화 시점을 분류하고 최근 일주일 내 변경사항이 있는 정보를 조회하는 API를 구현
문제 발생
-
update_or_create
메소드를 사용해batch_task
를 구현 했을 경우 새로운 정보를 레코드로 입력하는 과정은 계획대로 구현됨 -
받아온 정보와 이미 DB에 입력 된 정보를 비교해 값을 업데이트 하는 과정을 진행하던 중
update_or_create
메소드의 로직 문제로 최신화 할 값이 없더라도update
가 진행,updated_at
항목이 batch_task를 진행한 시각으로 최신화 되어버림.
2-1. update_or_create
의 소스코드를 확인해 본 결과 create
가 아닌 경우에는 update
메소드의 로직을 그대로 따른 후 마지막에 save()
를 실행함을 확인
2-2. 이럴 경우 값의 변화 유무에 상관 없이 일단 값 변경에 대한 쿼리를 날리므로 updated_at
이 최신화 될 수 밖에 없음
문제 해결
-
update_or_create
대신get_or_create
를 사용 -
얻은 정보의
task_id
를 기준으로create
,get
여부를 결정
- `create`일 경우 : 해당 정보의 새로운 레코드 생성
- `get`일 경우 : 정보의 `task_id` 를 가진 레코드를 불러온 뒤
- 변경될 것이라 판단한 ’연구기간’, ‘전체목표연구대상자수’, ‘임상시험단계(연구모형)’의 세부정보만 비교, 변경된 것이 있다면 `save()` 하여 `updated_at`의 변동을 최소화 시킴
🗣 위의 세 요소를 변동될 것이라 판단한 이유는 다음과 같음
-
세 요소만 빈 값이 존재
-
과제번호, 과제명, 연구책임기관, 진료과의 경우 고유명사와 비슷하다 판단, 쉽게 변하지 않을 것
-
연구범위의 경우 단일/다수로 뚜렷한 차이를 보였으며 연구종류의 경우 기타 항목이 존재하여 기준이 확실치 않을 경우 해당 항목으로 분류 될 것이라 판단
결론
-
처음 계획했던
-
DB에 입력되지 않은 임상연구 정보를 받아왔을 경우 DB에 새로 입력
-
이미 존재하는 임상연구 정보일 경우 변동 예상 항목을 비교해 변동이 있을 경우 해당 항목을 최신화
-
세부정보 최신화가 진행될 경우
updated_at
값 역시 최신화하며updated_at
값을 통해 최신화 시각을 식별
등의 목표에 부합할 수 있었다.
-
-
또한 해당 목표를 달성함에 따라 요구 사항에 존재했던
-
기존 데이터와 API 데이터간의 수정된 사항을 비교하여 해당 임상시험이 업데이트 된 것인지 새로 추가된 것 인지 구별이 가능해야함
-
최근 일주일내에 업데이트(변경사항이 있는) 된 임상정보 리스트
조건을 만족시킬 수 있었으며 특히 2번 요구 사항을 조회하는 API 구현을 쉽게 마무리 할 수 있었다.
-
김석재
계획
- 목적 - project app api를 컨테이너로 만들어 온라인에서 사용 할 수 있게 배포
-
완성된 django proejct를 dockerfile을 이용해 컨테이너 화 한다
-
여태까지 사용했던 django runserver는 production 환경에선
보안 및 퍼포먼스 이슈로 추천하지 않으므로 다른웹 서버
를 찾게되었다 -
Apache와 nginx중
많고 긴 처리가 필요없는 이번 project app에는 비동기적 처리
를 사용하는nginx
가 적합해 보였다 -
웹 서버인 nginx와 웹 어플리케이션인 django를 연결할 WSGI
로gunicorn
을 선택했다(상대적으로 가볍고 빠르고 쉽다
) -
컨테이너 화 한 것은
nginx , mysql , django (+ gunicorn)
이고 이를 서로 연동하기위해docker-compose
를 사용했다 -
docker-compose로 정상 작동하는것을 확인 한 후
EC2
에서docker, docker-compose, git 정도만 설치
했다 -
그 다음은 git pull을 이용해 프로젝트를 받고 (
key값이 들어있는 파일은 git에서 받을수 없고 직접 올리거나 작성
해야합니다)간편하게 docker-compose
를 실행 하면 된다 -
그 이후 몇번의 수정이 있었지만
git pull만 하고 바로 배포 할 수 있다
문제 -1
문제
docker-compose시 api를 통해 데이터 리스트를 조회하면 아무것도 나오지 않는(비어있는 db)
문제가 생겼다
문제상황
-
DB의 데이터가 비어있다
-
docker-compose는 실행이된다
-
api는 정상적으로 동작한다
추측
- docker-compose 시 db가 제대로 입력되지 않는 것 같다
원인 분석
-
docker-compose시 나오는 로그를 전부 읽어보았다
-
sql 파일로 db 초기셋팅하는 부분이 동작하지 않는 것을 알게되었다
원인 -1
volumes: - data:/var/lib/mysql/ - ./db_name.sql:/docker-entrypoint-initdb.d/db_name.sql
/var/lib/mysql/
은 mysql 설치 폴더인데
if문으로 mysql 폴더를 체크하고 없으면 initdb.d폴더를 확인해 초기 db를 불러들인다
즉, data:/var/lib/mysql/ 이있으면 initdb.d가 동작을 안한다
-
/var/lib/mysql/에 내가 쓰던 mysql을 넣었다면 database를 다 가져왔을 것이다 하지만 dockerhub의 image로 된 mysql을 가져와 비어있게되는데 이걸 놓쳤다
-
또 compose 실행시 migrate를 하기때문에 스키마만 생성된것으로 추측된다
해결
-
db voluens에서
/var/lib/mysql/
삭제 -
project app 에
restart: always
추가 ,migrate
실행 삭제
추가 - sql파일을 이용해 db를 직접 넣을 경우 migrate가 필요 하지않다 migrate를 빼주면 project app이 종료되지않는다
무엇이 이 문제를 어렵게 만들었는가?
나는 db 데이터의 유무를 제작한 api를 통해
체크했는데 원인 -1
을 해결했을때는 **project app의 도커가 종료**
되면 api를 통해 체크를 할 수 없었다.
사실 도커가 종료되었다는 것도 모르고 **db의 문제로 api가 동작하지 않는다고 생각했다**
팀원들과 상의해 각 도커 내부를 확인
하기로 하고 도커에 접속해 체크 하던 중 원인-1
을 해결할 때 project app의 도커가 오류로 종료
된다는 것을 알았다.
그리고 그때 db 도커는 작동 중
이며 db의 데이터가 채워져
있는 것을 확인 할 수 있었다.
원인-1
을 해결하고 project app의 도커를 다시 키는 것
만으로 해결되는 간단한 문제였다.
- 결론적으로 docker에서 리눅스 cli를 처음 접해 사용이 익숙치 않다는 이유로
보기 편한 api로만 체크
를 했다는 점과추측으로 문제는 여기서 나는거다 라고 확신
해버린 점이 간단한 문제를 어렵게 만들었다.
내 이런 문제점을 알고 있었다 프로젝트에서 배포를 맡은 것 도 배포를 해본적이없어 도전해 보고싶기 때문이었다. 새로운 것에 도전해 익숙하지 않음에 대한 두려움을 이겨낼것이다
문제 -2
문제
원인 분석 및 해결
-
docker-compose시 나오는
로그를 전부 읽어보았다
-
문제 1 - 배포환경에서 crontab을 인식하지 못하는것을 알게되었다
-
원인 ⇒ 배포환경에서
django -crontab을 설치하지 못함
-
해결 ⇒
RUN*apt-get install -y cron
Dockerfile 에서 cron 설치*
-
-
문제 2 ⇒ cronjobs가 추가 되지 않음을 알게되었다
-
원인⇒
crontab을 추가하는 명령어를 입력 해야한다
-
해결⇒ docker-compose시
python [manage.py](http://manage.py/) crontab add
추가
-
-
문제 3 ⇒ log파일이 생성되지않는다
-
원인⇒
cron 서비스를 켜주어야 했다
-
해결 ⇒
service cron start
cron 서비스를 켜준다
-
Batch_task.log2 가 생성됨
-
문제 4 ⇒
Batch_task.log2
가 아닌Batch_task.log
가 생성되어야함-
원인 ⇒ log 파일 문제를 해결하고자
cronjobs 구문을 수정
했기 때문 -
해결 ⇒ cronjobs 구문을 원래 대로 되돌림 하지만
log가 다시 생성되지 않았다
-
-
문제 5 ⇒ cronjobs 구문을
제대로 사용했음에도
log가 생성되지않는다
- 정상 출력을확인
무엇이 이 문제를 어렵게 만들었는가?
cromtab의 문제만 본다면 crontab의 log가 어떤식으로 출력되는지 알지못해 문제를 더욱 어렵게 만들었다
할 수 있지만 근본적인 문제는 구현과 배포의 조화가 이루어 지지않았다
,
동작을 확인 할 수 있는 테스트코드가 존재하지않았다
그리고 코드리뷰가 부족했다
이번 프로젝트에서 배포를 메인으로 작업했는데 개발이 완성된 것을 배포한다는 생각을 하고 있었다 그리고 정신없이 배포를 구현하다보니 나는 개발쪽 정보를 잘 모르고 개발쪽에선 배포쪽 정보를 모르게 되었다
일찍 로컬에서 docker-compose 사용하는 방법을 알려 배포환경에서 테스트
를 할 수 있게 하거나, 반대로 무엇을 테스트 하는지 알았다면
배포환경에서 테스트를 해 이런 문제를 방지 하지 않았나 생각하게되었다.
서로 다른 업무를 맡아 각자 일을 했지만 하나의 작품을 만드는 만큼 프로젝트의 전반적인 이해가 필요하다는 점을 제대로 느꼈다
류성훈
- 구현한 기능
- Django 프로젝트 초기세팅
- 앱 생성, DB모델링
- 프로젝트 초기세팅 및 DB연동, 환경변수 분리
- Swagger 적용
- 구현 된 API을 확인할 수 있는 문서를 Swagger을 사용하여 작성했습니다.
- README 작성
- 프로젝트의 대문인 리드미를 작성하여 프로젝트를 설명해놓았습니다.
- Django 프로젝트 초기세팅
- 어려웠던 점 및 보완할 점
- Swagger
- Django View작성 시 GenericView 혹은 Viewset과 같은 기능을 사용하였을 때, CRUD기능이 내장되어 있어서 Swagger 데코레이션을 넣을 수가 없었습니다.
- Swagger 데코레이션을 붙여 API를 문서에 보이게 하기 위해서는 문서화하고싶은 기능들을 오버라이드 해야한다는 사실을 알게되었습니다.
- 더욱 큰 프로젝트에서 정교한 API문서를 작성하기위해서는 꼼꼼함이 필요할 것이라는 생각이 들었습니다.
- README
- 마크다운 문법을 제대로 다루어 본 적이 없었어서, 리드미를 작성하는데 꽤 오랜 시간이 들었습니다.
- 특히나 토글을 적용하는 것이 많이 헷갈렸습니다. 노션에서는
‘>’
로 토글 기능이 간단히 되지만, 리드미에 적용시키려면<detail> <summary> </summary> <div markdown=”1”> </div> </detail>
을 하여 토글 하나를 적용시켜야 했습니다. - 깊이가 최대 4까지 되는 토글을 적용하는것이 어려웠고, HTML지식과 마크다운 문법에 더 익숙해 져야하고, 집중력을 보완해야겠다는 생각이 들었습니다.
- Swagger
정미정
구현한 기능
- DB 모델링
-
모델을 여러 개로 나눈 이유는 추후 데이터 확장성 고려 또한 공통적으로 중복된 값을 가지는 필드이였기 때문에 데이터베이스 정규화 규칙을 따라 구현했습니다.
-
과제 번호는 고유한 값이기 때문에
pk
로 설정했습니다.
- OPEN API 통신
-
데이터의 개수가 145개로 적어 한 페이지에 모든 데이터를 담아 호출했습니다..
page=1 perpage=145
- 임상 정보 APIs
-
정보의 수정이나 삭제는 OPEN API에서 이루어지는 것이고
지금 작성한 API는 정보를 보여주는 기능만 하는 게 맞다고 판단해서
읽는 기능만 구현했습니다.. (
ListAPIView
,RetriveAPIView
) -
generics.view를 이해하고자 사용해봤습니다.
- 임상정보 리스트 API
-
최근 일주일내에 변경사항이 있는 임상정보 리스트
updated_at__gte
에 일주일 전 날짜를 적용했습니다.
-
pagination을 위한 offset, limit
-
쿼리 파라미터로 받아 적용했습니다.
-
검색 기능 제공
-
검색에 특정 필드를 검색하라는 조건이 없어 사용자가 주로 검색을 할 것 같은 필드를 임의로 선정했습니다..(과제명, 연구범위, 연구종류, 연구책임기관, 임상시험단계, 진료과)
-
여러 개의 조건을 사용하기 위해 Q를 사용했습니다.
-
어려웠던 점
-
gernics.view를 처음 사용해보는데 제공해주는 기능이 많지만 원하는 추가적인 기능이 있을 때 메소드 오버라이딩의 필요성을 느꼈습니다..
-
쿼리 파라미터가 많아
ListAPIView
→GenericAPIView -> get_queryset
를 커스텀했습니다. -
foreignkey로 연결한 값들을
select_related
로 불러왔으나 쿼리문을 확인해보니 foreignkey로 연결한 테이블의 다른 값을 더 가져오지 않아select_related
가 필요없다는 것을 알게 되어select_related
의 개념을 이해하게 됐습니다. -
serializer에 foreignkey로 연결한 테이블들은 id로 나와서 값으로 보이도록 수정해야했다.
serializer.Charfield
로 값을 보여줌으로 해결했습니다.
이전
이후
- 특정 임상정보 API
-
특정 임상정보 읽기
- task_id가 pk 이므로
lookup_field=task_id
로 설정하는 것이 직관적이라고 생각했습니다.
- task_id가 pk 이므로
- 배포 후 swagger ui 의 request url관련
어려웠던 점
❗ 로컬에서 swagger 주소에 들어가 실행했을 때 잘 작동했으나 배포 후 실행해보니 failed fetch
가 출력됐습니다.
ec2주소/api/research
로 요청이 가야하는 데 nginx.conf에서 설정해준 proxy_path인 web/api/research
로 연결이 되어 오류가 났습니다.
proxy_path는 docker-compose에서 작성된 컨테이너명으로 변경하면 아예 연결이 되지 않아 변경할 수 있는 부분이 아니였습니다.
location / {
proxy_pass http://web/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
찾아본 결과 proxy_set header로 변수를 설정해야한다는 것을 알게되어
-
Host : 요청이 들어왔을 때 host 명
-
X-Real-IP: 요청한 클라이언트의 실제 IP
$remote_addr
: 요청한 클라이언트 주소
-
X-Forwarded-For: 클라이언트의 IP 주소, 이전에 프록시 서버가 또 있었다면 그 IP 를 의미
- $proxy_add_x_forwarded_for: 요청 헤더와 그 뒤에 따라오는 클라이언트의 원격 주소를 포함
이 세가지를 추가 후 다시 배포한 결과
정상적인 값이 반환되는 것을 확인할 수 있었습니다.
프로젝트를 Docker를 이용해 컨테이너화 해 EC2에 배포했습니다
AWS 요금 문제로 배포가 중지 될 수 있습니다
AWS 요금 문제로 배포가 중지 될 수 있습니다
API 실행 가이드
-
Read
curl http://52.78.198.214/api/research/C130012
-
List
curl http://52.78.198.214/api/research
-
Search
curl http://52.78.198.214/api/research?agency=%EA%B3%A0%EB%A0%A4%EB%8C%80
상세 설명
-
[GET] /research 임상정보 데이터 리스트 조회,검색 기능
-
List 조회: [Try it out] 버튼 클릭 후 아무 입력 없이 [Execute] 클릭 데이터 List 조회 완료
-
특정 정보로 검색 : [Try it out]클릭 후 원하는 정보를 입력 후 [Exectute]
- ex) agency = 고려대
[execute] <img width="200" src="https://user-images.githubusercontent.com/72593394/168237177-1d14fb9f-a4d7-409f-90ca-f0123817ae3d.png"> agency에 '고려대'가 포함된 데이터들의 리스트가 조회됩니다.
-
pagination
-
원하는 offset, limit값을 넣어 pagination기능을 적용하여 데이터를 조회할 수 있습니다.
-
-
[GET] /research/{task_id}
임상정보 상세데이터 가져오기(detail)