134 lines
4.7 KiB
Markdown
134 lines
4.7 KiB
Markdown
# 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)
|
|
|
|
## 예시
|
|
|
|
```typescript
|
|
// 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.SubmitEvent) => {
|
|
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`
|
|
- 동사 형태 권장: 사용자 액션을 나타내는 이름 사용
|
|
- 명확하고 구체적으로: 기능의 목적이 명확하게 드러나도록
|
|
|
|
## 주의사항
|
|
|
|
1. **하나의 기능에만 집중**: 각 feature는 단일 사용자 시나리오를 구현해야 합니다
|
|
2. **재사용성 고려**: 여러 페이지에서 사용될 수 있도록 설계
|
|
3. **독립성 유지**: 다른 features에 의존하지 않고 독립적으로 작동
|
|
4. **적절한 추상화**: 너무 많은 feature를 만들지 말고, 중요한 기능 위주로 구성
|