Features Layer
Features는 앱의 주요 사용자 상호작용과 기능을 구현하는 레이어입니다. 사용자가 실제로 수행하는 작업들로, 주로 비즈니스 엔티티와 관련이 있습니다.
목적
- 사용자가 중요하게 생각하는 주요 상호작용 구현
- 재사용 가능한 비즈니스 기능 제공
- 여러 페이지에서 사용되는 기능의 중복 제거
Feature로 만들어야 하는 경우
⚠️ 중요: 모든 것을 feature로 만들 필요는 없습니다.
Feature로 만드는 것이 좋은 지표:
- 여러 페이지에서 재사용됨: 예를 들어, 여러 에디터가 있고 모두 댓글 기능이 있다면, 댓글은 재사용 가능한 feature입니다
- 중요한 비즈니스 기능: 신규 프로젝트 참여자가 Features 폴더를 보고 앱의 주요 기능을 빠르게 파악할 수 있어야 합니다
Feature가 너무 많으면 중요한 기능이 묻히므로, 신규 참여자의 경험을 최적화하여 결정하세요.
구조
각 feature 슬라이스는 다음 세그먼트로 구성됩니다:
features/
└── [feature-name]/
├── ui/ # 상호작용을 수행하는 UI (폼, 버튼 등)
├── api/ # 액션 수행에 필요한 API 호출 (선택적)
├── model/ # 검증 및 내부 상태 관리 (선택적)
├── lib/ # 유틸리티 함수 (선택적)
├── config/ # feature flags (선택적)
└── index.ts # Public API
세그먼트 설명
- ui: 사용자 상호작용을 위한 UI 컴포넌트 (폼, 모달, 버튼 등)
- api: feature 실행에 필요한 API 요청 함수
- model: 폼 검증, 내부 상태, 비즈니스 로직
- lib: feature 내부에서만 사용되는 유틸리티
- config: feature flags, feature별 설정값
- index.ts: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export)
예시
// features/auth-login/ui/login-form.tsx
import { useState } from 'react';
import { loginUser } from '../api/login-user';
import type { LoginCredentials } from '../model/types';
export function LoginForm() {
const [credentials, setCredentials] = useState<LoginCredentials>({
email: '',
password: ''
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await loginUser(credentials);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={credentials.email}
onChange={(e) => setCredentials({ ...credentials, email: e.target.value })}
/>
<input
type="password"
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
/>
<button type="submit">로그인</button>
</form>
);
}
// features/auth-login/api/login-user.ts
import type { LoginCredentials } from '../model/types';
export async function loginUser(credentials: LoginCredentials) {
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
return response.json();
}
// features/auth-login/model/types.ts
export interface LoginCredentials {
email: string;
password: string;
}
// features/auth-login/index.ts
export { LoginForm } from './ui/login-form';
export { loginUser } from './api/login-user';
export type { LoginCredentials } from './model/types';
레이어 의존성 규칙
✅ 허용되는 의존성:
shared레이어의 모듈entities레이어의 모듈- 같은 feature 슬라이스 내의 다른 세그먼트
❌ 금지된 의존성:
- 다른
features슬라이스 (feature 간 직접 의존 금지) widgets레이어pages레이어app레이어
💡 팁: 여러 features가 공통 기능을 필요로 한다면, 그 기능을
shared레이어로 이동시키거나entities레이어에서 관리하세요.
명명 규칙
- 소문자와 대시 사용:
auth-login,post-create,comment-edit - 동사 형태 권장: 사용자 액션을 나타내는 이름 사용
- 명확하고 구체적으로: 기능의 목적이 명확하게 드러나도록
주의사항
- 하나의 기능에만 집중: 각 feature는 단일 사용자 시나리오를 구현해야 합니다
- 재사용성 고려: 여러 페이지에서 사용될 수 있도록 설계
- 독립성 유지: 다른 features에 의존하지 않고 독립적으로 작동
- 적절한 추상화: 너무 많은 feature를 만들지 말고, 중요한 기능 위주로 구성