본문 바로가기
프로젝트 회고

아웃소싱 팀 프로젝트 회고: Docker로 프론트 연동

by taeung515 2025. 6. 23.

깃허브 레포지토리: https://github.com/taeung515/spring-practice

프론트 연동 시연영상: https://www.youtube.com/watch?v=4DFnYRQ9CRQ

프로젝트 개요:

외주 팀 프로젝트의 기한은 단 일주일! 프론트엔드 팀이 이미 개발을 완료했고, 현재 백엔드 연동을 기다리고 있는 상황입니다. API 명세서에 맞춰 정확하고 빠른 개발이 필요합니다!

프로젝트 설계:

팀원들과 함께 API 명세와 ERD를 작성하며 각자의 기능을 명확히 이해할 수 있었습니다. 공동응답객체와 코드컨벤션을 사전에 정함으로써 PR 및 코드 리뷰도 빠르게 진행할 수 있었고, 그 결과 기한 내에 프로젝트를 성공적으로 완수할 수 있었습니다.

태스크, 인증인가쪽을 담당하였고, 구현하며 생각한 것들과, 문제상황에 대한 해결을 정리하며 복습하고자 합니다.

공통영역과 도메인

1. 공통영역(Global)

  • 재사용성 증가: 모든 도메인에서 쉽게 import 가능
  • 관심사 분리 명확: 비즈니스 로직과 시스템 구성 설정을 분리
  • 의존성 흐름이 깔끔해짐: 도메인 -> global방향으로만 참조됨 (단방향)

2. domain/ 하위로 도메인별 분리

  • 도메인 단위 팀 협업 및 유지보수가 쉬워짐
  • 향후 MSA(마이크로서비스 아키텍처)로 전환하기 용이함
  • 도메인 맥락이 명확해져 구조 파악이 빠름
  • DDD, 클린 아키텍처 등 다양한 개발 방법론과의 결합하여 복잡성 대응

ObjectMapper의 LocalDateTime JSON 형태로 직렬화 실패

문제 상황

필터와 같은 환경에서 공동응답객체 형태로 JSON 응답을 직접 생성하기 위해 다음과 같은 두 가지 방식으로 ObjectMapper를 사용할 수 있습니다.

  • 직접 생성하는 경우
    • 문제: 기본 ObjectMapper는 Java 8의 LocalDateTime 등을 직렬화할 수 없어 예외가 발생합니다.
  • 스프링에서 관리하는 ObjectMapper 빈을 주입받아 사용하는 경우
    • 장점: 스프링이 기본 설정한 JavaTimeModule을 포함하여 Java 8의 LocalDateTime 등을 자동으로 직렬화할 수 있습니다.

해결 방법(스프링에서 관리하는 ObjectMapper 빈을 주입받아 사용)

 

 


엔티티 생성 시 빌더 패턴과 정적 팩토리 메서드

빌더 패턴은 엔티티 생성 시 가독성을 높여주지만, 필수 값을 누락하는 치명적인 실수를 할 가능성이 있습니다. 이러한 이유로 정적 팩토리 메서드를 사용하는 것이 보다 효율적이라고 판단하였으며, 추후 리팩토링 과정에서 정적 팩토리 메서드로 변경할 예정입니다.


스프링 시큐리티 컨텍스트 사용 전략

스프링 시큐리티의 SecurityContextHolder에 사용자 정보를 저장할 때, UserDetails 전체를 저장할지 userId만 저장할지 고민했습니다. 최종적으로 인증과 인가가 주요 목적임을 고려하여 userId만 저장하고, 만약 토큰 정보가 필요하다면 JwtUtil 클래스를 활용하도록 설계했습니다.


@LastModifiedDate 적용 문제

수정해도 생성일, 수정일 동일한 시간으로 표시되는 문제


JPA의 @LastModifiedDate가 즉각적으로 반영되지 않은 이유는 영속성 컨텍스트의 변경 감지 때문입니다.

@LastModifiedDate은 Flush 이후 DB에 반영되므로, flush전 응답dto를 클라이언트에게 전달해주어 발생한 문제였습니다.

saveAndFlush 메서드로 Flush 후 dto로 값을 변환하여 문제를 해결하였습니다.


CORS 오류 해결

CORS 오류가 발생하는 이유:

  • CORS(Cross-Origin Resource Sharing)는 브라우저 보안 정책(Same-Origin Policy) 에 따라 발생합니다.
  • 브라우저는 출처(origin) 가 다른 서버로 API 요청을 보낼 경우, 이를 자동으로 차단합니다. 예:
    • 프론트엔드: http://localhost:3100
    • 백엔드: http://localhost:8080
    • 위 두 URL은 포트 번호가 다르기 때문에 다른 출처(origin) 로 간주되고, 브라우저는 이 요청을 막습니다.
  • 브라우저는 이 요청을 "예비 요청(Preflight Request)" 으로 먼저 보내 서버가 허용하는지를 확인합니다. 서버가 명시적으로 허용하지 않으면 CORS 오류가 발생합니다.

개발 과정에서 발생한 CORS 오류는 다음 설정으로 해결했습니다:

@Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOriginPatterns(List.of("http://localhost:3100")); // cors 설정
        config.setAllowedMethods(List.of("*")); // 모든 HTTP 메서드 허용
        config.setAllowedHeaders(List.of("*")); // 모든 헤더 허용
        config.setAllowCredentials(true); // 자격 증명 허용 (쿠키 등)
        config.setMaxAge(3600L); // 캐싱 기간
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config); // 모든 경로에 대해 CORS 설정 적용
        return source;
    }

추후 더 알아볼 것들

BFF 패턴

Backend For Frontend(BFF) 패턴은 프론트엔드의 특정 요구에 맞춘 별도의 백엔드 API를 제공하여, 프론트엔드의 개발 효율성과 API 성능을 최적화하는 방법입니다.

MSA
마이크로서비스 아키텍처(MSA)는 애플리케이션을 독립적인 작은 서비스로 나누어 구축하는 구조로, 서비스 간 통신 복잡성과 보안 관리가 핵심적인 고려사항입니다.

CSRF와 Stateless
Stateless JWT 방식을 사용하면 세션 상태를 서버에 유지하지 않기 때문에 CSRF(Cross-Site Request Forgery) 공격의 위험이 사라집니다. 따라서 스프링 시큐리티 설정에서도 CSRF 보호를 비활성화(disable)할 수 있습니다.

.csrf(AbstractHttpConfigurer::disable)

마무리

프론트엔드와 함께 명확한 API 명세서를 바탕으로 작업을 진행하여 빠른 시간 내 개발을 마무리할 수 있었습니다. 이 과정에서 API 명세서의 중요성을 다시 한번 깨달았고, CSRF와 Stateless의 연관 관계 이해, CORS 오류 해결, JPA의 변경 감지 기능 등 다양한 경험을 통해 한층 더 성장할 수 있었습니다.