Files
HeuJung c0e0d82459 refactor(extension): 확장 미들웨어 선언적 self-gate 정규화
확장이 SP Kernel 미들웨어 그룹을 직접 조작하거나 라우트 파일에 미들웨어
FQCN 을 직접 부착하던 임시 방식을, 확장이 부착 대상(targets)을 명시 선언하고
코어가 요청 시점에 라우트명·URI 로 매칭해 실행하는 self-gate 로 정규화.
번들 7건(ecommerce/gdpr/pay 3종) 이전, audit 룰 3건·문서·버전 동기화 동반.

부수적으로 generate-skills.cjs 가 재생성 시 신의성실(부수의무) 섹션을 소실
시키던 회귀를 수정 — 스킬별 불릿을 스크립트 SSoT 로 편입해 재생성에도 보존.
2026-07-16 10:22:51 +09:00

346 lines
9.2 KiB
Markdown

# 모듈 라우트 규칙
> 이 문서는 모듈의 백엔드 라우트(API/Web)와 프론트엔드 라우트(routes.json) 작성 규칙을 설명합니다.
---
## TL;DR (5초 요약)
```text
1. URL prefix 자동: /api/modules/[vendor-module]/...
2. name prefix 자동: api.modules.[vendor-module]....
3. 활성화된 모듈만 라우트 등록됨
4. 프론트엔드: routes/admin.json, routes/user.json으로 admin/user 분기
5. 레거시 routes.json은 admin으로 폴백 (경고 로그 출력)
```
---
## 목차
1. [핵심 원칙](#1-핵심-원칙)
2. [라우트 등록 조건](#2-라우트-등록-조건)
3. [자동 적용되는 Prefix](#3-자동-적용되는-prefix)
4. [라우트 파일 작성](#4-라우트-파일-작성)
5. [라우트 네임 컨벤션](#5-라우트-네임-컨벤션)
6. [프론트엔드 라우트 (routes.json)](#6-프론트엔드-라우트-routesjson)
7. [캐시 관리](#7-캐시-관리)
8. [관련 문서](#8-관련-문서)
---
## 1. 핵심 원칙
```
중요: ModuleRouteServiceProvider가 URL prefix와 name prefix를 자동 적용
✅ 필수: 모듈 개발자는 모듈 내부 구조만 정의
✅ 조건: 활성화된 모듈만 라우트가 등록됨
```
---
## 2. 라우트 등록 조건
### 활성화된 모듈만 라우트 등록
- `ModuleRouteServiceProvider`가 DB에서 `status = 'active'`인 모듈만 조회
- 설치되지 않거나 비활성화된 모듈의 라우트는 등록되지 않음
- 모듈 활성화/비활성화 시 라우트가 자동으로 등록/해제됨
### 네임스페이스 변환 규칙
```
디렉토리명 → 네임스페이스
sirsoft-sample → Modules\Sirsoft\Sample
sirsoft-ecommerce → Modules\Sirsoft\Ecommerce
vendor-my-module → Modules\Vendor\My\Module
```
---
## 3. 자동 적용되는 Prefix
### API 라우트
| 항목 | 값 |
|------|-----|
| URL prefix | `api/modules/{module-name}` |
| Name prefix | `api.modules.{module-name}.` |
### 웹 라우트
| 항목 | 값 |
|------|-----|
| URL prefix | `modules/{module-name}` |
| Name prefix | `web.modules.{module-name}.` |
> 라우트 이름(`{web\|api}.modules.{module-name}.{name}`)은 확장 미들웨어 타게팅의 1급 키다. `Module::getMiddleware()` 의 `targets` 는 이 라우트명 패턴으로 미들웨어 부착 대상을 지정한다. 상세: [docs/backend/middleware.md "확장 미들웨어 선언 (self-gate)"](../backend/middleware.md#확장-미들웨어-선언-self-gate).
---
## 4. 라우트 파일 작성
### 라우트 파일 위치
```
modules/_bundled/{identifier}/src/routes/
├── api.php # API 라우트
└── web.php # 웹 라우트
```
### ✅ DO: 모듈 내부 구조만 정의
```php
// modules/_bundled/sirsoft-ecommerce/src/routes/api.php
Route::prefix('admin')->middleware(['auth:sanctum', 'admin'])->group(function () {
Route::get('products', [ProductController::class, 'index'])
->name('admin.products.index');
// 최종 Name: api.modules.sirsoft-ecommerce.admin.products.index
// 최종 URL: /api/modules/sirsoft-ecommerce/admin/products
});
```
### ❌ DON'T: prefix 중복 입력
```php
// 잘못된 예시 - prefix가 중복됨
Route::get('products', [ProductController::class, 'index'])
->name('api.modules.sirsoft-ecommerce.admin.products.index');
```
### 라우트 파일 주석 템플릿
```php
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| [Module Name] API Routes
|--------------------------------------------------------------------------
|
| 주의: ModuleRouteServiceProvider가 자동으로 prefix를 적용합니다.
| - URL prefix: 'api/modules/{module-name}'
| - Name prefix: 'api.modules.{module-name}.'
|
*/
// 라우트 정의...
```
---
## 5. 라우트 네임 컨벤션
### 구조
```
{type}.modules.{module-name}.{area}.{resource}.{action}
```
### 예시
```
api.modules.sirsoft-ecommerce.admin.products.index
api.modules.sirsoft-ecommerce.admin.products.store
api.modules.sirsoft-ecommerce.admin.orders.show
web.modules.sirsoft-ecommerce.checkout.cart
```
### 구성 요소
| 구성 요소 | 설명 | 적용 방식 |
|-----------|------|-----------|
| `type` | api 또는 web | 자동 적용 |
| `modules` | 모듈임을 나타냄 | 자동 적용 |
| `module-name` | 모듈 식별자 | 자동 적용 |
| `area` | admin, public 등 영역 | 개발자 정의 |
| `resource` | 리소스명 | 개발자 정의 |
| `action` | index, store, show, update, destroy 등 | 개발자 정의 |
---
## 6. 프론트엔드 라우트 (routes.json)
```
중요: 백엔드 라우트(routes/api.php)와 별개로 프론트엔드 라우트도 지원
✅ 위치: modules/{identifier}/resources/routes/admin.json, routes/user.json
✅ 병합: 템플릿 타입(admin/user)에 맞는 모듈 routes만 필터링 병합
✅ 레거시: routes.json은 admin으로 폴백 (경고 로그 출력)
✅ 캐시: 모듈 활성화/비활성화 시 자동 무효화
```
### 디렉토리 구조
```
modules/{identifier}/resources/
├── routes/
│ ├── admin.json ← admin 템플릿 전용 라우트
│ └── user.json ← user 템플릿 전용 라우트 (선택)
└── routes.json ← [레거시] 있으면 admin으로 취급 + 경고 로그
```
### admin/user 분기 규칙
| 라우트 파일 | 서빙 대상 | 설명 |
|------------|----------|------|
| `routes/admin.json` | admin 타입 템플릿 | 관리자 화면 라우트 |
| `routes/user.json` | user 타입 템플릿 | 일반 사용자 화면 라우트 |
| `routes.json` (레거시) | admin 타입 템플릿만 | 하위 호환성, 경고 로그 출력 |
**탐색 우선순위**:
1. `routes/{templateType}.json` 우선 탐색
2. (admin 타입만) `routes.json` 레거시 폴백
3. user 타입은 레거시 `routes.json`을 로드하지 않음
### 기본 구조
```json
{
"version": "1.0.0",
"routes": [
{
"path": "*/admin/sample",
"layout": "admin_sample_index",
"auth_required": true,
"meta": {
"title": "$t:sirsoft-sample.admin.index.title"
}
}
]
}
```
### 병합 방식
**병합 우선순위**:
1. 템플릿 routes 배열이 기본
2. 활성화된 모듈 routes를 **템플릿 타입에 맞게** 필터링하여 병합 (`array_merge`)
3. 플러그인 routes는 **admin 템플릿에만** 포함 (settings 페이지 등 admin 전용)
4. version은 템플릿의 version 사용
**레이아웃 필드 자동 변환**:
- 모듈 routes의 `layout` 필드에 모듈 식별자 접두사 자동 추가
- 예: `"admin_sample_index"` → `"sirsoft-sample.admin_sample_index"`
**최종 API 응답**:
```json
{
"version": "1.0.0",
"routes": [
// 템플릿 routes...
{
"path": "*/admin/sample",
"layout": "sirsoft-sample.admin_sample_index",
"auth_required": true,
"meta": {
"title": "$t:sirsoft-sample.admin.index.title"
}
}
// 다른 모듈 routes (템플릿 타입에 맞는 것만)...
]
}
```
### 모듈 레이아웃과의 연계
**모듈 레이아웃 정의**:
```
modules/{identifier}/resources/layouts/admin/{layout_name}.json
modules/{identifier}/resources/layouts/user/{layout_name}.json
```
**routes에서 레이아웃 참조**:
```json
{
"path": "*/admin/sample",
"layout": "admin_sample_index"
}
```
**레이아웃 조회 우선순위**:
1. 템플릿 오버라이드: `templates/{template}/layouts/overrides/{module}/{layout}.json`
2. 모듈 원본: `modules/{module}/resources/layouts/admin/{layout}.json`
### 다국어 키 사용
**routes.json에서 다국어 참조**:
```json
{
"meta": {
"title": "$t:sirsoft-sample.admin.index.title"
}
}
```
**모듈 다국어 파일**:
```json
// modules/sirsoft-sample/resources/lang/ko.json
{
"admin": {
"index": {
"title": "샘플 모듈 관리"
}
}
}
```
**프론트엔드에서 최종 키**:
- 템플릿 다국어 API가 모듈 다국어 자동 병합
- 최종 키: `sirsoft-sample.admin.index.title`
### 구현 세부사항
**TemplateService**:
```php
// 템플릿 + 모듈 routes 병합 (템플릿 타입에 따라 필터링)
public function getRoutesDataWithModules(string $identifier): array
// 템플릿 타입에 맞는 모듈 routes 로드 (routes/{type}.json 우선, 레거시 폴백)
private function loadActiveModulesRoutesData(string $templateType = 'admin'): array
```
**PublicTemplateController API**:
```
GET /api/public/templates/{identifier}/routes
```
**ClearsTemplateCaches Trait**:
```php
protected function clearAllTemplateRoutesCaches(): void
```
---
## 7. 캐시 관리
### 캐시 키 패턴
```
template.routes.{identifier}
```
### 자동 캐시 무효화
| Manager | 메서드 | 동작 |
|---------|--------|------|
| ModuleManager | `activateModule()` | routes 캐시 무효화 |
| ModuleManager | `deactivateModule()` | routes 캐시 무효화 |
| PluginManager | `activatePlugin()` | routes 캐시 무효화 |
| PluginManager | `deactivatePlugin()` | routes 캐시 무효화 |
---
## 8. 관련 문서
- [모듈 기초](module-basics.md) - 모듈 구조, AbstractModule
- [모듈 레이아웃](module-layouts.md) - 레이아웃 등록, 오버라이드
- [모듈 다국어](module-i18n.md) - 백엔드/프론트엔드 다국어
- [템플릿 라우트](template-routing.md) - 템플릿 라우트 규칙
- [확장 시스템 개요](index.md) - 전체 인덱스