본문으로 건너뛰기

시스템 아키텍처 가이드

Caffeine 플랫폼의 시스템 아키텍처, 미들웨어 파이프라인, DTO 분리 패턴, DI 등록 전략에 대한 상세 가이드이다.

4-Tier 계층 구조

Caffeine은 명확한 4계층(Presentation, Engine, Edge, Driver) 아키텍처로 설계되어 있으며, 각 계층은 독립적으로 배포 및 확장이 가능하다.

계층별 역할

계층프로젝트역할
PresentationAdmin, Mobile, CLI, Client SDK사용자 인터페이스 및 외부 연동
EngineCaffeine.Engine중앙 오케스트레이터. REST, gRPC, SignalR, GraphQL, MQTT, Kafka 통합
EdgeCaffeine.Bridge.Host엣지 게이트웨이. 드라이버 동적 로드(Plugin Architecture)
DriverCaffeine.Drivers.*IDriverModule 인터페이스 구현. 산업 프로토콜 플러그인

미들웨어 파이프라인 (Engine)

Engine의 HTTP 요청 처리 파이프라인은 다음 순서로 구성된다.

ExceptionHandlingMiddleware

파이프라인 최상단에 위치하여 모든 처리되지 않은 예외를 catch한다. 내부 에러 상세(스택 트레이스, DB 쿼리 등)를 외부에 노출하지 않으며, 일관된 JSON 에러 응답을 반환한다.

위치: src/Caffeine.Engine/Middleware/ExceptionHandlingMiddleware.cs

동작 방식:

  1. RequestDelegate를 호출하여 다음 미들웨어로 요청 전달
  2. 예외 발생 시 ILogger로 에러 로깅 (경로 정보 포함)
  3. 예외 타입별 HTTP 상태코드 분기 (v2.2.1+):
    • ValidationException → 400 (Bad Request)
    • KeyNotFoundException → 404 (Not Found)
    • UnauthorizedAccessException → 403 (Forbidden)
    • OperationCanceledException → 499 (Client Closed Request)
    • 기타 예외 → 500 (Internal Server Error)
  4. ApiErrorResponse 형식의 JSON 응답 반환

에러 응답 형식 (ApiErrorResponse):

{
"code": "INTERNAL_SERVER_ERROR",
"message": "서버 내부 오류가 발생했습니다.",
"traceId": "00-abc123...",
"details": "..." // Development 환경에서만 포함 (v2.2.1+)
}
  • code: 에러 분류 코드 (문자열)
  • message: 사용자에게 표시할 안전한 메시지 (내부 상세 미포함)
  • traceId: OpenTelemetry Activity ID 또는 ASP.NET Core TraceIdentifier
  • details: 스택 트레이스 (Development 환경에서만, v2.2.1+)

JWT Bearer Authentication (v2.2.1+)

모든 REST API 엔드포인트에 대해 JWT Bearer 토큰 인증을 강제한다.

위치: src/Caffeine.Engine/Program.cs (Middleware 등록)

동작 방식:

  1. HTTP 요청 헤더에서 Authorization: Bearer <token> 추출
  2. JWT 서명 검증 (Issuer, Audience, Secret)
  3. 토큰 유효성 검사 (만료 시간, 클레임)
  4. 성공 시 HttpContext.User에 ClaimsPrincipal 설정
  5. 실패 시 401 Unauthorized 반환

설정 (appsettings.json):

{
"Jwt": {
"Issuer": "Caffeine.Engine",
"Audience": "Caffeine.Client",
"Secret": "your-secret-key-here"
}
}

Controller 인증 강제:

[Authorize]  // v2.2.1+: 모든 Controller에 적용
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class AlarmController : ControllerBase
{
// 모든 Action 메서드에 인증 필수
}

파이프라인 등록 순서 (Program.cs)

1. ExceptionHandlingMiddleware  (전역 예외 처리 -- 최우선)
2. Authentication Middleware (JWT Bearer 인증 -- v2.2.1+)
3. gRPC-Web Middleware (gRPC-Web 프로토콜 지원)
4. WebSocket Middleware (GraphQL Subscription용)
5. CORS Middleware (Admin UI 연동)
6. Authorization Middleware ([Authorize] 속성 검증 -- v2.2.1+)
7. Controller / gRPC / SignalR / GraphQL 매핑

상세한 미들웨어 사용 가이드는 미들웨어 가이드를 참조한다.

DTO 분리 패턴

Controller에서 도메인 엔티티를 직접 노출하지 않고, 전용 DTO(Data Transfer Object)를 사용하여 API 계약을 관리한다.

DTO 위치 및 구조

DTO위치용도
ApiErrorResponseCaffeine.Core/Models/통합 에러 응답 (모든 Controller 공용)
CreateSilenceRequestCaffeine.Engine/DTOs/Alert Silence 생성 요청
WebhookResponseCaffeine.Engine/DTOs/Alertmanager Webhook 처리 결과
LicenseStatusDto, LicenseUploadRequestCaffeine.Engine/DTOs/라이선스 관리 API

배치 원칙

  • Core 계층 DTO (Caffeine.Core/Models/): 여러 계층에서 공유되는 공통 모델 (예: ApiErrorResponse)
  • Engine 계층 DTO (Caffeine.Engine/DTOs/): Engine API에 특화된 요청/응답 모델
  • 도메인 엔티티와 DTO 간 변환은 Controller 내부에서 직접 수행 (별도 Mapper 미사용)

Controller 표준 패턴

모든 REST Controller는 다음 표준을 따른다.

API Versioning

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[resource]")]
public class ResourceController : ControllerBase
  • Asp.Versioning 패키지 사용
  • URL 경로 기반 버전 관리: /api/v1/...
  • 기본 버전: 1.0 (미지정 시 자동 적용)

반환 타입

  • 성공: Ok(data), CreatedAtAction(...), NoContent()
  • 실패: BadRequest(new { message = "..." }), NotFound(...), StatusCode(500, ...)
  • ProducesResponseType 어트리뷰트로 Swagger 문서 자동 생성

Controller 목록

Controller경로역할
AlarmController/api/v1/alarms활성 알람 조회, 이력 조회, 알람 확인(Acknowledge)
AlertHistoryController/api/v1/alert-history알림 히스토리 페이징 조회, 심각도/소스별 통계
AlertSilenceController/api/v1/alert-silenceSilence 규칙 CRUD
AlertmanagerController/api/v1/alertmanagerPrometheus Alertmanager Webhook 수신
ConfigController/api/v1/config태그 설정 조회/일괄 등록 (Excel Import/Export)
DriverController/api/v1/drivers드라이버 설정 조회/업데이트
LicenseController/api/v1/license라이선스 상태 조회, 업로드, 재검증, 기능/이력 조회
PredictiveMaintenanceController/api/v1/predictive-maintenance고장 예측, RUL, 이상 탐지, 모델 재학습

DI 등록 전략

등록 위치 분리

Caffeine의 서비스 등록은 계층별로 분리되며, 명확한 소유권 원칙을 따른다.

CaffeineServiceCollectionExtensions.AddCaffeine() (Caffeine.Core):

  • 데이터 코덱 (IDataCodec<double>, IDataCodec<short>, IDataCodec<int>) Singleton 등록
  • Big-Endian/Little-Endian 코덱 옵션 지원 (CaffeineOptions.UseBigEndianCodecs)
  • AddCaffeine(o => o.UseBigEndianCodecs = true) 한 줄 호출로 코어 서비스 일괄 등록

InfrastructureExtensions.AddCaffeineInfrastructure():

  • 외부 서비스 어댑터 (Redis, InfluxDB, TypeDB)
  • 메시징 (NullMessageProducer, RxEventBus)
  • ML 서비스 (PredictiveMaintenanceService 등)
  • 알림 시스템 (AlertDeduplicator, AlertSilenceService 등)

Engine/Program.cs (Composition Root):

  • 백그라운드 워커 (HostedService 등록)
  • gRPC/SignalR 엔드포인트
  • Engine 전용 서비스 (ReasoningEngine, AlarmManagerService 등)
  • 라이선스 서비스

NullMessageProducer (Null Object 패턴)

Kafka 메시지 브로커가 구성되지 않은 환경에서 안전한 폴백을 제공한다.

위치: src/Caffeine.Infrastructure/Messaging/NullMessageProducer.cs

IMessageProducer (Core 인터페이스)
|
+-- KafkaMessageProducer (Kafka 연결 시)
+-- NullMessageProducer (Kafka 미연결 시 -- No-op)
  • InfrastructureExtensions에서 기본 등록: services.AddSingleton<IMessageProducer, NullMessageProducer>()
  • ProduceAsync() 호출 시 Debug 로그만 기록하고 즉시 반환
  • Engine이 Kafka 없이도 정상 기동 가능 (Graceful Degradation)

백그라운드 워커

Engine에는 13개의 BackgroundService 워커가 등록되어 있다.

워커역할
IngestionWorker데이터 수집 파이프라인 구동 (Channel 기반)
HistorianService시계열 데이터 InfluxDB 저장
ReasoningServiceAI 추론 및 이상 감지
AlarmManagerService알람 관리 V2 (SQLite 기반)
AlarmServiceReactive Extensions 기반 온도 알람
HealthCheckAlertServiceHealth Check 기반 알림
RedisBulkServiceRedis 대량 쓰기
MqttListenerServiceMQTT 메시지 리스너
SecsGemServiceSECS/GEM 시뮬레이션 연동
ModelLoaderService시작 시 태그 모델 로드
TagStreamingService실시간 태그 값 SignalR 브로드캐스트
MetricReporterServicePrometheus 메트릭 수집
BackgroundLicenseMonitorService라이선스 상태 주기적 모니터링

외부 서비스 통합

Data Store

서비스라이브러리역할Graceful Degradation
RedisStackExchange.Redis실시간 캐시, Shadow RepositoryAbortOnConnectFail = false
InfluxDBInfluxDB.Client시계열 저장, ML 피처 추출토큰 미설정 시 NullTraceWriter
TypeDBTypeDB Driver지식 그래프Lazy Connection
SQLiteEF Core알람 이력, 부품 교체 이력로컬 파일 기반

Messaging

서비스역할폴백
Kafka이벤트 스트리밍NullMessageProducer
MQTT장비 메시지 수신환경변수 미설정 시 비활성화
RxEventBusIn-Memory Reactive 이벤트 버스항상 활성

통신 프로토콜

프로토콜포트용도
HTTP/REST5001Swagger API, Admin UI 연동
gRPC (h2c)5050Bridge 양방향 스트리밍
SignalR5001 (/hubs/alarms, /hubs/monitoring)실시간 Push
GraphQL5001 (/graphql)Query, Mutation, Subscription
MQTT외부 브로커장비 메시지 수신
Kafka외부 브로커이벤트 스트리밍

멀티테넌시 아키텍처 (v2.4+)

Caffeine은 TenantScope(AsyncLocal) + ITenantContext(Scoped) 이중 구조로 멀티테넌시를 지원한다. 기존 싱글 테넌트 환경과 완전히 호환되며, MultiTenancy.Enabled = true 설정 시 활성화된다.

테넌트 해석 흐름

이중 컨텍스트 구조

구조라이프타임용도접근 방법
TenantScopeAsyncLocal (static)Singleton 서비스 (InfluxDB, Redis, TypeDB)TenantScope.GetEffectiveTenantId()
ITenantContextScoped (DI)Controller, Blazor, Handler생성자 주입

이중 구조의 이유: InfluxDB 클라이언트, Redis ConnectionMultiplexer 등은 Singleton으로 등록되어 Scoped DI에 접근할 수 없다. AsyncLocal은 async/await 체인을 따라 자동 전파되므로 Singleton에서도 현재 테넌트를 읽을 수 있다.

Blazor Server 특수 처리

Blazor Server에서는 각 렌더링 사이클이 새로운 async 컨텍스트에서 실행되어 AsyncLocal이 초기화된다. TenantCircuitHandlerCircuitHandler.OnCircuitOpenedAsyncOnConnectionUpAsync에서 TenantScope를 자동 복원한다.

데이터 격리

  • EF Core Global Query Filter: IMultiTenantEntity를 구현한 엔티티에 자동 WHERE TenantId = @tenantId 적용
  • 저장소 자동 할당: JsonFileRepository, SqliteAlarmRepository가 저장 시 TenantScope.GetEffectiveTenantId()로 자동 설정

Edge↔Cloud 통신 추상화 (v2.4+)

Bridge(Edge)와 Engine(Cloud) 간의 통신을 IEdgeCloudTransport 인터페이스로 추상화하여, 전송 프로토콜을 DI 등록만으로 교체할 수 있다.

통신 아키텍처

Transport 교체 전략

Transport환경프로토콜장점
GrpcEdgeCloudTransportLANgRPC h2c (양방향 스트리밍)고성능, 낮은 지연, Protobuf 직렬화
MqttEdgeCloudTransportWANMQTT v5 (QoS 1)방화벽 관통, 테넌트 인식 토픽, LWT 지원

교체 방법: DI 등록만 변경하면 BridgeWorker 코드 수정 없이 전환된다.

// LAN 환경 (기본)
services.AddSingleton<IEdgeCloudTransport, GrpcEdgeCloudTransport>();

// WAN 환경
services.AddSingleton<IEdgeCloudTransport, MqttEdgeCloudTransport>();

오프라인 복원력

SqliteOfflineBuffer는 네트워크 단절 시 메시지를 SQLite에 영속 저장한다.

항목기본값설명
최대 크기100 MB초과 시 FIFO 순서로 오래된 메시지 삭제
메시지 TTL24시간만료 시 자동 정리
Flush 전략FIFO순서 보장 전송, 실패 시 즉시 중단

복원 시나리오:

  1. 네트워크 단절 감지 → 전송 실패한 메시지를 IOfflineBuffer.EnqueueAsync()로 저장
  2. SQLite에 FIFO 순서로 영속 (프로세스 재시작에도 보존)
  3. TransportState.Connected 이벤트 수신 → FlushAsync()로 일괄 재전송
  4. 전송 성공한 메시지만 삭제, 실패 시 중단하여 순서 보장

MQTT 테넌트 인식 토픽

MqttEdgeCloudTransport는 토픽에 테넌트 ID를 포함하여 멀티테넌트 환경에서 데이터를 격리한다.

caffeine/{tenantId}/{driverId}/data      — 드라이버 데이터 (Outbound)
caffeine/{tenantId}/{driverId}/command — Engine 명령 (Inbound, 구독)
caffeine/{tenantId}/{driverId}/status — 온라인/오프라인 (Retained, LWT)

참고 자료


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