사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
스키마 설계
Akan에서는
<model>.constant.ts가 데이터의 모양을 정합니다. 가장 쉬운 설계 방법은 그 데이터를 읽을 화면이나 API에서 출발하는 것입니다.대부분은 간단한 규칙 하나로 정해집니다:
한 document에 함께 두기
작고 늘 함께 읽는 데이터는 document 하나 안에 둡니다.
links: field([ExternalLink])별도 모델로 나누기
계속 늘어나는 데이터는 따로 모델을 만들어 옮깁니다.
post: field(ID, { ref: "post" })이 페이지에서 쓰는 말
용어설명
document
게시글 한 개처럼, 모델로 저장된 레코드 하나입니다.
relation
타입이 다른 모델인 필드로, id만 저장하고 응답을 만들 때 서버가 그 모델을 불러옵니다.
scalar
lib/__scalar/ 아래에 선언하고 document 안에 그대로 저장하는 값 객체입니다.Light<Model>
LightPost처럼, 목록의 행 하나가 보여 주는 몇 개의 필드입니다.child model
게시글을 가리키는 댓글처럼, document마다 부모를 가리키는 모델입니다.
화면에서 시작하기
필드를 추가하기 전에 목록 화면, 상세 화면, 입력 폼을 먼저 떠올려 보세요. 스키마는 자주 읽는 화면을 쉽게 만드는 모양이어야 합니다.
| 화면 | 담기는 곳 |
|---|---|
| ↳ 먼저 물어볼 것 | |
| 목록 화면 | LightPost |
| 각 행에 어떤 작은 정보가 보여야 하나요? | |
| 상세 화면 | Post |
| 어떤 데이터를 한 번에 함께 불러와야 하나요? | |
| 입력 폼 | PostInput |
| 사용자가 직접 입력하는 값은 무엇인가요? | |
| 하위 목록 | Comment |
| 댓글이나 로그처럼 끝없이 늘어나서 따로 모델이 필요한 데이터는 무엇인가요? | |
목록 화면에 대한 답이 곧
LightPost의 key 목록이 됩니다:apps/myapp/lib/post/post.constant.ts
- 목록의 모든 행이 이 모양입니다. slice 목록은
LightPost행을 담으므로, 여기 적은 key는 모든 행에 실려 갑니다. - id와 시각 필드는 저절로 들어갑니다.
id,createdAt,updatedAt,removedAt는 항상 포함되므로 목록에 적지 않습니다. - key 목록은 타입 검사를 받습니다.
PostObject에 있는 key만 쓸 수 있고, 배열 끝에as const를 붙입니다.
관계 크기
데이터 하나가 여러 하위 데이터를 가질 때는 먼저 몇 개까지 늘어날지 생각해 보세요. 그 개수에 따라 스키마가 달라집니다.
개수저장 방법
몇 개 (one to few)
사용자의 링크 몇 개나 게시글의 작은 설정값처럼, scalar 배열로 document 안에 넣습니다.
여러 개 (one to many)
선택한 파일이나 담당자 목록처럼, id만 저장하는 relation 배열로 둡니다.
끝없이 (one to squillions)
댓글, 로그, 이벤트, 장비 상태 기록처럼, 부모를 가리키는 child 모델을 따로 만듭니다.
댓글은 끝없이 늘어날 수 있으므로, 게시글을 가리키는 별도 모델로 만듭니다:
apps/myapp/lib/comment/comment.constant.ts
- 자식이 부모를 가리킵니다. 게시글에는 댓글 배열이 없으므로, 댓글이 아무리 늘어도 게시글의 크기는 그대로입니다.
ref는 부모 모델의 이름입니다.ref: "post"는 저장된 id가 어느 모델의 것인지 알려 줍니다.cascade: "removeWith"를 달면 게시글을 삭제할 때 댓글도 함께 삭제됩니다. 댓글이 게시글보다 오래 남아야 한다면 빼세요.


document 안의 배열이 끝없이 커지게 두지 마세요. 필드는 모두 document 자체에 저장되므로, 그 행을 읽을 때마다 배열 전체가 함께 실려 옵니다.
참조할까, 복사할까
게시글 목록에는 보통 글마다 작성자 이름과 사진이 함께 나옵니다. 아래 두 방법 모두 이 값을 같은 응답에 담아 주므로, 목록 화면에서 요청을 더 보낼 필요가 없습니다.
특징
참조
field(LightUser)
복사
field(AuthorCard)
목록을 읽을 때
같은 응답에 담김
✓
✓
어느 쪽이든 목록 화면이 요청을 한 번 더 보내지 않습니다.
항상 최신
✓
서버가 응답을 만들 때마다 사용자를 새로 찾아 옵니다.
읽을 때 조회 없음
✓
복사해 둔 값을 저장된 그대로 읽습니다.
게시글을 저장할 때
id만 저장
✓
게시글에는 사용자 id만 남습니다.
갱신은 직접
✓
복사본은 코드가 다시 써 줄 때만 바뀝니다.
✓해당해당 없음
- 복사하기 좋은 값: 이름, 썸네일, 짧은 상태 문구처럼 작고 잘 바뀌지 않는 값입니다.
- 참조가 나은 값: 매초 바뀌거나 항상 완벽하게 최신이어야 하는 값입니다.
각각 선언하는 법
참조는 타입이
LightUser나 File 같은 모델인 필드입니다:apps/myapp/lib/post/post.constant.ts
복사하려면 먼저 스냅샷을 담을 scalar를 만듭니다:
apps/myapp/lib/__scalar/authorCard/authorCard.constant.ts
그다음 게시글에 그 타입의 필드를 둡니다:
apps/myapp/lib/post/post.constant.ts
- 작성자는
PostInput이 아니라PostObject에 둡니다. 썸네일은 사용자가 올리지만, 작성자는 서버가 로그인한 사용자로 채우고 클라이언트가 보낸 값은 믿지 않습니다. - 참조는
Light<Model>을 가리키게 합니다.field(LightUser)는 light 필드만 보내고,field(User)는 사용자 전체를 보냅니다. - scalar는
akan create-scalar authorCard로 만듭니다.lib/__scalar/authorCard/에 생성됩니다.
모델을 이루는 클래스 다섯 개
모든
<model>.constant.ts는 클래스 다섯 개를 항상 이 순서로 선언합니다. 다섯 개 모두 같은 데이터를 쓰임에 따라 다른 모양으로 보여 줍니다.클래스설명
<Model>Input
사용자가 폼으로 입력할 수 있는 필드입니다.
<Model>Object
<Model>Input에 status나 카운터처럼 서버가 관리하는 필드를 더한 것입니다.Light<Model>
목록 행과 relation에 쓰는 작은 모양으로,
isNew() 같은 표시용 메서드도 여기에 둡니다.<Model>
<Model>Object와 Light<Model>을 모두 합친 전체 document입니다.<Model>Insight
대시보드에 쓰는 요약 숫자로,
count가 항상 들어 있습니다.다섯 개를 모두 갖춘
post.constant.ts입니다:apps/myapp/lib/post/post.constant.ts
- 비어 있어도 다섯 개를 모두 씁니다. 대시보드에 숫자가 필요해질 때까지
PostInsight는(field) => ({})로 둡니다. - 각 클래스는 앞의 클래스를 바탕으로 합니다.
PostObject는PostInput을 넓히고,LightPost는PostObject에서 고르고,Post는 둘을 합칩니다. - enum은 클래스들보다 위에 둡니다.
enumOf에는as const배열을 넘기고,default에는 그중 한 값을 적습니다.
설계 체크리스트
필드를 추가하기 전에 아래 네 가지를 확인하세요.
- 매일 읽는 흐름에 맞춥니다. 완벽한 DB 다이어그램보다 사람들이 매일 여는 화면이 더 중요합니다.
- 끝없이 커지는 배열은 모델로 분리합니다. 댓글, 로그, 이벤트는 배열 필드가 아니라 child 모델에 둡니다.
Light<Model>은 작게 유지합니다. 상세 페이지가 아니라 목록의 한 행처럼 느껴져야 합니다.- 기본은 참조, 복사는 이유가 있을 때만. 작고 잘 바뀌지 않는 값만 복사하세요.
이어서 읽기