메인 콘텐츠로 건너뛰기
Sonamu는 엔티티 중심의 개발 워크플로우를 제공합니다. 이 가이드에서는 Sonamu 프로젝트에서 기능을 추가하고 수정하는 전체 흐름을 알아봅니다.

개발 사이클 개요

Sonamu의 개발은 다음과 같은 사이클로 진행됩니다:
1

1. 엔티티 설계 및 정의

Sonamu UI에서 엔티티 구조를 정의합니다.
2

2. 마이그레이션

데이터베이스에 테이블을 생성하거나 수정합니다.
3

3. 스캐폴딩

Model 파일과 테스트 파일을 자동 생성합니다.
4

4. 비즈니스 로직 구현

생성된 Model 파일에 비즈니스 로직을 추가합니다.
5

5. 동기화 (Sync)

변경사항을 프론트엔드와 동기화합니다.
6

6. 테스트

작성한 코드를 테스트합니다.
7

7. 프론트엔드 통합

자동 생성된 Service를 사용하여 UI를 구현합니다.

🎬 필요: 전체 개발 워크플로우 순서도 (위 7단계를 시각화)

1단계: 엔티티 설계 및 정의

Sonamu UI에서 엔티티 생성

개발은 항상 Sonamu UI에서 엔티티를 정의하는 것으로 시작합니다.
# API 서버 실행 (Sonamu UI 포함)
cd api
pnpm dev
브라우저에서 http://localhost:1028/sonamu-ui 에 접속하여:
  1. Entity 탭에서 새 엔티티 추가
  2. 필드 정의 - 타입, 제약조건, 기본값 설정
  3. Subset 정의 - API 응답 형태 정의
  4. Enum 정의 - 정렬/검색 옵션 정의
  5. Relation 정의 - 다른 엔티티와의 관계 설정
엔티티를 저장하면 다음 파일들이 자동 생성됩니다:
  • {entity}.entity.json - 엔티티 정의
  • {entity}.types.ts - TypeScript 타입 및 Zod 스키마
  • sonamu.generated.ts - Base 스키마 (모든 엔티티)

📸 필요: Sonamu UI에서 엔티티 정의하는 화면

CLI로 엔티티 생성 (선택사항)

CLI를 사용하여 빈 엔티티를 생성할 수도 있습니다:
pnpm sonamu stub entity User
이후 Sonamu UI에서 필드를 추가하고 편집할 수 있습니다.
더 알아보기

2단계: 마이그레이션

엔티티 정의를 데이터베이스에 반영합니다.

Sonamu UI에서 마이그레이션

  1. Migration 탭 클릭
  2. Generate Migration - SQL 자동 생성
  3. 생성된 SQL 검토
  4. Run Migration - 데이터베이스에 적용

📸 필요: Migration 탭에서 마이그레이션 실행하는 화면

CLI로 마이그레이션 (선택사항)

# 마이그레이션 상태 확인
pnpm sonamu migrate status

# 마이그레이션 실행
pnpm sonamu migrate run
마이그레이션은 되돌릴 수 없는 작업일 수 있습니다. 프로덕션 환경에서는 특히 주의하세요.
더 알아보기

3단계: 스캐폴딩

마이그레이션 후 Model 파일과 테스트 파일을 자동 생성합니다.

Sonamu UI에서 스캐폴딩

  1. Scaffolding 탭 클릭
  2. 엔티티 선택
  3. 생성할 템플릿 선택:
    • Model - 비즈니스 로직 및 API 엔드포인트
    • Model Test - 테스트 파일
  4. Preview (선택사항) - 생성될 코드 미리보기
  5. Generate - 파일 생성

📸 필요: Scaffolding 탭에서 Model 파일 생성하는 화면

CLI로 스캐폴딩 (선택사항)

# Model 파일 생성
pnpm sonamu scaffold model User

# 테스트 파일 생성
pnpm sonamu scaffold model_test User
생성되는 파일
  • {entity}.model.ts - 기본 CRUD API 포함
  • {entity}.model.test.ts - 테스트 템플릿
생성된 파일은 언제든지 수정하여 비즈니스 로직을 추가할 수 있습니다.
더 알아보기

4단계: 비즈니스 로직 구현

생성된 Model 파일에 비즈니스 로직을 추가합니다.

Model 파일 구조

user.model.ts
import { api, BaseModelClass, NotFoundException } from "sonamu";
import type { UserSubsetKey, UserSubsetMapping } from "../sonamu.generated";
import { userLoaderQueries, userSubsetQueries } from "../sonamu.generated.sso";
import type { User, UserListParams, UserSaveParams } from "./user.types";

class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries,
  typeof userLoaderQueries
> {
  constructor() {
    super("User", userSubsetQueries, userLoaderQueries);
  }

  @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"] })
  async findById<T extends UserSubsetKey>(
    subset: T,
    id: number,
  ): Promise<UserSubsetMapping[T]> {
    // 기본 CRUD 로직 (스캐폴딩으로 생성됨)
  }

  // 커스텀 비즈니스 로직 추가
  @api({ httpMethod: "POST", clients: ["axios"] })
  async changePassword(userId: number, newPassword: string): Promise<void> {
    // 비밀번호 변경 로직
    const hashedPassword = await this.hashPassword(newPassword);
    
    await this.db()
      .where("id", userId)
      .update({ password: hashedPassword });
  }

  private async hashPassword(password: string): Promise<string> {
    // 비밀번호 해싱 로직
    return password; // 실제로는 bcrypt 등 사용
  }
}

export const UserModel = new UserModelClass();

API 데코레이터

@api 데코레이터는 메서드를 REST API 엔드포인트로 자동 등록합니다:
@api({
  httpMethod: "GET" | "POST" | "PUT" | "DELETE",
  clients: ["axios", "tanstack-query"],  // 생성할 클라이언트 타입
  resourceName?: string,                  // 리소스명 (선택)
})
HMR (Hot Module Replacement)Model 파일을 수정하면 API 서버가 자동으로 재시작됩니다. 별도의 서버 재시작이 필요 없습니다!
더 알아보기

5단계: 동기화 (Sync)

변경사항을 프론트엔드와 동기화합니다.

자동 동기화 (HMR)

pnpm dev로 개발 서버를 실행하면 파일 변경 시 자동으로 동기화됩니다:
  • ✅ Model 파일 변경 → Service 파일 자동 재생성
  • ✅ Types 파일 변경 → Web 프로젝트로 자동 복사
  • ✅ Entity 정의 변경 → 스키마 자동 재생성

📸 필요: 터미널에서 HMR이 동작하는 로그 화면

수동 동기화

필요한 경우 수동으로 동기화할 수 있습니다:
pnpm sync
pnpm sync는 다음 작업을 수행합니다:
  1. sonamu.shared.ts 복사 - 공통 타입 및 유틸리티를 Web으로 복사
  2. 변경 파일 감지 - Checksum 기반으로 변경된 파일 확인
  3. 타입 동기화 - *.types.ts, *.generated.ts 파일을 Web으로 복사
  4. Service 생성 - Model의 API를 기반으로 프론트엔드 Service 생성
  5. Config 동기화 - .sonamu.env 파일 업데이트
Sync 명령어가 필요한 경우
  • 개발 서버를 종료한 상태에서 여러 파일을 수정한 경우
  • Git에서 다른 브랜치로 전환한 경우
  • 동기화 상태가 꼬였다고 생각될 경우
대부분의 경우 HMR이 자동으로 처리하므로 수동 sync는 드물게 필요합니다.
더 알아보기

6단계: 테스트

작성한 비즈니스 로직을 테스트합니다.

테스트 파일 작성

스캐폴딩으로 생성된 테스트 파일에 테스트 케이스를 추가합니다:
user.model.test.ts
import { beforeAll, describe, expect, test } from "vitest";
import { UserModel } from "./user.model";

describe("UserModel", () => {
  beforeAll(async () => {
    // 테스트 전 초기화
  });

  test("findById should return user", async () => {
    const user = await UserModel.findById("A", 1);
    expect(user).toBeDefined();
    expect(user.id).toBe(1);
  });

  test("changePassword should update password", async () => {
    await UserModel.changePassword(1, "newPassword123");
    
    // 비밀번호 변경 확인
    const user = await UserModel.findById("A", 1);
    expect(user.password).not.toBe("oldPassword");
  });
});

테스트 실행

# 모든 테스트 실행
pnpm test

# 특정 파일 테스트
pnpm test user.model.test.ts

# Watch 모드
pnpm test --watch

📸 필요: 터미널에서 테스트 실행 결과 화면

Sonamu는 Vitest를 사용합니다. 자세한 내용은 테스트 작성하기 문서를 참고하세요.
더 알아보기

7단계: 프론트엔드 통합

자동 생성된 Service를 사용하여 UI를 구현합니다.

생성된 Service 파일

Model에 정의된 API는 자동으로 프론트엔드 Service로 생성됩니다:
web/src/services/UserService.ts (자동 생성)
export class UserService {
  static async findById(subset: UserSubsetKey, id: number) {
    const res = await axios.get(`/api/users/${id}`, {
      params: { subset }
    });
    return res.data;
  }

  static async changePassword(userId: number, newPassword: string) {
    const res = await axios.post("/api/users/changePassword", {
      userId,
      newPassword
    });
    return res.data;
  }
}

React 컴포넌트에서 사용

web/src/pages/UserProfile.tsx
import { useState, useEffect } from "react";
import { UserService } from "@/services/UserService";
import type { User } from "@/services/user.types";

export function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadUser();
  }, [userId]);

  async function loadUser() {
    try {
      const userData = await UserService.findById("A", userId);
      setUser(userData);
    } catch (error) {
      console.error("Failed to load user:", error);
    } finally {
      setLoading(false);
    }
  }

  async function handlePasswordChange(newPassword: string) {
    await UserService.changePassword(userId, newPassword);
    alert("Password changed successfully!");
  }

  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      {/* 비밀번호 변경 UI */}
    </div>
  );
}
타입 안전성Service와 타입이 백엔드와 자동 동기화되므로, 타입 불일치 오류를 컴파일 타임에 발견할 수 있습니다!
더 알아보기

전체 워크플로우 예시

실제 기능을 추가하는 전체 과정을 예시로 살펴봅시다.

시나리오: “좋아요” 기능 추가

1

1. 엔티티 수정

Post 엔티티에 like_count 필드 추가
{
  "name": "like_count",
  "type": "integer",
  "desc": "좋아요 수",
  "dbDefault": "0"
}
2

2. 마이그레이션

ALTER TABLE posts ADD COLUMN like_count INTEGER DEFAULT 0;
3

3. Model에 비즈니스 로직 추가

@api({ httpMethod: "POST", clients: ["axios"] })
async addLike(postId: number): Promise<Post> {
  await this.db()
    .where("id", postId)
    .increment("like_count", 1);
  
  return await this.findById("A", postId);
}
4

4. 자동 동기화

HMR이 자동으로:
  • post.types.ts → Web으로 복사
  • PostService.tsaddLike 메서드 추가
5

5. 프론트엔드 구현

async function handleLike() {
  await PostService.addLike(post.id);
  // UI 업데이트
}
6

6. 테스트

test("addLike should increment like_count", async () => {
  const before = await PostModel.findById("A", 1);
  await PostModel.addLike(1);
  const after = await PostModel.findById("A", 1);
  
  expect(after.like_count).toBe(before.like_count + 1);
});

🎬 필요: 위 6단계를 실제로 시연하는 영상 (좋아요 기능 추가 전체 과정)

개발 팁

1. Entity First 원칙

모든 개발은 엔티티 정의에서 시작합니다. 데이터 구조가 명확해야 API와 UI도 명확해집니다.

2. Subset 활용

다양한 Subset을 미리 정의해두면 API 응답 크기를 최적화할 수 있습니다:
  • A (All): 모든 필드 (상세 페이지)
  • C (Compact): 주요 필드만 (목록 페이지)
  • S (Summary): 최소 필드 (미리보기)

3. 관계(Relation) 설계

엔티티 간 관계를 명확히 정의하면 JOIN 쿼리가 자동으로 생성됩니다.

4. 테스트 우선 작성

복잡한 비즈니스 로직은 테스트를 먼저 작성하면 안전하게 개발할 수 있습니다.

5. HMR 활용

개발 중에는 pnpm dev를 계속 실행해두고, 파일만 수정하면 자동으로 동기화됩니다.

6. 문제 발생 시

동기화 상태가 이상하면:
  1. 개발 서버 재시작
  2. pnpm sync 수동 실행
  3. rm sonamu.lock 후 재시작

다음 단계

개발 워크플로우를 이해했다면, 다음 주제를 학습해보세요: