본문으로 건너뛰기

멀티테넌시 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 우선순위

OrderResolver설명
10JwtClaimTenantResolverJWT 토큰의 tenant_id Claim에서 추출
20HeaderTenantResolverX-Tenant-Id 헤더에서 추출 (개발용)
30SubdomainTenantResolver호스트명 서브도메인에서 추출 (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.iofactory-a
parking.caffeine.ioparking
www.caffeine.ionull (제외 목록)
localhost:5000null (서브도메인 없음)
192.168.1.1null (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"
}
}
설정타입기본값설명
Enabledboolfalsefalse이면 싱글 테넌트 모드 (기존 호환)
DefaultTenantIdstring"default"싱글 테넌트 모드 시 기본 테넌트 ID
StrategyenumJwtClaim테넌트 해석 전략

참고 자료


작성일: 2026-02-11 버전: 2.4