이중연결리스트 next,prev 개념이 헷갈린다.

|

next는 꼬리쪽, prev는 머리쪽?

강의를 보고 문제를 푸는데 이해가 잘 안갔다. 찾아보니 한국어의 앞,뒤 라는 표현이 상황에따라 중의적으로 표현되기 떄문이라고한다.

그래서 헷갈리지않기위해 next는 꼬리(Tail) 쪽, prev는 머리(Head) 쪽이라고 명확히 정해둔다고 한다.

내 상대적 위치에따라 변한다.

그래도 이해가 안갔고 게속 탐구한 끝에..

내 현재 위치에따라 prev,next 개념이 바뀌는거라고 이해했다.

현재 this.head = A 라고 생각하고, 연결리스트 노드가 A - B - C 일때

B의 prev(이전)는 A고 Next(다음)는 C다. 오른쪽 진행방향이라고생각햇을때 현재 위치에따라 바뀐다.

Medusa.js 트러블슈팅과 디버깅의 자세

|

회사 쇼핑몰 개발에 Medusa.js를 활용하던 중, 상품 장바구니 추가 버튼을 누르면 calculate_amount를 찾을 수 없다는 에러 메시지가 발생했다. 원인을 찾기 위해 고군분투하다 결국 팀장님께 도움을 요청하게 되었다. 이 과정을 통해 기술적인 해결책을 넘어 에러를 대하는 개발자의 원초적인 접근법에 대해 큰 배움을 얻었다.

node_modules라는 블랙박스를 여는 용기

예전에 팀장님께서 “프레임워크를 사용하다 원인을 알 수 없는 에러를 마주치면 node_modules를 직접 까서 하나씩 접근해 보는 방법이 있다”고 말씀하신 적이 있었다. 당시에는 그 말이 크게 와닿지 않았으나, 이번에 팀장님이 에러에 접근하는 과정을 지켜보며 그 의미를 제대로 체감할 수 있었다.

팀장님은 에러 로그를 확인한 뒤, 망설임 없이 node_modules 내의 라이브러리 경로를 따라 들어갔다. 부끄럽게도, 라이브러리 내부 코드를 직접 수정하거나 분석할 수 있다는 사실을 처음으로 실감한 순간이었다. 팀장님은 코드의 흐름을 파악하며 의심 가는 지점에 console.log()를 찍어 데이터의 변화를 확인했는데, 라이브러리를 단순히 ‘가져다 쓰는 도구’가 아닌 ‘내가 분석하고 제어할 수 있는 코드’로 대하는 모습이 뭐랄까.. 이게 진짜 개발자구나 나는 아직 코더에서 벗어나려면 멀었구나를 느꼈다.

복잡한 구조 속에서 단서를 좁혀가는 과정

Medusa는 워크플로우(Workflow)와 스텝(Step) 단위로 로직이 구성되어 있고, 내부적으로 SQL 쿼리와 Join이 복잡하게 얽혀 있다. 이 때문에 특정 지점에서 값이 어긋나면 원인을 파악하기가 매우 까다로웠다.

한정된 시간 내에 모든 원인을 완벽히 규명하기는 어려워 디버깅을 잠정 중단해야 했지만, 팀장님이 로그를 통해 좁혀놓은 범위와 단서들은 나에게 결정적인 힌트가 되었다. 흐름을 타고 들어가며 원인을 좁혀가는 디버깅의 정석을 엿본 기분이었다.

결국 범인은 ‘대소문자’ 하나 (with Claude Code)

팀장님과 함께 찾아낸 단서들을 정리하여 클로드(Claude)에 전달했고, 마침내 근본적인 원인을 파악할 수 있었다. 원인은 정말이지 단순하게도 country_code의 대소문자 문제였다.

데이터베이스에 국가 코드를 넣을 때 대문자(KRW)로 저장해 두었는데, 정작 Medusa 내부 로직은 소문자를 기대하고 있었다. 이 사소한 불일치가 워크플로우를 꼬이게 만들었고, 결과적으로 calculate_amount를 참조해야 할 객체를 undefined로 만들어버린 것이었다.

디버깅은 결국 흐름을 쫓는 일이다

이번 트러블슈팅을 통해 두 가지 큰 깨달음을 얻었다.

첫째, 라이브러리 내부를 들여다보는 것을 두려워하지 말아야 한다는 점이다. node_modules는 성역이 아니라 분석 대상일 뿐, 그 안에 암호같은 코드들도 결국 하나씩 차근히 들여다보면 눈에 들어오게되어있다.

둘째, 데이터 정합성의 중요성이다. 대소문자 하나가 거대한 프레임워크의 워크플로우를 멈추게 할 수 있다는 사실에 소름이 좀 돋았다.

이번 경험은 정말 내 개발자 인생에 큰 도움이 되는 경험인것 같다.
팀장님의 디버깅 방식을 곁에서 배우며 한 단계 더 성장할 수 있었던 의미 있는 시간이였고, 역시 에러를 해결하는 과정이야말로 개발자에게 가장 큰 공부가 된다는 것을 느꼈다. 그렇지만 마주치고 싶지 않은것도 에러인것같다..

프로젝트에 첫 별을 얻다.

|

이미지

프로젝트에 첫 별을 얻었다.

숫자로 보면 별것 아니다.
별 하나. 그저 누군가 버튼을 한 번 눌렀을 뿐이다.
하지만 그 버튼 하나가 생각보다 오래 마음에 남았다.

사실 이 프로젝트는
처음부터 거창한 목표를 가지고 시작한 건 아니었다.

완전히 새로운 걸 만들어보겠다는 생각보다는,
이미 누군가 잘 만들어놓은 프로젝트를 보면서
연습 삼아 따라 만들어본 작업에 가깝다.

주제만 조금 바꿨고,
디자인과 레이아웃 구조는 참고했다.
“이런 구성은 왜 이렇게 했을까”,
“이 흐름은 어떤 의도일까”를 하나씩 따라가 보려는 목적이었다.

어쩌면 누군가 보기엔
창작이라기보다는 모방에 가까울 수도 있다.
나 스스로도 그걸 알고 시작했다.

그래도 괜찮다 생각헀다.
무언가를 처음부터 잘 만들어내는 단계라기보다
잘 만들어진 것을 이해하는 단계에 더 가깝다고 느꼈으니까.

레이아웃을 쪼개보고,
구조를 다시 정리해보고,
“나라면 여기서 어떻게 할까?”를 계속 물었다.

그 과정에서
이 프로젝트는 단순한 연습을 넘어서
조금씩 내 것이 되어갔다.

그래서일까.
이 프로젝트에 찍힌 첫 별은
‘대단한 창작물에 대한 인정’이라기보다
이 과정을 지나온 나 자신에 대한 표시처럼 느껴졌다.

완벽하지 않아도,
처음부터 독창적이지 않아도,
누군가는 이 저장소를 열어봤고
그 안에서 뭔가를 느꼈다는 사실.

그게 좋았다.

움직임

|

예전에 어떤 영화를 본 것이 기억난다.
주인공이 영원히 반복되는 꿈속에 갇히는 내용인데,
끝없이 악몽에서 깨어나는 일은
먼저 반복을 인지하는 것에서 시작됐다.
그는 깨어나기위해 갖은 시도를 했지만,
늘 같은 자리로 되돌아올 뿐이였고,
포기하려던 찰나,
주인공은 허무하게도 악몽에서 깨어난다.

그가 움직이기 시작한 순간부터
그가 주인공인 악몽은 ‘반복’되고 있는게 아니었다는
그런 싱거운 이야기였다.

어쩌면 삶도 그 영화와 다르지 않을지도 모른다.
제자리걸음이라 생각했던 어제가 사실은 밖으로 나가기 위한 치열한 움직임은 아니였을까


간절히 바라건대,
익숙함이라는 악몽에 속아 내일을 포기하지 않게 하소서.
어제와 같은 해가 뜬다 해도 그 아래 서 있는 나는 분명 다른 사람이기를,
멈춰 서서 고민하기보다 한 걸음이라도 더 내딛는 뜨거운 하루를 살게 하소서.


nextjs server Action을 적극 도입하게 되었다.

|

우리 회사의 기존 방식은 이러했다.

msa 구조였고, 레일웨이에 서버 컨테이너들을 올리고, 게이트웨이 api를 통해 개발을 했는데, 레일웨이의 공용 config 환경을 dev로 해놨지만 레일웨이 게이트웨이 주소가 https다보니까 nestjs 백엔드 서버에서 보내는 cookie가 프론트 및 브라우저에 저장이 안됬다.

즉 레일웨이 주소는 https고 프론트는 http localhost 환경이고 도메인이 다르니 sameSite:none, secure는 false로 해줬어야 했는데 문제는, sameSite:none 이라면 secure는 무조건 true여야 브라우저가 에러를 안던진다.

그래서 기존 프로젝트의 구조를 바꿨다.

바꾼 구조 1

회사 백엔드 환경을 그냥 회사 백엔드라 칭하겠다.

  • 모든 요청은 Route Handler를 경유한다.

Route Handler도 서버지만 nextjs위에서 돌아가기 때문에 모든 요청을 Route Handler를 경유하게 했다. 즉, 회사 서버 레일웨이 요청을 Router Handler가 담당하게 해줬다.

  • Route Handler에서 Cookie를 설정한다.

jwt token을 요청해서 return 값으로 받고 그대로 다시 setCookies(accessToken)을 해주었다.

회사 백엔드에서 jwt token의 payload 및 유효기간을 설정해놓은 token을 그대로 return해주고있는데, 그 값을 그대로 Route Handler에서 받아서 setCookies를 해준다.

결과적으로 이렇게 Route Handler를 경유하게 해주니까 Route Handler를 오케스트라 형식으로도 쓸 수 있다는 장점이 생겼다.

바꾼 구조 1의 문제점

  • f12 네트워크탭에서의 디버깅이 어려웠다.

Route Handler는 브라우저와 백엔드 사이의 독립된 서버레이어로 동작하기 때문에 네트워크 탭에 요청이 안떠서 디버깅이 힘들었다.

  • Route Handler → lib/api → 프론트 및 SSR → 다시 Route Handler 로 이어지는 어떻게 보면 ‘순환’에 가까운 구조가 되어버렸다.

이렇게 된 가장 큰 배경은, 에러 처리를 프론트에서 직접 제어해야 하는 상황 때문이었다.

  • 프론트에서 에러 처리를 따로해주어야했다.

Route Hanlder에서 에러 처리를 보통 다음과 같이 해준다고 치자

export async function GET(req: NextRequest) {

...

const res =  await fetch('/somthingsAPI')
const data = await res.json()
if (!res.ok) {
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
...

}

이걸 프론트에서 호출하면 아래와 같이 호출하게 될텐데

const res = await fetch('/api/somthingRouteHandler');

라우트핸들러에서 던지는 에러를 다시 감지해줘야하는 번거로움이 있었다.

const res = await fetch('/api/somthingRouteHandler');

if (!res.ok) {
  // 에러처리
}

라우트핸들러는 서버고, 프론트에서 그걸 호출하는것이기 때문에 에러 처리를 해줘야하는건 당연하지만 뭔가 아름답지못한 구조임에는 분명했다.

그래서 현 개발팀장이면서 CTO님께서 주신 의견(또는 조언..) 끝에 구조를 변경하게되었다.

바꾼 구조 2

Route Handler를 거치지 않고 Server Action을 직접 도입하는 방식으로 구조를 재정비했다. Server Action 역시 서버 환경에서 실행되기 때문에, 쿠키 처리와 같은 브라우저 제약에서 비교적 자유롭다.

물론, 이 선택이 개발 측면에서 무조건적인 이득만 있는 것은 아니다. 개발팀장님께 배운 것 중 하나는, 어떤 기술 선택에도 트레이드오프가 존재하며 그 손익을 비교했을 때 전체적으로 이득이 크다면 과감하게 선택해도 된다는 것이었다.

이번 변화로 얻은 점은 다음과 같다.

  • 순환 구조에 가까웠던 요청 흐름을 완전히 끊을 수 있었다.

  • 프론트에서 별도로 에러 처리를 구현할 필요가 없어졌다. (Server Action 내부에서 예외를 제어하며 보다 일관적인 흐름 유지 가능)

반면, 잃은 점도 분명히 존재한다.

  • 고통스러운 리팩토링 과정 (기존 lib/api 구조와 사용 패턴을 대거 수정해야 함)

  • 빠듯한 일정 속에서 추가적인 작업 부담이 발생했다.

마무리

이 문제를 덮어두고 점진적으로 리펙토링하는 방식을 따를 수 있겠지만, 현실적으로 바라봤을때 msa 구조고 그걸 호출하는 service 폴더가 연쇄적으로 묶여있다보니 지금 그나마 코드가 쌓여있지 않을 때 확실하게 잡고가는것이 낫다는 판단을했다.