Seaography 2.0: 강력하고 확장 가능한 GraphQL 프레임워크 🧭

발행: (2025년 12월 5일 오후 07:46 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

Seaography란

Seaography는 SeaORM 위에 구축된 GraphQL 프레임워크로, DataLoader 통합을 통해 N+1 문제를 해결하는 리졸버를 자동으로 생성합니다. 풍부한 커스터마이징 옵션, 손쉬운 커스텀 엔드포인트 추가, 세밀한 권한 제어(RBAC, 훅/가드)를 제공합니다.

짧은 데모에서는 다음을 보여줍니다:

  • 기존 데이터베이스(예: Sakila SQLite 데모)에서 SeaORM 엔티티 생성
  • GraphQL 서버 구동(Axum, Actix, Poem과 호환)
  • GraphQL Playground를 통한 쿼리 실행

동일한 과정을 quick‑start guide에서도 따라 할 수 있습니다.


어떤 종류의 쿼리를 지원하나요?

Filter, Ordering and Pagination

{
  film(
    filters: {
      title: "{ contains: \"sea\" } # ⬅ like '%sea%'"
      and: [{ releaseYear: { gt: "2000" } }, { length: { gt: 120 } }]
    }
    orderBy: { filmId: ASC }
    pagination: { page: { page: 0, limit: 10 } }
    # cursor‑based pagination is also supported:
    # pagination: { cursor: { limit: 10, cursor: "Int[3]:100" } }
  ) {
    nodes {
      filmId
      title
      description
    }
    paginationInfo {
      pages
      current
    }
  }
}

중첩 관계 쿼리

{
  film(
    # filter by related entity
    having: {
      actor: { firstName: { eq: "David" } }
      category: { name: { eq: "Documentary" } }
    }
  ) {
    nodes {
      filmId
      title
      actor {
        nodes {
          firstName
          lastName
        }
      }
      inventory {
        nodes {
          store {
            address {
              address
              city {
                city
              }
            }
          }
        }
      }
    }
  }
}

사용된 조인 경로

film -> film_actor -> actor
     -> inventory -> store -> address -> city

DataLoader가 이러한 관계를 효율적으로 해결하여 N+1 문제를 없애줍니다.

Mutations: create, update, delete

전체 CRUD를 지원합니다(CreateOne, CreateBatch, Update, Delete).

mutation {
  filmTextCreateBatch(
    data: [
      { filmId: 1, title: "\"Foo bar\"", description: "\"Lorem ipsum dolor sit amet\"" }
      { filmId: 2, title: "\"Fizz buzz\"", description: "\"Consectetur adipiscing elit\"" }
    ]
  ) {
    filmId
    title
    description
  }
}

Custom Query

Seaography 2.0에서는 매크로를 도입해 프레임워크의 페이지네이션 및 커넥션 유틸리티를 재사용하면서 커스텀 쿼리 엔드포인트를 작성할 수 있게 되었습니다.

페이지네이션이 포함된 Custom Query

Sakila 스키마에서 생성된 Customer 엔티티가 있다고 가정합니다:

//! Entity from the sakila schema, generated by sea-orm-cli
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "customer")]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub customer_id: i32,
    pub store_id: i32,
    pub first_name: String,
    pub last_name: String,
    // …
}

현재 요청의 스토어에 속한 고객들을 반환하는 커스텀 엔드포인트를 만들면 다음과 같습니다:

use seaography::{apply_pagination, Connection, CustomFields, PaginationInput};

pub struct Operations;

#[CustomFields]
impl Operations {
    async fn customer_of_current_store(
        ctx: &Context,
        pagination: PaginationInput,
    ) -> async_graphql::Result> {
        let db = ctx.data::()?;
        let session = ctx.data::()?;

        let query = customer::Entity::find()
            .filter(customer::Column::StoreId.eq(session.store_id));

        // Seaography handles pagination and execution
        let connection = apply_pagination(&CONTEXT, db, query, pagination).await?;
        Ok(connection)
    }
}

엔드포인트는 다음과 같이 노출됩니다:

customer_of_current_store(
  pagination: PaginationInput
): CustomerConnection!

예시 쿼리

{
  customer_of_current_store(pagination: { page: { page: 0, limit: 10 } }) {
    nodes {
      storeId
      customerId
      firstName
      lastName
      email
    }
    paginationInfo {
      pages
      current
    }
  }
}

몇 줄의 코드만으로 완전한 페이지네이션 API 엔드포인트를 추가할 수 있으며, 무거운 작업은 Seaography와 SeaORM이 담당합니다.


어떻게 동작하나요?

  • Bridge – Seaography는 SeaORM 타입을 Async‑GraphQL에 매핑해, 어떤 SeaORM 엔티티든 GraphQL 출력으로 사용할 수 있게 합니다.
  • Schema generation – 애플리케이션 시작 시, Seaography는 SeaORM 엔티티의 메타 정보를 실시간으로 GraphQL 스키마로 변환합니다.
  • Request lifecycle
    1. Async‑GraphQL이 들어오는 HTTP 요청을 파싱하고 요청 컨텍스트를 구축합니다.
    2. HTTP 핸들러가 요청을 가로채어 컨텍스트(예: DB 연결, 세션)를 Seaography에 전달합니다.
    3. (생성된 혹은 커스텀) 리졸버가 SeaORM을 통해 쿼리를 실행하고, 필요 시 DataLoader를 사용해 관계 로딩을 효율화합니다.
    4. 페이지네이션, 필터링, 정렬, 권한 훅이 자동으로 적용된 뒤 최종 응답이 반환됩니다.
Back to Blog

관련 글

더 보기 »

JavaScript 첫 걸음: 간단한 정리

JavaScript에서 변수 let: 나중에 값이 변경될 수 있는 경우에 사용합니다. 예시: ```javascript let age = 20; age = 21; ``` const: 값이 절대로 변경되지 않아야 할 때 사용합니다.