본문 바로가기

프로젝트/고민

내가 작성하지 않은 코드를 개선해보자

개요

최근 프로젝트 한개를 이어받게 됐다.

프로젝트에 대해 간단하게 소개하자면 농림축산식품부 창업 경진대회에서 우승한 프로젝트이며 광고비를 지원받고 실제 사용자를 모을 수 있다는 점에 매료되어 참여를 결심했다.

 

간단하게 설명하자면 텃밭에 대해 임대인과 임차인 간 연결을 해주는 플랫폼이다. 주말 농장을 하고 싶어하는 분들 또는 텃밭 체험을 하고 싶지만 텃밭 정보를 구하기 어려워 하는 분들을 위해 좋은 경험을 제공하는 목적을 갖고 있다.

 

실제 사용자가 편하게 이용할 수 있게 하나씩 고민했고 코드적인 문제가 가장 눈에 띄었다.

 

문제 정의

가장 먼저 개선한 부분은 날씨 정보를 제공하는 API이다. 기존의 코드에는 날씨 정보를 제공하는 API가 존재했고 이는 공공데이터 날씨정보로부터 받아오는 구조였다.

 

엔드포인트는 총 3개가 존재했다.

1. 현재 시간 기준 전국의 날씨 정보 조회하는 엔드포인트

2. 현재 내 위치, 시간 기준 현재 시간으로부터 1시간 단위로 날씨 정보를 조회하는 엔드포인트

3. 현재 내 위치 기준 일주일 만큼의 날씨 정보를 조회하는 엔드포인트

 

내가 코드를 보며 생각한 문제점은 다음과 같다.

1. 하는 일에 비해 코드가 너무 복잡했다.

2. 복잡한 코드임에도 불구하고 유연성이 떨어진다. 날씨를 조회하는 api를 공공데이터가 아닌 다른 api로 변경할 경우 서비스 코드를 전부 뒤집어야 할만큼 유연성이 떨어졌다.

3. 모든 지역의 날씨 정보를 조회하기 위해서는 모든 지역의 고유한 코드를 알고 있어야 하는데 이 코드를 메모리가 아닌 DB에 저장하고 있다.

4. 모든 지역의 날씨 정보를 조회할 때 전국 17개의 시도의 날씨를 순차적으로 조회하여 사용자가 이 정보를 받으려면 10초정도 기다려야 한다.

5. 공공데이터 API에서는 2일 뒤의 날씨부터 제공해서 다른 api에서 2일만큼의 날씨 정보를 얻고 공공 데이터에서 이후 5일만큼의 날씨정보를 얻는 비효율적인 과정을 겪고 있었다.

 

 

내가 식별한 위 5가지 문제를 해결하면서 기존의 api 스펙이 변경되지 않도록 코드를 작성하는 것을 목표로 잡았다.

 

 

문제를 해결 가능한 범위만큼 쪼개기

문제를 정의했으니 각 문제들을 해결할 수 있는 정도로 쪼개어 하나씩 해결해보자.

1, 2, 3의 문제는 그림을 그리며 코드를 분석해서 해결할 수 있을 것이라 생각이 들었고 4번은 비동기, 5번은 프론트엔드 개발자와 소통을 통해 해결할 수 있을 것이라고 생각했다.

 

그림 그리기

기존의 코드가 이해하기 어려워 각 클래스의 의존 관계를 그림을 그려 파악했다.

 

다음은 기존 코드의 의존(협력) 관계를 나타낸 그림이다. (A4용지에 그린 내용을 옮긴 것이다.)

 

다행스럽게도 의존의 방향은 한방향으로 흘러 그림을 그리니 어느정도 파악이 가능했다. ApiFetcher 클래스에서 외부 api를 호출하는 구조였고, Mapper 클래스를 통해 서비스와 api fetcher 또는 서비스와 서비스 간 데이터를 주고 받을 수 있게 하여 의존관계의 순환이 안되도록 하는 구조였다.

 

코드의 개선은 다음의 순서로 진행했다.

1. api 스펙이 변경되지 않도록 컨트롤러를 먼저 작성했다.

2. 서비스 코드가 외부 api 변경에 영향을 받지 않게 하기 위해 인터페이스를 주입 받도록 했다.

3. api를 통해 받는 데이터 정제 코드를 한 곳에 모아 복잡도를 줄였다.

4. mapper 클래스를 제거하고 레이어 간 dto 내에 정적 팩토리 메소드를 작성하여 복잡도를 줄였다.

5. 전체 지역의 날씨 조회를 위해 알아야 하는 지역 정보는 메모리에 올리고 일급 컬렉션으로 만들어 불필요한 db io를 제거했다.

 

 

리팩토링 구조는 위와 같다. 데이터 파싱을 하는 외부 api에 의존적인 코드는 역할이 너무 많고 가독성이 떨어져 클래스를 한개 나눴다. 나눈 클래스는 sealed 클래스로 작성하고 역할을 분리했다. 

 

 

또한 모든 지역(17개의 시도)의 날씨 정보를 얻는 코드를 병렬적으로 수행할 수 있게 다음과 같이 수정했다.

List<Pair<Region, WeatherForecastResponse>> pairs = regionProvider.findAll()
            .parallelStream()
            .map(region -> Pair.of(region, openAPIClient.getWeatherForecast(secretKey, 1, 1000, "JSON", baseDate, baseTime, region.nx(), region.ny())))
            .toList();

 

 

결과적으로 10초 걸리는 요청이 0.3초로 개선됐다.

 

이 밖에도 날씨를 제공해주는 공공 api가 불안정하기 때문에 open feign과 서킷 브레이커를 이용해 장애 전파를 방지하고 api 호출 코드를 깔끔하게 작성했다.

 

5번 문제와 같은 경우 프론트엔드 개발자와 이야기를 나눠 오늘의 날씨를 조회할 때 내일의 날씨 또한 포함시켜 조회하여 결과적으로 7일치의 날씨를 조회하는 엔드포인트에서는 2일 뒤의 날씨부터 제공해도 되도록 변경했다.

 

 

코드의 세부적인 내용은 아래의 링크에서 확인할 수 있다.

https://github.com/everyone-s-garden/back-V2/blob/dev/core/src/main/java/com/garden/back/weather/service/WeatherService.java