Seaography 2.0: 강력하고 확장 가능한 GraphQL 프레임워크 🧭
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
- Async‑GraphQL이 들어오는 HTTP 요청을 파싱하고 요청 컨텍스트를 구축합니다.
- HTTP 핸들러가 요청을 가로채어 컨텍스트(예: DB 연결, 세션)를 Seaography에 전달합니다.
- (생성된 혹은 커스텀) 리졸버가 SeaORM을 통해 쿼리를 실행하고, 필요 시 DataLoader를 사용해 관계 로딩을 효율화합니다.
- 페이지네이션, 필터링, 정렬, 권한 훅이 자동으로 적용된 뒤 최종 응답이 반환됩니다.