콘텐츠로 이동

KPubData 아키텍처 다이어그램 (Architecture Diagrams)

이 문서는 KPubData 프로젝트의 전체적인 구조와 동작 원리를 시각화된 다이어그램을 통해 설명합니다. 새로운 개발자가 프로젝트의 설계 철학을 빠르게 이해하고 기술적 세부 사항을 파악하는 데 도움을 주기 위해 작성되었습니다.

1. 시스템 전체 구조 (High-Level System Architecture)

KPubData는 5개의 주요 계층으로 구성된 레이어드 아키텍처를 따릅니다. 각 계층은 명확한 역할 분담을 가지고 있으며, 상위 계층은 하위 계층의 구체적인 구현을 알 필요 없이 추상화된 인터페이스를 통해 통신합니다.

graph TB
    subgraph Public_API_Layer [Public API Layer]
        Client[Client]
        Catalog[Catalog]
        Dataset[Dataset]
    end

    subgraph Core_Layer [Core Layer]
        DatasetRef[DatasetRef]
        Query[Query]
        RecordBatch[RecordBatch]
        SchemaDescriptor[SchemaDescriptor]
        Operation[Operation]
        Representation[Representation]
    end

    subgraph Provider_Layer [Provider Layer]
        ProviderAdapter["<<Protocol>><br/>ProviderAdapter"]
        DataGoAdapter[DataGoAdapter]
        SeoulAdapter[SeoulAdapter]
        BokAdapter[BokAdapter]
        KosisAdapter[KosisAdapter]

        ProviderAdapter -.-> DataGoAdapter
        ProviderAdapter -.-> SeoulAdapter
        ProviderAdapter -.-> BokAdapter
        ProviderAdapter -.-> KosisAdapter
    end

    subgraph Transport_Layer [Transport Layer]
        HttpTransport[HttpTransport]
        TransportConfig[TransportConfig]
        decode[decode JSON/XML]
        retry[retry logic]
    end

    subgraph External [External API]
        DataGoAPI["공공 API<br/>(data.go.kr / data.seoul.go.kr / ecos 등)"]
    end

    Client --> Catalog
    Client --> Dataset
    Dataset --> ProviderAdapter
    Catalog --> ProviderAdapter
    ProviderAdapter --> Core_Layer
    DataGoAdapter --> HttpTransport
    HttpTransport --> Transport_Layer
    HttpTransport --> DataGoAPI

    style Public_API_Layer fill:#f9f,stroke:#333,stroke-width:2px
    style Core_Layer fill:#bbf,stroke:#333,stroke-width:2px
    style Provider_Layer fill:#bfb,stroke:#333,stroke-width:2px
    style Transport_Layer fill:#fbb,stroke:#333,stroke-width:2px
    style External fill:#ddd,stroke:#333,stroke-dasharray: 5 5

2. 모듈 의존성 그래프 (Module Dependency Graph)

내부 모듈 간의 임포트(Import) 관계를 보여줍니다. 순환 참조를 방지하고 레이어 간의 경계를 유지하기 위해 하위 레이어 모듈은 상위 레이어 모듈을 참조하지 않도록 설계되었습니다.

graph LR
    init["__init__.py"] --> client[client.py]
    init --> capability["core/capability.py"]
    init --> models["core/models.py"]
    init --> representation["core/representation.py"]
    init --> exceptions[exceptions.py]

    client --> catalog[catalog.py]
    client --> config[config.py]
    client --> dataset["core/dataset.py"]
    client --> registry[registry.py]
    client --> http["transport/http.py"]

    catalog --> models
    catalog --> protocol["core/protocol.py"]
    catalog --> exceptions
    catalog --> registry

    registry --> exceptions
    config --> exceptions

    dataset --> capability
    dataset --> models
    dataset --> protocol
    dataset --> exceptions
    dataset --> http

    protocol --> models
    models --> capability
    models --> representation

    http --> exceptions
    datago["providers/datago/adapter.py"] --> config
    datago --> capability
    datago --> models
    datago --> representation
    datago --> exceptions
    datago --> decode["transport/decode.py"]
    datago --> http

    style init fill:#f9f
    style client fill:#f9f
    style catalog fill:#f9f
    style registry fill:#f9f
    style config fill:#f9f
    style dataset fill:#bbf
    style models fill:#bbf
    style protocol fill:#bbf
    style capability fill:#bbf
    style representation fill:#bbf
    style exceptions fill:#ddd
    style http fill:#fbb
    style decode fill:#fbb
    style retry fill:#fbb
    style datago fill:#bfb

3. 클래스 계층 구조 (Class Hierarchy)

프로젝트에서 사용되는 주요 클래스들의 상속 관계와 구조를 설명합니다. 특히 예외(Exception) 클래스들은 세분화되어 있어 구체적인 에러 상황에 대응할 수 있습니다.

classDiagram
    class Exception
    class PublicDataError {
        <<Exception>>
    }
    class ConfigError
    class AuthError
    class TransportError
    class TransportTimeoutError
    class RateLimitError
    class ServiceUnavailableError
    class ParseError
    class InvalidRequestError
    class ProviderResponseError
    class UnsupportedCapabilityError
    class DatasetNotFoundError
    class ProviderNotRegisteredError

    Exception <|-- PublicDataError
    PublicDataError <|-- ConfigError
    PublicDataError <|-- AuthError
    PublicDataError <|-- TransportError
    TransportError <|-- TransportTimeoutError
    TransportError <|-- RateLimitError
    TransportError <|-- ServiceUnavailableError
    PublicDataError <|-- ParseError
    PublicDataError <|-- InvalidRequestError
    PublicDataError <|-- ProviderResponseError
    PublicDataError <|-- UnsupportedCapabilityError
    PublicDataError <|-- DatasetNotFoundError
    PublicDataError <|-- ProviderNotRegisteredError

    class DatasetRef {
        <<dataclass>>
        +String id
        +String name
        +String provider
    }
    class Query {
        <<dataclass>>
        +Dict filters
        +Int page
        +Int page_size
    }
    class RecordBatch {
        <<dataclass>>
        +List items
        +DatasetRef dataset
        +Int total_count
    }
    class SchemaDescriptor {
        <<dataclass>>
        +List fields
    }

    class Operation {
        <<enumeration>>
        LIST
        GET
        SCHEMA
        RAW
        DOWNLOAD
    }

    class ProviderAdapter {
        <<Protocol>>
        +list_datasets()
        +search_datasets()
        +get_dataset()
        +query_records()
        +get_schema()
        +call_raw()
    }
    class DataGoAdapter {
        +list_datasets()
        +search_datasets()
        +get_dataset()
        +query_records()
        +get_schema()
        +call_raw()
    }
    ProviderAdapter <|.. DataGoAdapter

    class Client {
        +dataset(id)
        +catalog
    }
    class Catalog {
        +list()
        +resolve(id)
        +search(text)
    }
    class Dataset {
        +list()
        +schema()
        +call_raw()
    }
    class HttpTransport {
        +request()
    }

    Client *-- Catalog
    Dataset o-- ProviderAdapter
    Dataset o-- HttpTransport

4. 요청-응답 흐름 (Request-Response Data Flow)

client.dataset("datago.village_fcst").list(base_date="20250401") 호출 시 발생하는 내부 처리 과정을 시퀀스 다이어그램으로 추적합니다.

sequenceDiagram
    participant User
    participant Client
    participant Catalog
    participant Registry
    participant DataGoAdapter
    participant HttpTransport
    participant ExternalAPI as data.go.kr API

    User->>Client: dataset("datago.village_fcst")
    Client->>Catalog: resolve("datago.village_fcst")
    Catalog->>Catalog: split "datago" + "village_fcst"
    Catalog->>Registry: get("datago")
    Registry-->>Catalog: returns DataGoAdapter
    Catalog->>DataGoAdapter: get_dataset("village_fcst")
    DataGoAdapter-->>Catalog: returns DatasetRef
    Catalog-->>Client: (adapter, ref)
    Client-->>User: returns Dataset(ref, adapter, transport)

    User->>Dataset: list(base_date="20250401")
    Dataset->>Dataset: check Operation.LIST support
    Dataset->>Dataset: create Query(filters={"base_date": "20250401"})
    Dataset->>DataGoAdapter: query_records(ref, query)
    DataGoAdapter->>DataGoAdapter: build URL + params
    DataGoAdapter->>HttpTransport: request("GET", url, params)
    HttpTransport->>ExternalAPI: HTTP GET (with API Key)
    ExternalAPI-->>HttpTransport: JSON/XML Response
    HttpTransport-->>DataGoAdapter: httpx.Response (raw)
    DataGoAdapter->>DataGoAdapter: detect_content_type()
    DataGoAdapter->>DataGoAdapter: decode_json/xml()
    DataGoAdapter->>DataGoAdapter: _validate_envelope() (check resultCode)
    DataGoAdapter->>DataGoAdapter: _normalize_items()
    DataGoAdapter-->>Dataset: RecordBatch(items, total_count)
    Dataset-->>User: RecordBatch

5. 에러 처리 흐름 (Error Handling Flow)

각 계층에서 발생하는 원천 에러가 어떻게 KPubData의 표준 예외로 변환되어 사용자에게 전달되는지 보여줍니다.

flowchart TB
    subgraph Origin [Error Origin]
        HttpError[httpx.HTTPError / Timeout]
        DecodeError[JSON/XML Decode Error]
        ApiCode[resultCode != '00']
        MissingKey[Missing API Key]
        UnknownProv[Unknown Provider ID]
    end

    subgraph Transformation [Exception Transformation]
        HttpError -->|HttpTransport| TransportErr[TransportError / TimeoutError]
        DecodeError -->|decode.py| ParseErr[ParseError]

        ApiCode -->|DataGoAdapter| Mapping{Code Mapping}
        Mapping -->|30/31/20/32| AuthErr[AuthError]
        Mapping -->|22| RateLimit[RateLimitError]
        Mapping -->|10| InvalidReq[InvalidRequestError]
        Mapping -->|12| NotFound[DatasetNotFoundError]
        Mapping -->|01/02| ServiceUnavail[ServiceUnavailableError]
        Mapping -->|others| ResponseErr[ProviderResponseError]

        MissingKey -->|Config| CfgErr[ConfigError]
        UnknownProv -->|Registry| RegErr[ProviderNotRegisteredError]
    end

    subgraph UserFacing [User-Facing Exception]
        TransportErr & ParseErr & AuthErr & RateLimit & InvalidReq & NotFound & ServiceUnavail & ResponseErr & CfgErr & RegErr --> PublicDataError
    end

    style Origin fill:#fbb
    style Transformation fill:#fff
    style UserFacing fill:#bbf

6. 데이터셋 발견 흐름 (Dataset Discovery Flow)

사용자가 사용 가능한 데이터셋을 나열하거나 검색할 때의 내부 동작 방식입니다. 등록된 모든 프로바이더로부터 정보를 수집하여 통합된 결과를 제공합니다.

flowchart LR
    User([User]) -->|client.datasets.list| Catalog
    User -->|client.datasets.search| Catalog

    subgraph Discovery [Discovery Process]
        Catalog --> Registry
        Registry --> P1[DataGoAdapter]
        Registry --> P2[SeoulAdapter]
        Registry --> P3[BokAdapter]
        Registry --> P4[KosisAdapter]

        P1 -->|list_datasets| L1[Dataset List]
        P2 -->|list_datasets| L2[Dataset List]
        P3 -->|list_datasets| L3[Dataset List]
        P4 -->|list_datasets| L4[Dataset List]

        L1 & L2 & L3 & L4 --> Merge[Merge & Filter]
    end

    Merge -->|return list| User

    style Discovery fill:#f9f,stroke-dasharray: 5 5

7. data.go.kr 응답 구조 (data.go.kr Response Envelope)

공공데이터포털(data.go.kr)의 복잡한 중첩 응답 구조가 KPubData의 정규화된 RecordBatch 모델로 매핑되는 과정을 설명합니다.

graph LR
    subgraph RawJSON [Raw JSON Response]
        Response --> Header
        Response --> Body
        Header --> resultCode["resultCode: '00'"]
        Header --> resultMsg["resultMsg: 'NORMAL_CODE'"]
        Body --> totalCount["totalCount: 100"]
        Body --> items
        items --> item["item: [ {...}, {...} ]"]
    end

    subgraph Mapping [Canonical Mapping]
        item -->|normalize| RB_Items[RecordBatch.items]
        totalCount -->|extract| RB_Total[RecordBatch.total_count]
        resultCode -->|validate| Validation[Success Validation]
    end

    subgraph Canonical [RecordBatch Model]
        RB_Items
        RB_Total
        DatasetRef[RecordBatch.dataset]
    end

    style RawJSON fill:#eee
    style Canonical fill:#bbf

8. 프로바이더 어댑터 계약 (Provider Adapter Contract)

새로운 데이터 제공처를 지원하기 위해 구현해야 하는 ProviderAdapter 인터페이스 명세와 현재 구현 상태입니다.

graph TB
    subgraph Protocol [ProviderAdapter Protocol]
        direction TB
        M1["list_datasets() -> list[DatasetRef]"]
        M2["search_datasets(text) -> list[DatasetRef]"]
        M3["get_dataset(key) -> DatasetRef"]
        M4["query_records(dataset, query) -> RecordBatch"]
        M5["get_schema(dataset) -> SchemaDescriptor"]
        M6["call_raw(dataset, operation, params) -> object"]
    end

    Protocol --- Implementation

    subgraph Implementation [Implementation Status]
        DataGo[DataGoAdapter] -- "Fully Implemented" --> Protocol
        Seoul[SeoulAdapter] -- "Fully Implemented" --> Protocol
        Bok[BokAdapter] -- "Fully Implemented" --> Protocol
        Kosis[KosisAdapter] -- "Fully Implemented" --> Protocol
    end

    style DataGo fill:#bfb
    style Seoul fill:#bfb
    style Bok fill:#bfb
    style Kosis fill:#bfb

9. 재시도 및 Rate Limit 전략 (Retry & Rate Limit Strategy)

네트워크 불안정성이나 API 호출 제한(Rate Limit)에 대응하는 HttpTransport의 재시도 로직입니다.

stateDiagram-v2
    [*] --> Request: Start Attempt
    Request --> Success: HTTP 200 & Valid Body
    Request --> RetryCheck: Timeout / 429 / 5xx

    RetryCheck --> Delay: Attempt < Max Retries
    RetryCheck --> Failure: Attempt >= Max Retries

    Delay --> Request: Wait (Exponential Backoff)

    Success --> [*]
    Failure --> RaiseError: Raise TransportError
    RaiseError --> [*]

    note right of Delay : delay = backoff_factor × 2^(attempt-1), or use Retry-After header

10. 패키지 구조 (Package Structure)

프로젝트의 실제 파일 구조와 각 구성 요소의 역할입니다.

```text src/kpubdata/ ├── init.py # Public API 진입점 및 주요 클래스 노출 ├── client.py # 사용자 인터페이스를 제공하는 최상위 Client 클래스 ├── config.py # API 키 및 환경 변수 설정 관리 ├── catalog.py # 데이터셋 검색 및 해결(Resolution) 담당 ├── registry.py # 프로바이더 어댑터 등록 및 관리 ├── exceptions.py # 프로젝트 전체에서 사용되는 예외 클래스 정의 ├── core/ │ ├── capability.py # 지원 가능한 연산(Operation), 페이지네이션 모드 정의 │ ├── dataset.py # 어댑터와 연결된 데이터셋 객체 구현 │ ├── models.py # DatasetRef, Query, RecordBatch 등 정규화된 모델 │ ├── protocol.py # ProviderAdapter 프로토콜(인터페이스) 정의 │ └── representation.py # 데이터 표현 형식(JSON/XML) 열거형 ├── transport/ │ ├── http.py # httpx 기반의 HTTP 통신 및 재시도 로직 │ ├── decode.py # 응답 데이터(JSON/XML) 파싱 및 정규화 │ └── retry.py # 범용적인 재시도 유틸리티 └── providers/ ├── datago/ │ ├── adapter.py # 공공데이터포털(data.go.kr) 전용 어댑터 구현 │ └── catalogue.json # 큐레이션된 데이터셋 정의 데이터 ├── seoul/ │ ├── adapter.py # 서울 열린데이터광장 전용 어댑터 구현 │ └── catalogue.json # 서울 데이터셋 정의 ├── bok/ # 한국은행 ECOS 어댑터 ├── kosis/ # 통계청 KOSIS 어댑터 ├── lofin/ # 지방재정365 어댑터 ├── krx/ # 한국거래소 어댑터 └── ... # 기타 프로바이더