Skip to content
dlsnfl0615 edited this page Jul 21, 2026 · 3 revisions

📄 원본(노션): https://app.notion.com/p/3a319935b8ae80b499f7e8c0ffda1aeb — 최종 동기화 2026-07-21

아침 스크럼

스크린샷 2026-07-20 오후 10 59 08

제미나이는 안되는걸로 ㄱ-

  • 현서 - JPA, JDBC 차이점 정리
  • 석희 - 슬랙 방에 영상 올림. JDBC, ORM 공부함.
    • 노션 → README 스킬을 만들었지만 멤버라 권한이 없었음. → 복제해서 진행
      • 추가적으로 이미지가 노션에서 migrate가 안돼서 할 것을 해야 함.
  • 현성 - WAS 할 때 정리 못한 것 정리
  • 동혁 - 스프링 강의 API 부분 시청.

ERD, 웹 뷰 프로토타입 진행해야 함. 점심은 한식 뷔페 (땅땅땅)

점심 메뉴


현성: 로그 찍을 때는 @Slf4j 어노테이션을 통해서 로깅하자.

package com.naengsam.quick;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class QuickApplication {

    public static void main(String[] args) {
        log.debug("abcd");
        SpringApplication.run(QuickApplication.class, args);
    }
}

Nest js → 모듈끼리 의존하는 방식.

폴더 구조를 어떻게 나눌 것인지

  • 계층형 구조
    • MVC가 각 폴더에 나뉘어 있음.
  • 도메인형 구조
    • 작업 단위당으로 폴더를 나눔
    • 석희 - 코드의 규모가 커지고 많은 사람들이 협업하는 방식에서는 유리함
    • 참고 자료
비교 항목 도메인 중심 (Package by Feature) 계층 중심 (Package by Layer)
코드 탐색 도메인 단위로 모여있어 매우 직관적 서비스/레포지토리 폴더를 계속 넘나듦
응집도 / 결합도 높은 응집도, 낮고 명확한 결합도 도메인 간 경계가 무너지기 쉬움
접근 제어 (캡슐화) Package-private 활용 가능 대부분 public으로 열어야 함
확장성 멀티 모듈 및 MSA 분리 용이 모듈 분리 시 의존성 해소가 매우 어려움
추천 대상 실무 프로젝트, 중대형 서비스 간단한 CRUD 튜토리얼, 소규모 토이 프로젝트

🔖 (노션 원본의 북마크 링크 — 상세는 노션에서 확인)

  • global 폴더에 config, auth
    • Spring Security에서는 built-in 기능이지만 Servlet Filter를 먼저 잡아서 패턴 매칭 - Whitelist도 필요.
  • 스프링 시큐리티가 없으니깐, 우리가 직접 시큐리티 Servelt Filter을 이용해서 도입하자.
  • JWT?
  • 의존성 주입
    • 의존성 주입은 생성자 주입 사용 ⇒ 가장 권장되는 방식 (테스트 코드 작성의 편의성 등..)
    • @RequiredArgsConstructor 사용 권장
  • API는 버전을 도입해서 쓰기. ex) api/v1/user

스웨거 세팅

🔖 (노션 원본의 북마크 링크 — 상세는 노션에서 확인)

검색 결과를 보니, Spring Boot 4.x 환경에서 실질적인 선택지는 생각보다 많지 않습니다. 정리해드릴게요.

1. springdoc-openapi 3.x (사실상 유일한 정답)

가장 먼저 말씀드려야 할 건, springdoc-openapi 자체는 Boot 4를 지원한다는 겁니다. 다만 버전을 3.x대로 올려야 합니다(2.6.0이 아니라). 앞서 드린 답변처럼:

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.2'

"다른 라이브러리"를 찾으신다기보다, 같은 라이브러리의 맞는 버전을 쓰는 게 먼저 확인할 부분입니다.

2. Springfox — 추천하지 않음

과거 대안이었지만, 2020년 이후 업데이트가 없어 사실상 폐기된 상태이며 Spring Boot 3.x 이상과 호환되지 않습니다. Boot 4에서는 당연히 동작하지 않을 가능성이 매우 높습니다. Medium

3. Scalar (UI만 다른 대안)

springdoc-openapi 내에 Scalar라는 대체 UI가 통합되어 있습니다. Swagger UI 대신 좀 더 모던한 인터페이스로 API 문서를 보여줍니다.

  • Swagger UI는 개발팀이나 최종 사용자가 구현 로직 없이도 API 리소스를 시각화하고 상호작용할 수 있게 해주는데, Scalar도 같은 역할을 하되 디자인이 다릅니다. SpringDoc
  • springdoc 의존성만 있으면 별도 라이브러리 추가 없이 설정으로 전환 가능합니다:

API 버저닝

  • 실제 서비스가 운영될 때 기존 클라이언트를 깨트리지않고 API를 수정할 수 있음
  • /v1/api/.. vs /api/v1..
    • /api/v1.. 방식이 대중적
    • 스프링 시큐리티 등에서 매핑할 때도 http.securityMatcher("/api/**") 이렇게 설정할 수 있는 장점이 있음

응답반환

석희 : api 응답을 특정 타입으로 통일해서 클라이언트 처리를 수월하게 하자

{
  "resultType": "SUCCESS",
  "error": null,
  "success": {
		data...
  }
}
  • 장점

    • 모든 api가 같은 스키마를 사용해 응답하기에 클라이언트에서 처리하는게 편리하다.
    • 에러 처리를 한곳에서 모아서 처리 할 수 있다.
    • 팀 내 커뮤니케이션 비용이 감소한다.
  • 단점

    • http 상태코드 자체의 의미가 퇴색 될 수 있다.
    • 불필요한 응답크기가 증가된다.
    • 과도판 표준화로 오버엔지니어링이 이뤄 질 수 있다.
  • 보완점

    • 글로벌 예외도 동일한 형식으로 응답하도록 따로 설정 필요함
    • 대소문자(SUCCESS) 및 오타 및 공백 문자 오류, resultType의 null등의 문제가 있을 수 있으니 resultType은 boolean 타입으로 하는 것이 좋을 것 같음
    • 완전히 Custom한 객체를 만들면 추후에 어떤 예상하지 못한 다른 문제가 생길 수 있음 ⇒ 스프링에서 제공하는 ResponseBodyAdvice를 사용
    • (선택) 에이전트 코드 작성 자동화에 응답 반환에 대한 지침 추가 필요 (ResponseEntity를 사용하지말고 CommonResponse를 사용해라)
  • DTO 설계시에는 Java의 record를 사용하기 (불변성 및 편의성)

  • ResponseBodyAdvice를 사용해서 Json응답체를 반환하는 방향으로 결정. ResponseEntity 타입을 반환하는 메소드가 있을 때 응답을 customize 가능하게 됨. https://colabear754.tistory.com/231#ResponseBodyAdvice_구현체를_작성하자

구현 방식: ResponseBodyAdvice 채택 (확정)

대안 대비 채택 이유

  • 컨트롤러마다 직접 CommonResponse로 감싸는 방식은 반복 작업이 많고, 감싸는 걸 누락하면 그 API만 스키마가 깨진 채 나가는 위험이 있음
  • ResponseBodyAdvice를 쓰면 컨트롤러는 원래 도메인 객체를 그대로 반환하면 되고, 포장은 전역에서 한 번에 처리 → 누락 가능성 자체가 사라짐
  • @ResponseBody뿐 아니라 ResponseEntity 반환 메소드에도 적용 가능해서 기존 코드와 공존 가능
  • 포맷 변경 시 CommonResponseAdvice 한 곳만 수정하면 되므로 유지보수 지점 단일화
  • 결론: 스키마 통일이라는 목표를 "코드 컨벤션"이 아니라 프레임워크 레벨에서 강제할 수 있다는 점이 핵심 채택 근거

장점 (확장)

1. 모든 API가 같은 스키마 → 클라이언트 처리 단순화

만약 스키마가 통일되어 있지 않다면?

API마다 응답 형태가 제각각이면, 프론트엔드는 API별로 다른 파싱 로직을 짜야 합니다.

// API A: 데이터만 반환
const resA = await axios.get('/api/a'); // { id: 1, name: "..." }

// API B: data로 감싸서 반환
const resB = await axios.get('/api/b'); // { data: { id: 1 } }

// API C: 에러 시 message 필드, 성공 시 result 필드 사용
const resC = await axios.get('/api/c'); // { result: {...} } or { message: "error" }

// 결과: 매 API 호출마다 이런 코드가 반복됨
if (resC.message) {
  // 에러 처리
} else if (resC.result) {
  // 성공 처리
}

스키마가 통일되어 있다면?

resultType / success / error 구조가 모든 API에서 동일하므로, 공통 처리 함수 하나로 모든 API를 다룰 수 있습니다.

// 공통 응답 타입 정의 (한 번만)
interface CommonResponse<T> {
  success: boolean;
  error: { code: string; message: string } | null;
  data: T | null;
}

// 공통 처리 함수 (한 번만 작성하면 모든 API에 재사용)
async function request<T>(url: string): Promise<T> {
  const res = await axios.get<CommonResponse<T>>(url);
  const { resultType, error, success } = res.data;

  if (resultType === false) {
    throw new ApiError(error!.code, error!.message);
  }
  return success as T;
}

// 실제 사용 - 어떤 API든 동일한 방식으로 호출
const workspace = await request<WorkspaceResponse>('/api/workspaces/123');
const users = await request<User[]>('/api/users');

→ API가 몇 개든, 어떤 팀이 만들었든 호출부 코드가 똑같아집니다.

2. Axios Interceptor로 에러를 전역에서 한 번에 처리

스키마가 통일되어 있으면 개별 API 호출마다 try-catch를 반복할 필요 없이, 인터셉터 하나로 전역 에러 처리가 가능합니다.

axios.interceptors.response.use(
  (response) => {
    const { success, error, data } = response.data;

    if (!success) {
      // 에러 코드별 공통 처리 (토스트, 리다이렉트 등)
      if (error.code === "AUTH_EXPIRED") {
        router.push("/login");
      } else {
        toast.error(error.message);
      }
      return Promise.reject(error);
    }

    // success 데이터만 뽑아서 그대로 반환 → 호출부는 wrapping 신경 안 써도 됨
    response.data = success;
    return response;
  }
);

// 이후 모든 컴포넌트에서는 이렇게 깔끔하게 사용 가능
const { data } = await axios.get('/api/workspaces/123');
console.log(data.workspaceId); // wrapping 벗겨진 상태로 바로 접근
axios.interceptors.response.use(
  (response) => {
    const { success, error, success } = response.data;

    if (!resultType) {
      // 에러 코드별 공통 처리 (토스트, 리다이렉트 등)
      if (error.code === "AUTH_EXPIRED") {
        router.push("/login");
      } else {
        toast.error(error.message);
      }
      return Promise.reject(error);
    }

    // success 데이터만 뽑아서 그대로 반환 → 호출부는 wrapping 신경 안 써도 됨
    response.data = success;
    return response;
  }
);

// 이후 모든 컴포넌트에서는 이렇게 깔끔하게 사용 가능
const { data } = await axios.get('/api/workspaces/123');
console.log(data.workspaceId); // wrapping 벗겨진 상태로 바로 접근
axios.interceptors.response.use(
  (response) => {
    const { resultType, error, success } = response.data;

    if (!resultType) {
      // 에러 코드별 공통 처리 (토스트, 리다이렉트 등)
      if (error.code === "AUTH_EXPIRED") {
        router.push("/login");
      } else {
        toast.error(error.message);
      }
      return Promise.reject(error);
    }

    // success 데이터만 뽑아서 그대로 반환 → 호출부는 wrapping 신경 안 써도 됨
    response.data = success;
    return response;
  }
);

// 이후 모든 컴포넌트에서는 이렇게 깔끔하게 사용 가능
const { data } = await axios.get('/api/workspaces/123');
console.log(data.workspaceId); // wrapping 벗겨진 상태로 바로 접근

적용 시 반드시 처리해야 할 예외 케이스 (참고 자료 기반)

  • 예외 응답 이중 포장 문제: ExceptionHandler가 이미 CommonResponse로 감싸 반환해도 Advice가 재포장하면 에러인데 SUCCESS로 덮여버림 → body 타입이 이미 공통 응답 DTO면 재포장하지 않도록 조건 분기 필요
  • String 반환 메소드에서 ClassCastException: StringHttpMessageConverter는 제네릭이 String으로 고정되어 있어 객체로 감싸면 캐스팅 에러 발생 → supports()에서 StringHttpMessageConverter(및 서브클래스)는 적용 대상에서 제외 필요

댕글님 피드백

  • 가짜 데이터를 넣어놓고 이 사람이 요청했을 때 근거리에 얼마나 사람들이 있나?
  • 발표 영상
  • 부스를 하면 시연을 어떻게 해야하나?
    • 가짜로 움직이는걸 만들어야할 필요가 있음.
      • GPS의 정확도 문제
      • 부스가 작고 실내면 GPS가 잘 안됨.
      • 모바일 웹으로 위치가 잡히나?
    • 비대면 대면 인증은 어떻게?
  • 우선순위 결정을 할 때 시연 중심으로 설정하는 것이 좋음.
    • 파손, 분실 등은 후순위로!

웹소켓 구현 방식

  • 제안1 : 소켓은 오직 매칭과정에서만 사용하자
    • 복구 위험성
      • 만약에 이미 배송중에 소켓이 끊기면 웹소켓 연결 복구 로직이 필요함
      • 매칭 과정에서는 소켓이 끊겨도 다시 연결하면 됨
    • 구현 난이도가 훨씬 쉬움
    • 상대의 위치는 실시간 0.1초 단위로 정확해야할 필요가 없음
    • 예를 들어 5초마다 요청 → 5초 사이의 시간 평균을 이용해서 애니메이션으로 부드럽게 이동처리 가능

드리미가 배달 중에 웹사이트(브라우저)를 꺼버린 경우

  1. 재접속 대기
  2. 알림 보내기 (FCM)
  3. 문자 보내기
  4. 고객센터에서 전화

휴대전화를 생각 할 때는 상단을 생각한다.

Delivery 파트 할 일

  • 매칭 후에 배달 중에 소켓 or API
  • 우선순위 로직
    • 우선순위 알고리즘
    • 소켓에서 채널 하나에 N명 넣고 여기서 몇명뽑아서 보낼지 or 채널분리
  • 비용 계산
    • 우체국 유사하게 고정 비용
    • 사용자가 어느정도 비용 정할 수 있게
      • 비용 추천 → 올리거나 내리거나? 일정 이상 시간이 지나면 가격이 바뀔 수 있게?
  • 픽업, 수령은 대면 기준. 서명. 클라이언트에서 svg 형식으로 전송 한다고 가정하고 작업 → 넣을 순 있다.
  • 클라이언트, 서버에서 → heartbeat 추가적으로 드리미가 배달 중에 웹사이트(브라우저)를 꺼버린 경우
    1. 재접속 대기
    2. 알림 보내기 (FCM)
    3. 문자 보내기
    4. 고객센터에서 전화
  • current state → next state로 이동 할 때 필요한 input, output 확실히 정의

    가짜 위치 정보 or 이걸 먼저 구현 하나?

🛵 쉼, 부름

Softeer Bootcamp 8th · WEB Team 3


🏠 시작하기

🤝 협업

📋 기획안

🧠 기술 결정 기록 (ADR)

🕸️매칭 시스템

🧪 부하 테스트

🔧 트러블슈팅

🗓️ 데일리 스크럼


Repo

Clone this wiki locally