멀티테넌시 API
Caffeine 멀티테넌시 시스템의 인터페이스, 타입, 설정 레퍼런스이다.
개요
Caffeine은 TenantScope(AsyncLocal) + ITenantContext(Scoped) 이중 구조로 멀티테넌시를 지원한다. Singleton 서비스(InfluxDB, Redis)는 TenantScope에서, Scoped 서비스(Controller, Blazor)는 ITenantContext에서 현재 테넌트를 읽는다.
Core 인터페이스
IMultiTenantEntity
테넌트별 격리가 필요한 엔티티의 마커 인터페이스. 이 인터페이스를 구현한 Entity는 EF Core Global Query Filter가 자동 적용된다.
위치: src/Caffeine.Core/Abstractions/MultiTenancy/IMultiTenantEntity.cs
public interface IMultiTenantEntity
{
string? TenantId { get; set; }
}
사용 예시:
// BaseStoredEntity가 IMultiTenantEntity를 구현
public class BaseStoredEntity : IMultiTenantEntity
{
public string? TenantId { get; set; }
// ...
}
ITenantContext
현재 요청의 테넌트 컨텍스트. Scoped 라이프타임으로 DI에 등록된다.
위치: src/Caffeine.Core/Abstractions/MultiTenancy/ITenantContext.cs
public interface ITenantContext
{
/// 현재 테넌트 ID (null = 싱글 테넌트 모드)
string? TenantId { get; }
/// 현재 테넌트 정보 (null = 싱글 테넌트 모드)
TenantInfo? CurrentTenant { get; }
/// 멀티테넌시가 활성화되어 있는지
bool IsMultiTenantEnabled { get; }
}
ITenantResolver
요청 컨텍스트에서 테넌트를 해석하는 전략 인터페이스. Chain of Responsibility 패턴으로 여러 Resolver를 순차 시도한다.
위치: src/Caffeine.Core/Abstractions/MultiTenancy/ITenantResolver.cs
public interface ITenantResolver
{
/// 우선순위 (낮은 값이 먼저 시도)
int Order { get; }
/// 현재 요청에서 테넌트 ID를 해석
Task<string?> ResolveAsync(TenantResolveContext context, CancellationToken ct = default);
}
TenantResolveContext
public class TenantResolveContext
{
/// HTTP 요청의 ClaimsPrincipal (JWT Claims)
public ClaimsPrincipal? Principal { get; init; }
/// HTTP 요청 헤더
public IDictionary<string, string>? Headers { get; init; }
/// 추가 컨텍스트 (MQTT Topic 등)
public IDictionary<string, object>? Properties { get; init; }
}
내장 Resolver 우선순위
| Order | Resolver | 설명 |
|---|---|---|
| 10 | JwtClaimTenantResolver | JWT 토큰의 tenant_id Claim에서 추출 |
| 20 | HeaderTenantResolver | X-Tenant-Id 헤더에서 추출 (개발용) |
| 30 | SubdomainTenantResolver | 호스트명 서브도메인에서 추출 (SaaS용) |
타입 정의
TenantInfo
테넌트 기본 정보 record.
위치: src/Caffeine.Core/Abstractions/MultiTenancy/TenantInfo.cs
public record TenantInfo(
string TenantId,
string Name,
string? DisplayName = null,
bool IsActive = true
);
TenantScope
AsyncLocal 기반 테넌트 범위. Singleton 서비스(InfluxDB, Redis, TypeDB)에서 현재 요청의 테넌트를 읽을 수 있게 한다.
위치: src/Caffeine.Core/Abstractions/MultiTenancy/TenantScope.cs
public static class TenantScope
{
/// 현재 async 컨텍스트의 테넌트 ID
static string? CurrentTenantId { get; set; }
/// 멀티테넌시 활성화 여부
static bool IsMultiTenantEnabled { get; set; }
/// 비활성화 시 null 반환
static string? GetEffectiveTenantId();
}
동작 원리:
TenantResolutionMiddleware에서 요청 시작 시 설정async/await체인을 따라 자동 전파 (AsyncLocal 특성)- 각 저장소(InfluxDB, Redis 등)에서
TenantScope.GetEffectiveTenantId()로 읽기
MultiTenancyOptions
멀티테넌시 설정 클래스. appsettings.json의 "MultiTenancy" 섹션에 바인딩된다.
위치: src/Caffeine.Core/Abstractions/MultiTenancy/MultiTenancyOptions.cs
public class MultiTenancyOptions
{
/// 멀티테넌시 활성화 여부 (기본: false)
public bool Enabled { get; set; } = false;
/// 기본 테넌트 ID (싱글 테넌트 모드일 때 사용)
public string DefaultTenantId { get; set; } = "default";
/// 테넌트 해석 전략
public TenantResolutionStrategy Strategy { get; set; } = TenantResolutionStrategy.JwtClaim;
}
TenantResolutionStrategy
public enum TenantResolutionStrategy
{
JwtClaim, // JWT Bearer 토큰의 tenant_id Claim
Header, // HTTP 헤더 X-Tenant-Id (개발용)
Subdomain // 호스트명 서브도메인 (SaaS용)
}
구현체
SubdomainTenantResolver
호스트명 서브도메인에서 테넌트를 해석하는 Resolver. SaaS 배포 시 사용한다.
위치: src/Caffeine.Engine/MultiTenancy/SubdomainTenantResolver.cs
해석 예시:
| 호스트명 | 결과 |
|---|---|
factory-a.caffeine.io | factory-a |
parking.caffeine.io | parking |
www.caffeine.io | null (제외 목록) |
localhost:5000 | null (서브도메인 없음) |
192.168.1.1 | null (IP 주소) |
제외 서브도메인: www, api, admin, localhost
TenantCircuitHandler
Blazor Server CircuitHandler로, 매 회로 활성화 시 TenantScope(AsyncLocal)를 복원한다.
위치: src/Caffeine.Admin/Services/TenantCircuitHandler.cs
필요한 이유: Blazor Server에서는 각 렌더링 사이클이 새로운 async 컨텍스트에서 실행되므로, TenantSwitcherService에 저장된 테넌트 ID를 AsyncLocal에 다시 설정해야 한다.
복원 시점:
OnCircuitOpenedAsync— 새 회로 열릴 때OnConnectionUpAsync— SignalR 재연결 시
설정
appsettings.json
{
"MultiTenancy": {
"Enabled": true,
"DefaultTenantId": "default",
"Strategy": "JwtClaim"
}
}
| 설정 | 타입 | 기본값 | 설명 |
|---|---|---|---|
Enabled | bool | false | false이면 싱글 테넌트 모드 (기존 호환) |
DefaultTenantId | string | "default" | 싱글 테넌트 모드 시 기본 테넌트 ID |
Strategy | enum | JwtClaim | 테넌트 해석 전략 |
참고 자료
- 아키텍처 가이드 — 멀티테넌시 아키텍처 섹션
- Edge↔Cloud 통신 API — 테넌트 인식 통신
- Changelog v2.4.0 — 멀티테넌시 변경 이력
작성일: 2026-02-11 버전: 2.4