Files
sam-docs/standards/api-rules.md
권혁성 3b6e97dadc docs: API 라우터 분리 및 버전 폴백 시스템 문서화
- api-rules.md: 라우팅 섹션 전면 개편
  - 도메인별 라우트 파일 구조 (13개)
  - ApiVersionMiddleware 설명
  - API 버전 폴백 시스템 상세 설명
- system-overview.md: 라우팅 구조 섹션 확장
  - 도메인별 분리 구조 다이어그램
  - 미들웨어 스택에 ApiVersionMiddleware 추가
- dev-commands.md: 라우트 관리 명령어 추가
- INDEX.md: 2026-01-28 변경 이력 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 18:52:24 +09:00

6.3 KiB

SAM API 개발 규칙

업데이트: 2026-01-28


1. Architecture Philosophy

  • Service First: All business logic must be written in Service classes (public functions)
  • Controller: Only responsible for DI injection of Services and calling them. Use ApiResponse::handle() for responses
  • Exception Flow: Errors are thrown → converted to JSON by global handler
  • Context Enforcement: Base Service requires tenantId(), apiUserId(). Throws exception if not set

2. Multi-tenancy & Models

  • BelongsToTenant global scope applied
  • ModelTrait for is_active, date handling
  • SoftDeletes by default
  • Common columns: tenant_id, created_by, updated_by, deleted_by (COMMENT required)
  • FK constraints: Created during design, minimal in production

2.1 ModelTrait 사용 가이드

ModelTrait는 모든 모델에서 공통으로 사용하는 기능을 제공합니다.

위치: app/Traits/ModelTrait.php

제공 기능:

// 1. 날짜 직렬화 포맷 (Y-m-d H:i:s)
protected function serializeDate(DateTimeInterface $date)

// 2. Active 상태 조회 Scope
public function scopeActive($query)
// 사용: Model::active()->get()
// SQL: WHERE is_active = 1

⚠️ 필수 요구사항:

scopeActive() 메서드 사용 시 테이블에 is_active 컬럼이 반드시 존재해야 함

-- 마이그레이션 예시
$table->boolean('is_active')
    ->default(true)
    ->comment('활성화 여부 (ModelTrait::scopeActive() 사용)');

모델 설정:

class YourModel extends Model
{
    use BelongsToTenant, ModelTrait, SoftDeletes;

    protected $fillable = [
        // ...
        'is_active',  // 반드시 추가
    ];

    protected $casts = [
        'is_active' => 'boolean',  // 반드시 추가
    ];
}

is_active 컬럼 적용 테이블 (2025-12-05 기준):

테이블 is_active 비고
materials 있음
products 있음
item_pages 있음
item_fields 있음 2025-12-05 추가
item_sections 없음 필요시 마이그레이션 추가

3. Middleware Stack

  • ApiKeyMiddleware, CheckSwaggerAuth, CorsMiddleware, CheckPermission, PermMapper
  • ApiVersionMiddleware - API 버전 선택 및 폴백 처리 (v2 없으면 v1 사용)
  • Default route group: auth.apikey (some with auth:sanctum)

4. Routing

4.1 라우트 파일 구조

API 라우트는 도메인별로 분리되어 있습니다:

routes/api/
├── v1/                    # v1 API 라우트
│   ├── auth.php           # 인증 (login, logout, signup, token)
│   ├── admin.php          # 관리자 (users, global-menus, FCM)
│   ├── users.php          # 사용자 (me, profiles, invitations, roles)
│   ├── tenants.php        # 테넌트 (CRUD, settings, stat-fields)
│   ├── hr.php             # HR (departments, positions, employees, attendances)
│   ├── finance.php        # 재무 (cards, deposits, withdrawals, payrolls)
│   ├── sales.php          # 영업 (clients, quotes, orders, pricing)
│   ├── inventory.php      # 재고 (items, BOM, stocks, shipments)
│   ├── production.php     # 생산 (processes, work-orders, inspections)
│   ├── design.php         # 설계 (models, versions, BOM templates)
│   ├── files.php          # 파일 (upload, download, folders)
│   ├── boards.php         # 게시판 (boards, posts, comments)
│   └── common.php         # 공통 (menus, roles, permissions, settings)
├── v2/                    # v2 API 라우트 (필요시 생성)
└── api.php                # 라우트 로더

4.2 API 버전 폴백 시스템

버전 선택 방법 (우선순위 순):

  1. Accept-Version 헤더: Accept-Version: v2
  2. X-API-Version 헤더: X-API-Version: v2
  3. api_version 쿼리 파라미터: ?api_version=v2
  4. 기본값: v1

폴백 동작:

  • v2 요청 시 해당 라우트가 v2에 없으면 자동으로 v1 라우트 사용
  • 응답 헤더 X-API-Version에 실제 사용된 버전 표시

사용 예시:

# v1 명시적 요청
curl -H "Accept-Version: v1" https://api.sam.kr/api/v1/users

# v2 요청 (v2 없으면 v1으로 폴백)
curl -H "Accept-Version: v2" https://api.sam.kr/api/v1/users

# 쿼리 파라미터로 버전 지정
curl "https://api.sam.kr/api/v1/users?api_version=v2"

# 버전 미지정 (기본 v1)
curl https://api.sam.kr/api/v1/users

4.3 REST 컨벤션

  • 기본 CRUD: index, show, store, update, destroy
  • 확장 메서드: toggle, bulkUpsert, reorder, stats, options

5. Controller/Service Rules

  • Controller: FormRequest type-hint → only pass $request->validated() to Service
  • Service: extends Service, tenantId()/apiUserId() required
  • Lists: Pagination, explicit search parameters
  • Responses: {success, message, data}, message must be i18n key only

6. i18n & Response Messages

  • Messages: lang/{locale}/message.php
  • Errors: lang/{locale}/error.php
  • Rule: No direct strings, must use __('message.xxx'), __('error.xxx')
  • Common keys: message.fetched/created/updated/deleted/bulk_upsert/reordered
  • Resource-specific keys allowed: message.product.created, message.bom.bulk_upsert

7. Validation (FormRequest)

  • No direct validate() calls. Separate all into FormRequest classes
  • Reuse common Requests: PaginateRequest, BomItemsRequest, DateRangeRequest
  • Boundary validation (effective_from ≤ effective_to) in Request

8. Audit Logging

  • Table: audit_logs (tenant_id, target_type, target_id, action, before/after(json), actor_id, ip, ua, created_at)
  • Actions: created, updated, deleted, released, cloned, items_replaced, diff_viewed
  • Retention: 13 months default, audit:prune scheduler cleanup
  • Failure tolerance: Log failures don't block business operations

9. Domain Rules

  • Status: Strings (DRAFT/RELEASED/ARCHIVED)
  • Unique/Indexes: models(code), model_versions(version_no), bom_items(parent, order)
  • BOM: Supports summary/validate/bulkUpsert/reorder

개발 시 필수 체크

✓ Service-First 패턴 (Controller는 단순)
✓ BelongsToTenant scope 적용
✓ ModelTrait 사용
✓ SoftDeletes 적용
✓ FormRequest 검증
✓ i18n 키 사용 (__('message.xxx'))
✓ 감사 로그 고려
✓ tenant_id 필터링 확인