Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

일반적인 사용 사례를 위한 실용적인 예제입니다.

사용자 관리

인증 필드가 있는 클래식 사용자 엔티티:

use entity_derive::Entity;
use uuid::Uuid;
use chrono::{DateTime, Utc};

#[derive(Entity)]
#[entity(table = "users", schema = "auth")]
pub struct User {
    #[id]
    pub id: Uuid,

    #[field(create, update, response)]
    pub username: String,

    #[field(create, update, response)]
    pub email: String,

    #[field(create, update)]  // 받지만 절대 반환하지 않음
    pub password_hash: String,

    #[field(response)]
    pub email_verified: bool,

    #[field(update, response)]
    pub avatar_url: Option<String>,

    #[field(response)]
    pub role: String,

    #[field(skip)]  // 내부 감사 필드
    pub last_login_ip: Option<String>,

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,

    #[auto]
    #[field(response)]
    pub updated_at: DateTime<Utc>,
}

사용법:

// 사용자 생성
let request = CreateUserRequest {
    username: "john_doe".into(),
    email: "john@example.com".into(),
    password_hash: hash_password("secret123"),
};
let user = pool.create(request).await?;

// 사용자 업데이트
let update = UpdateUserRequest {
    avatar_url: Some(Some("https://cdn.example.com/avatar.jpg".into())),
    ..Default::default()
};
let user = pool.update(user.id, update).await?;

// Response는 안전함 - password_hash 없음, last_login_ip 없음
let response = UserResponse::from(&user);

블로그 시스템

작성자 관계가 있는 게시물:

#[derive(Entity)]
#[entity(table = "posts", schema = "blog")]
pub struct Post {
    #[id]
    pub id: Uuid,

    #[field(create, update, response)]
    pub title: String,

    #[field(create, update, response)]
    pub slug: String,

    #[field(create, update, response)]
    pub content: String,

    #[field(create, update, response)]
    pub excerpt: Option<String>,

    #[field(create, response)]  // 한 번 설정, 작성자 변경 불가
    pub author_id: Uuid,

    #[field(update, response)]
    pub published: bool,

    #[field(update, response)]
    pub published_at: Option<DateTime<Utc>>,

    #[field(response)]  // 읽기 전용, 트리거로 관리
    pub view_count: i64,

    #[field(skip)]  // 내부 검수
    pub moderation_status: String,

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,

    #[auto]
    #[field(response)]
    pub updated_at: DateTime<Utc>,
}

다대다 관계의 카테고리:

#[derive(Entity)]
#[entity(table = "categories", schema = "blog")]
pub struct Category {
    #[id]
    pub id: Uuid,

    #[field(create, update, response)]
    pub name: String,

    #[field(create, update, response)]
    pub slug: String,

    #[field(create, update, response)]
    pub description: Option<String>,

    #[field(response)]
    pub post_count: i64,  // 계산된 필드

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,
}

E-Commerce

제품 카탈로그:

#[derive(Entity)]
#[entity(table = "products", schema = "catalog")]
pub struct Product {
    #[id]
    pub id: Uuid,

    #[field(create, update, response)]
    pub name: String,

    #[field(create, update, response)]
    pub sku: String,

    #[field(create, update, response)]
    pub description: Option<String>,

    #[field(create, update, response)]
    pub price_cents: i64,

    #[field(create, update, response)]
    pub currency: String,

    #[field(update, response)]
    pub stock_quantity: i32,

    #[field(update, response)]
    pub is_active: bool,

    #[field(create, response)]
    pub category_id: Uuid,

    #[field(skip)]  // 내부 원가 추적
    pub cost_cents: i64,

    #[field(skip)]  // 공급업체 정보
    pub supplier_id: Option<Uuid>,

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,

    #[auto]
    #[field(response)]
    pub updated_at: DateTime<Utc>,
}

상태 추적이 있는 주문:

#[derive(Entity)]
#[entity(table = "orders", schema = "sales")]
pub struct Order {
    #[id]
    pub id: Uuid,

    #[field(create, response)]
    pub customer_id: Uuid,

    #[field(response)]
    pub order_number: String,  // DB 시퀀스로 생성

    #[field(update, response)]
    pub status: String,

    #[field(create, response)]
    pub total_cents: i64,

    #[field(create, response)]
    pub currency: String,

    #[field(create, update, response)]
    pub shipping_address: String,

    #[field(update, response)]
    pub tracking_number: Option<String>,

    #[field(skip)]  // 결제 프로세서 데이터
    pub payment_intent_id: Option<String>,

    #[field(skip)]  // 내부 메모
    pub admin_notes: Option<String>,

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,

    #[auto]
    #[field(response)]
    pub updated_at: DateTime<Utc>,
}

멀티테넌트 SaaS

조직 범위의 엔티티:

#[derive(Entity)]
#[entity(table = "organizations", schema = "tenants")]
pub struct Organization {
    #[id]
    pub id: Uuid,

    #[field(create, update, response)]
    pub name: String,

    #[field(create, response)]
    pub slug: String,  // 생성 후 불변

    #[field(update, response)]
    pub plan: String,

    #[field(response)]
    pub member_count: i32,

    #[field(skip)]  // 결제 정보
    pub stripe_customer_id: Option<String>,

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,
}

#[derive(Entity)]
#[entity(table = "projects", schema = "tenants")]
pub struct Project {
    #[id]
    pub id: Uuid,

    #[field(create, response)]  // 한 번 설정
    pub organization_id: Uuid,

    #[field(create, update, response)]
    pub name: String,

    #[field(create, update, response)]
    pub description: Option<String>,

    #[field(update, response)]
    pub archived: bool,

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,

    #[auto]
    #[field(response)]
    pub updated_at: DateTime<Utc>,
}

DTO만 (데이터베이스 없음)

영속성 없는 API 계약용:

#[derive(Entity)]
#[entity(table = "webhooks", sql = "none")]
pub struct WebhookPayload {
    #[id]
    pub id: Uuid,

    #[field(create, response)]
    pub event_type: String,

    #[field(create, response)]
    pub payload: String,

    #[field(create, response)]
    pub timestamp: DateTime<Utc>,

    #[field(response)]
    pub signature: String,
}

생성되는 것:

  • CreateWebhookPayloadRequest
  • WebhookPayloadResponse
  • From 구현

리포지토리 없음, SQL 없음, Row/Insertable 구조체 없음.

커스텀 쿼리만

표준 CRUD가 충분하지 않을 때:

#[derive(Entity)]
#[entity(table = "analytics_events", schema = "analytics", sql = "trait")]
pub struct AnalyticsEvent {
    #[id]
    pub id: Uuid,

    #[field(create, response)]
    pub event_name: String,

    #[field(create, response)]
    pub user_id: Option<Uuid>,

    #[field(create, response)]
    pub properties: serde_json::Value,

    #[auto]
    #[field(response)]
    pub created_at: DateTime<Utc>,
}

// 커스텀 쿼리를 직접 구현:
impl AnalyticsEventRepository for PgPool {
    type Error = sqlx::Error;

    async fn create(&self, dto: CreateAnalyticsEventRequest) -> Result<AnalyticsEvent, Self::Error> {
        // 배치 삽입, 파티션 테이블 등
    }

    async fn find_by_id(&self, id: Uuid) -> Result<Option<AnalyticsEvent>, Self::Error> {
        // 시간 기반 파티셔닝을 사용한 쿼리
    }

    // CRUD를 넘어선 커스텀 메서드:
    async fn aggregate_by_event(&self, start: DateTime<Utc>, end: DateTime<Utc>)
        -> Result<Vec<EventAggregate>, Self::Error> {
        // 복잡한 집계 쿼리
    }
}

대량 작업

모든 리포지토리에 배치 프리미티브가 생성됩니다: find_by_ids(WHERE id = ANY($1), 단일 왕복), 원자적 create_many(하나의 트랜잭션, 한 행이라도 실패하면 전체 롤백), soft delete를 인지하는 delete_many(실제 영향받은 행 수 반환). 이벤트 전달(streams 또는 outbox)이 활성화되면 같은 트랜잭션 안에서 행별 이벤트가 발행됩니다.

let posts: Vec<Post> = pool.find_by_ids(ids).await?;
let created: Vec<Post> = pool.create_many(dtos).await?;
let removed: u64 = pool.delete_many(stale_ids).await?;