사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
일반▾
인터페이스▾
관측성▾
성능▾
네이티브▾
개발▾

적은 코드로 CRUD 만들기

CRUD는 보통 가장 먼저 만드는 화면입니다. 목록을 보고, 하나를 열고, 새로 만들고, 고치고, 지웁니다. Akan에는 이 다섯 가지에 쓸 store 액션과 컴포넌트가 이미 있어서, 아래 세 조각과 이를 배치할 page만 직접 쓰면 됩니다.
post.signal.ts
slice입니다. 이 화면이 어떤 레코드를 읽고 고칠 수 있는지 정합니다.
Post.Template.tsx
폼 필드를 그립니다. 생성과 수정이 같은 Template을 씁니다.
Post.Zone.tsx
Load와 Model 컴포넌트로 slice를 화면 동작에 연결합니다.
할 일마다 쓰는 컴포넌트와, 자동으로 생기는 store 액션은 다음과 같습니다:
할 일store 키 · 액션
↳ 컴포넌트
목록postListInAdmin
Load.Units
열기viewPost(id)
Model.ViewWrapper + Model.ViewEditModal
생성newPost → submitPost
Load.Edit · Model.New
수정editPost(id) → submitPost
Load.Edit · Model.Edit
삭제removePost(id)
Model.Remove

Slice부터 시작하기

Slice는 모델을 바라보는 이름 붙은 창, 즉 한 화면이 보여 줄 목록입니다. inPublic, inAdmin, inProject처럼 화면 목적이 드러나는 이름을 붙이세요.
1. document에 filter 선언하기
"공개"가 무엇인지 document에 적습니다. filter마다 service에 query<Name>() 메서드가 생깁니다:
apps/blog/lib/post/post.document.ts
2. signal에 slice로 내보내기
그다음 signal에 화면마다 키 하나씩 slice로 적습니다:
apps/blog/lib/post/post.signal.ts
  • queryInPublic()은 filter에서 생깁니다. queryAny()는 모든 모델에 있는 any filter에서 생깁니다.
  • 키가 fetch 메서드 이름이 됩니다. inAdmin에서 fetch.initPostInAdmin()과 fetch.slice.postInAdmin이 생기고, 다음 단계에서 이것을 씁니다.
  • query는 반환만 하고 다듬지 않습니다. 정렬과 페이지 크기는 query의 .sort()가 아니라 fetch 옵션({ sort, page, limit })으로 정합니다.
누가 무엇을 부를 수 있나
guards 옵션은 자동으로 생기는 endpoint를 지키고, 이름 있는 slice는 자기 guard를 따로 가집니다:
root
어떤 filter든 실행할 수 있는 root slice initPost(queryKey, args)이므로, 항상 Admin으로 둡니다.
get
레코드 하나를 읽는 fetch.post(id)이며, viewPost와 editPost도 이것을 부릅니다.
cru
createPost, updatePost, removePost이며, create, update, remove 키로 하나씩 따로 덮어쓸 수 있습니다.
init({ guards })
이름 있는 slice의 목록이며, 위의 guards 옵션은 여기에 닿지 않습니다.

목록과 열기

page가 목록의 첫 페이지를 서버에서 불러옵니다. Zone이 Load.Units로 목록을 그리고, 목록 옆의 Model.ViewEditModal이 상세 보기와 수정 폼을 함께 처리합니다.
1. page에서 불러오기
page는 slice query를 시작하고 그 promise를 Zone에 넘깁니다:
apps/blog/page/admin/post/_index.tsx
2. Zone에서 그리기
카드마다 자기 post를 열고, 모달 하나가 지금 열린 post를 보여 줍니다:
apps/blog/lib/post/Post.Zone.tsx
  • Zone에 두는 이유. renderItem, renderView, renderTemplate은 함수인데, 서버 page는 클라이언트 컴포넌트에 함수를 넘길 수 없습니다.
  • 모달 하나가 모든 카드를 맡습니다. Model.ViewWrapper가 st.do.viewPost(id)를 부르면, 모달이 renderView를 그립니다.
  • 수정 기능이 들어 있습니다. 모달의 편집 버튼을 누르면 renderTemplate으로 바뀌고, 저장하면 상세 보기로 돌아옵니다. ⋮ 메뉴에는 삭제가 있고, menu={false}로 숨깁니다.
  • 빈 목록과 페이지 처리는 필요 없습니다. Load.Units가 행이 없으면 <Empty />를 보여 주고, 기본으로 페이지를 나눕니다.

생성하고 수정하기

생성과 수정에는 같은 Template을 씁니다. 폼을 감싸는 셸 컴포넌트가 postForm을 준비하고 저장하므로 Template은 필드만 신경 쓰면 됩니다. 폼에 id가 없으면 새로 만들고, 있으면 수정합니다.
폼이 나타날 자리에 맞춰 셸을 고릅니다:
Load.Edit
new.tsx 같은 전용 page에 두며, 그 경로에 들어오면 폼이 바로 열립니다.
Model.New
어디든 두면 새로 만들기 버튼을 직접 그리고, 누르면 st.do.newPost()를 부릅니다.
Model.Edit
어디든 두면 편집 버튼을 직접 그리고, 누르면 st.do.editPost(id)를 부릅니다.
Model.ViewEditModal
Zone 안 목록 옆에 두며, 상세 보기 안의 편집 버튼이 폼을 엽니다.
생성 page
생성 page는 새 post가 가질 초깃값으로 폼을 채웁니다:
apps/blog/page/admin/post/new.tsx
수정 page
수정 page는 레코드를 서버에서 불러와 같은 셸에 넘깁니다:
apps/blog/page/admin/post/[postId]/edit.tsx
  • type="form"은 폼을 그 자리에 그립니다. 저장 버튼도 함께 그리며, 기본값인 "modal"은 폼을 모달로 엽니다.
  • slice는 새 post가 들어갈 목록을 정합니다. postInAdmin으로 만든 post는 postListInAdmin 맨 위에 들어가고, 수정은 이미 불러온 모든 목록에 반영됩니다.
  • onSubmit과 onCancel에는 경로를 넘깁니다. "back"은 뒤로 가고, onSubmit 경로 속 [postId]는 저장된 post의 id로 바뀝니다.
  • 실수로 닫아도 폼이 남습니다. 셸은 입력하는 동안 폼을 저장해 두었다가 다음에 열 때 되살려 줍니다. draft={false}로 끕니다.
모달에서 수정하기
화면을 떠나지 않고 고치려면, Model.Edit이 편집 버튼과 모달을 함께 그립니다:
apps/blog/lib/post/Post.Util.tsx
  • 누르면 레코드를 불러옵니다. st.do.editPost(postId)가 postForm을 채우고 모달을 엽니다.
  • 버튼과 모달을 따로 둬야 하나요? Model.Edit은 Model.EditWrapper(트리거)와 Model.EditModal id={postId}(모달)를 합친 것입니다. 모달만 두면 editPost가 불리기 전까지 아무것도 열리지 않습니다.
  • 생성용 짝은 Model.New입니다. modelId 대신 partial로 새 폼의 초깃값을 넘깁니다.

삭제는 Util에 두기

삭제 버튼은 카드, 상세 보기, 메뉴처럼 여러 곳에 나타나곤 합니다. Post.Util.tsx의 Edit 옆에 Remove 하나를 두면 Unit, View, Zone 파일이 단순해집니다:
apps/blog/lib/post/Post.Util.tsx
  • 먼저 확인을 받습니다. 누르면 확인 모달이 열리고, 확인하면 st.do.removePost(postId)가 불린 뒤 성공 토스트가 뜹니다.
  • 모든 목록에서 행이 빠집니다. 삭제는 id로 하므로 이 모델의 어떤 slice를 넘겨도 됩니다. fetch.slice.post는 root slice입니다.
  • 상세 page라면 redirect="back"이나 경로를 넘겨, 삭제한 뒤 다른 곳으로 이동합니다. name은 확인 문구를 채우고, title, description, action은 모달의 각 부분을 바꿉니다.

꿀팁

  • Slice 이름은 DB query가 아니라 화면 목적에 맞춥니다. inAdmin은 목록을 어떻게 가져오는지가 아니라 누가 보는지를 말합니다.
  • Template은 단순하게 둡니다. postForm을 읽고 필드를 그리며, setter는 onChange={st.do.setTitleOnPost}처럼 그대로 넘깁니다.
  • 반복되는 동작은 Util로 뺍니다. 삭제, 발행, 승인, 대화상자 열기는 Post.Util.tsx에 한 번 쓰고 어디에든 둡니다.
더 읽기

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman