Spring에서 Node.js(Fastify)으로 전환하기(5) - Playwright로 E2E 테스트 자동화

들어가며 지난 글에서 Plan 도메인의 CRUD API를 모두 구현했다. 이제 과제를 시작하기 전에 진행했었던 Playwright MCP를 에이전트에 붙여 E2E테스트 자동화 하는 과정을 현재 앱에 적용하여 보겠다. Playwright Playwright는 마이크로소프트에서 만든 브라우저 자동화 도구다. 원래는 웹 페이지를 자동으로 클릭하고, 폼을 채우고 스크린샷을 찍는 등의 E2E 테스트를 위해 만들어졌다. Selenium이나 Puppeteer 같은 도구와 비슷하지만 다음과 같은 몇 가지 장점이 있다. 빠른 실행 속도 - 다른 도구들보다 빠르다 안정적인 테스트 - 요소가 나타날 때까지 자동으로 기다려준다 다양한 브라우저 지원 - Chromium, Firefox, WebKit 모두 지원 API 테스트 가능 - 브라우저 없이도 HTTP 요청을 보낼 수 있다 플레이라이트는 request fixture라는 걸 제공해서, 실제 브라우저를 띄우지 않고도 API 테스트를 할 수 있다. ...

2025년 11월 14일 PM05:23 · PolarBear

Spring에서 Node.js(Fastify)으로 전환하기(4) - Plan 도메인 구현하기

들어가며 지난 글에서 환경변수 관리와 데이터베이스 플러그인 설정까지 마쳤다. 이제 비즈니스 로직을 구현할 차례다. 스프링에서는 Controller, Service, Repository로 계층을 나눠서 개발했었는데 fastify에서도 비슷한 구조를 유지하려고 했다. Controller 대신 Routes라고 부르고, DTO 클래스 대신 Zod 스키마를 사용하는 등 약간의 차이점은 있었다. 이번 글에서는 Plan 도메인의 CRUD API를 node.js 환경으로 전환했던 과정을 정리해보려고 한다. Zod를 사용하여 스키마 작성 핵심로직, 라우트 등을 먼저 구현하기 전에 스프링 코드와 대칭되는 DTO를 작성했다. 라우트 내부에서 schema 블럭에 요청DTO의 형식을 정할 수도 있지만 Zod라는 것을 사용해봤다. ...

2025년 11월 13일 PM10:34 · PolarBear

Spring에서 Node.js(Fastify)으로 전환하기(3) - 환경변수와 데이터베이스 플러그인 설정 Feat. Spring vs Fastify

들어가며 지난 글에서 prisma를 설치하고 마이그레이션까지 성공했다. 이제 실제로 애플리케이션에서 데이터베이스를 사용할 수 있도록 연동 작업을 진행해야 한다. 스프링 부트에서는 application.yml에 데이터베이스 설정만 해두면 자동으로 연결이 되고, 생성자 주입으로 Repository를 주입받아 사용하면 끝이었다. 하지만 fastify는 그런 자동화가 없다. 모든 걸 직접 설정해야 한다고 한다. 이번 글에서는 환경변수를 타입 안전하게 관리하고, prisma를 fastify 플러그인으로 등록하는 과정을 정리해보려고 한다. 환경변수 관리의 문제 기존에는 server.ts에서 이렇게 환경변수를 사용하고 있었다. const PORT = Number(process.env.PORT) || 3000; 문제는 이렇게 사용하면 ...

2025년 11월 12일 PM09:25 · 🛠 업데이트: 2025년 11월 13일 PM09:25 · PolarBear

Spring에서 Node.js(Fastify)으로 전환하기(2) - Prisma 연동

들어가며 이전 포스팅에서 프로젝트 환경 셋업을 진행한데 이어, 데이터베이스 연동을 해보려고한다. 스프링에서는 jpa와 hibernate를 사용해서 엔티티 관리가 비교적 편했는데, node 생태계에서는 어떤 ORM이 좋을지 고민이 많았다. TypeORM도 고려했지만 이전에 잠깐이나마 회사에서 prisma를 사용해보기도 했고, 타입 안정성과 직관적인 스키마 작성 방식이 마음에 들어서 prisma로 결정했다. 이번 글에서는 prisma를 처음 설정하면서 겪었던 시행착오와 해결 과정을 공유하려고 한다. Prisma 초기 설정 패키지 설치 Prisma를 사용하려면 두 가지 패키지가 필요하다. npm install prisma @prisma/client prisma: CLI 도구 및 스키마 관리 @prisma/client: 실제 쿼리를 실행하는 클라이언트 Prisma 초기화 설치가 끝나면 초기화 명령어를 실행한다. ...

2025년 11월 11일 PM09:59 · PolarBear

Spring에서 Node.js(Fastify)으로 전환하기(1) - 프로젝트 환경 셋업

들어가며 최근 스프링부트로 구현된 REST API 프로젝트를 node.js, fastify 환경으로 마이그레이션 하고, 클로드 코드 및 코파일럿 등의 에이전트에 PlayWright MCP서버를 연동하여 E2E 테스트를 자동화하는 과제를 진행하게 되었다. 과제 진행을 하면서, 새롭게 학습하고 그 과정 속에서 힘들었던 부분 등을 기록하고자 한다. 이번 글에서는 본격적인 마이그레이션 작업에 앞서, Node.js + TypeScript + Fastify 기반의 프로젝트 환경을 세팅한 과정을 정리해보려고 한다. 목표 기존 Spring Boot 프로젝트는 전형적인 레이어드 아키텍처로 구성되어 있다. Controller -> Service -> Repository -> Entity (JPA) 이 구조를 최대한 유지하면서 node 환경으로 옮기는 게 우선적인 목표다. 다만 스프링의 의존성 주입이나 자동 설정 같은 편의 기능이 없으니 그 부분을 어떻게 해결할지 고민이 많았다. ...

2025년 11월 10일 PM07:55 · PolarBear

Facade 패턴, SRP 원칙 기반으로 리팩토링을 해보자

들어가며 현재 서비스중인 AuthService가 387줄의 거대한 God Object로 성장해버렸다. 로그인, 회원가입, 소셜 로그인, 이메일 찾기, 비밀번호 변경 등 인증 관련 모든 기능을 한 클래스에서 처리하다 보니 테스트도 어렵고 수정할 때마다 긴 코드를 찾아봐야 하다보니 유지보수에 있어서 너무 불편했다. 이를 SRP 원칙에 따라 3개 서비스로 분리하고, Facade 패턴으로 재구성한 과정을 기록했다. 문제 상황 God Object가 된 AuthService AuthService - 387줄 - 의존성: 11개 - 담당 책임: 6가지 1. 로그인/로그아웃 (토큰 관리) 2. 회원가입 (검증 + 외부 API + DB 저장) 3. 소셜 로그인 (카카오/구글/네이버) 4. 이메일/전화번호 찾기 5. 이메일 인증 발송/확인 6. 비밀번호 변경 한 클래스가 6가지 책임을 가지고 있고 명백한 SRP 위반이다. ...

2025년 11월 3일 PM08:56 · PolarBear

템플릿 메서드 패턴을 활용하여 리팩토링을 해보자

들어가며 현재 사내에서 서비스중인 앱의 백엔드 코드에서 중복되는 코드가 심하게 일어나는 부분이 있었다. 회원과 관련된 추가정보(경력, 학력, 논문 등)들이 총 7개가 있는데 이 7개의 도메인들의 CRUD 로직이 100% 동일하다는 것에 템플릿 메서드 패턴을 적용하여 리팩토링을 해보면 어떨까 생각이 들었다. 추가정보 도메인 총 7개의 기존 서비스 로직의 코드 라인에는 생성, 조회, 리스트조회, 수정, 일괄 수정, 삭제, 일괄 삭제, 소프트 삭제, 삭제복원을 포함하여 약 210줄의 코드가 동일한 로직으로 중복되고 있었다. 문제 상황 분석 중복 코드의 심각성 7개의 서비스 클래스가 거의 동일한 구조를 가지고 있었다. 예를 들어 경력(Career) 서비스와 학력(Education) 서비스를 비교해보면 다음과 같았다. ...

2025년 10월 30일 PM09:13 · PolarBear

외부에서 AWS ElastiCache Redis 접근하기 (SSH 터널 방식)

들어가며 현재 서비스중인 앱의 세션 상태가 불안정한 이유로, 캐시서버로 토큰관리 전략을 하고있기에 운영중인 AWS ElasticCache RedisOSS에 접근하여 확인해보기로 했다. 먼저 여느때와 다름없이 외부에서 접근하기 위해 인바운드 규칙을 추가하여 레디스 인사이트에서 접근해보니 연결이 안되는 것이다. Public Subnet에 있는 일반적인 RDBMS나 SSH로 서버접근 등은 인바운드 규칙을 추가하는 것만으로도 가능한데, AWS ElastiCache(OSS Redis)는 기본적으로 VPC(Virtual Private Cloud) 내부 전용 서비스이기때문에, 퍼블릭 IP 자체가 존재하지 않는다. 이 때문에 단순한 외부접근이 불가능하다. 외부(로컬 PC 등)에서 직접 붙을 수 없으므로, Bastion EC2 를 경유해 SSH 터널링으로 접근해야한다. ...

2025년 10월 28일 AM10:39 · PolarBear

gradle jib 플러그인으로 이미지 빌드할 때 발생한 에러

문제상황 jib으로 프로젝트 이미지 빌드도중 발생한 에러입니다. 에러의 내용은 다음과 같습니다. > Task :jib Containerizing application to seunggison/msa-currency-exchange-app:0.0.1-SNAPSHOT... Base image 'openjdk:21' does not use a specific image digest - build may not be reproducible The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The base image requires auth. Trying again for openjdk:21... The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The system does not have docker-credential-desktop CLI Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory Using base image with digest: sha256:af9de795d1f8d3b6172f6c55ca9ba1c5768baa11bb2dc8af7045c7db9d4c33ac Executing tasks: Executing tasks: [=========== ] 35.4% complete > pushing blob sha256:96791907cbb3a871c6611df3b... > pushing blob sha256:f8a02f907a1ab530de20915a2... > pushing blob sha256:fd4dae8efc3d04714f85e9343... > checking base image layer sha256:5262579e8e45... > checking base image layer sha256:0eab4e2287a5... > checking base image layer sha256:7c002e8f6062... > launching layer pushers > scheduling building manifests > scheduling pushing container configurations > Task :jib FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':jib'. > com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException: Build image failed, perhaps you should make sure your credentials for 'registry-1.docker.io/seunggison/msa-currency-exchange-app' are set up correctly. See https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#what-should-i-do-when-the-registry-responds-with-unauthorized for help * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. > Get more help at https://help.gradle.org. BUILD FAILED in 7s 3 actionable tasks: 1 executed, 2 up-to-date Caused by: Cannot run program "docker-credential-desktop": error=2, No such file or directory The system does not have docker-credential-desktop CLI <- 해당 오류는 도커허브 자격증명 문제때문에 발생하는 오류입니다. ...

2024년 10월 11일 PM03:07 · PolarBear

마이크로 서비스 간 분산추적 with Zipkin

MSA 분산 추적 위 같이 마이크로서비스가 여러개 있을 경우, 각 서비스들간에 호출 체인이 일어납니다. 간단한 예로 이전 포스팅에서 통화 변환 서비스가 환율정보를 받아오기 위해서 환율 서비스의 API를 호출 했었죠. 마이크로 서비스들이 많아지면 많아질수록 이런 호출체인은 더 복잡해지겠죠. 그럼 상황에서 문제가 발생하기 마련입니다. 이렇게 발생하는 문제를 추적하고 분석할 수 있어야하는데 이를 위해 분산 추적을 사용합니다. 이미지와 같이 모든 마이크로서비스의 모든 정보를 분산 추적 서버로 보냅니다. 그리고 분산 추적 서버는 각 서비스에서 전송된 트레이스 데이터를 수집하고 이를 DB에 저장합니다. 이 데이터는 서비스 간 호출 정보, 요청의 시작 시간과 종료 시간, 응답 시간, 서비스 간의 관계, 그리고 각 단계에서 발생한 메타데이터(i.g. HTTP 상태 코드, 에러 등) 를 포함하고 있으며, 이를 기반으로 트랜잭션의 흐름이나 성능 병목 지점 등을 분석할 수 있습니다. ...

2024년 10월 8일 PM01:01 · PolarBear