Member¶
길드에 속한 Discord 사용자를 관리하는 도메인. 웹 조인(
join.umbra.ink/{slug}) 흐름의 주체이며, 복구 시 멤버 단위의 수동 복원 대상이다.
Bounded context¶
- Type — Supporting
- Sibling contexts — Identity, Guild, Recovery (멤버 정보 참조)
- Location in codebase —
engine/member/
Why this domain exists¶
Discord 길드의 멤버는 수백~수만 명 규모로 확장 가능하며, Umbra 는 두 가지 책임을 가진다.
첫째, 웹 조인 플로우 — join.umbra.ink/{slug} 링크로 외부에서 유저가 Discord OAuth2 후 길드에 가입하도록 돕는다. 특히 인증 시스템(나이 인증, 외부 가입 승인) 연동 포인트로 활용된다.
둘째, 멤버 상태 저장 — 복구 대상이 되는 멤버 목록과 역할 매핑을 DB 에 유지한다. 단 "자동 멤버 복구" 는 대량 DM 과 rate limit 부담으로 MVP 에서 수동 단위만 지원.
Member 는 Guild 의 정보를 참조하되, 자체적으로 소유한 데이터는 "Umbra 가 본 멤버의 시점별 상태" 다.
Domain model¶
Member¶
길드 멤버 레코드.
Member
├─ id UUID v7 PK
├─ guild_id UUID → guild.guilds.id
├─ discord_user_id TEXT Discord snowflake
├─ user_id UUID → identity.users.id (nullable)
├─ joined_via TEXT 'web_join' | 'direct' | 'restored'
├─ joined_at TIMESTAMPTZ
├─ left_at TIMESTAMPTZ 탈퇴 시
├─ role_ids TEXT[] Discord role IDs (현재 상태)
├─ nickname TEXT 길드 내 닉네임
├─ created_at TIMESTAMPTZ
└─ updated_at TIMESTAMPTZ
user_id는 멤버가 Umbra 에 OAuth2 로그인 이력이 있으면 연결 (웹 조인이 아니면 NULL)role_ids는 Live Sync 로 갱신되는 현재 상태 (Recovery 도메인이 참조)
WebJoinRequest¶
웹 조인 도중의 진행 상태.
WebJoinRequest
├─ id UUID v7 PK
├─ guild_id UUID → guild.guilds.id
├─ discord_user_id TEXT
├─ state TEXT 'pending' | 'completed' | 'failed'
├─ discord_access_token TEXT 암호화, OAuth2 완료 시 저장
├─ attempted_at TIMESTAMPTZ
├─ completed_at TIMESTAMPTZ
└─ failure_reason TEXT
웹 조인은 guilds.join Discord API 호출이 즉시 성공하지 않을 수 있어(역할 설정, 권한 체크 등) 상태 추적이 필요.
Aggregates¶
- Member aggregate — Member + 연결된 WebJoinRequest (있으면)
Invariants¶
- Guild + Discord User 유일성 —
UNIQUE(guild_id, discord_user_id)(한 길드에 같은 Discord 유저 멤버 레코드 하나) - Soft leave — 탈퇴 시
left_at설정, row 유지 (이력 추적) - WebJoinRequest TTL —
pending상태가 1시간 초과하면failed로 전환
State machine¶
Member:
stateDiagram-v2
[*] --> Active : Joined
Active --> Left : Left or kicked
Left --> Active : Re-joined (새 Member row? 동일 row update?)
재가입 처리 정책: 같은 (guild_id, discord_user_id) 로 재가입 시 기존 row 의 left_at 을 NULL 로 초기화하고 joined_at 갱신. 별도 row 생성하지 않음.
WebJoinRequest:
stateDiagram-v2
[*] --> Pending : User clicks slug link
Pending --> Completed : guilds.join success
Pending --> Failed : guilds.join error or TTL
Completed --> [*]
Failed --> [*]
Domain events¶
Published¶
| Event | Trigger | Payload | Subscribers |
|---|---|---|---|
MemberJoinedViaWebJoin | 웹 조인으로 멤버 가입 완료 | member_id, guild_id, slug, user_id | Audit |
MemberRestored | 수동 Member 복원 실행 | member_id, guild_id, restored_roles | Audit |
MemberLeft | 탈퇴/추방 감지 | member_id, guild_id, reason | Audit |
Consumed¶
Member 는 Guild 이벤트를 직접 구독하지 않는다. Live Sync 가 상태를 배치로 갱신.
Ports¶
Supporting 도메인이므로 단순 구조.
Inbound¶
type Service interface {
// 웹 조인
StartWebJoin(ctx, slug, discordAuthCode) (*WebJoinRequest, error)
CompleteWebJoin(ctx, requestID) error
// 멤버 조회
GetMember(ctx, memberID) (*Member, error)
GetMembersByGuild(ctx, guildID, options) ([]*Member, error)
// 복원 (수동)
RestoreMember(ctx, memberID, snapshotMemberData) error
// Live Sync 에서 호출
UpsertMember(ctx, guildID, discordUserID, roleIDs, nickname) error
MarkMemberLeft(ctx, guildID, discordUserID) error
}
Outbound¶
- MemberRepository — sqlc 래퍼
- DiscordClient —
guilds.join,guilds.members.roles엔드포인트 - IdentityReader — Identity 도메인 조회 (user_id 연결)
Adapters¶
- Persistence —
engine/member/repository.go - Discord —
engine/member/discord_client.go
Permission model¶
- 웹 조인 — 누구나 링크로 접근 가능. Discord OAuth2 완료 + Guild 의 웹 조인 활성 여부가 조건
- Member 복원 — 길드 owner 또는
MANAGE_GUILD권한자, 대시보드에서 실행 - Member 목록 조회 — Pro 플랜 이상 (Free 는 대시보드 없음)
Web Join flow¶
대략의 흐름:
- 사용자가
join.umbra.ink/{slug}접근 - umbra-web 이 slug 로 Guild 조회, 웹 조인 활성 확인
- "가입하기" 버튼 → Discord OAuth2
authorize리다이렉트 (scope:identify,guilds.join) - Discord 콜백 → umbra-api 가 auth code 로 access token 교환
WebJoinRequest생성 (state = pending)guilds.joinAPI 호출- 성공 시
state = completed, Member row upsert, auto_role 부여 - 사용자에게 성공 페이지 + Discord 로 리다이렉트
상세 시퀀스는 flows/web-join.md 참조.
Failure modes¶
- Discord OAuth2 scope 부족 —
guilds.joinscope 거부 시 WebJoinRequestfailed, 사용자에게 재시도 유도 guilds.joinrate limit — Discord API 가 제한하면 backoff 후 재시도, 최대 3회- Bot 이 guild 에 없음 —
guilds.join실패. GuildRemoved상태 전환 트리거 - Auto role ID 가 유효하지 않음 — role 부여만 실패, 가입 자체는 유지
- Live Sync 지연 — 신규 멤버가 DB 에 즉시 반영 안 됨. 복구 시 snapshot 기준이므로 문제 없음
See also¶
data/member-schema.md— DB 스키마domain/guild.md— 멤버가 속한 길드domain/recovery/overview.md— 멤버 복원flows/web-join.md— 웹 조인 전체 흐름adr/0027-message-content-exclusion.md— 메시지는 저장하지 않음