[230210 - TIL] 외부 API를 사용할 때 고려해야할 점 (feat. 셀프피드백)

Dongwoo Kim·2023년 2월 10일
0

TIL / WIL

목록 보기
80/113

1. 개요

지난 이틀간 진행했던 외부 API를 통해 특정 리소스 목록을 조회하고 DB의 데이터 목록에 비교/업데이트하는 로직을 설계하고 코드를 작성했다.

해당 리소스는 다른 데이터의 정보도 가지고있었기 때문에 다른 데이터의 정보를 다시 외부 API로 요청하고 업데이트해야했다. 따라서 막무가네로 최신 목록을 전부 업데이트 해버리면 너무나 작업이 커지기 때문에 현재 목록과 최신 목록을 비교하고 변경된 리소스만 작업하는 식으로 최대한 필요한 작업만 할 수 있도록 로직을 작성하고자 노력했다.

이후 팀장님께 피드백을 받은 내용을 정리하고 다음 작업부터는 좀 더 고려해보려 한다.


2. 예상 시나리오

  • 외부 API를 통해 할일 목록을 받아온다고 가정하자.
RESPONSE_DATA = [
    {
        "due_date": "2022-12-28 12:12:12",
        "title": "DictionartiesCompare",
        "content": "viewset TEST 1",
        "status": "DONE",
        "rating": 0,
        "user_id": 4,
      	"like_users": [1, 2, 3]
    },
    {
        "due_date": "2022-12-28 12:12:12",
        "title": "DictionartiesCompare",
        "content": "viewset TEST 2",
        "status": "DONE",
        "rating": 0,
        "user_id": 4,
        "like_users": [2, 3]
    },
    {
        "due_date": "2022-12-28 12:12:12",
        "title": "DictionartiesCompare",
        "content": "F TEST 3",
        "status": "DONE",
        "rating": 0,
        "user_id": 4,
        "like_users": []
    },
    {
        "due_date": "2022-12-28 12:12:12",
        "title": "DictionartiesCompare",
        "content": "F TEST 4",
        "status": "DONE",
        "rating": 0,
        "user_id": 4,
        "like_users": [1, 2, 3, 4]
    }
]
  • 할일 개체에는 각각의 정보와 더불어 좋아요를 누른 유저들의 id가 포함되어있다. (like_users)
  • 그리고 유저 테이블에는 자신이 누른 좋아요를 누른 개수를 저장하고 있다고 가정하자.
  • 할일 목록을 다시 조회해서 업데이트를 할 때 like_users 의 상태 변화에 따라 유저의 정보도 조회해서 변경해야할 필요성이 생긴다.

3. 피드백 이전에 생각한 방법

피드백 이전에 생각한 방법도 설계할 때는 나름 합리적이라고 생각했다. 일단 앞선 TIL에서 작성했던 것과 같이 set()과 dictionaries compare를 이용하여 최대한 업데이트해야할 데이터를 찾고 해당 데이터만 업데이트 하는 것이다. 설계했던 로직은 다음과 같다.

# 1. 기존 데이터 로딩
db_data = Data.objects.filter(...)

# 2. 최신 데이터 요청
response_data = ...

# 3. set()를 이용한 기존/최신 데이터에서 추가/삭제/중복 데이터 찾기
db_set = set([data.id for data in db_data])         
response_set = set([data.id for data in response_data])

deleted_data_list = list(db_set - response_set)  # 삭제해야할 데이터
added_data_list = list(response_set - db_set)    # 추가해야할 데이터
update_data_list = list(response_set & db_set)   # 업데이트해야할 데이터

# 4. 삭제 데이터 삭제
for deleted_data in deleted_data_list:
        deleted_data_obj = Data.objects.get(id=deleted_data)
        deleted_benefit_obj.delete()

# 5. 추가 데이터 추가
for added_data in added_data_list:
        Data.objects.create(**added_data)

# 6. 중복 데이터에 대해서 dictionaries compare를 이용해 
# 	 실재로 업데이트할 것인지 판단
for update_data in update_data_list:
	cur_data = db_data.objects.get(id=update_data)
    cur_data = DataSerializer(cur_data).data
    new_data = response_data[update_data]

	if cur_data == new_data:
    	# 7. 변경할 필요없으면 pass
        continue

	# 8. 업데이트 해야할 항목에 대해서 다시 외부 API 요청 & 업데이트 
    new_response_data = ...

    # 9. 해당 데이터의 관련 데이터에 대해서도  외부 API 요청 & 업데이트
    another_response_data = ...

작성 당시에는 나름 합리적이라고 생각했는데 여러가지 헛점이 존재했다.


4. 피드백 내용

여러가지 피드백 내용이 있었지만 가장 중요했던 2가지 포인트가 존재했다.

추가/삭제할 데이터에 대한 업데이트

업데이트해야할 데이터를 비교를 통해 최대한 찾아야한다는 생각에 갇혀서 실제로 업데이트가 꼭 필요한 데이터를 놓치고 있었다. 위 코드에서 4, 5번에 경우에서 추가/삭제할 데이터에 대해서 단순히 추가/삭제만 수행하는 것이 아니라 그로 인한 다른 데이터의 업데이트도 진행해야했는데 해당 로직을 빼먹은 것이다.

작은 것에 연연하다 정작 가장 큰 것을 노친 격이었다. 피드백을 받자마자 아차! 싶었다.

외부 API 요청

위 코드에서는 업데이트할 데이터를 찾을 때마다 외부 API로 요청을 보내서 바로바로 업데이트를 진행했다. 하지만 이럴 경우 계속 외부 API에 요청을 보내야되서 불안정적이기 때문에 할 수 있는 한 업데이트할 항목을 나중에 한번에 업데이트하는 방법을 찾아보라는 것이었다.

외부 API 요청을 내부 함수 호출하듯이 로직에 반영한 것이 실수였다고 생각한다. 다음부터는 외부 API를 요청할 때 횟수를 최대한 줄이고 의존도를 낮출 수 있는 방법을 충분이 생각해봐야할 것이다.


5. 피드백 이후 개선된 코드

사실 피드백 내용이 코드로 구현하기 힘든 것은 전혀 아니었고 로직을 설계할 때 고려해서 반영할 사항이었기 때문에 피드백 받은 내용으로 코드를 고치는데 큰 무리는 없었다. 개선된 코드 로직은 대충 다음과 같다.

# 1. 기존 데이터 로딩
db_data = Data.objects.filter(...)

# 2. 최신 데이터 요청
response_data = ...

# 3. set()를 이용한 기존/최신 데이터에서 추가/삭제/중복 데이터 찾기
db_set = set([data.id for data in db_data])         
response_set = set([data.id for data in response_data])

deleted_data_list = list(db_set - response_set)  # 삭제해야할 데이터
added_data_list = list(response_set - db_set)    # 추가해야할 데이터
update_data_list = list(response_set & db_set)   # 업데이트해야할 데이터

# (+) 최종적으로 업데이트할 항목을 저장하는 집합 오브젝트
total_update_data_set = set()

# 4. 삭제 데이터 삭제
for deleted_data in deleted_data_list:
        deleted_data_obj = Data.objects.get(id=deleted_data)

        # (+) 삭제 전에 해당 데이터와 관련된 데이터들 업데이트하는 함수
        update_another_data(deleted_data)

        deleted_benefit_obj.delete()

# 5. 추가 데이터 추가
for added_data in added_data_list:
        Data.objects.create(**added_data)

        # (+) 추가 후에 해당 데이터와 관련된 데이터들 업데이트하는 함수
        update_another_data(added_data)

# 6. 중복 데이터에 대해서 dictionaries compare를 이용해 
# 	 실재로 업데이트할 것인지 판단
for update_data in update_data_list:
	cur_data = db_data.objects.get(id=update_data)
    cur_data = DataSerializer(cur_data).data
    new_data = response_data[update_data]

	if cur_data == new_data:
    	# 7. 변경할 필요없으면 pass
        continue

	# 8. (+) 업데이트 해야할 항목을 누적
    total_update_data_set = total_update_data_set.add(update_data)

# 9. (+) 최종적으로 업데이트할 항목을 외부 API에 요청 & 업데이트
response_data = requests(..., data=total_update_data_set)

# 10. (+) 업데이트한 항목과 관련된 데이터들도 일괄처리
another_response_data = ...

6. 느낀점 (셀프피드백)

내 스스로 느끼기에 나는 코드를 작성할 때 작성하기 전부터 너무 많은 것을 생각하고 어떻게 작성해야할까 고민하느라 시간을 많이 소비한다고 생각한다. 때문에 작은 어느 예외사항 하나를 처리하기위한 방법을 생각하느라 정작 가장 중요한 부분을 빼먹곤 하는 듯하다.

따라서 프로젝트를 기획할 때 MVP를 세우고 조금씩 기능을 추가해 나가듯 코드를 작성할 때도 가장 중요한 기능이 무엇인지 생각하고 큰 틀을 일단 작성한뒤 조금씩 예외사항을 반영해나가는 방향으로 고치려고 노력해야할 것이고 고민은 조금 덜해야겠다고 생각했다.

생각할 때와 직접 직면했을 때가 전혀 다를 상황일때도 많지 않은가.

생각만 하지말고, 고민만 하지말고, 일단 행동으로 옮기자!!!

profile
kimphysicsman

0개의 댓글