Lsiron

AIR PANG(ts, express, mysql)- 2(openAPI 데이터 받아오기 그리고 트랜잭션 적용 및 node-cron 사용법) 본문

개발일지/AIR PANG

AIR PANG(ts, express, mysql)- 2(openAPI 데이터 받아오기 그리고 트랜잭션 적용 및 node-cron 사용법)

Lsiron 2024. 8. 16. 09:10

data project를 진행하면서 이 부분이 가장 머리아팠던 부분이 아니였나 싶다.

 

프로젝트에서 의도한 openAPI를 찾기도 힘들었는데 이걸 받아오는 로직을 짜는건 정말 맨땅에 헤딩이었다. 

 

여차저차 여러 블로그와 코드들을 뒤져보며, openAPI로 부터 데이터를 가져왔는데 음? 중간에 데이터를 못 가져오거나 잘못 가져왔는데도 이전에 받아온 데이터가 그대로 db에 삽입이 되고 결국 데이터가 이리저리 섞이네? 하며 적용시킨게 트랜잭션 개념이다.

 

트랜잭션 개념을 배우고 직접 적용시킨 결과, 한번 실행했을 때 데이터를 모두 성공적으로 가져오면 db에 저장되었고, 중간에 데이터를 못 가져오거나 잘못 가져 올 경우 이전까지 성공적으로 받은 데이터들을 db에 넣는 작업까지 모두 취소되었다.

 

마지막으로, 자동으로 데이터를 받아오고 db에 저장되도록 하는 스케쥴링을 node-cron으로 구현했는데

HELLO FOLIO 프로젝트에서 jwt 발급을 구현 했을 때 보다 더 쾌감에 절여졌다.

 

분명히 머릿속에 과정은 있는데 이를 코드로 구현하는 것은 상당히 어려웠다.

 

특히나, 7월내내 배운 ts를 처음으로 적용하다보니 오랜만에 코딩을 처음 시작했을 때의 기분을 느꼈다.

 

이제 한번 구현을 해 보자.

 

먼저 과정은 이렇다. 

 

1. 사전에 db에 넣어둔 지역데이터를 가져온다.

2. openAPI에 설정된 지역과 db에 넣어둔 지역이 일치하는 경우, 설정된 지역의 데이터들을 가져온다.

3. 지역의 데이터들을 db에 넣는다. ( 데이터가 안 들어오거나, 잘못 들어올 경우 이 전의 작업들을 취소시킨다. )

4. 위 작업이 자동으로 돌아가도록 한다.

 

먼저 위 과정대로 작업하기 위한 대략적인 디렉토리 구조는 아래와 같다.

data_project/
├── back                                   # 백엔드 프로젝트 루트 디렉토리
│   ├── logs                               # 로그 파일들이 위치하는 디렉토리
│   ├── node_modules                       # 의존성 모듈들이 위치하는 디렉토리
│   └── src                                # 소스 파일들이 위치하는 디렉토리
│       ├── config                         # 환경설정 관련 파일들이 위치하는 디렉토리
│       │   └── db.config.ts               # 데이터베이스 설정 파일 (MySQL 연결 설정)
│       ├── controllers                    # 컨트롤러 파일들이 위치하는 디렉토리
│       │   └── updateDataCron.ts          # 데이터 업데이트 스케줄링 컨트롤러
│       ├── dto                            # 데이터 전송 객체들이 위치하는 디렉토리
│       ├── middlewares                    # 미들웨어 파일들이 위치하는 디렉토리
│       ├── repositories                   # 데이터 저장소 파일들이 위치하는 디렉토리
│       │   ├── locationRepository.ts      # 위치 정보 데이터 저장소 파일
│       │   └── updateDataRepository.ts    # 데이터 업데이트를 위한 저장소 파일
│       ├── routes                         # 라우트 정의 파일들이 위치하는 디렉토리
│       ├── services                       # 서비스 파일들이 위치하는 디렉토리
│       │   ├── locationService.ts         # 위치 정보 관련 비즈니스 로직 파일
│       │   └── updateDataService.ts       # 데이터 업데이트 관련 비즈니스 로직 파일
│       ├── types                          # 타입 정의 파일들이 위치하는 디렉토리
│       │   └── location.ts                # 위치 및 대기오염 데이터 관련 타입 정의 파일
│       ├── utils                          # 유틸리티 함수들이 위치하는 디렉토리
│       │   ├── aqi.ts                     # AQI 계산 등 유틸리티 함수들이 포함된 파일
│       │   └── logger.ts                  # 로깅 유틸리티 (Winston 설정)
│       ├── app.ts                         # 애플리케이션 진입점 파일 (Express 설정 및 미들웨어 구성)
│       └── index.ts                       # 서버 시작 파일 (Express 서버 실행)
│   ├── .env                               # 환경변수 설정 파일 (개발 환경)
│   ├── .env.production                    # 프로덕션 환경변수 설정 파일
│   ├── .gitignore                         # Git에서 무시할 파일을 지정하는 파일
│   ├── nodemon.json                       # Nodemon 설정 파일 (개발 중 서버 자동 재시작 설정)
│   ├── package-lock.json                  # 정확한 버전의 패키지를 설치하기 위한 파일
│   ├── package.json                       # 프로젝트 메타데이터 및 의존성 목록
│   └── tsconfig.json                      # TypeScript 설정 파일 (컴파일러 옵션 등)

 

db설정과 데코레이터 사용을 위한 모듈 설치 및 tsconfig.json 설정 그리고 winston 설정은 생략하도록 하겠다.

 

1. 사전에 db에 넣어둔 지역 데이터를 가져온다.

첫 번째 과정은 데이터베이스에 사전에 저장해 둔 지역 데이터를 가져오는 것이다. 이 데이터는 이후 OpenAPI를 통해 받아온 데이터와 매칭하기 위해 필요하다.

 

데이터베이스에 사전에 저장해 둔 locations 테이블의 지역 데이터는 아래와 같다. (일부분만 발췌)

 

TS를 사용하기 때문에 먼저 불러올 데이터의 타입을 지정해야 한다. types 폴더의 location.ts로 가서 location의 타입을 지정 해 주자.

import { RowDataPacket } from 'mysql2';

export interface Location extends RowDataPacket {
  id: number;
  address_a_name: string;
  address_b_name: string;
}

 

참고로 location 타입만 지정 해 주면 되는데 왜 RowDataPacket 이라는 것을 쓰냐면, RowDataPacket은 MySQL에서 데이터를 가져올 때 사용하는 기본 틀이고, Location은 이 틀을 사용해서 특정한 타입의 데이터를 만드는 설계도 같은 거라고 보면 된다.

 

두 개를 함께 쓰면, 데이터가 제대로 된 타입으로 만들어지고, 우리는 그 데이터를 쉽게 다룰 수 있게 된다.

 

이제 불러올 데이터의 타입을 지정 해 주었으니, locationRepository.ts 로 가서 쿼리문을 짜주자.

import pool from '@_config/db.config';
import type { Location } from '@_types/location';

export class LocationRepository {
  // 모든 위치 데이터 가져오기
  public async getAllLocations(): Promise<Location[]> {
    const query = 'SELECT id, address_a_name, address_b_name FROM locations ORDER BY id ASC';
    try {
      const [results] = await pool.query<Location[]>(query);
      return results;
    } catch (err) {
      throw new Error('위치 데이터를 가져오는 중 오류가 발생했습니다.');
    }
  }

 

참고로 AirPang 프로젝트는 mysql createPool 방식을 사용한다.

 

createConnection 방식은 예를들어 친구에게 전화를 걸 때마다 새로운 번호를 누르고 통화하는 것처럼, 매번 새로운 연결을 만드는 방식이라, 간단한 작업을 할 때 적합하지만. (소규모)

 

createPool 방식은 예를들어 자주 연락하는 친구들을 위해 여러 개의 전화선을 미리 준비해두고 필요할 때마다 사용하는 것처럼, 미리 만들어진 연결을 재사용하는 방식이라, 많은 요청을 효율적으로 처리할 때 적합하기 때문이다. (대규모)

 

( 사실 처음에 createConnection 방식을 사용했었는데 db를 오랜시간 작업 없이 열어 놓을경우 MySQL 서버가 db 연결을 닫는 상황이 발생했었다. 이로 인한 리팩토링을 통해 연결 풀을 사용하여 MySQL 연결을 관리하는 createPool 방식으로 변경하고 keep-alive 옵션을 설정 해 주었다. => 이 후, 거짓말처럼 연결이 닫히는 상황이 발생하지 않았다! )

 

LocationRepository 클래스를 만들어 주고 getAllLocations() 메서드를 클래스 외부에서 접근 할 수 있도록 public 지정을 해 주었으며, 비동기 처리를 해 주었다.

 

비동기 처리가 된 메서드에 처음 지정 해 주었던 location 타입을 가져와서 Promise로 입혀준 후, 오름차순으로 지역을 가져오는 쿼리문을 작성하였다.

( 'address_a_name' 은 전국8도/광역시/자치시/자치도 이름, 'address_b_name' 은 시/군/구 이름을 나타낸다.

 

이제 locationService.ts로 가서 비즈니스 로직을 처리 해 주자.

import { LocationRepository } from '@_repositories/locationRepository';
import type { Location } from '@_types/location';

export class LocationService {
  private locationRepository: LocationRepository;

  constructor() {
    this.locationRepository = new LocationRepository();
  }

  // 모든 위치 데이터 로드
  public async loadAllLocations(): Promise<Location[]> {
    return await this.locationRepository.getAllLocations();
  }
}

 

외부로부터 접근해서 가져온 locationRepository를 클래스 내부에서만 접근할 수 있도록 private으로 지정해 주었으며, 이 locationRepository를 클래스가 생성될 때 초기화하고 인스턴스를 생성하기 위해 constructor에서 초기화해 주었다.

 

비동기 처리한 loadAllLocations 메서드에 location 타입을 가져와서 Promise로 입혀 주었으며, locationRepository의 getAllLocations 메서드를 그대로 클래스 외부에서 접근할 수 있도록 public으로 지정하였다.

 

이제 지역 데이터를 가져오는 작업이 끝났다. 본격적으로 openAPI에서 데이터를 가져와보자.

 

2. OpenAPI에 설정된 지역과 DB에 넣어둔 지역이 일치하는 경우, 설정된 지역의 데이터들을 가져온다.

openAPI에서 데이터를 가져오는 비즈니스 로직을 짜기위해 services 폴더의 updateDataService.ts 파일로 가자.

 

먼저 openAPI 요청을 위해 우리는 axios 라이브러리를 다운 받아야한다.

 

아래 명령어를 통해 axios를 다운 받아주자.

$ npm install axios

 

이 후, updateDataService.ts 에서 사용할 모듈들을 셋팅 해 주고, 가져온 모듈들에 대한 기본 로직을 짜주자.

import axios from 'axios';
import { LocationService } from '@_services/locationService';
import pool from '@_config/db.config';
import logger from '@_utils/logger'; // winston logger 가져오기

export class UpdateDataService {
    private locationService: LocationService;   // locationService 클래스 접근
  
    constructor() {
      this.locationService = new LocationService();  // locationService 인스턴스 생성
    }
}

 

다음으로 https://www.data.go.kr/data/15073855/openapi.do 사이트에서 발급받은 openAPI 키를 .env로 가서 환경변수에 등록해주자.

LOCATION_API_KEY='오픈&API&키=='

 

다시 updataDataService로 가서 코드를 삽입해주자.

import axios from 'axios';
import { LocationService } from '@_services/locationService';
import pool from '@_config/db.config';
import logger from '@_utils/logger'; 

const API_KEY = encodeURIComponent(process.env.LOCATION_API_KEY || ''); //openAPI 키

export class UpdateDataService {
    private locationService: LocationService;   

    constructor() {
      this.locationService = new LocationService(); 
    }
}

 

encodeURIComponent로 씌워준 이유는 URL에 포함될 수 없는 문자나 특수문자를 안전하게 인코딩 하기 위함이다.

( openAPI 키에는 보통 &와 = 같은 특수문자가 보통 포함되어 있다. )

 

URL에 공백이나 특수문자같은 특정 문자를 그대로 포함하면 잘못된 URL로 처리될 수 있기 때문에  encodeURIComponent로 씌워줌으로써, 특정 문자를 안전하게 변환해서 URL의 일부로 사용 할 수 있다.

 

내가 사용하는 openAPI 는 데이터를 불러올 때,

서울의 경우 URL에 'http://apis.data.go.kr/어쩌구/저쩌구?sidoName=서울&serviceKey=API키' 형식으로 적어야 서울의 모든 지역 데이터를 가져온다.

또한 경기의 경우, URL에 'http://apis.data.go.kr/어쩌구/저쩌구?sidoName=경기&serviceKey=API키' 형식으로 적어야 경기의 모든 지역 데이터를 가져온다.


참고로 데이터는 위와 같은 형식으로 불러온다.

 

그렇다면? db에 있는 address_a_name 을 URL에 있는 sidoName의 값에 변수로 적어주고 address_b_name 과 일치하는 cityName의 대기오염물질 데이터만 가져오면 되겠다. 

 

먼저 address_a_name을 가져와서 URL에 변수로 집어넣는 작업을 해야한다. API_KEY 까지 한꺼번에 URL에 변수로 넣어주자.

import axios from 'axios';
import { LocationService } from '@_services/locationService';
import pool from '@_config/db.config';
import logger from '@_utils/logger'; 

const API_KEY = encodeURIComponent(process.env.LOCATION_API_KEY || '');

export class UpdateDataService {
    private locationService: LocationService;   
  
    constructor() {
      this.locationService = new LocationService(); 
    }
  
    public async fetchAndStoreData(): Promise<void> {
      logger.info('Fetching and storing data process started.');
      //locations 테이블의 모든 값들 객체 배열
      const locations = await this.locationService.loadAllLocations();
      //locaitons 테이블의 address_a_name 들만 추출, set은 address_a_name 중복을 막기 위함, Array.from은 객체를 다시 배열로 돌리기 위함 
      const provinces = Array.from(new Set(locations.map(loc => loc.address_a_name))); 
      //전국 8도, 광역시, 자치도, 자치시 마다 url 순회
      for (const province of provinces) {
        logger.info(`Fetching real-time data for ${province}.`);
        const url = `http://apis.data.go.kr/어쩌구/저쩌구?sidoName=${encodeURIComponent(province)}&serviceKey=${API_KEY}`;
        const response = await axios.get(url);
        //데이터 없을경우 에러 발생
        if (!response.data || !response.data.response || !response.data.response.body || !response.data.response.body.items) {
          throw new Error(`Failed to fetch real-time data for ${province}`); 
        }
         
        const items = response.data.response.body.items || [];
      }   
    }
}

 

먼저 비동기로 fetchAndStoreData 메서드를 선언 해 주고 반환값은 없기 때문에 void타입으로 Promise를 입혀주자.

 

이 후, locations 변수에 loadAllLocations(); 메서드를 넣어주자.

 

이로써 locations 변수에는 아래와 같이 모든 id, address_a_name, address_b_name 데이터가 담긴다.

const locations = [
  { id: 1, address_a_name: '강원', address_b_name: '강릉시' },
  { id: 2, address_a_name: '강원', address_b_name: '고성군' },
  { id: 3, address_a_name: '강원', address_b_name: '동해시' },
  { id: 4, address_a_name: '강원', address_b_name: '삼척시' },
  { id: 5, address_a_name: '강원', address_b_name: '속초시' },
  { id: 18, address_a_name: '경기', address_b_name: '가평군' },
  { id: 19, address_a_name: '경기', address_b_name: '고양시' }
];

 

address_a_name만 먼저 가져와야 하기 때문에, 다시 provinces 변수에 address_a_name만을 담아주자.

 

본래 배열 속에서, 하나의 키의 값을 가져올 땐 locations.map(loc => loc.address_a_name); 과 같이 map함수로 씌워주면 된다. 허나 이렇게만 처리를 할 경우  [ '강원', '강원', '강원', '강원', '강원', '경기', '경기' ] 이런식으로 데이터를 가져오게 되어 인덱스 값이 중복되는 결과가 발생한다.

 

따라서 JS 내장 객체인 new Set을 사용하여, 중복되지 않는 유일한 값들의 집합으로 바꿔준다.

 

즉, address_a_name에서의 중복된 값을 모두 처리 해 주는 것. 허나 결과값이 Set { '강원', '경기' } 형식으로, 이터러블 객체로 바뀐다. 

 

때문에 다시 사용하기 위해서는 이터러블 객체를 배열로 변환 해 주는 메서드 Array.from을 써 주어야 한다.

 Array.from(new Set(locations.map(loc => loc.address_a_name)));

 

이로인해 위 코드는 [ '강원' , '경기' ] 형태로 저장이 되는 것 이다.

 

이제 for of 반복문을 통해 province마다 url을 순회하도록 한다. 받아오는 데이터가 없을 경우엔 에러를 던지고, 있을 경우엔 해당 province의 데이터들을 items에 저장한다.

 

그렇다면 items에는 강원의 경우 강릉시, 고성군, 동해시 등등의 모든 대기오염 물질 정보가 다 담겨있을 것이다.

 

허나, 현재 items에는 데이터들이 아래와 같이 난장판으로 받아져 있을 것이다.

 

그러면 이런 난잡한 데이터를 우리가 정해놓은 locations 테이블에 맞게 정형화 해서, 다시 우리가 만들어놓은 realtime_air_quality테이블에 넣어주어야한다. 

 

이제 items에 담겨있는 데이터 들을 하나씩 걸러보자. 먼저 address_b_name과 비교할 cityName, 그리고 realtime_air_quality 테이블에 있는 컬럼들.

(sidoName은 이미 url변수에 province로 입력해주어서 해당 sidoName 데이터들만 추출되기 때문에 따로 걸러내지 않아도 된다.

 

종합하면 cityName, pm10Value, pm25Value, o3Value, no2Value, coValue, so2Value, dataTime이다.

 

이제 걸러낸 데이터들의 타입을 정의 해 주자.

 

다시 처음에 Location 타입을 지정 해 두었던 types 폴더의 location.ts 파일로 가서 내가 openAPI로부터 가져와서 사용할 속성들의 타입을 설정 해 주자.

import { RowDataPacket } from 'mysql2';

export interface Location extends RowDataPacket {
  id: number;
  address_a_name: string;
  address_b_name: string;
}

export interface AirQualityItem {
  cityName: string;
  pm10Value: number;
  pm25Value: number;
  o3Value: number;
  no2Value: number;
  coValue: number;
  so2Value: number;
  dataTime: string;
}

 

받아올 데이터들의 타입을 정해줬으니 이제 다시 updateDataService.ts로 가서 세부지역의 데이터를 뽑아내는 로직을 짜보자.

import axios from 'axios';
import { LocationService } from '@_services/locationService';
import pool from '@_config/db.config';
import logger from '@_utils/logger'; 

const API_KEY = encodeURIComponent(process.env.LOCATION_API_KEY || '');

export class UpdateDataService {
    private locationService: LocationService;   

    constructor() {
      this.locationService = new LocationService(); 
    }
  
    public async fetchAndStoreData(): Promise<void> {
      logger.info('Fetching and storing data process started.');
      //locations 테이블의 모든 값들 객체 배열
      const locations = await this.locationService.loadAllLocations();
      //locaitons 테이블의 address_a_name 들만 추출, set은 address_a_name 중복을 막기 위함, Array.from은 객체를 다시 배열로 돌리기 위함 
      const provinces = Array.from(new Set(locations.map(loc => loc.address_a_name))); 
      //전국 8도, 광역시, 자치도, 자치시 마다 url 순회
      for (const province of provinces) {
        logger.info(`Fetching real-time data for ${province}.`);
        const url = `http://apis.data.go.kr/어쩌구/저쩌구?sidoName=${encodeURIComponent(province)}&serviceKey=${API_KEY}`;
        const response = await axios.get(url);
        //데이터 없을경우 에러 발생
        if (!response.data || !response.data.response || !response.data.response.body || !response.data.response.body.items) {
          throw new Error(`Failed to fetch real-time data for ${province}`); 
        }
         
        const items = response.data.response.body.items || [];
        //각 province 마다 세부지역들 순회하며 데이터 가져오기
        for (const loc of locations.filter(loc => loc.address_a_name === province)) {
          const item = items.find((it: AirQualityItem) => it.cityName === loc.address_b_name) || {
            pm10Value: 0,
            pm25Value: 0,
            o3Value: 0,
            no2Value: 0,
            coValue: 0,
            so2Value: 0,
            dataTime: new Date().toISOString(),
          };
        }
      }   
    }
}

 

먼저 locations 를 반복문으로 돌린다. 그리곤 province와 같은 address_a_name으로 locations를 필터링 해줌으로써 province와 같은 address_a_name을 순회하도록 한다.

 

이어서 address_b_name과 cityName이 동일한 대기오염 물질 데이터들 중, find 메서드를 통해 가장 첫 번째에 있는 데이터를 item에 담아주고,

( 한 페이지당 결과 수를 100으로 지정 해 놓았기 때문에 예를 들어, 강릉시의 2시 대기오염 물질, 1시 대기오염 물질로, 이전 시간대의 데이터 또한 중복되게 들어 가 있었음. 때문에 지역별로 가장 최근의 데이터를 받아오기 위해 find로 첫 번째 데이터만 받아오도록 함. )

데이터가 없을 경우, 기본 값으로 0을 담아준다.

( 한번씩, 특정 지역에서 대기오염물질 데이터를 업데이트 해주지 않는 경우가 있었음.

 

허나, 정상적인 openAPI 데이터의 경우 dataTime을 'YYYY-MM-DD HH:mm:ss' 형식으로 받아오지만,

기본 값으로 가져오는 경우 dataTime에 지정된 형식이 없기 때문에 dateTime이 ' YYYY-MM-DDTHH:mm:ss.sssZ' 형식으로 db에 저장되어 다른 데이터와 형식이 달라 에러를 발생시킨다.

 

때문에 dataTime을 db에 넣기전에 'YYYY-MM-DD HH:mm:ss' 형식으로 변환 시키는 로직을 짜주자.

 

이를 위해선 먼저 dayjs 라이브러리를 설치해야한다. 아래 명령어로 설치 해 주자.

$ npm install dayjs

 

다시 돌아와서 dayjs 기본 셋팅을 해 주고 db에 넣기 전에 item.data의 형식을 'YYYY-MM-DD HH:mm:ss' 로 바꿔주자.

import axios from 'axios';
import { LocationService } from '@_services/locationService';
import pool from '@_config/db.config';
import logger from '@_utils/logger'; 
//dayjs 라이브러리 불러오기
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';

//플러그인 사용설정
dayjs.extend(utc);
dayjs.extend(timezone);

const API_KEY = encodeURIComponent(process.env.LOCATION_API_KEY || '');

export class UpdateDataService {
    private locationService: LocationService;   
  
    constructor() {
      this.locationService = new LocationService(); 
    }
  
    public async fetchAndStoreData(): Promise<void> {
      logger.info('Fetching and storing data process started.');
      
      const locations = await this.locationService.loadAllLocations();
      const provinces = Array.from(new Set(locations.map(loc => loc.address_a_name))); 
      
      for (const province of provinces) {
        logger.info(`Fetching real-time data for ${province}.`);
        const url = `http://apis.data.go.kr/어쩌구/저쩌구?sidoName=${encodeURIComponent(province)}&serviceKey=${API_KEY}`;
        const response = await axios.get(url);
        
        if (!response.data || !response.data.response || !response.data.response.body || !response.data.response.body.items) {
          throw new Error(`Failed to fetch real-time data for ${province}`); 
        }
         
        const items = response.data.response.body.items || [];
        
        for (const loc of locations.filter(loc => loc.address_a_name === province)) {
          const item = items.find((it: AirQualityItem) => it.cityName === loc.address_b_name) || {
            pm10Value: 0,
            pm25Value: 0,
            o3Value: 0,
            no2Value: 0,
            coValue: 0,
            so2Value: 0,
            dataTime: new Date().toISOString(),
          };
          //dataTime 'YYYY-MM-DD HH:mm:ss' 형식으로 변경
          item.dataTime = dayjs(item.dataTime).tz('Asia/Seoul').format('YYYY-MM-DD HH:mm:ss');          
        }
      }   
    }
}

 

종국엔 province와 같은 address_a_name을 한 바퀴 돌면서 cityName과 같은 address_b_name 들의 대기오염 물질 데이터들을 하나씩 뽑을 것이다.

const locations = [
  { id: 1, address_a_name: '강원', address_b_name: '강릉시' },
  { id: 2, address_a_name: '강원', address_b_name: '고성군' },
  { id: 3, address_a_name: '강원', address_b_name: '동해시' },
  { id: 4, address_a_name: '강원', address_b_name: '삼척시' },
  { id: 5, address_a_name: '강원', address_b_name: '속초시' },
];

 

예를 들어, 위와 같이 강원 을 한 바퀴 돌면서, 강릉시, 고성군, 동해시, 삼척시, 속초시 의 대기오염 물질 데이터들을 뽑을 것이다. 

 

이로써 세부지역의 데이터들을 가져오는 과정이 끝났다. 이제 이 데이터를 db에 넣어보자. 

 

1. 사전에 db에 넣어둔 지역데이터를 가져온다.

2. openAPI에 설정된 지역과 db에 넣어둔 지역이 일치하는 경우, 설정된 지역의 데이터들을 가져온다.

3. 지역의 데이터들을 db에 넣는다. ( 데이터가 안 들어오거나, 잘못 들어올 경우 이 전의 작업들을 취소시킨다. )

4. 위 작업이 자동으로 돌아가도록 한다.

 

--2부에 계속--