아키텍처

이 페이지

EmDash는 Astro와 깊이 통합되어 완전한 CMS 경험을 제공합니다. 이 페이지에서는 주요 아키텍처 결정과 각 부분이 어떻게 연결되는지 설명합니다.

고수준 개요

┌──────────────────────────────────────────────────────────────────┐
│                         귀하의 Astro 사이트                        │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                   EmDash 통합                              │  │
│  │                                                            │  │
│  │  ┌──────────────┐   ┌──────────────┐   ┌───────────────┐   │  │
│  │  │   콘텐츠     │   │    관리      │   │   플러그인    │   │  │
│  │  │    API       │   │    패널      │   │               │   │  │
│  │  └──────────────┘   └──────────────┘   └───────────────┘   │  │
│  │                                                            │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │                    데이터 계층                       │  │  │
│  │  │   데이터베이스 (D1/SQLite)  +  스토리지 (R2/S3)       │  │  │
│  │  └──────────────────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                    Astro 프레임워크                        │  │
│  │       Live Collections · 미들웨어 · 세션                  │  │
│  └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

EmDash는 Astro 통합으로 실행됩니다. 관리 패널과 REST API를 위한 라우트를 주입하고, Live Collections를 위한 콘텐츠 로더를 제공하며, 데이터베이스 마이그레이션과 스토리지 연결을 관리합니다.

데이터베이스 우선 스키마

코드에서 스키마를 정의하는 전통적인 CMS와 달리, EmDash는 스키마 정의를 데이터베이스 자체에 저장합니다. 두 개의 시스템 테이블이 콘텐츠 구조를 추적합니다:

  • _emdash_collections — 컬렉션 메타데이터 (slug, 레이블, 기능)
  • _emdash_fields — 각 컬렉션의 필드 정의

관리 UI를 통해 title과 price 필드가 있는 “products” 컬렉션을 생성하면 EmDash는:

  1. _emdash_collections_emdash_fields에 레코드 삽입
  2. ALTER TABLE을 실행하여 적절한 컬럼이 있는 ec_products 생성

이 설계로 다음이 가능합니다:

  • 런타임 스키마 수정 — 코드 변경이나 재빌드 없이 콘텐츠 타입 생성 및 편집
  • 비개발자 친화적 설정 — 콘텐츠 편집자가 UI를 통해 데이터 모델 설계 가능
  • 실제 SQL 컬럼 — 적절한 인덱싱, 외래 키, 쿼리 최적화

컬렉션별 테이블

각 컬렉션은 ec_ 접두사가 있는 자체 SQLite 테이블을 가집니다:

-- "posts" 컬렉션 추가 시 생성
CREATE TABLE ec_posts (
  -- 시스템 컬럼 (항상 존재)
  id TEXT PRIMARY KEY,
  slug TEXT UNIQUE,
  status TEXT DEFAULT 'draft',  -- draft, published, scheduled
  author_id TEXT,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now')),
  published_at TEXT,
  deleted_at TEXT,              -- 소프트 삭제
  version INTEGER DEFAULT 1,    -- 낙관적 잠금

  -- 콘텐츠 컬럼 (필드 정의에서)
  title TEXT NOT NULL,
  content JSON,                 -- Portable Text
  excerpt TEXT
);

JSON이 포함된 단일 콘텐츠 테이블 대신 컬렉션별 테이블을 사용하는 이유는?

  • 실제 SQL 컬럼으로 적절한 인덱싱 및 쿼리 가능
  • 외래 키가 올바르게 작동
  • 스키마가 데이터베이스에서 자체 문서화
  • 필드 액세스에 JSON 파싱 오버헤드 없음
  • 데이터베이스 도구로 스키마 직접 검사 가능

Live Collections 통합

EmDash는 Astro 6의 Live Collections를 사용하여 런타임에 콘텐츠를 제공합니다. 콘텐츠 변경은 정적 재빌드 없이 즉시 사용 가능합니다.

emdashLoader()는 Astro의 LiveLoader 인터페이스를 구현합니다:

// src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

export const collections = {
	_emdash: defineLiveCollection({ loader: emdashLoader() }),
};

제공된 래퍼 함수를 사용하여 콘텐츠를 쿼리합니다:

import { getEmDashCollection, getEmDashEntry } from "emdash";

// 모든 게시된 포스트 가져오기
const { entries: posts } = await getEmDashCollection("posts");

// 초안 가져오기
const { entries: drafts } = await getEmDashCollection("posts", {
	status: "draft",
});

// slug로 단일 항목 가져오기
const { entry: post } = await getEmDashEntry("posts", "my-post-slug");

라우트 주입

EmDash 통합은 Astro의 injectRoute API를 사용하여 관리 및 API 라우트를 추가합니다:

경로 패턴목적
/_emdash/admin/[...path]관리 패널 SPA
/_emdash/api/manifest관리 매니페스트 (컬렉션, 플러그인)
/_emdash/api/content/[collection]콘텐츠 항목 CRUD
/_emdash/api/media/*미디어 라이브러리 작업
/_emdash/api/schema/*스키마 관리
/_emdash/api/settings사이트 설정
/_emdash/api/menus/*내비게이션 메뉴
/_emdash/api/taxonomies/*카테고리, 태그, 사용자 정의 분류법

라우트는 emdash 패키지에서 주입됩니다 — 프로젝트에 복사되지 않습니다.

데이터 계층

EmDash는 Kysely를 사용하여 지원되는 모든 데이터베이스에서 타입 안전 SQL 쿼리를 수행합니다:

SQLite

sqlite({ url: "file:./data.db" })로 로컬 개발

D1

Cloudflare의 서버리스 SQL, d1({ binding: "DB" }) 사용

libSQL

원격 SQLite, libsql({ url: "...", authToken: "..." }) 사용

데이터베이스 구성은 astro.config.mjs에서 통합에 전달됩니다:

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { sqlite } from "emdash/db";
import { local } from "emdash/storage";

export default defineConfig({
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data.db" }),
			storage: local({
				directory: "./uploads",
				baseUrl: "/_emdash/api/media/file",
			}),
		}),
	],
});

스토리지 추상화

미디어 파일은 데이터베이스와 별도로 저장됩니다. EmDash는 다음을 지원합니다:

  • 로컬 파일 시스템 — 개발 및 간단한 배포
  • Cloudflare R2 — 엣지의 S3 호환 객체 스토리지
  • S3 호환 — 모든 S3 호환 객체 스토리지

업로드는 서명된 URL을 사용하여 클라이언트에서 스토리지로 직접 업로드하며, Workers 본문 크기 제한을 우회합니다.

플러그인 아키텍처

플러그인은 WordPress에서 영감을 받은 훅 시스템을 통해 EmDash를 확장합니다:

  • 콘텐츠 훅content:beforeSave, content:afterSave, content:beforeDelete, content:afterDelete
  • 미디어 훅media:beforeUpload, media:afterUpload
  • 격리된 스토리지 — 각 플러그인은 네임스페이스가 지정된 KV 액세스 획득
  • 관리 UI 확장 — 대시보드 위젯, 설정 페이지, 사용자 정의 필드 편집기

플러그인은 두 가지 모드로 실행될 수 있습니다:

  1. 네이티브 — 호스트 환경에 대한 전체 액세스 (자사 플러그인용)
  2. 샌드박스 — 기능 기반 권한이 있는 V8 isolates에서 실행 (Cloudflare의 타사 플러그인용)
// astro.config.mjs
import { seoPlugin } from "@emdash-cms/plugin-seo";

emdash({
	plugins: [seoPlugin({ maxTitleLength: 60 })],
});

요청 흐름

일반적인 콘텐츠 요청은 다음 경로를 따릅니다:

  1. Astro가 요청 수신 — 페이지 컴포넌트 실행
  2. 콘텐츠 쿼리getEmDashCollection()이 Astro의 getLiveCollection() 호출
  3. 로더 실행emdashLoader가 Kysely를 통해 적절한 ec_* 테이블 쿼리
  4. 데이터 반환 — 항목이 id, slug, data를 포함하는 Astro의 항목 형식으로 매핑
  5. 페이지 렌더링 — 컴포넌트가 콘텐츠를 받아 HTML 렌더링

관리 요청의 경우:

  1. 미들웨어 인증 — 세션 토큰 검증
  2. API 라우트가 요청 처리 — 리포지토리를 통한 CRUD 작업
  3. 훅 실행beforeCreate, afterUpdate
  4. 데이터베이스 업데이트 — Kysely가 SQL 실행
  5. 응답 반환 — 관리 SPA에 JSON 응답 반환

가상 모듈

EmDash는 빌드 시 런타임을 구성하기 위한 가상 모듈을 생성합니다:

모듈목적
virtual:emdash/config데이터베이스 및 스토리지 구성
virtual:emdash/dialect데이터베이스 방언 팩토리
virtual:emdash/plugin-admins플러그인 관리 UI의 정적 임포트

이 접근 방식은 번들러가 플러그인 코드를 올바르게 해결하고 트리 쉐이크할 수 있도록 보장합니다.

다음 단계