회사에서 지정된 API 설계 및 배치 프로세스를 작성한 회고록을 작성하고자 한다.

1. 배치 프로세스 

- 배치 프로세스 는 cms 서버에서 돌아간다. cms 서버에서 메모리 할당량이 넉넉해서 cms 서버에서 구축하게 되었고 하나의 고 루틴을 할당하고 ticker를 설정해 매 1분마다 신호를 보내 고 루틴이 실행될 수 있도록 작성하였다.

- 배치의 특성상 프로세스 내에 설정값들을 yaml 파일에 따로 뺴서 작성하고자 했으나 table에 설정하여  설정값에 대해 유연성을 두었다.

- 컨피그 에서 설정한 키값 "delete_cron"을 이용해 값을 가져오고 조회가 실패하고, record not found 에러가 생긴다면 테이블에 인서트를 수행하게 작성하였으나. (이 코드는 추후 버그가 발생할 수 있는 코드가 될 수 있다고 리뷰를 받게 되어 삭제했다.)

- 테이블 구성으로는

  • pk 값 "delete_cron 을 설정해 주고", Is_Use라는 필드를 넣어 사용되는 필드인지에 대해 추가를 했으나 실제 코드상에서 검증하는 부분이 없어 리뷰에서 검증하는 부분에 추가적인 코드작성 요청을 받았었다.
  • code_name을 넣어 이 컨피그 값이 어떤 역할을 하는지에 대해 명확하게 작성하기 위해 작성하였으며
  • code_value는 크론 값 자체를 스트링으로 넣어 배치 가 언제 돌아가는지에 대한 설정값을 추가하였다.
    (크론 [분 / 시간 / 일 / 월 / 요일 ] 총 5자리,  이며  각 자리는 정해진 범위가 존재하며 알맞게 사용되어야 한다. https://en.wikipedia.org/wiki/Cron)
  • 마지막 실행시간
  • Msg 마지막 실행 에서 몇 개의 라인이 삭제되었는지에 대해 히스토리를 남기기 위해 추가했다.

- 크론 의 값을 가져와 cron 라이브러리 의 cron.Schedlue 타입을 가져와 크론 값 parse를 수행한다.

- parse 에서 전해진 Job과 마지막 실행시간을 비교해서 마지막 실행시간 다음 job 배치 의 실행 시간 비교해서 함수 실행 여부를 판별한다.

- 실행 여부가 결정되면 정해진 배치 임무에 따라 배치 삭제를 진행하게 되고 배치 삭제에 대한 결과 로우와 마지막 수행 시간을 각각 업데이트해주면 된다.

 

예전 스프링배치 를 이용해 배치 프로세스를 작성했는데, 그때와는 사뭇 다른 배치 프로세스를 고 루틴을 이용해 이렇게 쉽게 작성하였다. 

물론 배치 인서트 작업이 배로 어렵다는것은 안다 추가적인 필드가 필요하고 실패지점에 대한 관리와, 다음 배치실행 시 실패지점으로 부터 시작하는 등의 추가적인 로직 작업이 필요하지만, 삭제 작업을 위와 같이 고 루틴을 이용해 손쉽게 작성할 수 있다는 점이 매력적인 것 같다.

 

2. API 설게

 

- 콘서트 의 특정 공연에 대해 관리 및 수정을 해야 하는 API를 개발해야 했다. 이에 따라 추가적인 테이블 설계를 하였는데 회사의 모든 테이블 은 연관관계 가 없다. 다시 말해 db 상의 연관관계가 존재하지 않으며, 코드상에 연관관계를 설정해 사용하고 있다. 

이러한 특성에 따라 복합키 의 사용이 생각보다 많이 있는데 어떤 테이블 에는 복합키 에 설정된 값과 중복해서 인덱스를 다시 설정해서 사용하는 게 아닌가? 복합키 설정에 있어서 순서가 매우 중요하다. 

예를 들어 "a,b,c,d" 를 이용해 복합키를 설정하게 된다면 b, b and c , b and d , c and d와 같이  조회를 한다면? 모두 all type으로 전체 로우를 조회하게 된다.

대신 a 를 포함한 where 조건절을 사용하게 된다면 range 혹은 const 타입으로 인덱스를 활용해 조회를 수행하게 된다.

MYSQL에서 복합키 설정을 첫 번째 열을 기준으로 인덱스가 생성되는 특징이 있는데 이를 고려하지 않고 테이블 설계, 쿼리를 작성하다 보니 나중에 실행계획서 작성할 때 인덱스 활용이 없는 경우가 발생되었고, 추가적인 인덱스 설정을 추가하게 되었다.

 

- 핵사고 날 아키텍처 가 적용된 프로젝트에 적응하기 가 너무 힘들었다. 각종 어댑터 가 존재해, 외부 시스템과 상호작용하게 된다. 도메인의 메인 비즈니스 로직이 외부요소에 의존하지 않도록 분리되어 있는데 이렇게 되다 보니 , 어느 패키지에서 어떤 역할을 할지 어떤 계층이 될지에 대해 고민하는 게 생각보다 많은 시간이 할애되었다.
spring-mvc 패턴, 레이어드 아키텍처에 적응되어 있는 나에게, 템플릿 패턴, 퍼사드 패턴 등등 디자인 패턴이 범벅되어버린 프로젝트 구조에 있어 코드를 해석하는 게 너무 힘들었다. 

인터페이스를 이용해 웬만한 호출들은 전부 추상화되어 있기 때문에 언제는 미들웨어의 인터페이스가 언제는 레포지토리의 인터페이스가  발생하기 에 어느 부분을 수정을 해야 하는지 어디에 추가하는지 이게 이벤트 발행은 가능한지에 대한 의문이 너무 많이 들었다.

 

모든 프로젝트가 동일한 패키지 구조와 코드 플로우를 가져가기에 하나의 프로세스 만 이해하게 되면 나머지는 그래도 이해하기 쉬웠다. 

메인 고 루틴 과 리퀘스트 발생에 따른 고루틴 생성과 서비스 핸들링, 카프카 설정에 따른 어드민 생성 호출 시 이벤트 발행과 같은 코드의 호출, 이벤트 컨슘에 따른 핸들링을 모두 작성하다 보니 고려해야 할 사항이 많았으며 

msa 가 적용되어 프로세스 가 각 서비스 별로 분리되어 있다. 이에 따라 공통으로 사용하는 부분을 common 패키지로  관리되고 이에 따라 common 패키지 구조체 설정이 생각보다 어려웠다. 

멀티모듈과 유사하게 common 패키지는 다른 모듈? 프로세스 컴파일 간에 같이 포함되게 된다. 따라서 가볍게 유지하는 것이 상식적으로 맞지 않겠는가? 이에 따라 고 의특성상 메서드 작성을 어디에 해야 할지에 너무 큰 고민이 되었다. toDomain? 이 된다면? 바뀌는 코드에서 가야 하는 코드에 대한  구조체에 작성하면 된다. 그런데 이 toDomain 이 다른 프로세스, 모듈에서 사용되는가? 그것은 또 아니다. 이렇게 되다 보니 common 패키지에 무엇을 작성해야 하는지에 대한 혼동이 온다. 

그 외에도 특정 이벤트 혹은 인터페이스 타입에 맞게 변경해야 하는 경우가 있다면? 팀 내부적인 특정 컨벤션이 존재하지 않다 보니, 중구난방으로 코드를 작성한 게 없지 않아 존재한다.

사수님 : "중복으로 사용하게 된다면 common에 작성하세요". 

중복으로 사용되지 않지만 함수의 의미상 common 에 위치해야 하는데 그게 아니기에 타입을 따로 받아서 새로운 타입을 만들어서 사용하게 되었는데. 참 아이러니하다.

여기서 고의 강타입 언어의 단점을 너무나도 크게 느껴졌다. db에서 가져오는 타입, 도메인으로 변경하는 타입, 이벤트 발행하는 타입에 대한 모든 변경 코드가 필요하게 되고 이에 따른 코드 작성이 어마어마하게 많다. (물론 코파일럿이 대부분 도와주었다.) 그렇다 보니 이벤트 발행에 항상 동일한 to 함수가 사용되는 것이 아니고 이에 따른 서로 다른 변환함수가 필요하게 된다. 

작성할 코드가 많고, 재사용 가능한 코드에 대해 고민을 많이 하게 되다 보니 생각보다 많은 시간을 소모했다.

 

- go 메서드 의 receiver method, value method 즉 포인터, 일반 함수라고 생각하면 된다. go uber 가이드에서 제공하는 방법으로는 하나의 방법으로 통일할 것을 권장하지만 아무래도 프로젝트에 참여한 사람이 생각보다 많다 보니 아무래도 혼합된 방법으로 구조체의 함수를 작성하게 되고 이에 따라 고 랜드에서 경고가 있는데 하나의 함수선언 방식으로 통일해 달라는 경고이다. 

이에 관해 사수 님께 여쭤보고, 이번 개발건에 대해서는 전부 receiver로 작성하게 되었다. 

 

- [에러처리]

dvt, cvt, qa 팀 의 모든 테스트를 통과했고 상용배포까지 마무리가 되었지만 갑자기 날아온 문의사항, 데이터가 삭제된다는 문의였다.

어드민 사용자 입장에서 api 호출에 대해 에러응답이 발생된다면 마치 데이터가 삭제된 것처럼 나오는데 그 현상인데 데이터가 삭제된다는 문의에 식은땀이 흘렀다. 팀장님 빠르게 로그를 검색하고, 에러의 원인을 파악하셔서 손쉽게 해결되었지만. 문제의 발생은 내가 작성한 코드에서 생겼다.  db 조회에 대한 err를 받고 그다음 로직에서 err에 대한 핸들링 없이, 코드 작성이 되어있다 보니 memory invalid exception으로 패닉으로 떨어진다.

비즈니스 로직에서 특정 칼럼 값은 항상 이벤트 발생으로 방송센터에서 넣어주는 값으로 알고 코딩을 해서 위와 같은 결과가 발생했지만, 에러에 대한 핸들링을 하지 않아서 크게 혼났다.

 

- [테스트 코드]

tdd를 적용할 시간조차 없었다. 단순 함수를 작성하고 이게 내가 원하는 쿼리가 발생하는지 에 대한 검증 정도만 있었지 tdd의 t 자조차 꺼낼 수가 없었다.. 그러다 보니 테스트 코드를 작성했어도 단순 확인하는 용도이기에 커밋을 전혀 하지 않았다.

프로젝트 종료 이후 세미나 간에 tdd 주제를 맡아 발표를 하게 되었고, tdd를 과연 저 프로젝트하는 동안 적용이 가능한가에 대한 나의 답은 NO이다.

저런 짧은 시간과, CDR 문서를 비롯한 각종 문서 지옥에서 tdd와 cdr 문서 작성 등  과 같은 중복된 일을 해야 하는가? 에 대한 의문이 생긴다. 

내가 작성하는 신규 api, function, module 은 테스트 할 수 있다. 그 코드에 대한 사이드 이펙트에 대해 어느 정도 예상을 하고 코드 작성을 하게 되니깐, 그런데 내가 작성한 것이 아닌 코드에 대해 테스트 코드를 작성하는 것은 정말 개발자 역량에 따른 천차만별이라고 생각한다. 

물론 코드를 보고 어떤 의도로 작성이 되었는지 어떤 문제점이 생길지에 대해 코드에 대한 테스트 코드는 작성할 수 있다. 고작 API 개발이니깐 그런데 이런 API 가 아닌 시스템 비즈니스 로직을 검증해야 한다면?????
또한 API 개발 간에도 문의사항으로 지속적인 로직 수정이 발생되었고, 이에 따른 테스트 코드 관리는 내가 너무 벅찬 부분이라고 생각되었다.

 

-[gorm]

정말 많은 삽질을 했다. transaction 관리를 db 호출할 때마다 개별적으로 해주어야 한다. 프로젝트 특성상 10개의 db 풀이 상주되고 있으며 상황에 따라 최대 20개까지 늘어날 수 있다. 

mysql 기본 격리 수준인 Repeatable Read에 따라 하나의 트랜잭션과 다른 트랜잭션 에서의 조회 결과는 다를 수가 있다. 따라서 nested transaction을 구성할 때 생각보다 조심해서 작성되어야 한다. 이 부분은 추후 포스팅에서 자세하게 다뤄보겠다.

 

 

 

 

'개발일지' 카테고리의 다른 글

FRP 적용  (0) 2024.12.31
10월 개발일지  (1) 2023.11.01
7월개발~ 8월초  (0) 2023.08.16
6월 개발  (1) 2023.07.09

+ Recent posts