Skip to content

prepare_frontend_interview

아키텍처

"아키텍처에 대해 고민이 있으신가요?"라는 질문에 대한 답변 전략 가이드

답변 전략: "아키텍처라고 하면 범위가 넓은데, 저는 크게 4가지 레벨로 나눠서 고민하고 있습니다." 라고 운을 뗀 뒤, 면접관의 반응에 따라 깊이를 조절합니다.

아키텍처 부분은 5년차 미드급 프론트엔드 개발자의 면접 상황에 받았던 질문에 대한 개인적인 답변입니다.

목차

  • 시스템/인프라 아키텍처 🔥

    • 모놀리식 vs 마이크로서비스 vs BFF
    • BFF 도입 배경과 인증/권한 통합
    • 꼬리 질문 대비 (SPOF, 전환 기준, BFF 역할 범위)
  • 프론트엔드 애플리케이션 아키텍처 🔥🔥

    • 상태의 성격에 따른 도구 분리 (서버 상태 / 전역 클라이언트 상태 / 레거시)
    • 혼합형 폴더 구조 (feature 기반 + layer 기반)
    • 꼬리 질문 대비 (Redux vs Zustand 공존, React Query 역할, feature 구조 단점)
  • 컴포넌트 설계 아키텍처 🔥🔥

    • 공통 컴포넌트 래핑 전략 (MUI 래핑)
    • 비즈니스 로직 배치 기준 (재사용 여부)
    • 꼬리 질문 대비 (래핑 단점, Presentational/Container, 추상화 시점)
  • 코드 설계 레벨 (관심사 분리/의존성 방향) 🔥

    • 의존성 방향 한 방향 유지 (UI → Business Logic → Data Access)
    • TypeScript 타입 시스템을 활용한 레이어 간 계약
    • 꼬리 질문 대비 (API/도메인 타입 분리, 과도한 추상화, 팀 공유 방법)

시스템/인프라 아키텍처

📌 관련 주제: 프론트엔드 애플리케이션 아키텍처, 코드 설계 레벨, CS - 네트워크

답변 예시

현재 프로젝트에서는 모놀리식, 마이크로서비스, BFF를 모두 경험했습니다. 초기에는 모놀리식 구조로 빠르게 개발했지만, 서비스가 성장하면서 도메인별로 마이크로서비스로 분리하게 되었습니다.

그 과정에서 프론트엔드가 여러 마이크로서비스와 직접 통신하면서 인증/권한 처리가 분산되는 문제가 생겼습니다. 각 서비스마다 토큰 검증 로직이 중복되고, 프론트엔드에서 여러 서비스의 인증 상태를 관리해야 했죠.

이를 해결하기 위해 BFF 레이어를 도입해서 인증/권한 처리를 한 곳으로 통합했습니다. 프론트엔드는 BFF만 바라보고, BFF가 각 마이크로서비스에 인증된 요청을 전달하는 구조입니다.

꼬리 질문 대비

예상 질문답변 포인트
BFF 도입 후 단점은?BFF가 단일 장애점(SPOF)이 될 수 있고, BFF 자체의 배포/관리 부담 증가. 이를 위해 health check와 graceful shutdown 적용
모놀리식에서 마이크로서비스로 전환 기준은?팀 규모 확대, 독립 배포 필요성, 도메인 경계가 명확해졌을 때
BFF에서 인증 외에 다른 역할도 하나?데이터 집계(aggregation)도 일부 수행하지만, 과도한 비즈니스 로직은 넣지 않으려 한다

프론트엔드 애플리케이션 아키텍처

📌 관련 주제: 시스템/인프라 아키텍처, 컴포넌트 설계 아키텍처, 코드 설계 레벨, React - 상태 관리

답변 예시

프론트엔드 애플리케이션 레벨에서는 상태의 성격에 따라 도구를 분리하는 것을 가장 중요하게 생각합니다.

상태를 크게 세 가지로 나눕니다:

  • 서버 상태 (비동기 데이터): React Query로 관리합니다. 캐싱, 리페칭, 로딩/에러 상태를 선언적으로 처리할 수 있어서, 예전에 Redux에서 isLoading, error, data를 일일이 관리하던 보일러플레이트를 크게 줄였습니다.
  • 전역 클라이언트 상태 (인증 정보, 테마 등): Zustand를 사용합니다. Redux 대비 보일러플레이트가 적고, 필요한 슬라이스만 구독할 수 있어 리렌더링 최적화가 쉽습니다.
  • 레거시/복잡한 상태: 기존 코드베이스에 Redux Toolkit이 이미 있는 경우 유지하되, 새로운 기능에서는 위 조합을 우선 사용합니다.

폴더 구조는 혼합형을 사용합니다. 최상위는 feature 기반(/features/auth, /features/dashboard)으로 나누되, feature 내부는 layer 기반(components/, hooks/, api/, types/)으로 구성합니다. 공통으로 쓰이는 것들은 /shared 디렉토리에 둡니다.

폴더 구조 예시 보기
src/
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── api/
│   │   ├── store/        # Zustand slice 또는 Redux slice
│   │   └── types/
│   ├── dashboard/
│   │   ├── components/
│   │   ├── hooks/
│   │   └── ...
├── shared/
│   ├── components/       # 공통 UI (Button, Modal 등)
│   ├── hooks/            # 공통 커스텀 훅
│   ├── utils/
│   └── types/
├── app/                  # 라우팅, 프로바이더 설정
└── styles/               # 글로벌 테마, MUI 커스터마이징

꼬리 질문 대비

예상 질문답변 포인트
Redux가 이미 있는데 왜 Zustand도 쓰나?점진적 마이그레이션. 한 번에 갈아엎기보다 새 기능부터 적용하고, 팀원 학습 곡선도 고려
React Query와 Redux는 어떻게 공존?서버 상태는 React Query, 클라이언트 전용 상태만 Redux/Zustand. 역할이 겹치지 않게 경계를 명확히 한다
feature 기반 구조의 단점은?feature 간 공유 코드가 애매할 때 /shared로 빼야 하는 판단이 필요. 너무 이르게 빼면 premature abstraction

컴포넌트 설계 아키텍처

📌 관련 주제: 프론트엔드 애플리케이션 아키텍처, 코드 설계 레벨

답변 예시

컴포넌트 설계에서 가장 신경 쓰는 부분은 공통 컴포넌트의 일관성비즈니스 로직 배치의 유연성입니다.

공통 컴포넌트는 MUI를 래핑해서 사용합니다. MUI의 Button, Modal, TextField 등을 그대로 쓰지 않고, 프로젝트의 디자인 시스템에 맞게 래핑된 컴포넌트를 만듭니다. 이렇게 하면 디자인 변경이나 라이브러리 교체 시 래핑 레이어만 수정하면 되고, 사용하는 쪽은 영향을 받지 않습니다.

MUI 래핑 예시 코드 보기
tsx
// shared/components/Button.tsx
const StyledButton = styled(MuiButton)({
  // 프로젝트 공통 스타일
})

export const Button = ({ ...props }: ButtonProps) => <StyledButton {...props} />

비즈니스 로직의 위치는 케이스마다 다르게 판단합니다. 기준은 해당 로직이 재사용되는가입니다:

  • 한 컴포넌트에서만 쓰이는 로직: 컴포넌트 내부에 둡니다. 불필요한 추상화를 피하기 위해서입니다.
  • 여러 컴포넌트에서 공유되는 로직: 커스텀 훅으로 분리합니다.
  • UI와 무관한 순수 비즈니스 로직: 별도 유틸 함수나 서비스 레이어로 분리합니다.

처음부터 분리하기보다, 중복이 발생하는 시점에 추출하는 것을 선호합니다. "두 번째 사용처가 생길 때 추상화한다"는 원칙을 따릅니다.

꼬리 질문 대비

예상 질문답변 포인트
MUI 래핑의 단점은?래핑 레이어가 두꺼워지면 MUI의 props를 다 전달하기 번거로움. 필요한 props만 열어두는 것과 전부 열어두는 것의 트레이드오프
Presentational/Container 패턴은 사용하나?Hook의 등장 이후로 엄격하게 나누진 않지만, "로직 없이 렌더링만 하는 컴포넌트"와 "데이터를 조합하는 컴포넌트"의 구분은 여전히 유효하다고 본다
"두 번째 사용처" 원칙이 안 맞는 경우는?명확하게 공통이 될 것이 예상되는 경우(디자인 시스템 컴포넌트 등)는 처음부터 분리한다

코드 설계 레벨 (관심사 분리/의존성 방향)

📌 관련 주제: 시스템/인프라 아키텍처, 프론트엔드 애플리케이션 아키텍처, 컴포넌트 설계 아키텍처

답변 예시

코드 설계에서 가장 중시하는 원칙은 의존성 방향을 한 방향으로 유지하는 것입니다.

프론트엔드에서 이를 풀어보면, 아래와 같은 레이어를 의식합니다:

[UI Layer] → [Business Logic Layer] → [Data Access Layer]
  컴포넌트        커스텀 훅 / 서비스       API 호출 / React Query
  • UI 레이어는 비즈니스 로직 레이어에 의존하지만, 그 반대는 안 됩니다. 커스텀 훅이 특정 컴포넌트의 구현을 알 필요가 없어야 합니다.
  • Data Access 레이어는 API 호출을 추상화합니다. 컴포넌트가 fetch나 axios를 직접 호출하지 않고, React Query의 쿼리 훅이나 별도 API 함수를 통해 접근합니다.

이 원칙의 실질적 장점은 테스트와 교체가 쉬워진다는 것입니다. API 응답 구조가 바뀌어도 Data Access 레이어만 수정하면 되고, UI를 리디자인해도 비즈니스 로직은 그대로입니다.

또한 TypeScript의 타입 시스템을 활용해서 레이어 간 계약(contract)을 명시합니다. API 응답 타입, 컴포넌트 Props 타입, 도메인 모델 타입을 분리해서, 한 레이어의 변경이 다른 레이어로 전파되는 범위를 타입 체크로 즉시 파악할 수 있습니다.

꼬리 질문 대비

예상 질문답변 포인트
API 응답 타입과 도메인 타입을 왜 분리하나?API 응답은 snake_case일 수 있고, 프론트 도메인에선 불필요한 필드가 있을 수 있다. 변환 레이어를 두면 백엔드 변경 영향을 최소화
과도한 추상화가 되지 않나?소규모 프로젝트에선 오버엔지니어링일 수 있다. 팀 규모와 프로젝트 수명을 고려해서 판단한다
이 구조를 팀에 어떻게 공유하나?ADR(Architecture Decision Record)이나 코드 리뷰 컨벤션으로 문서화. 새 멤버 온보딩 시 참고 자료로 활용

면접 마무리 멘트

정리하면, 저는 아키텍처를 한 가지 정답으로 보기보다는 프로젝트의 규모, 팀의 상황, 변경 가능성에 따라 적절한 수준을 선택하는 것이 중요하다고 생각합니다.

작은 프로젝트에서 마이크로서비스나 엄격한 레이어 분리를 적용하면 오버엔지니어링이 되고, 반대로 큰 프로젝트에서 구조 없이 진행하면 유지보수가 어려워집니다.

그래서 항상 **지금 이 수준의 구조가 현재 상황에 적절한가?**를 고민하려고 합니다.