포시코딩

1월27일 - 궁금증 해결 본문

TIL

1월27일 - 궁금증 해결

포시 2023. 1. 27. 00:20
728x90

오늘은 묵혀놓은 궁금한 것들을 해결하는 시간을 가져보았다.

구글링 및 그동안 공부한 것들 종합 + 튜터님한테 직접 물어보는 방법을 통해

아래 다섯가지 상황과 결과를 정리해보았다.

 

api 통합 vs 분리

개요

  • 대기중인요청
  • 진행중인 요청
  • 완료된 요청

위 세가지 요청 리스트 api에 대해

정리하던중 다시 path를 깔끔하게 재할당 해줘야하는 상황이 생겼는데

두가지 선택사항이 생겼다.

 

방법 찾기

1. 라우터에서 부터 나눠지게?
/api/orders/waiting?p=1
/api/orders/doing
/api/orders/done?p=1

 

2. GET /api/orders로 들어가 컨트롤러에서 type에 따라 다른 service 호출?
/api/orders?type=waiting&p=1

/api/orders?type=doing

/api/orders?type=done&p=1

 

두 방법의 차이를 정리해봤다.

방법 1.

장점: 라우터부터 분리되어 처리되므로 직관적이고 코드가 매우 깔끔해진다.

단점: 엔드포인트가 많아진다는 점
api의 개수가 많아지며 만약 waiting, doing, done 3가지가 아니라 더 많을 경우 다 각각 구현해야함

 

방법 2.

장점: api에 대해 하나만 사용해도됨.
더 추가되더라도 type에 대한 query parameter를 받아 처리해주면 문제 없음

단점: 각각의 type에 대한 로직, 예외처리가 달라지는 과정에서 어려움을 겪을 수 있다.

 

결론

뭘 선택해도 정답인 느낌이라 튜터님의 도움을 받았다.

대답은 어차피 지금 상황에서 3개 이후로 안늘어나는거면 1번의 방법을 추천하셨는데

10개 이상으로 많은 상황에서도 최대한 1번의 방법을 사용하되

query parameter로 구분할 수 있는건 2번 방법으로 받아들여

typescript의 type으로 필터링 하여

const getOrders = (filter: Filter) => {
  // filter -> 안에는 query parameter 넣어서 
}

 

이런식으로 처리하는 방법을 추천해주셨다.

아직 TypeScript를 써서 본격적으로 프로젝트를 만들어보질 않아 

정확한 예시 코드가 떠오르진 않지만 대충 어떤 느낌인진 알 것 같다.

해결!

 

service -> repository 호출 방법 고민

1. 여러 개의 repo를 부르는 방법

const { User, Token, Order } = require('../sequelize/models');

class UsersService {
  usersRepository = new UsersRepository(User);
  tokensRepository = new TokensRepository(Token);
  ordersRepository = new OrdersRepository(Order);

2. 한 개의 repo에 여러 개의 model을 넣는 방법

const { Order, User } = require('../sequelize/models');

class OrdersService {
  ordersRepository = new OrdersRepository(Order, User);

1번의 고민과 마찬가지로 취향차이 문제.

위 두 가지 방법 다 잘 작동하는데 

앞으로 있을 프로젝트에서 어떤 방법을 사용할지 결정이 안난 상태였다.

 

애초에 나는 1번의 방법으로 전 프로젝트를 구성했었는데

생각보다 많은 팀이 2번의 방법으로 프로젝트를 구현해서 더 고민이 된 문제였음

 

1번의 경우엔 해당 service를 호출할 때 수많은 repo 인스턴스를 만든다는게 걸렸고

2번의 경우엔 해당 repo의 이름과 맞지 않는 메소드가 그 안에 위치하고 있는 경우가 생긴다는게 걸렸는데

 

결국 1번의 방법으로 진행하기로 했다.

 

이유는, 우선 repo 하나에는 두개 이상의 model이 들어가면 안된다는게 요지였고

무엇보다 2번을 고려했을 때 걸렸던 성격과 맞지 않는 메소드가 자리 잡는다는게 결정에 영향을 미쳤다.

 

new ~ 로 새 인스턴스를 만드는게 불편한건

어차피 추후 Nest.js를 배우게 되면 모두 해결될 문제니 당장은 참는 것으로 결론.

해결!

 

파일명 고민

(트리구조)

위 두 같은 구조에 대해 파일명을 어떻게 해야할지에 대한 고민이었는데

그냥 바로 .controller, .service를 붙이는걸로 결정했다.

 

이유는 다음과 같다.

  • 종류가 더 많아질수록 구분하기 용이해짐
  • 파일명 길면 어쩔거임
  • 확장자와 비슷한 역할로 받아들일 수 있다.
    실제로 bash를 써서 개발한다면 파일 찾을 때 도움 될듯
  • 굳이 안 쓸 이유가 없다.

해결!

 

쿠키 생성 주체

https://github.com/9hezo/save_my_keyboard/issues/31

 

쿠키를 생성하는 주체 · Issue #31 · 9hezo/save_my_keyboard

save_my_keyboard/app/src/controllers/users.controller.js Line 32 in 3d43b9d res.cookie('accessToken', response.accessToken); 이후 프로젝트에서 프론트가 완전 분리 된다면 백엔드에선 쿠키를 만들어 전달하지 않습니다. 응

github.com

이전 프로젝트에서 지적 받았던 피드백이다.

요즘엔 거의 다 프론트엔드 서버 - 백엔드 서버가 분리되기도 하고

애초에 app에서는 쿠키 자체가 존재하지 않아

서버에서 res.cookie를 통해 쿠키 설정한다 해도 로그인 유지가 되지 않는다. (폰 유저 포기할거임?)

app에다가도 걍 res.json으로 만든 토큰 던져줘서 알아서 세팅하게끔 하자라는 느낌

 

before

controller

res.cookie('accessToken', response.accessToken);
res.cookie('refreshToken', response.refreshToken);

 

after

controller

return res.status(response.code).json({
  accessToken: response.accessToken,
  refreshToken: response.refreshToken,
});

login.js

fetch('/api/users/login', {
    method: 'POST',
    // ...생략
    .then(async (res) => {
      // ...생략
      document.cookie = `accessToken=${res.accessToken}`;
      document.cookie = `refreshToken=${res.refreshToken}`;
    })

 

위 피드백을 받은 후 변경한 모습인데 여기까진 잘했으나 한가지 문제가 있었다.

authMiddleware

if (accessToken인증실패) {
    // ...생략 (대충 refreshToken으로 저장해놓은 userId 가져오는 로직)
    const newAccessToken = await TokenManager.createAccessToken(tokenInfo.userId);

    // 새롭게 발급받은 accessToken 세팅 필요
    // 클라이언트에서 저장하게 변경되어야함
    res.cookie('accessToken', newAccessToken);
  }

어떤 요청에 대해 로그인 확인을 할 때 accessToken만 만료됐을 경우

refreshToken을 통해 다시 재발급받으며

자연스럽게 해당 요청을 수행하는데

이 과정에서 accessToken 세팅하는 부분이 아직 서버에서 직접 설정해주고 있는 상태였다.

 

이런 경우 어떻게 해야하나 궁금해서 튜터님한테 조언을 구했는데

그냥 이것도 해당 요청을 들어주지 말고 일단 accessToken을 리턴시키고

프론트엔드에서 받아서 로그인 처리를 먼저 하게끔 유도하는게 맞다고 하셨다.

 

그럴경우 내가 만약 프론트엔드라면 요청했는데 accessToken을 응답받으면

다시 저장한다음 재요청을 하여 

사용자 입장에서는 자연스러운 흐름이 되도록 할 것 같았다.

 

근데 이것도 개발자나, 회사마다 다 방법이 달라서 내가 생각한 방법도 맞고 

진짜 여러가지 방법이 있을거라고 하심

 

어쨋든 결과적으로는 그냥 서버에서 처리할 생각 하나도 하지말고

token은 다 넘겨버려라! 가 결론

해결!

 

status 관리

위 status 값에 대해 

아래와 같은 피드백을 받았다.

 

https://github.com/9hezo/save_my_keyboard/issues/33

 

값을 알아 볼수 있는 값으로 사용 · Issue #33 · 9hezo/save_my_keyboard

save_my_keyboard/app/src/services/orders.service.js Line 34 in 3d43b9d changeStatus.status = 1; 1, 2 이런 status 는 나중에 무엇을 의미하는지 알기가 어려워져 유지보수가 힘들어지고, 버그의 원인이 되는경우가 많습니

github.com

 

처음에는 db에 저장되는 값은 괜찮지만

코드로서 개발자가 봤을 때 가독성이 좋지 않아 발생하는 문제로 인식해서

 

최근에 배우는 TypeScript의 enum을 통해 해결할 수 있다고 생각했다.

실제로

enum Status {
  waiting,
  collected,
  collecting,
  delivering,
  delivered,
  cancelled
}

let status1: Status = Status.waiting;
let status2: Status = Status.collecting;
console.log('status1: ' + status1);  // 0
console.log('status2: ' + status2);  // 2

위 코드를 통해 db와 api문서 수정 없이 해결할 수 있을거라고 봤는데

물론 이 방법도 괜찮은 방법이지만

 

튜터님이 말하고자 했던건

이제는 여러 성능이 받쳐주기 때문에 굳이 status 값을 tinyint와 같은 값으로 사용하지 않고

그냥 'waiting', 'cancelled' 같은 varchar를 써도 된다는 입장이었다.

 

예로

어떤 문제가 발생해서 디비상에서 직접 특정 status의 사용자들에 대해 

어떤 값을 수정해줘야 한다는 상황이 발생했을 때

디비상에서 status 숫자만 막 0, 1, 4, 2, ... 이런식일텐데 

어떻게 하나하나 다 구별할거임? 이거 다 외울거냐고

그리고 만에 하나 외웠다고 생각해서 쿼리문 써서 딱 엔터 눌렀는데

사실 그게 아니었다면? 대형 사고가 나는 것이다. 

이런 휴먼 에러를 방지하는게 

그 용량 조금 때문에 고민하는거보다 훨씬 낫다는 의견을 주셨다.

 

물론 위의 얘기들은 모두 다 개인 또는 회사의 생각 차이가 있을 수 있다는 점을 같이 얘기 하셨는데

내 경험상으로도 실제로 디비상에서 직접 값을 수정하는 일들이 가끔씩 있었던 경험을 생각해봤을 때

틀린 말이 아니었다. 

때문에 당장은 status를 varchar로 바꾸는건 너무 큰 공사라 

이번만 숫자값으로 사용하고

다음 프로젝트들에서는 varchar값을 사용해보는걸로 결론을 지었다.

해결!

728x90