-
-
Notifications
You must be signed in to change notification settings - Fork 0
entity-derive가 지원하는 모든 속성에 대한 완전한 가이드입니다.
#[entity(...)]를 사용하여 구조체에 적용합니다:
#[derive(Entity)]
#[entity(
table = "users",
schema = "core",
sql = "full",
dialect = "postgres",
uuid = "v7",
soft_delete,
returning = "full",
error = "AppError",
events,
hooks,
commands
)]
pub struct User { /* ... */ }| 속성 | 필수 | 기본값 | 설명 |
|---|---|---|---|
table |
예 | — | 데이터베이스 테이블 이름 |
schema |
아니오 | "public" |
데이터베이스 스키마 |
sql |
아니오 | "full" |
SQL 생성 레벨 |
dialect |
아니오 | "postgres" |
데이터베이스 방언 |
uuid |
아니오 | "v7" |
ID 생성용 UUID 버전 |
soft_delete |
아니오 | false |
소프트 삭제 활성화 |
returning |
아니오 | "full" |
RETURNING 절 모드 |
upsert(...) |
아니오 | — |
INSERT ... ON CONFLICT 기반 upsert 메서드 생성 |
error |
아니오 | sqlx::Error |
커스텀 에러 타입 |
events |
아니오 | false |
생명주기 이벤트 생성 |
hooks |
아니오 | false |
생명주기 훅 트레이트 생성 |
commands |
아니오 | false |
CQRS 커맨드 패턴 활성화 |
데이터베이스 테이블 이름입니다.
#[entity(table = "users")] // → FROM users
#[entity(table = "user_profiles")] // → FROM user_profiles데이터베이스 스키마입니다. 기본값: "public".
#[entity(table = "users")] // → FROM public.users
#[entity(table = "users", schema = "core")] // → FROM core.users
#[entity(table = "users", schema = "auth")] // → FROM auth.usersSQL 생성 레벨입니다. 기본값: "full".
| 값 | Repository Trait | PgPool 구현 | 사용 사례 |
|---|---|---|---|
"full" |
예 | 예 | 표준 CRUD 엔티티 |
"trait" |
예 | 아니오 | 커스텀 쿼리 (joins, CTE) |
"none" |
아니오 | 아니오 | DTO만, 데이터베이스 없음 |
#[entity(table = "users", sql = "full")] // 전체 자동화 (기본값)
#[entity(table = "users", sql = "trait")] // 트레이트만, SQL은 직접 구현
#[entity(table = "users", sql = "none")] // 데이터베이스 레이어 없음SQL 생성용 데이터베이스 방언입니다. 기본값: "postgres".
| 방언 | 별칭 | 클라이언트 타입 | 상태 |
|---|---|---|---|
"postgres" |
"pg", "postgresql"
|
sqlx::PgPool |
안정 |
"clickhouse" |
"ch" |
clickhouse::Client |
예정 |
"mongodb" |
"mongo" |
mongodb::Client |
예정 |
자동 생성되는 기본 키용 UUID 버전입니다. 기본값: "v7".
| 버전 | 메서드 | 속성 |
|---|---|---|
"v7" |
Uuid::now_v7() |
시간순 정렬, 정렬 가능 (권장) |
"v4" |
Uuid::new_v4() |
랜덤, 널리 호환 |
#[entity(table = "users", uuid = "v7")] // 시간순 정렬 (기본값)
#[entity(table = "sessions", uuid = "v4")] // 랜덤 UUIDUUID v7을 사용하는 이유:
- 시간순 정렬: 생성 시간별 자연스러운 정렬
- 더 나은 데이터베이스 인덱스 성능
- 조정이 필요 없음 (시퀀스와 달리)
- 분산 시스템에서 전역 고유
레코드를 삭제하는 대신 삭제로 표시하는 소프트 삭제를 활성화합니다.
#[derive(Entity)]
#[entity(table = "documents", soft_delete)]
pub struct Document {
#[id]
pub id: Uuid,
#[field(create, response)]
pub title: String,
#[field(skip)]
pub deleted_at: Option<DateTime<Utc>>, // 필수 필드
}생성되는 메서드:
-
delete()— DELETE 대신deleted_at = NOW()설정 -
hard_delete()— 레코드를 영구적으로 삭제 -
restore()—deleted_at = NULL설정 -
find_by_id()/list()— 자동으로 삭제된 레코드 필터링 -
find_by_id_with_deleted()/list_with_deleted()— 삭제된 레코드 포함
INSERT/UPDATE 후 어떤 데이터를 가져올지 제어합니다. 기본값: "full".
| 모드 | SQL 절 | 사용 사례 |
|---|---|---|
"full" |
RETURNING * |
DB 생성 값 포함 모든 필드 가져오기 (기본값) |
"id" |
RETURNING id |
삽입 확인, 미리 빌드된 엔티티 반환 |
"none" |
(RETURNING 없음) | Fire-and-forget, 가장 빠른 옵션 |
"col1, col2" |
RETURNING col1, col2 |
특정 컬럼 반환 |
#[entity(table = "logs", returning = "none")] // 가장 빠름
#[entity(table = "users", returning = "full")] // DB 생성 값 가져오기
#[entity(table = "events", returning = "id, created_at")] // 커스텀 컬럼INSERT ... ON CONFLICT 기반의 upsert 리포지토리 메서드를 생성합니다.
#[derive(Entity)]
#[entity(table = "users", upsert(conflict = "email"))]
pub struct User {
#[id]
pub id: Uuid,
#[field(create, response)]
#[column(unique)]
pub email: String,
#[field(create, update, response)]
pub name: String,
}| 옵션 | 필수 | 기본값 | 설명 |
|---|---|---|---|
conflict |
예 | — | 쉼표로 구분된 충돌 대상 컬럼 |
action |
아니오 | "update" |
"update" (DO UPDATE) 또는 "nothing" (DO NOTHING) |
생성되는 코드:
-
action = "update"→async fn upsert(&self, dto: CreateUserRequest) -> Result<User, Error>— 충돌 컬럼을 제외한 모든 컬럼을 덮어쓰고(DO UPDATE SET col = EXCLUDED.col) 저장된 행을 반환 -
action = "nothing"→async fn upsert(&self, dto: CreateUserRequest) -> Result<Option<User>, Error>— 기존 행을 그대로 유지;None은 충돌 행이 이미 존재했음을 의미
컴파일 타임 검증:
- 충돌 컬럼이 존재해야 하며 고유성 보장이 필요 (
#[id],#[column(unique)]또는 일치하는unique_index(...)) -
returning = "full"필요 (기본값) -
action = "update"는 충돌 컬럼 외에 갱신 가능한 컬럼이 최소 하나 필요
streams가 활성화되면 upsert는 반환하는 모든 행에 대해 Created 알림을 발행합니다.
리포지토리용 커스텀 에러 타입입니다. 기본값: sqlx::Error.
#[derive(Debug)]
pub enum AppError {
Database(sqlx::Error),
NotFound,
Validation(String),
}
impl std::error::Error for AppError {}
impl std::fmt::Display for AppError { /* ... */ }
// 필수: sqlx::Error에서 변환
impl From<sqlx::Error> for AppError {
fn from(err: sqlx::Error) -> Self {
AppError::Database(err)
}
}
#[derive(Entity)]
#[entity(table = "users", error = "AppError")]
pub struct User { /* ... */ }
// 생성된 리포지토리는 AppError를 사용:
// impl UserRepository for PgPool {
// type Error = AppError;
// ...
// }생명주기 이벤트 열거형을 생성합니다. 자세한 내용은 이벤트를 참조하세요.
#[entity(table = "orders", events)]생성됨:
pub enum OrderEvent {
Created(Order),
Updated { id: Uuid, changes: UpdateOrderRequest },
Deleted(Uuid),
}생명주기 훅 트레이트를 생성합니다. 자세한 내용은 훅을 참조하세요.
#[entity(table = "users", hooks)]생성됨:
#[async_trait]
pub trait UserHooks: Send + Sync {
type Error: std::error::Error + Send + Sync;
async fn before_create(&self, dto: &mut CreateUserRequest) -> Result<(), Self::Error>;
async fn after_create(&self, entity: &User) -> Result<(), Self::Error>;
async fn before_update(&self, id: &Uuid, dto: &mut UpdateUserRequest) -> Result<(), Self::Error>;
async fn after_update(&self, entity: &User) -> Result<(), Self::Error>;
async fn before_delete(&self, id: &Uuid) -> Result<(), Self::Error>;
async fn after_delete(&self, id: &Uuid) -> Result<(), Self::Error>;
}CQRS 커맨드 패턴을 활성화합니다. 자세한 내용은 커맨드를 참조하세요.
#[entity(table = "users", commands)]
#[command(Register)]
#[command(Deactivate, requires_id)]개별 필드에 적용됩니다.
기본 키 필드를 표시합니다.
동작:
- UUID 자동 생성 (기본 v7,
uuid속성으로 설정 가능) -
ResponseDTO에 항상 포함 -
CreateRequest와UpdateRequest에서 제외
#[id]
pub id: Uuid,자동 생성 필드를 표시합니다 (timestamps, sequences).
동작:
-
From<CreateRequest>에서Default::default()획득 -
CreateRequest와UpdateRequest에서 제외 -
#[field(response)]로Response에 포함 가능
#[auto]
#[field(response)]
pub created_at: DateTime<Utc>,DTO 포함을 제어합니다. 여러 옵션을 조합하세요:
#[field(create)] // CreateRequest에만
#[field(update)] // UpdateRequest에만
#[field(response)] // Response에만
#[field(create, response)] // Create와 Response에
#[field(create, update, response)] // 세 가지 모두에
#[field(skip)] // 모든 DTO에서 제외CreateRequest DTO에 필드를 포함합니다.
#[field(create)]
pub email: String,
// 생성됨:
pub struct CreateUserRequest {
pub email: String,
}UpdateRequest DTO에 필드를 포함합니다.
중요: 비선택적 필드는 부분 업데이트를 위해 자동으로 Option<T>로 래핑됩니다.
#[field(update)]
pub name: String, // Option 아님
// 생성됨:
pub struct UpdateUserRequest {
pub name: Option<String>, // 자동으로 래핑됨
}Response DTO에 필드를 포함합니다.
#[field(response)]
pub email: String,
// 생성됨:
pub struct UserResponse {
pub id: Uuid, // 항상 포함 (#[id] 있음)
pub email: String, // 포함됨
}모든 DTO에서 필드를 제외합니다. 민감한 데이터에 사용하세요.
#[field(skip)]
pub password_hash: String,중요: skip은 다른 모든 필드 옵션을 무시합니다. 필드는 다음에만 존재합니다:
- 원본 엔티티 구조체
-
Row구조체 (데이터베이스 읽기용) -
Insertable구조체 (데이터베이스 쓰기용)
ValueObject Postgres enum을 DDL 생성에 연결합니다.
#[derive(ValueObject, Debug, Clone, Serialize, Deserialize)]
#[value_object(pg_type = "order_status", sqlx)]
pub enum OrderStatus { Pending, Shipped, Delivered }
#[derive(Entity)]
#[entity(table = "orders", migrations)]
pub struct Order {
#[id]
pub id: Uuid,
#[field(create, update, response)]
#[column(pg_enum = "order_status")]
pub status: OrderStatus,
}
for ddl in Order::MIGRATION_TYPES {
sqlx::query(ddl).execute(&pool).await?;
}
sqlx::query(Order::MIGRATION_UP).execute(&pool).await?;- DDL 컬럼 타입을 설정 (지정하지 않으면 enum 필드는 TEXT로 폴백)
- enum의 멱등
PG_CREATE_TYPEDDL을{Entity}::MIGRATION_TYPES에 등록 —MIGRATION_UP전에 실행하세요 - 선언한 이름은 컴파일 타임에 enum의
PG_TYPE상수와 대조 검증되며, 불일치 시 빌드가 실패합니다 -
ValueObject의 선택적sqlx플래그는sqlx::Type/Encode/Decode구현을 생성합니다; 이미sqlx::Type을 derive한다면 생략하세요
쿼리 필터 필드를 생성합니다. 자세한 내용은 필터링을 참조하세요.
#[filter] // 정확히 일치: WHERE field = $n
#[filter(eq)] // 위와 동일
#[filter(like)] // 패턴 일치: WHERE field ILIKE $n
#[filter(range)] // 범위: WHERE field >= $n AND field <= $m외래 키 관계입니다. 자세한 내용은 관계를 참조하세요.
#[belongs_to(User)]
pub user_id: Uuid,생성됨: 리포지토리에 find_user() 메서드.
일대다 관계 (엔티티 레벨)입니다. 자세한 내용은 관계를 참조하세요.
#[has_many(Post)]
pub struct User { /* ... */ }생성됨: 리포지토리에 find_posts() 메서드.
부분 뷰 구조체를 생성합니다 (엔티티 레벨).
#[projection(Public: id, name, avatar)]
#[projection(Admin: id, name, email, role)]
pub struct User { /* ... */ }생성됨:
UserPublic { id, name, avatar }UserAdmin { id, name, email, role }-
From<User>구현 -
find_by_id_public(),find_by_id_admin()메서드
#[command(...)]를 사용하여 엔티티 레벨에 적용됩니다.
| 구문 | 효과 |
|---|---|
#[command(Name)] |
모든 #[field(create)] 필드 사용 |
#[command(Name: field1, field2)] |
지정된 필드만 사용 (requires_id 추가) |
#[command(Name, requires_id)] |
ID 필드 추가, 다른 필드 없음 |
#[command(Name, source = "create")] |
명시적으로 create 필드 사용 (기본값) |
#[command(Name, source = "update")] |
update 필드 사용 (선택적, requires_id 추가) |
#[command(Name, source = "none")] |
payload 필드 없음 |
#[command(Name, payload = "Type")] |
커스텀 payload 구조체 사용 |
#[command(Name, result = "Type")] |
커스텀 결과 타입 사용 |
#[command(Name, kind = "create")] |
힌트: 엔티티 생성 (기본값) |
#[command(Name, kind = "update")] |
힌트: 엔티티 수정 |
#[command(Name, kind = "delete")] |
힌트: 엔티티 삭제 (() 반환) |
#[command(Name, kind = "custom")] |
힌트: 커스텀 작업 |
자세한 내용은 커맨드를 참조하세요.
#[derive(Entity)]
#[entity(
table = "posts",
schema = "blog",
sql = "full",
dialect = "postgres",
uuid = "v7",
soft_delete,
returning = "full",
events,
hooks,
commands
)]
#[has_many(Comment)]
#[projection(Summary: id, title, author_id, created_at)]
#[command(Publish)]
#[command(Archive, requires_id)]
pub struct Post {
#[id]
pub id: Uuid,
#[field(create, update, response)]
#[filter(like)]
pub title: String,
#[field(create, update, response)]
pub content: String,
#[field(create, response)]
#[belongs_to(User)]
#[filter]
pub author_id: Uuid,
#[field(update, response)]
pub published: bool,
#[field(response)]
#[filter(range)]
pub view_count: i64,
#[field(skip)]
pub moderation_notes: String,
#[field(skip)]
pub deleted_at: Option<DateTime<Utc>>,
#[auto]
#[field(response)]
#[filter(range)]
pub created_at: DateTime<Utc>,
#[auto]
#[field(response)]
pub updated_at: DateTime<Utc>,
}| 하고 싶은 것... | 속성 |
|---|---|
| 기본 키 자동 생성 | #[id] |
| 랜덤 UUID 사용 | 엔티티에 uuid = "v4"
|
| 시간순 UUID 사용 |
uuid = "v7" (기본값) |
| POST 본문에서 받기 | #[field(create)] |
| PATCH 본문에서 받기 | #[field(update)] |
| API 응답에서 반환 | #[field(response)] |
| 받고 반환 | #[field(create, update, response)] |
| 모든 API에서 숨기기 | #[field(skip)] |
| timestamp 자동 생성 |
#[auto] + #[field(response)]
|
| 읽기 전용 (DB 관리) |
#[field(response)]만 |
| 쓰기 전용 (반환 없음) |
#[field(create)]만 |
| 커스텀 SQL 쿼리 | sql = "trait" |
| DTO만, DB 없음 | sql = "none" |
| 레코드 소프트 삭제 | 엔티티에 soft_delete
|
| 커스텀 에러 타입 | 엔티티에 error = "MyError"
|
| 정확한 값으로 필터 | 필드에 #[filter]
|
| 패턴으로 필터 | 필드에 #[filter(like)]
|
| 범위로 필터 | 필드에 #[filter(range)]
|
| 엔티티 변경 추적 | 엔티티에 events
|
| 생명주기에서 코드 실행 | 엔티티에 hooks
|
| 도메인 커맨드 사용 | 엔티티에 commands + #[command(...)]
|
| 관계 정의 |
#[belongs_to(Entity)] 또는 #[has_many(Entity)]
|
| 부분 엔티티 뷰 | #[projection(Name: fields)] |
🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文
🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文
Getting Started
Features
Advanced
Начало работы
Возможности
Продвинутое
시작하기
기능
고급
Comenzando
Características
Avanzado
入门
功能
高级