예제
Resource 생성하기
Resource를 생성해 App Menu에 공개하고 목록·폼·상세·Inspector 화면이 생기는 과정을 확인합니다.
예제 유형 레시피
개요
Resource 예제
첫 App 예제가 App 생성부터 테넌트 설치까지의 전체 흐름을 보여준다면, 이 예제는
nexia-apps:make-package-resource workshop Note 전후에 화면이 어떻게 달라지는지에
집중합니다. App Menu의 노트가 생기고 목록, 생성, 상세, 수정, Inspector 화면으로
이어지는 과정을 단계별로 확인합니다.
최종 결과
| 구분 | 생기는 것 | 확인 위치 |
|---|---|---|
| App Menu | Workshop App의 운영 → 노트 항목 | 테넌트 Shell의 Workshop App Menu |
| 업무 화면 | 목록, 생성, 상세, 수정 화면과 목록 옆 Inspector | /apps/workshop/notes |
| 기본 데이터 | 이름, 상태, 생성일, 수정일을 가진 wsp_notes 레코드 | 화면과 테넌트 데이터베이스 |
| API·권한 | 조회·생성·수정·삭제 Route 5개와 workshop.note.* 권한 | Package Route와 Resource Module |
| App 소스 | 모델, 상태 enum, Policy, Controller, migration, Resource Module, Filament Resource, 프런트엔드 화면, 로케일, Feature 테스트 | packages/workshop/ 아래 Generator 출력 |
생성 명령은 App 소스만 준비합니다. App 활성화, 테넌트 설치, 권한 배정, 실행 중인 프런트엔드의 재로딩까지 끝나야 메뉴와 화면이 나타납니다.
Resource Module을 Core가 처리하는 방식
생성 파일 중 Core integration의 중심은 NoteModule입니다. 이 클래스는 SDK의
AbstractResourceModule을 상속하고, 필요한 capability 인터페이스를 명시적으로
구현합니다.
final class NoteModule extends AbstractResourceModule implements
NavigationContribution,
PermissionContribution,
ResourceAuthorizationContribution,
ResourceCatalogContribution,
ShellResourceContributionAbstractResourceModule은 resourceModuleDefinition()을 받아 public
ResourceDescriptor를 게시하는 appDescriptors()를 고정합니다. 생성된 traits는
Resource key 하나에서 navigation, CRUD permission, destination action, Shell 화면
계약을 파생하지만, 모델 조회와 쓰기 권한은 여전히 NoteController와 NotePolicy가
처리합니다.
WorkshopAppManifest가 Contribution/을 discovery root로 등록
→ NexiaContributionRegistry가 NoteModule이 구현한 인터페이스별로 class를 발견
├─ ResourceCatalogRegistry: workshop.note ↔ Note model, 소유 App, 인가 계약
├─ AppDescriptorCatalog: 공개 field/search schema
├─ NavigationContributionRegistry: 운영 → 노트 destination
├─ PermissionCatalog: workshop.note.{read,create,update,delete}
└─ Shell resource 조합: list/show/form/inspector component와 route
→ 설치 상태와 actor 권한을 검사한 뒤 Core Shell이 화면을 호스팅
그래서 Generator가 여러 파일을 함께 만드는 이유는 복사 편의를 위해서가 아닙니다.
하나의 workshop.note identity를 backend model·Policy·Route, Core catalog,
frontend surface가 함께 사용해야 목록에서 상세·수정·Inspector까지 일관되게
동작하기 때문입니다.
사람 field는 Party에서 시작하기
Resource Module scaffold 세 경로에는 사람 field를 위한 같은 주석 레시피가 들어 있습니다.
| 생성 Resource | 레시피 소유 stub |
|---|---|
| App Package Resource | stubs/package-resource/resource-module.stub |
| Host standard Resource | stubs/resource/resource-module.stub |
| Host document Resource | stubs/resource-document/resource-module.stub |
모든 일반 Resource가 사람 field를 소유하는 것은 아니므로 레시피는 주석 상태로 생성됩니다. 도메인에 사람 field가 필요하면 생성된 Module에서 주석을 풀고 업무 의미에 맞는 이름으로 바꾸되, 선택 identity는 Party로 유지합니다.
'author_party_public_id' => [
'type' => 'resource_reference',
'accepted_resource_keys' => ['directory.party'],
'selector_purpose' => 'workshop.reference-options',
'selector_permissions' => ['workshop.note.create'],
'party_selection' => [
'types' => ['person'],
// 이 field가 active login membership을 요구할 때만 추가:
// 'eligibility' => 'active_legal_entity_member',
],
],선택적인 eligibility constraint는 선택할 수 있는 Party만 좁히며 membership을 permission으로 바꾸지 않습니다. 대응하는 migration column, model/API 처리, validation, form/display field는 한 변경으로 함께 추가합니다. Party, Legal Entity, Operating Unit, owner domain의 exact employment binding을 고르는 전체 Party-first 규칙은 Resource Reference를 참조하세요.
실행 전 확인
- Core의
app,vite,postgres,redis가 실행 중이어야 합니다. - Core는
APP_ENV=local로 실행해야 합니다. 이 예제는 로컬 전용 개발 설치 명령을 사용합니다. packages/workshop/App Package와app.installed:workshopRoute 가드가 있어야 합니다.- 처음부터 생성 결과를 보려면
Note가 아직 없는 Workshop App 기준선에서 실행해야 합니다. 기존 파일이 있으면 생성기는 덮어쓰지 않고SKIP합니다. - 화면 확인에 사용할 테넌트 ID와 Workshop App 권한을 배정할 수 있는 접근 관리자가 필요합니다.
테넌트 ID를 확인합니다.
task artisan -- tenants:list --no-ansi1. 생성 결과 미리 보기와 생성
commands.sh는 먼저 --dry-run으로 변경 대상을 보여주고, 같은 명령을 실제로
실행합니다.
sh docs/developers/examples/resource/commands.shdry-run은 새 Resource 파일 20개와 App Manifest·Route·로케일의 변경 대상을
WOULD로 표시하고 파일을 쓰지 않습니다. 실제 실행은 새 파일을 CREATE, 기존
파일의 병합 지점을 UPDATE로 표시하고 다음 메시지로 끝납니다.
Package resource scaffolded.
nexia-apps:make-package-app는 App Launcher 항목과 개요 화면의 소스를 이미
생성합니다. 이 Resource 명령이 새로 만드는 것은 운영 → 노트 메뉴와 노트의
목록·생성·상세·수정·Inspector 화면입니다.
두 Generator 모두 명령 직후에는 소스만 작성하므로 브라우저가 바로 바뀌지는 않습니다. 각 결과는 App 활성화, 테넌트 설치, 프런트엔드 재로딩을 마친 뒤 다음처럼 구분되어 나타납니다.
| 생성 명령 | 즉시 만들어지는 소스 | 공개 뒤 브라우저에서 보이는 것 |
|---|---|---|
nexia-apps:make-package-app | Workshop App 정의와 개요 화면 | App Launcher의 Workshop과 App Menu의 개요 |
nexia-apps:make-package-resource | Note Resource와 업무 화면 | 운영 → 노트, 목록·생성·상세·수정·Inspector |
대표 생성 경로는 다음과 같습니다.
packages/workshop/
src/Enums/NoteStatus.php
src/Models/Note.php
src/Policies/NotePolicy.php
src/Http/Controllers/NoteController.php
src/Contribution/Resources/NoteModule.php
database/migrations/tenant/*_create_wsp_notes_table.php
resources/js/resources/notes/
surface/Note{List,Show,Form}Surface.tsx
section/Note{List,Show,Form}Section.tsx
inspector/NoteInspector.tsx
resources/lang/{en,ko,zh}.json
tests/Feature/NoteAuthorizationTest.php
2. 테넌트에서 화면 켜기
TENANT에 앞에서 확인한 실습 테넌트 ID를 넣습니다. 새 번역과 권한을 동기화하고,
선택한 로컬 테넌트에 App의 마이그레이션·초기화·설치를 적용한 뒤 장기 실행 프로세스를 다시
불러옵니다.
TENANT=abc123def456
task artisan -- nexia-runtime:clear-translation-catalog-cache --no-ansi
task artisan -- nexia-apps:activate-package-app workshop
task artisan -- nexia-apps:install-dev-app workshop --tenant="$TENANT" --with-prerequisites
task app:reload
docker compose restart vite
docker compose up --wait --no-deps viteApp만 활성화·설치된 상태와 Note Resource까지 공개된 상태를 비교하면 App Menu는 다음처럼 달라집니다.
실행 전 실행 후
Workshop Workshop
└─ 개요 ├─ 개요
└─ 운영
└─ 노트
노트를 선택하면 /apps/workshop/notes 목록 화면이 열립니다. 아직 레코드를
만들지 않았다면 검색·필터·새로고침·새 노트 액션과 빈 상태가 보입니다.
활성화는 권한 정의를 등록하지만 사용자에게 권한을 자동으로 부여하지 않습니다.
접근 관리에서 App Role에 workshop.note.read, create, update를 넣고
화면을 확인할 사용자에게 배정합니다. 이 예제는 --record-owner=legal_entity를
사용합니다. 목록은 페이지 단위로 동작하며, 권한이 있는 하나 이상의 Legal Entity를
legal_entity_public_ids[]로 지정할 수 있습니다. 노트 생성에는 별도의 단일
legal_entity_public_id가 필요하며, 삭제된 전역 Shell Legal Entity를 선택하지 않습니다.
3. 브라우저에서 확인
실습 테넌트를 새로고침한 뒤 다음 순서로 엽니다.
- 왼쪽 App 런처에서 기타 앱 → Workshop을 엽니다.
- App Menu의 운영 → 노트를 선택합니다.
- 빈 목록에서 새 노트를 누르고 이름을
설치 확인 노트로 저장합니다. - 생성된 행을 선택해 오른쪽 Inspector를 열고, 상세 화면과 편집 화면도 확인합니다.

위 화면은 이 단계를 마친 실제 결과입니다. 왼쪽에는 Generator가 추가한 운영 → 노트 메뉴가 보이고, 본문에는 생성된 상세 화면의 ID·이름·상태· 생성일시·수정일시와 권한에 따라 표시되는 편집 액션이 보입니다.
각 동작은 화면을 다음 상태로 바꿉니다.
| 동작 | 화면 변화 | 확인할 것 |
|---|---|---|
| 운영 → 노트 선택 | 빈 목록 화면 열림 | 검색, 상태 필터, 새 노트 액션 |
| 새 노트 선택 | 생성 폼이 작업 탭에 열림 | 필수 이름 입력과 저장·취소 액션 |
설치 확인 노트 저장 | 상세 화면으로 이동 | 이름, 초안 상태, 생성일시, 수정일시 |
| 목록에서 행 선택 | 오른쪽 Inspector 열림 | 같은 기본 정보와 보기·수정 액션 |
| 편집 선택 | 수정 폼이 작업 탭에 열림 | 기존 이름과 저장되지 않은 변경 내용 |
생성된 목록에는 다음 동작이 이미 연결되어 있습니다.
- 이름 검색, 상태 필터, 허용된 컬럼 정렬, 페이지 이동
- 이름·상태 컬럼과 권한에 따른 보기·수정 행 동작
- 행 선택 시 기본 정보 Inspector, 별도 탭으로 상세 열기
- 이름을 입력하는 생성·수정 폼과 필드 오류 표시
초안,활성,보관상태 라벨
삭제 Route와 UI도 scaffold되지만 생성된 Policy의 delete()는 항상 false를
반환합니다. 보존과 감사 규칙을 정하기 전에는 삭제 메뉴가 활성화되지 않는 것이
정상입니다.
터미널에서 확인
Package contribution과 Route가 발견되는지 확인합니다.
task artisan -- nexia-apps:doctor-package-app workshop
task artisan -- route:list --path=workshop/notesDoctor에는 FAIL이 없어야 합니다. Route 목록에서 기본 /api/workshop/notes 컬렉션·단건 경로와
기존 Legal Entity 호환 경로를 확인합니다.
자주 발생하는 오류
| 증상 | 원인 | 해결 | 다시 확인 |
|---|---|---|---|
Package app not found at packages/workshop. Run nexia-apps:make-package-app first. | 선행 레시피의 Workshop 패키지가 없음 | 첫 App 만들기로 packages/workshop을 먼저 생성 | Resource 명령의 dry run에 예상 파일이 WOULD로 표시됨 |
| 생성은 성공했지만 운영 → 노트가 없음 | App 활성화·개발 설치 또는 실행 process 갱신이 끝나지 않음 | 2단계의 활성화, 개발 설치, PHP·Vite 재시작을 같은 tenant에 적용 | /apps/workshop/notes가 열리고 App Menu에 노트가 표시됨 |
| 목록은 보이지만 새 노트나 편집이 없음 | 사용자에게 workshop.note.create 또는 workshop.note.update 권한이 배정되지 않음 | App Role에 필요한 권한을 넣고 현재 Legal Entity의 Access Grant로 배정 | 새로고침 후 권한에 맞는 액션이 나타남 |
| 삭제가 보이지 않음 | 생성된 NotePolicy::delete()가 기본적으로 삭제를 거부함 | 보존·감사 규칙을 먼저 정한 뒤 App Policy와 UI를 함께 구현 | 정의한 삭제 권한과 Policy를 가진 사용자에게만 삭제 동작이 나타남 |
다음에 추가할 기능
생성 결과는 실행 가능한 업무 화면의 기준선이지 완성된 노트 도메인이 아닙니다. 제품 요구에 맞춰 다음 위치를 함께 바꿉니다.
| 추가할 기능 | 바꿀 위치 |
|---|---|
| 본문, 작성자, 분류 같은 업무 필드 | migration, 모델, Controller 검증, 정보 schema와 폼 |
| 실제 상태와 전이 규칙 | NoteStatus, 명시적인 App 명령과 Policy |
| 역할별 조회·생성·수정 범위 | Resource Module의 권한과 NotePolicy |
| 메뉴 위치·아이콘·순서 | NoteModule::$navigation |
| CSV·XLSX 내보내기와 가져오기 | 모델의 ExportImportable과 transferColumns() |
resources/js/resources/notes/의 목록·상세·폼·Inspector는 한 Resource
계약을 공유합니다. 필드를 추가할 때 한 화면만 고치지 말고 API 직렬화와 모든 표시
위치를 함께 검토하세요.
포함된 파일
#!/usr/bin/env sh
set -eu
# --record-owner accepts only `tenant` or `legal_entity`.
# Preview first; the command reports its package-local output without writing.
task artisan -- nexia-apps:make-package-resource workshop Note \
--record-owner=legal_entity \
--label-ko=노트 \
--label-ko-plural=노트 \
--label-zh=笔记 \
--label-zh-plural=笔记 \
--dry-run
# Run the same command without --dry-run only after reviewing the preview.
task artisan -- nexia-apps:make-package-resource workshop Note \
--record-owner=legal_entity \
--label-ko=노트 \
--label-ko-plural=노트 \
--label-zh=笔记 \
--label-zh-plural=笔记