본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 20. 09:22

1개 토픽 → 다중 SNS 배포 SaaS 개인 개발: PlatformAdapter로 추상화한 이야기

요약

다양한 SNS 플랫폼에 콘텐츠를 자동 배포하는 SaaS 'NichePilot'의 설계 및 구현 과정을 다룹니다. 플랫폼별로 상이한 인증 방식과 규약을 추상화하기 위해 ABC(Abstract Base Class)와 Adapter 패턴을 적용한 아키텍처를 소개합니다.

핵심 포인트

  • PlatformAdapter 패턴을 통한 이종 플랫폼 통합 추상화
  • OAuth 2.0 state를 HMAC으로 처리하여 Redis 의존성 제거
  • asyncio.gather를 활용한 비동기 배포 및 부분 실패 대응
  • Fernet 암호화를 이용한 OAuth 토큰 보안 저장

부업 크리에이터를 위해, 1개 토픽 입력 → 4개 플랫폼에 최적화된 콘텐츠가 자동 배포되는 SaaS를 개인 개발했습니다.

  • WordPress 6,000자 SEO 기사 + Threads 500자 + X 117자 + Instagram 2,200자 캡션, 전부 동시 생성 및 동시 게시
  • Publisher ABC + 플랫폼별 Adapter로 이종 플랫폼을 통합 추상화
  • OAuth 2.0 state를 HMAC으로 자기 완결적 처리 (Redis 불필요)
  • asyncio.gather(return_exceptions=True)로 부분 실패 흡수
  • Fernet 암호화로 OAuth 토큰군을 BYTEA 저장
  • 기존 wp_sites를 파괴하지 않고 신규 connected_platforms 테이블을 추가하여 하위 호환성 유지

이 기사에서는 4개 플랫폼 배포를 하나의 아키텍처로 통합하는 설계 판단과 그 구현의 핵심을 다룹니다.

현재 가동 상황 (2026-06): WordPress / Threads는 고객이 직접 연동하여 운영 환경에서 자동 게시가 작동하고 있습니다. Instagram은 게시 Adapter는 구현 및 가동 완료되었으나, 신규 고객의 자기 연동은 Meta의 앱 심사 통과 후 순차적으로 개방됩니다 (심사 중에는 테스터 초대로 연동 가능). X는 Adapter 구현은 완료되었으나 운영 환경은 "곧 대응 예정"입니다 (무료 API에서는 refresh_token을 사용할 수 없어 유료 API tier 대기 중). 본 기사의 "4개 플랫폼 추상화"는 X/Instagram Adapter를 포함한 설계 전체에 관한 이야기이며, 지금 바로 고객이 자가적으로 동시 배포할 수 있는 것은 WordPress + Threads입니다.

만든 것

  • 서비스명: NichePilot (niche-pilot.com)
  • 컨셉: 부업 크리에이터를 위한 멀티 플랫폼 배포 SaaS
  • 입력: 1개 토픽 + 게시 대상 플랫폼 선택 (1~4개)
  • 출력: 각 플랫폼에 최적화된 콘텐츠가 자동 배포
  • 상태: β 초대 50명 모집 중

기술 스택:

  • FastAPI + PostgreSQL 16 (RLS) + Celery + Redis (Upstash)
  • Anthropic Claude Sonnet 4.6 (BYOK + prompt cache)
  • OAuth 2.0 PKCE (X) + Meta Graph API (Threads/Instagram)
  • Fly.io (Tokyo) + Neon (Postgres) + Cloudflare (DNS + Email Worker)
  • Fernet 암호화 (app-level, Fly secret 관리)

과제: 4개 플랫폼 횡단은 "글자 수·인증 방식·규약"이 전부 다름

부업 크리에이터를 위한 SaaS이기에, 처음에는 WordPress 단독으로 작동하고 있었습니다.

도중에 "SNS에도 동시 배포하고 싶다"는 요구사항이 생겨, 다중 플랫폼 대응으로 확장했습니다.

하지만 4개 플랫폼은 공통점이 거의 없습니다:

WordPressThreadsXInstagram
인증Application Password (Basic Auth)OAuth 2.0 + long-lived token (60일)OAuth 2.0 PKCE + refresh_tokenOAuth 2.0 + Business Account 필수
...OAuth 2.0 표준fb_exchange_token

이를 하나의 공통 인터페이스로 추상화하는 것이 이 프로젝트의 핵심이었습니다.

Publisher ABC + 플랫폼별 Adapter

설계:

# worker/publishers/base.py (발췌)
class PlatformType(str, Enum):
    WORDPRESS = "wordpress"
    ...

포인트:

  • publish() (기존 시그니처)를 남김으로써 기존 WordPress 구현의 하위 호환성을 보장
  • publish_content()를 도입함으로써, 새로운 SNS adapter는 dataclass 기반의 깔끔한 API를 사용할 수 있음

(新 시그니처)를 기본 구현(default implementation)과 함께 추가하고, registry에서 platform_type을 동적으로 해결하도록 함. 또한 platform_type을 서브클래스 속성(subclass attribute)으로 강제함.

이를 통해 "기존 로직을 파괴하지 않으면서, 새로운 플랫폼 추가가 용이함"이라는 양립을 실현.

SNS 어댑터(Adapter) 3가지 구현

Threads (Meta Graph API, 2단계 게시)

class ThreadsPublisher(Publisher):
platform_type = PlatformType.THREADS
API_BASE = "https://graph.threads.net/v1.0"
...

X (X API v2 + OAuth 2.0 PKCE)

class XPublisher(Publisher):
platform_type = PlatformType.X
MAX_TEXT_LENGTH = 280 # URL은 t.co 단축으로 23자로 카운트
...

Instagram (Meta Business API + 이미지 필수)

class InstagramPublisher(Publisher):
platform_type = PlatformType.INSTAGRAM
MAX_CAPTION_LENGTH = 2200
...

state를 HMAC로 서명 (Redis 불필요 설계)

OAuth 2.0 플로우 — 여러 플랫폼의 OAuth 콜백(callback)을 하나의 엔드포인트(endpoint)에서 받는 설계:

# api/utils/oauth_state.py
def make_state(*, tenant_id: str, platform: str, extra: dict | None = None) -> str:
payload = {
...

포인트:

  • HMAC-SHA256 + 10분 TTL로 변조 감지 + 만료 판정
  • Redis 불필요 (state에 모두 포함하여 자기 완결적 구조)
  • X의 PKCE code_verifierstateextra에 임베딩 (HMAC으로 보호되므로 변조 불가)
# X만 PKCE가 필요, verifier를 state에 임베딩
state = make_state(
tenant_id=str(tenant_id),
...

병렬 게시 오케스트레이션 (Orchestration)

# worker/topic_orchestrator.py
async def dispatch_sns_after_wp(
*, tenant_id, topic, wp_post_url, wp_title,
...

asyncio.gather(return_exceptions=True)를 사용하여 **부분 실패(partial failure)**를 흡수하는 것이 포인트:

  • 3개 플랫폼을 선택했는데 1개가 속도 제한(rate limit)에 걸림 → 나머지 2개는 성공
  • 결과는 Job.platform_results JSON에 집약
  • Job.statusdone / partial_failure / failed의 3가지 값

프롬프트 최적화 — 플랫폼마다 "다른 인격"으로 작성

Claude의 프롬프트를 플랫폼별로 분리:

# worker/prompts.py
THREADS_SYSTEM_PROMPT = """당신은 Threads에서 팔로워를 늘리는 부업 크리에이터 운영자입니다.
【절대 규칙】
...

동일한 토픽이라도 각 플랫폼용으로 별도의 Claude 호출을 통해 개별 생성.

이를 통해 플랫폼 문화에 맞춘 자연스러운 게시물이 생성됩니다 (단순히 글자 수를 자르는 것이 아님).

DB 스키마 — 기존 테이블을 파괴하지 않는 하위 호환성

WordPress는 wp_sites (Application Password 전용 컬럼)를 그대로 유지하고, SNS용은 별도 테이블로 구성:

id UUID PRIMARY KEY,
tenant_id UUID REFERENCES tenants(id),
...

Job 테이블 확장도 하위 호환성을 중시:

ALTER TABLE jobs
ALTER COLUMN wp_site_id DROP NOT NULL, -- SNS 단독 게시 대응
ADD COLUMN target_platforms TEXT, -- JSON array
...

target_platforms가 비어 있거나 ["wordpress"]만 있다면 **기존 플로우 (WP 단독)**를 유지합니다.

SNS를 포함한다면 새로운 플로우 (dispatch_sns_after_wp)를 호출합니다.

인증 정보의 암호화 — Fernet (app-level)

# api/utils/secrets.py
from cryptography.fernet import Fernet
_fernet = Fernet(settings.fernet_key.encode())
...

OAuth 토큰들을 JSON 문자열로 변환하여 암호화한 뒤 → DB에 BYTEA 형식으로 저장합니다:

creds_dict = {"access_token": "...", "refresh_token": "...", "account_id": "..."}
encrypted = encrypt(json.dumps(creds_dict))
  • Fly.io secretFERNET_KEY를 배치하여 DB 덤프가 유출되더라도 평문 토큰이 노출되지 않도록 설계
  • AWS KMS는 외부 의존성이 증가하므로 채택하지 않았으며, Fernet만으로 충분하다고 판단

플랜 구성 — "플랫폼 수"로 가격 결정

Solo (¥1,980): 1개 플랫폼 / 60개 게시물
Duo (¥2,980): 2개 플랫폼 / 120개 게시물
Trio (¥4,980): 3개 플랫폼 / 180개 게시물
...

API 측에서 제한:

PLAN_PLATFORM_LIMITS = {"solo": 1, "duo": 2, "trio": 3, "all": None}
if platform_limit is not None and len(payload.platforms) > platform_limit:
    raise HTTPException(402, "플랜 상한 초과")

월간 쿼터(Quota)는 토픽 단위로 카운트합니다 (선택한 플랫폼 수와 관계없이 1 Job = 1 카운트).

UX — 연동 상태의 시각화

App 대시보드 사이드바에 "연동 SNS" 카드 3개 배치:

  • 상태 배지: 미연동 / ✓연동 완료 / 만료 7일 이내 / 만료됨 / 확인 필요 (3회 연속 실패)
  • 테스트 게시 / 토큰 수동 갱신 / 연동 해제 버튼
  • OAuth 리턴 감지를 통한 환영 메시지 + 자동 리프레시(refresh) + URL 클리닝

부업 크리에이터 중에는 기술에 익숙하지 않은 층이 많으므로, OAuth 에러가 발생하더라도 친절한 일본어로 유도하는 설계를 적용했습니다.

요약

  • **추상화 레이어 (PlatformAdapter / PostContent / PublishResult)**를 통해 이기종 플랫폼을 통합
  • **OAuth state를 HMAC으로 자기 완결적(self-contained)**으로 처리하여 Redis가 필요 없는 설계
  • asyncio.gather + return_exceptions를 사용하여 부분적인 실패를 흡수
  • 기존 wp_sites를 파괴하지 않고 새로운 connected_platforms를 추가하여 하위 호환성 유지
  • Job 확장을 통해 target_platforms / image_url / platform_results를 JSON으로 저장 (스키마 복잡화 최소화)
  • 플랫폼별 프롬프트를 통해 단순히 "글자 수만 다른" 것이 아니라 "문화가 다른" 최적화 구현

부업 크리에이터가 "블로그 + SNS + Instagram"을 운영하는 기술적 비용을 월 ¥1,980으로 되살릴 수 있는 SaaS를 만들었습니다.

기술적인 질문이나 지적은 언제든 환영합니다.

🔗 서비스: niche-pilot.com

📧 문의: support@niche-pilot.com (24시간 이내에 운영자가 직접 답변)

💻 GitHub: 공개 예정 (런칭 후 수개월 내)

#NichePilot #개인개발 #SaaS #FastAPI #Claude #OAuth

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0