본문으로 건너뛰기

예제

Resource 생성하기

Resource를 생성해 App Menu에 공개하고 목록·폼·상세·Inspector 화면이 생기는 과정을 확인합니다.

예제 유형 레시피

개요

Resource 예제

첫 App 예제가 App 생성부터 테넌트 설치까지의 전체 흐름을 보여준다면, 이 예제는 nexia-apps:make-package-resource workshop Note 전후에 화면이 어떻게 달라지는지에 집중합니다. App Menu의 노트가 생기고 목록, 생성, 상세, 수정, Inspector 화면으로 이어지는 과정을 단계별로 확인합니다.

최종 결과

구분생기는 것확인 위치
App MenuWorkshop 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 인터페이스를 명시적으로 구현합니다.

코드 예시
PHP
final class NoteModule extends AbstractResourceModule implements
    NavigationContribution,
    PermissionContribution,
    ResourceAuthorizationContribution,
    ResourceCatalogContribution,
    ShellResourceContribution

AbstractResourceModule은 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 Resourcestubs/package-resource/resource-module.stub
Host standard Resourcestubs/resource/resource-module.stub
Host document Resourcestubs/resource-document/resource-module.stub

모든 일반 Resource가 사람 field를 소유하는 것은 아니므로 레시피는 주석 상태로 생성됩니다. 도메인에 사람 field가 필요하면 생성된 Module에서 주석을 풀고 업무 의미에 맞는 이름으로 바꾸되, 선택 identity는 Party로 유지합니다.

코드 예시
PHP
'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:workshop Route 가드가 있어야 합니다.
  • 처음부터 생성 결과를 보려면 Note가 아직 없는 Workshop App 기준선에서 실행해야 합니다. 기존 파일이 있으면 생성기는 덮어쓰지 않고 SKIP합니다.
  • 화면 확인에 사용할 테넌트 ID와 Workshop App 권한을 배정할 수 있는 접근 관리자가 필요합니다.

테넌트 ID를 확인합니다.

코드 예시
Shell
task artisan -- tenants:list --no-ansi

1. 생성 결과 미리 보기와 생성

commands.sh는 먼저 --dry-run으로 변경 대상을 보여주고, 같은 명령을 실제로 실행합니다.

코드 예시
Shell
sh docs/developers/examples/resource/commands.sh

dry-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-appWorkshop App 정의와 개요 화면App Launcher의 Workshop과 App Menu의 개요
nexia-apps:make-package-resourceNote 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의 마이그레이션·초기화·설치를 적용한 뒤 장기 실행 프로세스를 다시 불러옵니다.

코드 예시
Shell
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 vite

App만 활성화·설치된 상태와 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. 브라우저에서 확인

실습 테넌트를 새로고침한 뒤 다음 순서로 엽니다.

  1. 왼쪽 App 런처에서 기타 앱 → Workshop을 엽니다.
  2. App Menu의 운영 → 노트를 선택합니다.
  3. 빈 목록에서 새 노트를 누르고 이름을 설치 확인 노트로 저장합니다.
  4. 생성된 행을 선택해 오른쪽 Inspector를 열고, 상세 화면과 편집 화면도 확인합니다.

Workshop App Menu의 운영 아래 노트와 설치 확인 노트 상세 화면

위 화면은 이 단계를 마친 실제 결과입니다. 왼쪽에는 Generator가 추가한 운영 → 노트 메뉴가 보이고, 본문에는 생성된 상세 화면의 ID·이름·상태· 생성일시·수정일시와 권한에 따라 표시되는 편집 액션이 보입니다.

각 동작은 화면을 다음 상태로 바꿉니다.

동작화면 변화확인할 것
운영 → 노트 선택빈 목록 화면 열림검색, 상태 필터, 새 노트 액션
새 노트 선택생성 폼이 작업 탭에 열림필수 이름 입력과 저장·취소 액션
설치 확인 노트 저장상세 화면으로 이동이름, 초안 상태, 생성일시, 수정일시
목록에서 행 선택오른쪽 Inspector 열림같은 기본 정보와 보기·수정 액션
편집 선택수정 폼이 작업 탭에 열림기존 이름과 저장되지 않은 변경 내용

생성된 목록에는 다음 동작이 이미 연결되어 있습니다.

  • 이름 검색, 상태 필터, 허용된 컬럼 정렬, 페이지 이동
  • 이름·상태 컬럼과 권한에 따른 보기·수정 행 동작
  • 행 선택 시 기본 정보 Inspector, 별도 탭으로 상세 열기
  • 이름을 입력하는 생성·수정 폼과 필드 오류 표시
  • 초안, 활성, 보관 상태 라벨

삭제 Route와 UI도 scaffold되지만 생성된 Policy의 delete()는 항상 false를 반환합니다. 보존과 감사 규칙을 정하기 전에는 삭제 메뉴가 활성화되지 않는 것이 정상입니다.

터미널에서 확인

Package contribution과 Route가 발견되는지 확인합니다.

코드 예시
Shell
task artisan -- nexia-apps:doctor-package-app workshop
task artisan -- route:list --path=workshop/notes

Doctor에는 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 직렬화와 모든 표시 위치를 함께 검토하세요.

포함된 파일

코드 예시
Shell
#!/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=笔记