Files
ExperionCrawler/CODING_CONVENTIONS.md
windpacer 77bdcf1f7f feat: ExperionCrawler IIoT OPC UA Data Bridge Infrastructure
Major project initialization and feature implementation:

**Core Features:**
- OPC UA client for Honeywell Experion HS R530 integration
- Real-time data streaming and history data retrieval
- Text-to-SQL query engine with TimeScaleDB
- JSON-based node configuration system
- SQLite database with migration support

**Architecture:**
- Clean architecture with Domain, Application, Infrastructure layers
- ASP.NET Core Web API frontend
- Web UI with real-time visualization
- PKI-based OPC UA authentication (TLS)

**Infrastructure Components:**
- ExperionOpcClient: OPC UA connection management
- ExperionRealtimeService: Real-time data streaming
- ExperionHistoryService: Historical data queries
- TextToSqlService: Natural language to SQL queries
- SqlValidator: SQL injection prevention

**Database:**
- TimescaleDB integration (recommended) or SQLite fallback
- Entity Framework Core with Extenstion methods
- OPCTag, KeyValue tables for data storage

**Security:**
- Certificate-based OPC UA endpoint security
- SSL/TLS encryption for database connections
- Output param binding injection prevention

**Testing:**
- Unit tests for TextToSqlService and SqlValidator
- Integration tests for Korean time range extraction

See REVIEW_REQUEST.md for detailed code review information.
2026-04-26 19:28:56 +09:00

3.6 KiB

ExperionCrawler 코딩 컨벤션

1. ASP.NET Core 컨트롤러 JSON 직렬화

핵심 설정

src/Web/Program.cs:

builder.Services.AddControllers().AddJsonOptions(opt => {
    opt.JsonSerializerOptions.PropertyNamingPolicy = null;  // PascalCase 직렬화
});

PropertyNamingPolicy = null이므로 C# 속성명이 그대로 JSON 키가 된다.
프론트엔드(wwwroot/js/app.js)는 모든 JSON 필드를 camelCase로 접근한다.

규칙: 컨트롤러 응답은 반드시 명시적 camelCase 익명 객체 사용

금지 패턴

// ❌ shorthand — "Id", "TagName"(PascalCase)이 JSON 키가 됨 → JS에서 undefined
return Ok(new { x.Id, x.TagName, x.NodeId });

// ❌ typed 객체 직접 반환 — PascalCase 키 → JS에서 undefined
return Ok(myDto);
return Ok(new MyRecord { Id = 1, TagName = "abc" });

올바른 패턴

// ✅ 명시적 소문자 키 — JS에서 r.id, r.tagName으로 정상 접근
return Ok(new
{
    id        = x.Id,
    tagName   = x.TagName,
    nodeId    = x.NodeId,
    liveValue = x.LiveValue,
    timestamp = x.Timestamp
});

// ✅ 컬렉션
return Ok(new
{
    total = r.Total,
    items = r.Items.Select(x => new
    {
        id       = x.Id,
        tagName  = x.TagName,
        nodeId   = x.NodeId,
        dataType = x.DataType
    })
});

// ✅ C# 예약어 처리: @class → JSON "class"
return Ok(new
{
    id     = x.Id,
    @class = x.Class,   // JSON key: "class"
    name   = x.Name
});

프론트엔드 접근 방식 (참고)

// app.js에서 모든 응답 필드를 camelCase로 접근
items.forEach(x => {
    row.cells[0].textContent = x.id;        // ← "id" (소문자)
    row.cells[1].textContent = x.tagName;   // ← "tagName" (camelCase)
    row.cells[2].textContent = x.nodeId;    // ← "nodeId"
});

발생 이력

이 규칙을 어기면 다음 증상이 나타난다:

  • 테이블에 모든 셀이 빈칸 또는 [undefined]
  • 브라우저 콘솔에 오류 없음 (값이 undefined이므로 조용히 실패)
  • 서버 응답 자체는 정상 (Network 탭에서 데이터 확인 가능)

실제 발생 사례 (2026-04-26):

  • Browse 노드 목록: n.NodeId, n.DisplayNameundefined / "이름 없음"
  • NodeMap 대시보드: x.Id, x.Level, x.Classundefined
  • PointBuilder 포인트 목록: p.Id, p.TagName, p.LiveValueundefined

2. OPC UA SDK 버전 호환성 (v1.5.378.134)

Session 생성

// ❌ 구버전 — Session.Create()가 Task<ISession>을 반환하므로 cast 실패
var session = (ISession)Session.Create(config, endpoint, false, name, 60000, identity, null);

// ✅ 신버전
var session = await new DefaultSessionFactory(null).CreateAsync(
    config, endpoint, false, name, 60_000, identity, null, CancellationToken.None);

인증서 검증 이벤트

OpcUaConfigProvider.GetConfigAsync에서 config를 빌드한 후 이벤트 핸들러를 등록해야 한다.
ExperionOpcClient.BuildConfigAsync는 실제로 호출되지 않는 dead code이므로 거기에 등록해도 무효.

await config.ValidateAsync(ApplicationType.Client);
config.CertificateValidator.CertificateValidation += (_, e) => { e.Accept = true; };

3. 컨트롤러 응답 구조 체크리스트

컨트롤러에서 Ok(...) 사용 시 반드시 확인:

  • 익명 객체의 모든 키가 camelCase인가? (id, tagName, nodeId ...)
  • new { x.SomeProp } shorthand가 전혀 없는가?
  • typed record/class를 Ok()직접 전달하지 않는가?
  • C# 예약어(class)에 @ 접두사를 붙였는가?