https://github.com/wanted-wecode-subjects/humanscape-subject
요구사항
https://pocky-humanscape-subject.herokuapp.com/clinical
nodejs에는 fetch가 없습니다.
따라서 이에 대한 대책으로 node-fetch 또는 axios 패키지를 사용합니다.
nestjs에서는 @nestjs/axios를 제공합니다.
해당 패키지의 HttpModule을 사용하려는 모듈에 import 하여 HttpService를 사용할 수 있습니다.
clinical.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([ClinicalRepository]),
HttpModule,
StepModule,
],
controllers: [ClinicalController],
providers: [ClinicalService],
})
export class ClinicalModule {}
clinical.service.ts
export class ClinicalService {
constructor(
@InjectRepository(ClinicalRepository)
private readonly clinicalRepository: ClinicalRepository,
private httpService: HttpService,
private stepService: StepService,
) {}
HttpService.get() 메소드를 사용하여 get 요청을 할 수 있습니다.
clinical.service.ts
getAPIData(pageNo, start = 0) {
let url =
'http://apis.data.go.kr/1470000/MdcinClincTestInfoService/getMdcinClincTestInfoList';
url += '?' + `ServiceKey=${process.env.SERVICE_KEY}`;
url += '&' + `numOfRows=${CLINICAL_CONSTANT.NUM_OF_ROWS}`;
url += '&' + `pageNo=${pageNo}`;
return this.httpService.get(url);
그러나 axios의 get 메소드는 rxjs를 사용하여 이벤트 발생에 따른 동작을 수행합니다. 그래서 Observable 객체에 래핑된 AxiosResponse를 반환합니다.
반환받은 값을 통해 이후 동작을 정의하는 데에는 다음과 같은 방법이 있습니다.
const observer = this.getAPIData(pageNo);
observer.subscribe({
next(resposne) {
... // response 가공 후 DB에 저장
},
error(err) {
... // error 관련 처리
},
complete() {
... // 완료 후 처리
}
});
...
const observer = this.httpService.get(url).pipe(map(axiosResponse) => axiosResponse);
const doSomething = (axiosResponse) => {
const jsonResponse = xml2json.xml2json(axiosResponse.data);
const items = jsonResponse.response.body.items.item; // 임상 실험 데이터 배열
if (!items) {
return;
}
return items;
}
// api의 임상정보 데이터 배열(items)이 res에 담김
const res = await lastValueFrom(observer).then(doSomething);
... // DB에 clinical 저장
async getAPIData(pageNo, start = 0): Promise<Clinical[]> {
let url =
'http://apis.data.go.kr/1470000/MdcinClincTestInfoService/getMdcinClincTestInfoList';
url += '?' + `ServiceKey=${process.env.SERVICE_KEY}`;
url += '&' + `numOfRows=${CLINICAL_CONSTANT.NUM_OF_ROWS}`;
url += '&' + `pageNo=${pageNo}`;
return this.httpService
.get(url)
.toPromise()
.then(async (axiosResponse) => {
const jsonResponse = xml2json.xml2json(axiosResponse.data);
const items = jsonResponse.response.body.items.item; // 임상 실험 데이터 배열
if (!items) {
return;
}
// DB에 insert
for (let i = start; i < items.length; i++) {
await this.createClinical(items[i]);
}
return items;
});
}
이외에도 다양한 방법이 있겠지만, 과제 진행 당시에는 조급한 마음에 다른 선택지가 보이지 않고 세번째 방법을 선택하여 코드를 작성하였습니다.
(참고로 세번째 방법의 toPromise()는 deprecate 될 예정입니다)
참고: https://min9nim.vercel.app/2020-04-24-rxjs/
https://ichi.pro/ko/rxjs-7ui-saeloun-gineung-177144942872256
https://docs.nestjs.kr/techniques/http-module
참고한 openAPI 데이터는 반환형이 XML로 고정되어 있기 때문에 이를 json으로 바꿀 필요가 있었습니다.
때문에 이와 관련된 라이브러리인 xml2json-light를 사용하였습니다.
xml2json-light의 xml2json() 함수를 사용하면 XML 문자열이 JSON 객체로 변환됩니다.
import * as xml2json from 'xml2json-light';
const xml = `<APPLY_ENTP_NAME>한국아스트라제네카</APPLY_ENTP_NAME>
<APPROVAL_TIME>2012-03-22 00:00:00</APPROVAL_TIME>
<LAB_NAME>학교법인 고려중앙학원 고려대학교의과대학부속병원:서울대학교병원:분당서울대학교병원:연세대학교의과대학 강남세브란스병원:전북대학교병원:연세대학교의과대학 강남세브란스병원</LAB_NAME>
<GOODS_NAME>AZD8931</GOODS_NAME>
<CLINIC_EXAM_TITLE>HER2 상태에 따른 트라스투주맙 치료에 적합하지 않고, 일차요법에서 진행 된 전이성, 위암 또는 위-식도 접합부 암환자에서 파클리탁셀 단독 투여 대비 파클리탁셀과 병용한 AZD8931의 유효성, 안전성 및 약물동력학 평가를 위한 제 2a상 다기관 무작위배정 이중맹검 위약대조 시험 (SAGE)</CLINIC_EXAM_TITLE>
<CLINIC_STEP_NAME>2a상</CLINIC_STEP_NAME>`;
const json = xml2json.xml2json(xml);
console.log(json);
console
{
APPLY_ENTP_NAME: ‘한국아스트라제네카’,
APPROVAL_TIME: ‘2012-03-22 00:00:00’,
LAB_NAME: ‘학교법인 고려중앙학원 고려대학교의과대학부속병원:서울대학교병원:분당서울대학교병원:연세대학교의과대학 강남세브란스병원:전북대학교병원:연세대학교의과대학 강남세브란스병원’,
GOODS_NAME: ‘AZD8931’,
CLINIC_EXAM_TITLE: ‘HER2 상태에 따른 트라스투주맙 치료에 적합하지 않고, 일차요법에서 진행 된 전이성, 위암 또는 위-식도 접합부 암환자에서 파클리탁셀 단독 투여 대비 파클리탁셀과 병용한 AZD8931의 유효성, 안전성 및 약물동력학 평가를 위한 제 2a상 다기관 무작위배정 이중맹검 위약대조 시험 (SAGE)’,
CLINIC_STEP_NAME: ‘2a상’
}
nestjs에서는 Node.js node-cron 패키지와 통합되는 @nestjs/schedule 패키지를 제공합니다.
cron은 linux의 cron 처럼 특정 시간마다 정의한 메소드를 호출할 수 있도록 도와주는 데코레이터입니다.
데코레이터에 전달되는 값은 초 분 시 일 월 요일 순이며, *을 입력하면 매번 실행, 범위를 지정하고 싶다면(예: 1분~30분마다 실행) 1-30 과 같이 작성할 수 있습니다. 또한 /를 사용하여 주기적으로 실행할 수 있습니다.
예시
* * * * * *
: 매 초 실행45 * * * * *
: 매분 45초에 실행0 10 * * * *
: 매시간 10분 0초에 실행0 */30 9-17 * * *
: 오전 9시부터 오후 5시까지 30분마다 실행0 30 11 * * 1-5
: 월요일(1)부터 금요일(5) 오전 11시 30분에 실행이번 프로젝트에서는 월~토요일 자정 정각에 batch를 돌릴 수 있도록 cron을 설정하였습니다.
clinical.service.ts
@Cron('0 0 0 * * 1-6')
async batchData(): Promise<void> {
...
참고: https://docs.nestjs.kr/techniques/task-scheduling