Enum은 미리 정의된 값 목록 중 하나만 선택할 수 있는 열거형 타입입니다. 타입 안전성을 제공하고 사용자 인터페이스에서 선택 옵션을 쉽게 구성할 수 있습니다.
Enum이란?
Enum은 제한된 값의 집합을 정의하여 다음을 제공합니다:
타입 안전성
정의되지 않은 값 입력 방지TypeScript에서 Union 타입으로 자동 변환
UI 자동 생성
선택 목록 자동 구성Select, ButtonSet 컴포넌트에 즉시 사용
유지보수성
중앙 집중식 관리값 변경 시 한 곳만 수정
Enum 정의하기
entity.json에서 정의
Entity의 enums 섹션에 키-레이블 쌍으로 정의합니다.
{
"id": "User",
"props": [
{
"name": "role",
"type": "enum",
"id": "UserRole",
"desc": "사용자 역할"
},
{
"name": "status",
"type": "enum",
"id": "UserStatus",
"desc": "계정 상태"
}
],
"enums": {
"UserRole": {
"admin": "관리자",
"normal": "일반 사용자",
"guest": "게스트"
},
"UserStatus": {
"active": "활성",
"inactive": "비활성",
"suspended": "정지",
"deleted": "삭제됨"
}
}
}
구조:
- Enum ID: PascalCase (예:
UserRole, PostStatus)
- Key: 소문자, 숫자, 언더스코어 (예:
admin, draft_saved)
- Label: 표시용 텍스트, 한글 가능 (예: “관리자”, “임시저장”)
Sonamu UI에서 정의
Entity 편집 페이지로 이동
Sonamu UI에서 Entity를 선택합니다.
Enums 섹션으로 스크롤
Props, Indexes, Subsets 아래에 Enums 섹션이 있습니다.
Add Enum 클릭
새 Enum을 추가합니다.Enum ID 입력:
- PascalCase로 입력
- Entity와 연관시키려면
{Entity} 패턴 사용
- 예:
$ModelRole → UserRole (자동 변환)
Enum 값 추가
각 Enum은 별도 탭으로 표시되며, “Add Row” 버튼으로 키-레이블 쌍을 추가합니다.
- Key: 영문 소문자, 숫자, 언더스코어
- Label: 한글 또는 표시명
저장
Entity를 저장하면 Enum이 함께 저장됩니다.
📸 필요: Sonamu UI의 Enums 섹션 - 여러 Enum이 탭으로 구분되어 표시
자동 생성되는 코드
Enum을 정의하면 Sonamu가 자동으로 다음을 생성합니다:
1. Zod 스키마
import { z } from "zod";
// Enum 스키마
export const UserRole = z.enum(["admin", "normal", "guest"]);
// Enum 레이블
export const UserRoleLabels = {
admin: "관리자",
normal: "일반 사용자",
guest: "게스트",
} as const;
2. TypeScript 타입
import { UserRole } from "./sonamu.generated";
// Union 타입으로 추론
export type UserRole = z.infer<typeof UserRole>;
// 결과: "admin" | "normal" | "guest"
3. 레이블 헬퍼 함수
// 레이블 가져오기 함수
export function getUserRoleLabel(role: UserRole): string {
return UserRoleLabels[role];
}
Enum 사용하기
Props에서 사용
{
"props": [
{
"name": "role",
"type": "enum",
"id": "UserRole",
"desc": "사용자 역할",
"dbDefault": "normal"
}
]
}
데이터베이스:
- 컬럼 타입:
text
- 저장 값: Key 값 (예:
"admin")
TypeScript:
type User = {
id: number;
role: "admin" | "normal" | "guest"; // Union 타입
};
API에서 사용
import { UserRole } from "./user.types";
export class UserModel extends BaseModel {
@api({ httpMethod: "POST" })
async updateRole(userId: number, role: UserRole) {
// role은 타입 안전함
await this.puri()
.where("id", userId)
.update({ role });
return { success: true };
}
}
레이블 표시
import { getUserRoleLabel } from "./sonamu.generated";
const user = await UserModel.findById(1);
const roleLabel = getUserRoleLabel(user.role);
// "관리자"
자주 사용되는 Enum 패턴
1. OrderBy Enum
정렬 옵션을 정의합니다.
{
"enums": {
"UserOrderBy": {
"id-desc": "ID 최신순",
"id-asc": "ID 오래된순",
"created_at-desc": "등록일 최신순",
"created_at-asc": "등록일 오래된순",
"username-asc": "이름순"
}
}
}
활용:
async findAll(orderBy: UserOrderBy = "id-desc") {
const [field, direction] = orderBy.split("-");
return this.puri()
.orderBy(field, direction as "asc" | "desc")
.many();
}
2. SearchField Enum
검색 가능한 필드를 정의합니다.
{
"enums": {
"UserSearchField": {
"email": "이메일",
"username": "이름",
"phone": "전화번호"
}
}
}
활용:
async search(field: UserSearchField, keyword: string) {
return this.puri()
.whereLike(field, `%${keyword}%`)
.many();
}
3. Status Enum
상태를 정의합니다.
{
"enums": {
"PostStatus": {
"draft": "임시저장",
"published": "발행됨",
"archived": "보관됨",
"deleted": "삭제됨"
}
}
}
활용:
async publish(postId: number) {
await this.puri()
.where("id", postId)
.update({ status: "published" });
}
4. Type/Category Enum
분류를 정의합니다.
{
"enums": {
"NotificationType": {
"email": "이메일",
"sms": "문자메시지",
"push": "푸시 알림",
"in_app": "인앱 알림"
}
}
}
Enum 배열 타입
여러 Enum 값을 배열로 저장할 수 있습니다.
{
"props": [
{
"name": "permissions",
"type": "enum[]",
"id": "Permission",
"desc": "권한 목록"
}
],
"enums": {
"Permission": {
"read": "읽기",
"write": "쓰기",
"delete": "삭제",
"admin": "관리자"
}
}
}
데이터베이스:
- 컬럼 타입:
text[]
- 저장 값:
["read", "write"]
TypeScript:
type User = {
id: number;
permissions: ("read" | "write" | "delete" | "admin")[];
};
활용:
// 권한 체크
function hasPermission(user: User, permission: Permission): boolean {
return user.permissions.includes(permission);
}
// 권한 추가
async grantPermission(userId: number, permission: Permission) {
const user = await this.findById(userId);
if (!user.permissions.includes(permission)) {
await this.puri()
.where("id", userId)
.update({
permissions: [...user.permissions, permission]
});
}
}
프론트엔드에서 Enum 사용
Select 컴포넌트
import { UserRoleSelect } from "@/components/UserRoleSelect";
function UserForm() {
const [role, setRole] = useState<UserRole>("normal");
return (
<UserRoleSelect
value={role}
onChange={setRole}
/>
);
}
import { UserStatusButtonSet } from "@/components/UserStatusButtonSet";
function StatusFilter() {
const [status, setStatus] = useState<UserStatus | null>(null);
return (
<UserStatusButtonSet
value={status}
onChange={setStatus}
allowNull // 선택 해제 가능
/>
);
}
Enum 검증
Zod를 사용하여 자동으로 Enum 값을 검증합니다.
import { UserRole } from "./sonamu.generated";
export const UpdateRoleParams = z.object({
userId: z.number(),
role: UserRole, // 자동 검증
});
// 잘못된 값 입력 시
UpdateRoleParams.parse({
userId: 1,
role: "invalid_role" // Error!
});
Enum 네이밍 규칙
권장 패턴
| 용도 | 패턴 | 예시 |
|---|
| Entity 상태 | {Entity}Status | PostStatus, OrderStatus |
| Entity 타입 | {Entity}Type | NotificationType, PaymentType |
| Entity 역할 | {Entity}Role | UserRole, MemberRole |
| 정렬 옵션 | {Entity}OrderBy | UserOrderBy, PostOrderBy |
| 검색 필드 | {Entity}SearchField | UserSearchField |
| 카테고리 | {Entity}Category | ProductCategory |
| 권한 | {Entity}Permission | UserPermission |
$Model 패턴
Entity 이름을 동적으로 포함하려면 $Model을 사용합니다:
{
"enums": {
"$ModelRole": {
"admin": "관리자",
"normal": "일반"
},
"$ModelStatus": {
"active": "활성",
"inactive": "비활성"
}
}
}
변환 결과:
User Entity → UserRole, UserStatus
Post Entity → PostRole, PostStatus
주의사항
Enum 값 변경 시 주의이미 데이터베이스에 저장된 Enum 키를 변경하면 기존 데이터와 불일치가 발생합니다.변경 방법:
- 새 키 추가
- 데이터 마이그레이션 (기존 키 → 새 키)
- 구 키 삭제
Key 네이밍 제한Enum Key는 다음만 사용 가능합니다:
- 소문자 (a-z)
- 숫자 (0-9)
- 언더스코어 (_)
하이픈(-)은 사용 불가합니다.
레이블 변경은 자유롭게Label은 표시용이므로 언제든지 변경 가능합니다. 데이터베이스에는 Key만 저장됩니다.
Enum vs 별도 테이블
| 기준 | Enum | 별도 테이블 |
|---|
| 값 개수 | 적음 (< 20개) | 많음 (> 20개) |
| 변경 빈도 | 거의 없음 | 자주 변경 |
| 추가 정보 | 불필요 | 필요 (설명, 순서 등) |
| 성능 | 빠름 (JOIN 불필요) | 느림 (JOIN 필요) |
| 권장 사용 | 상태, 역할, 타입 | 카테고리, 코드 테이블 |
Enum 사용 예시:
- 사용자 역할 (admin, user, guest)
- 게시글 상태 (draft, published, deleted)
- 알림 타입 (email, sms, push)
별도 테이블 사용 예시:
- 지역 목록 (서울, 부산, …)
- 상품 카테고리 (계층 구조)
- 국가 코드 (추가 정보 많음)
다음 단계