본문 바로가기
개발생각

[개발생각] REST API URI 설계에 대한 고민(feat. Optional Chaining ?. 연산자)

by Johnny's 2023. 7. 12.

개발 환경 참고

Frontend :  Bootstrap(template), jQuery, ejs(template)

Backend : node, express(framework)

DB : mysql

 

개요

상품의 일시품절 상태를 설정하고, 확인날짜(날짜 선택 지정)를 설정하는 기능은 이미 구현되어 있는 상태

상품 상세 페이지에서 일시품절을 걸고, 확인날짜를 선택하고 수정하면 설정일자가 현재시간(NOW())으로 업데이트 된다.

 

문제점

상품 상세페이지에서 수정을 하면 설정일자가 현재시간(NOW())으로 업데이트가 되는데, form 형식으로 데이터를 보내고 수정하는 방식으로 개발되어 있기 때문에 상품명, 공급가 등 그 외 다른 상품정보를 수정하더라도 설정일자가 현재시간으로 업데이트가 된다. (설정일자가 설정일자가 아닌 상황..) 따라서, 해당 수정 기능을 상품 상세페이지에서 분리하고, 일시품절목록에서 별도로 추가, 삭제하는 기능을 구현한다.

 

일시품절 상태 : out_of_stock = 1 (일시품절) or 0(일시품절해제)

확인날짜 : check_date (TIMESTAMP) - default : NULL

설정일자 : ooc_date (TIMESTAMP) - default : CURRENT_TIMESTAMP

 

초기에 개발할 때는 빠르게 기능구현을 하기 위해 추가면 /add, 삭제면 /delete 등 의 방식으로 구현만을 위한 개발을 했었다. 당시 모르는 부분도 많았고 URI 설계에 대한 고민을 깊게 하지 않았다. 하지만 요즘에는 변수명을 짓더라도 누구나 봐도 쉽게 이해할 수 있게 짓기 위해 노력하고, 좋은 코드를 짜기 위해 노력하는 편이다.

 

기능 정리

1. 클라이언트한테는 일시품절 상품추가 및 삭제할 수 있는 기능을 제공

2. 실제로는 DB에서 UPDATE를 수행해야하는 상황
    - 추가 -> update : out_of_stock=1, ooc_date(NOW)
    - 삭제 -> update : out_of_stock=0, ooc_date(NULL), check_date(NULL)

 

여기서 중요한 것은 클라이언트 입장에서는 추가, 삭제이지만 백엔드 입장에서는 모두 수정(UPDATE)에 해당된다는 것이다.

 

처음에는 단순히 추가해서 수정하는 기능은 patch삭제해서 수정하는 기능은 delete 메서드를 사용하려 했지만, 메서드 자체와 DB 쿼리수행과 매칭이 안되고 URI를 2개나 만들어야 하고, 뿐만 아니라 컨트롤러, 모델(SQL)도 불필요하게 2번씩 작성해야 된다는 부분이 불필요하다고 생각되었다. 특히 클라이언트 입장에서는 delete 메서드지만 백엔드 입장에서는 update를 수행하기 때문에 더더욱 아니라는 생각이 들었다. (당연한 이야기)

 

routes.js

 

그렇다면 위의 코드에서 단순히 메서드를 patch, delete → post 로만 수정하면 해결될 문제일까? 아니다. 프로세스 처리하기 위해 post 메서드를 사용하지만 add, remove로 구분을 하면 결국 각각의 컨트롤러를 타야하고, 불필요한 코드가 중복이 될 수 있다. 결국에는 일시품절 상태인 out_of_stock을 1 또는 0으로 업데이트하는 부분인데 1개의 라우터, 1개의 컨트롤러,  1개의 모델로 설계하고 싶었다.

 

routes.js

그래서 생각해낸 것이 status 값을 파라미터로 전달하는 것이다. status 값으로 1 또는 0을 전달해서 editOutofStock 컨트롤러에서 추가, 삭제 기능을 구현하는 것이다. (addOutofStock, removeOutofStock → editOutofStock)

 

이렇게 되면 프론트쪽에서는 동일한 URI에 status 값만 구분해서 보내면 된다.

 

controller

 

model

 

status 값, 즉 out_of_stock 값을 0 또는 1로 구분해서 보내주면 1 컨트롤러, 1 모델로 불필요한 코드를 줄일 수 있다.

위의 코드를 보면 알겠지만, 추가해야 할 상품이나 삭제해야 할 상품을 각각  select_add_arr, select_delete_arr 배열에 담아서 join() 메서드를 이용해서 "" 으로 연결해준다. 이는 모델에서 sku IN ("A상품", "B상품", "C상품") 을 여러개 선택에 대한 부분을 처리하기 위함인데 여기서 문제가 있다.

 

한 컨트롤러에서 추가(select_add_arr) 또는 삭제(select_delete_arr) 기능을 구현했는데, 상품의 일시품절 등록을 해제(삭제) 할 경우 아래와 같은 에러가 발생했다.

 

이는 select_delete_arr의 키값을 받을 때 select_add_arr의 키 값이 없어서 발생하는 오류였다. 그래서 위 코드에서 보면 알 수있듯이 ?.연산자(Optional Chaining) 로 처리를 하였다. 

 

정확하게 이해하기 위해 같은 환경을 구성해서 테스트를 해봤다. (TDD 아님 주의)

삭제를 할경우, 프론트에서는 status : 0, select_delete_arr 값을 전달한다. 아래와 같이 select_add_arr 키 값이 없기 때문에 에러가 발생한다.

 

그렇다면 상품의 일시품절을 등록(추가) 해야하는 경우는 괜찮을까? 결론은 괜찮다.

 

그 이유는 OR 연산자( || )에 대한 이해도 필요하다. 둘 중 하나가 참이라면 true 값이라는 것은 누구나 알 것이다.

true || true // true
true || false // true
false || true // true
false || false // false

위에서 2번째의 true 값3번째의 true 값에는 약간의 차이가 있다. true가 앞에 있는 경우, 바로 true 값이기 때문에 뒤에 값이 상관없이 무조건 true 값이 된다. 자바스크립트 언어는 위에서 아래로, 왼쪽에서 오른쪽으로 코드를 순서대로 읽는데, 이 경우에는 뒤에 값을 읽지 않고 바로 true 값을 준다. 즉, 쉽게 말해 두번째 값이 뭐든 상관이 없다는 뜻이다.

위의 코드에 서보면 이미 a 변수에 10이 할당되어있고, true 값이기 때문에 b 값 상관 없이 10을 출력한다. (변수 b가 선언이 안되어 있음에도 불구하고 에러가 발생하지 않는다.)

위의 코드처럼 a와 b의 순서만 바꿔도 바로 변수 b가 정의되지 않았다고 에러가 발생하는 것을 알 수 있다.

그래서

let sku = req.body.select_add_arr?.join("','");

?. 연산자는 옵셔널 체이닝 연산자로, 객체의 프로퍼티를 안전하게 접근할 수 있도록 해준다.

해당 객체가 nullish 즉, undefined나 null인 경우에 TypeError 대신에 undefined을 얻게 된다. 그렇지 않은 경우에는  join() 메서드를 호출이 가능한 것이다.

undefined || req.body.select_delete_arr.join('","')

즉, 첫번째 값이 undefined 이 되고, 두번째 req.body.select_delete_arr의 값을 가져올 수 있게 되는 것이다.


사실 기능상으로는 복잡한 비지니스 로직이 아니고, 간단하게 업데이트 정도만 해주는 기능이다. 하지만, 프론트 입장에서는 추가, 삭제의 의미가 담겨져 있는 기능이고, 백엔드 입장에서는 수정을 처리하는 기능이라고 볼 수 있다. 간단한 기능을 구현하는 과정이라도 최대한 누구나 봐도 이해하기 쉽게 개발하는 것 또한 매우 중요하다고 생각한다. 원활한 협업을 하기 위해, 원활한 유지보수를 하기 위해 오늘도 좋은 코드를 짜기 위해 노력한다. (별거 아닌거에 너무 쓸데 없이 많이 고민하는 것 또한 낭비이다.) 그리고 당연한 이야기이지만, 기본은 중요하다.

 

* 참고

- Optional chaning (?.) - MDN Doc

댓글