# Phase 1: DB 메타 읽기 → FieldConfig 변환

> **목적**: PostgreSQL 테이블의 스키마(컬럼명, 타입, PK/FK, nullable 등)를 읽어서 INVYONE의 FieldConfig[] 배열로 변환하는 파이프라인 구축
> **전제 조건**: 없음 (최초 단계)
> **산출물**: 테이블명을 주면 FieldConfig[]을 반환하는 백엔드 API + 프론트엔드 타입/API 클라이언트
> **다음 단계**: Phase 2가 이 FieldConfig[]을 받아서 테이블/폼/검색 컴포넌트로 렌더링

---

## 1. 핵심 개념

INVYONE의 모든 UI는 **FieldConfig**라는 단일 규격으로 동작한다. FieldConfig는 DB 테이블의 컬럼 메타데이터를 UI가 소비할 수 있는 형태로 변환한 것이다.

```
PostgreSQL information_schema + table_type_columns (기존)
  → Java Service에서 변환
  → FieldConfig[] JSON 반환
  → 프론트엔드가 이걸 받아서 렌더링
```

---

## 2. 현재 존재하는 것

### 2.1 백엔드 (backend-spring)

| 파일 | 역할 | 활용 |
|---|---|---|
| `TableManagementController.java` | 테이블/컬럼 CRUD API | **확장 대상** — FieldConfig 변환 API 추가 |
| `TableManagementService.java` | 테이블 메타 조회 서비스 | **확장 대상** — 변환 로직 추가 |
| `mapper/TableManagementMapper.xml` | MyBatis SQL | **확장 대상** — 조회 쿼리 추가 |
| `DataController.java` | 범용 데이터 CRUD | 참고용 — 나중에 Phase 2에서 활용 |

### 2.2 DB 테이블 (이미 존재)

**`table_type_columns`** — VEX에서 가져온 컬럼 메타데이터 테이블:
- `table_name`, `column_name`, `input_type`, `detail_settings` (JSONB)
- `is_nullable`, `display_order`, `company_code`
- 회사별 오버라이드 지원 (`company_code = '*'`이면 글로벌)

**PostgreSQL `information_schema.columns`** — DB 원본 스키마:
- `table_name`, `column_name`, `data_type`, `is_nullable`, `column_default`
- `character_maximum_length`, `ordinal_position`

**PostgreSQL `information_schema.table_constraints` + `key_column_usage`** — PK/FK 정보

### 2.3 프론트엔드

| 파일 | 역할 |
|---|---|
| `frontend/types/invyone-component.ts` | **FieldConfig 타입 정의 (이미 완성)** — 이 파일의 `FieldConfig` 인터페이스가 진실의 원천 |
| `frontend/lib/api/tableSchema.ts` | 테이블 스키마 API 클라이언트 (기존 VEX용, 참고) |

---

## 3. 상세 설계

### 3.1 FieldConfig 타입 (이미 정의됨 — frontend/types/invyone-component.ts)

```typescript
interface FieldConfig {
  // 식별
  column: string;        // DB 컬럼명
  label: string;         // 화면 표시 라벨

  // 타입
  type: FieldType;       // 'text' | 'number' | 'date' | 'datetime' | 'select' | 'entity' | 'checkbox' | 'textarea' | 'file' | 'code'

  // 표시
  visible: boolean;
  order: number;
  width?: number;
  align?: 'left' | 'center' | 'right';

  // 입력
  required: boolean;
  editable: boolean;
  defaultValue?: unknown;
  placeholder?: string;

  // 타입별 확장
  options?: string[];    // select용
  ref?: FieldRef;        // entity FK 참조
  format?: string;       // 포맷 문자열
  computed?: string;     // 자동 계산 수식

  // 메타
  pk?: boolean;
  system?: boolean;      // company_code 같은 시스템 필드
  searchable?: boolean;
  sortable?: boolean;
}
```

### 3.2 PostgreSQL → FieldType 매핑 규칙

DB의 `data_type`을 FieldType으로 변환하는 규칙:

```
PostgreSQL data_type          → FieldType
─────────────────────────────────────────
character varying, varchar    → 'text'
text                          → 'textarea'
integer, bigint, smallint     → 'number'
numeric, decimal, real, double→ 'number'
boolean                       → 'checkbox'
date                          → 'date'
timestamp, timestamptz        → 'datetime'
jsonb, json                   → 'textarea'
bytea                         → 'file'
(그 외)                       → 'text' (기본값)
```

**단, `table_type_columns.input_type`이 있으면 그것이 우선한다.**

`table_type_columns.input_type` → FieldType 매핑:

```
input_type       → FieldType
────────────────────────────
text             → 'text'
number           → 'number'
date             → 'date'
datetime         → 'datetime'
select           → 'select'
entity           → 'entity'
checkbox         → 'checkbox'
boolean          → 'checkbox'
textarea         → 'textarea'
text_area        → 'textarea'
file             → 'file'
code             → 'code'
numbering        → 'code'
category         → 'select'
decimal          → 'number'
email            → 'text'
password         → 'text'
tel              → 'text'
(그 외)          → 'text'
```

### 3.3 변환 로직 상세

입력: `table_name` (String), `company_code` (String)

```
Step 1: information_schema.columns에서 해당 테이블의 모든 컬럼 조회
Step 2: information_schema.table_constraints + key_column_usage에서 PK 컬럼 목록 조회
Step 3: table_type_columns에서 해당 테이블의 커스텀 메타 조회 (company_code 우선순위: 해당 회사 > '*')
        → input_type='entity'인 컬럼의 detail_settings JSON에서 FK 참조 정보 추출
        → referenceTable, referenceColumn, displayColumn
Step 4: Step 1~3 병합하여 FieldConfig[] 생성
```

**★ FK는 DB 제약조건(FOREIGN KEY)이 아닌 `table_type_columns.detail_settings` JSON으로 관리.**
VEX는 비즈니스 테이블에 DB 레벨 FK 제약조건을 걸지 않는다. 관계 정보는 전부 메타데이터 테이블에서 관리한다.
이유: 유연성 (마이그레이션 없이 관계 변경), 멀티테넌시 (회사별 관계 설정), 데이터 이관 시 제약조건 우회 가능.

#### Step 5 병합 규칙:

```
FieldConfig.column    = information_schema.column_name
FieldConfig.label     = table_type_columns.column_label (있으면) || column_name을 한글화 시도 (없으면 그대로)
FieldConfig.type      = table_type_columns.input_type 매핑 (있으면) || data_type 매핑
FieldConfig.visible   = true (기본값, table_type_columns에서 오버라이드 가능)
FieldConfig.order     = table_type_columns.display_order (있으면) || information_schema.ordinal_position
FieldConfig.required  = (is_nullable = 'NO') 이고 (column_default가 없으면) true
FieldConfig.editable  = PK가 아니고 system이 아니면 true
FieldConfig.pk        = PK 컬럼이면 true
FieldConfig.system    = column_name이 'company_code', 'created_by', 'created_date', 'updated_by', 'updated_date', 'is_active' 중 하나면 true
FieldConfig.searchable = table_type_columns에서 지정 (없으면 text/select/entity/date만 true)
FieldConfig.sortable  = true (기본값)
FieldConfig.ref       = table_type_columns.input_type='entity'이면 detail_settings에서 { table, value_column, display_column } 추출
FieldConfig.options   = table_type_columns.detail_settings에서 options 추출 (select 타입)
FieldConfig.format    = number이면 '#,##0', date이면 'YYYY-MM-DD', datetime이면 'YYYY-MM-DD HH:mm'
FieldConfig.width     = type에 따라 기본값: text=150, number=100, date=120, select=130, entity=180
FieldConfig.align     = number이면 'right', 나머지 'left'
```

### 3.4 백엔드 API 설계

#### 3.4.1 신규 API 엔드포인트

**`GET /api/meta/tables`** — 접근 가능한 테이블 목록

```
Request:  (없음, JWT에서 company_code 추출)
Response:
{
  "success": true,
  "data": [
    {
      "table_name": "order_management_test",
      "table_label": "수주관리",
      "column_count": 13,
      "has_custom_meta": true
    },
    ...
  ]
}
```

**`GET /api/meta/tables/{tableName}/fields`** — 특정 테이블의 FieldConfig[] 반환

```
Request:  GET /api/meta/tables/order_management_test/fields
Headers:  Authorization: Bearer {jwt}

Response (★ 백엔드 응답은 전부 snake_case, 프론트에서 camelCase 변환):
{
  "success": true,
  "data": {
    "table_name": "order_management_test",
    "table_label": "수주관리",
    "primary_key": "order_no",
    "fields": [
      {
        "column": "order_no",
        "label": "수주번호",
        "type": "code",
        "visible": true,
        "order": 1,
        "width": 120,
        "align": "left",
        "required": true,
        "editable": false,
        "pk": true,
        "system": false,
        "searchable": true,
        "sortable": true,
        "format": null,
        "options": null,
        "ref": null,
        "computed": null
      },
      {
        "column": "order_date",
        "label": "수주일",
        "type": "date",
        "visible": true,
        "order": 2,
        "width": 120,
        "align": "left",
        "required": true,
        "editable": true,
        "pk": false,
        "system": false,
        "searchable": true,
        "sortable": true,
        "format": "YYYY-MM-DD",
        "options": null,
        "ref": null,
        "computed": null
      },
      {
        "column": "customer",
        "label": "거래처",
        "type": "entity",
        "visible": true,
        "order": 3,
        "width": 180,
        "align": "left",
        "required": true,
        "editable": true,
        "pk": false,
        "system": false,
        "searchable": true,
        "sortable": true,
        "format": null,
        "options": null,
        "ref": {
          "table": "customer_mng",
          "value_column": "customer_code",
          "display_column": "customer_name",
          "search_columns": ["customer_name", "biz_number"]
        },
        "computed": null
      },
      {
        "column": "amount",
        "label": "금액",
        "type": "number",
        "visible": true,
        "order": 7,
        "width": 100,
        "align": "right",
        "required": false,
        "editable": false,
        "pk": false,
        "system": false,
        "searchable": false,
        "sortable": true,
        "format": "#,##0",
        "options": null,
        "ref": null,
        "computed": "quantity * unit_price"
      },
      {
        "column": "status",
        "label": "상태",
        "type": "select",
        "visible": true,
        "order": 9,
        "width": 100,
        "align": "center",
        "required": false,
        "editable": true,
        "pk": false,
        "system": false,
        "searchable": true,
        "sortable": true,
        "format": null,
        "options": ["임시저장", "확정", "완료", "취소"],
        "ref": null,
        "computed": null
      },
      {
        "column": "company_code",
        "label": "회사코드",
        "type": "text",
        "visible": false,
        "order": 99,
        "required": false,
        "editable": false,
        "pk": false,
        "system": true,
        "searchable": false,
        "sortable": false
      }
    ]
  }
}
```

**★ 관계 정보 2소스 책임 분리 (B안 확정):**

| 소스 | 용도 | 사용 Phase |
|---|---|---|
| `table_type_columns.detail_settings` | **필드 레벨 참조** — FieldConfig.ref, entity picker, 폼/검색/테이블 렌더링 | Phase 1 (`getTableFields`) |
| `table_relationships` | **테이블 간 관계** — 제어 모드, 데이터플로우, 업무적 연결 (one-to-many 등) | Phase 5 (`getMetaRelations`) |

- Phase 1의 `getTableFields()`는 `table_type_columns`만 읽는다 (필드 참조 원천)
- Phase 5의 관계 그래프는 `table_relationships`를 읽는다 (업무 관계 원천)
- 제어 모드에서 두 소스가 필요하면 **서비스 레이어에서 읽기 시점에만 합친다** (저장 구조 통합 안 함)

따라서 **이 Phase 1에서는 relations API를 만들지 않는다.** `getTableFields()`가 `table_type_columns`에서 entity 참조(ref)를 추출하는 것까지만 책임진다. `table_relationships` 기반 관계 API는 Phase 5에서 구현한다.

#### 3.4.2 구현할 Java 파일 (★ 파이프라인 규칙 준수)

**★ 덕일 스타일 3레이어. Mapper Interface 금지. 파일명 1:1 매칭.**

| 파일 | 경로 | 비고 |
|---|---|---|
| `MetaController.java` | `controller/` | `/api/meta/*` |
| `MetaService.java` | `service/` | `extends BaseService` |
| `meta.xml` | `resources/mapper/` | `namespace="meta"` (소문자, Mapper 안 붙임) |

**MetaController.java:**

```java
@RestController
@RequestMapping("/api/meta")
@Slf4j
public class MetaController {

    @Autowired
    private MetaService metaService;

    @GetMapping("/tables")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMetaTableList(
            @RequestAttribute("companyCode") String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        return ResponseEntity.ok(ApiResponse.success(metaService.getMetaTableList(params)));
    }

    @GetMapping("/tables/{tableName}/fields")
    public ResponseEntity<ApiResponse<Map<String, Object>>> getMetaFields(
            @PathVariable String tableName,
            @RequestAttribute("companyCode") String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("table_name", tableName);
        params.put("company_code", companyCode);
        return ResponseEntity.ok(ApiResponse.success(metaService.getMetaFields(params)));
    }

    // ★ relations API는 Phase 1에서 안 만듦
    // table_relationships 기반 관계 조회는 Phase 5 (제어 모드)에서 구현
    // Phase 1은 getTableFields()의 FieldConfig.ref (entity 참조)까지만 책임
}
```

**MetaService.java:**

```java
@Service
@Slf4j
public class MetaService extends BaseService {

    @Autowired
    private CommonService commonService;

    /** 테이블 목록 */
    public List<Map<String, Object>> getMetaTableList(Map<String, Object> params) {
        return sqlSession.selectList("meta.getMetaTableList", params);
    }

    /** 특정 테이블의 FieldConfig[] 반환 */
    public Map<String, Object> getMetaFields(Map<String, Object> params) {
        List<Map<String, Object>> schemaCols = sqlSession.selectList("meta.getSchemaColumns", params);
        List<String> pks = sqlSession.selectList("meta.getPrimaryKeys", params);
        List<Map<String, Object>> customMeta = sqlSession.selectList("meta.getCustomMeta", params);
        // ★ FK는 DB 제약조건이 아닌 customMeta(table_type_columns)의 detail_settings에서 추출
        // input_type='entity'인 컬럼의 detail_settings JSON → referenceTable/referenceColumn/displayColumn

        List<Map<String, Object>> fields = buildFieldConfigs(schemaCols, pks, customMeta);

        Map<String, Object> result = new HashMap<>();
        result.put("table_name", params.get("table_name"));
        result.put("fields", fields);
        return result;
    }

    // ★ relations 조회는 Phase 5에서 table_relationships 기반으로 구현
    // Phase 1은 getMetaFields()의 FieldConfig.ref (entity 참조)까지만 책임

    /** 병합하여 FieldConfig Map 리스트 생성 (상세 로직은 Section 3.3 참고) */
    private List<Map<String, Object>> buildFieldConfigs(
        List<Map<String, Object>> schemaCols,
        List<String> pks,
        List<Map<String, Object>> customMeta
    ) {
        // column, label, type, visible, order, required, editable, pk, system 등
        // customMeta가 있으면 우선 적용
        // ★ input_type='entity'이면 detail_settings JSON 파싱 → ref Map 생성
        //   ObjectMapper로 detail_settings 파싱 → referenceTable, referenceColumn, displayColumn 추출
        // system 컬럼은 visible=false, system=true
    }
}
```

#### 3.4.3 MyBatis SQL — `meta.xml` (★ 덕일 스타일)

**★ 파일명: `meta.xml` (소문자, Mapper 안 붙임)**
**★ namespace: `meta`**
**★ SQL 키워드/테이블/컬럼: UPPER_SNAKE, #{파라미터}: snake_case**
**★ SELECT 쉼표 앞에, OGNL test 바깥 작은따옴표**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="meta">

  <!-- ═══ 테이블 목록 (public 스키마, 시스템 테이블 제외) ═══ -->
  <select id="getMetaTableList" parameterType="map" resultType="map">
    SELECT
        T.TABLE_NAME
        , (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS C
           WHERE C.TABLE_SCHEMA = 'public' AND C.TABLE_NAME = T.TABLE_NAME) AS COLUMN_COUNT
        , CASE WHEN EXISTS(
            SELECT 1 FROM TABLE_TYPE_COLUMNS TTC
            WHERE TTC.TABLE_NAME = T.TABLE_NAME
            <if test='company_code != null and company_code != "*"'>
              AND (TTC.COMPANY_CODE = #{company_code} OR TTC.COMPANY_CODE = '*')
            </if>
          ) THEN true ELSE false END AS HAS_CUSTOM_META
    FROM INFORMATION_SCHEMA.TABLES T
    WHERE T.TABLE_SCHEMA = 'public'
      AND T.TABLE_TYPE = 'BASE TABLE'
      AND T.TABLE_NAME NOT LIKE 'pg_%'
      AND T.TABLE_NAME NOT IN ('spatial_ref_sys')
    ORDER BY T.TABLE_NAME
  </select>

  <!-- ═══ 특정 테이블의 컬럼 정보 ═══ -->
  <select id="getSchemaColumns" parameterType="map" resultType="map">
    SELECT
        COLUMN_NAME
        , DATA_TYPE
        , IS_NULLABLE
        , COLUMN_DEFAULT
        , CHARACTER_MAXIMUM_LENGTH
        , ORDINAL_POSITION
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_SCHEMA = 'public'
      AND TABLE_NAME = #{table_name}
    ORDER BY ORDINAL_POSITION
  </select>

  <!-- ═══ PK 컬럼 목록 ═══ -->
  <select id="getPrimaryKeys" parameterType="map" resultType="string">
    SELECT KCU.COLUMN_NAME
    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
    JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU
        ON TC.CONSTRAINT_NAME = KCU.CONSTRAINT_NAME
        AND TC.TABLE_SCHEMA = KCU.TABLE_SCHEMA
    WHERE TC.CONSTRAINT_TYPE = 'PRIMARY KEY'
      AND TC.TABLE_SCHEMA = 'public'
      AND TC.TABLE_NAME = #{table_name}
  </select>

  <!-- ═══ 관계 조회 (★ DB FK 제약조건이 아닌 table_type_columns 메타 기반) ═══ -->
  <select id="getMetaRelations" parameterType="map" resultType="map">
    SELECT
        TABLE_NAME AS FROM_TABLE
        , COLUMN_NAME AS FROM_COLUMN
        , INPUT_TYPE
        , DETAIL_SETTINGS
    FROM TABLE_TYPE_COLUMNS
    WHERE TABLE_NAME = #{table_name}
      AND INPUT_TYPE IN ('entity', 'category')
      AND DETAIL_SETTINGS IS NOT NULL
    <if test='company_code != null and company_code != "*"'>
      AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
    </if>
  </select>
  <!--
    ★ detail_settings JSON에서 참조 정보 추출 (Java Service에서 파싱):
    {
      "referenceTable": "partner_info",
      "referenceColumn": "id",
      "displayColumn": "partner_name"
    }
    → FieldConfig.ref = { table, value_column, display_column } 로 변환
  -->

  <!-- ═══ TABLE_TYPE_COLUMNS 커스텀 메타 (회사 우선, '*' 폴백) ═══ -->
  <select id="getCustomMeta" parameterType="map" resultType="map">
    SELECT DISTINCT ON (COLUMN_NAME)
        COLUMN_NAME
        , INPUT_TYPE
        , DETAIL_SETTINGS
        , DISPLAY_ORDER
        , IS_NULLABLE
        , COMPANY_CODE
    FROM TABLE_TYPE_COLUMNS
    WHERE TABLE_NAME = #{table_name}
    <if test='company_code != null and company_code != "*"'>
      AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
    </if>
    ORDER BY COLUMN_NAME
        , CASE WHEN COMPANY_CODE = #{company_code} THEN 0 ELSE 1 END
  </select>

</mapper>
```

### 3.5 프론트엔드 API 클라이언트

**신규 파일: `frontend/lib/api/meta.ts`**

```typescript
import { client } from './client';

import type { FieldConfig } from '@/types/invyone-component';

/**
 * ★ 일반 API는 Record<string, any> — 별도 인터페이스 정의 안 함
 * ★ 단, FieldConfig만은 invyone-component.ts에 규격 확정된 예외 타입
 *   → getTableFields()의 fields 배열은 FieldConfig[]로 캐스팅
 */

/** 접근 가능한 테이블 목록 */
export async function getTableList(): Promise<Record<string, any>[]> {
  const res = await client.get('/api/meta/tables');
  return res.data.data;
}

/** 특정 테이블의 FieldConfig[] 반환 (★ fields만 FieldConfig[] 타입) */
export async function getTableFields(tableName: string): Promise<{
  table_name: string;
  fields: FieldConfig[];
  [key: string]: any;
}> {
  const res = await client.get(`/api/meta/tables/${tableName}/fields`);
  return res.data.data;
}

// ★ relations API는 Phase 5에서 구현 (table_relationships 기반)
// Phase 1은 getTableFields()의 FieldConfig.ref까지만 책임
```

---

## 4. 시스템 필드 판별 규칙

다음 컬럼명은 자동으로 `system: true`로 설정하고, `visible: false`, `editable: false`로 만든다:

```
company_code, created_by, created_date, updated_by, updated_date,
is_active, deleted_date, deleted_by, writer, write_date
```

---

## 5. 참고 파일

| 파일 | 용도 |
|---|---|
| `frontend/types/invyone-component.ts` | **FieldConfig 타입 정의 (진실의 원천)** — 이 파일의 인터페이스와 정확히 일치하는 JSON을 반환해야 함 |
| `notes/gbpark/2026-04-09-invyone-architecture.md` | 아키텍처 결정 문서 — 변환 규칙의 근거 |
| `notes/gbpark/2026-04-08-invyone-component-spec.md` | 컴포넌트 규격 — FieldType별 렌더링 계약 |
| `backend-spring/src/main/java/com/erp/service/TableManagementService.java` | 기존 테이블 관리 서비스 — 참고용 (패턴) |
| `backend-spring/src/main/resources/mapper/TableManagementMapper.xml` | 기존 MyBatis 매퍼 — SQL 패턴 참고 |
| `notes/gbpark/2026-04-08-invyone-mockup/js/08-admin-builder.js` | mockup의 테이블 메타 데이터 — `AB_TABLE_FIELDS` 객체가 FieldConfig의 mockup 버전 |

---

## 6. 완료 기준

1. **`GET /api/meta/tables`** 호출 시 DB의 public 스키마 테이블 목록이 반환된다
2. **`GET /api/meta/tables/{tableName}/fields`** 호출 시:
   - `FieldConfig[]` 형태의 JSON이 반환된다
   - `frontend/types/invyone-component.ts`의 `FieldConfig` 인터페이스와 필드명이 정확히 일치한다
   - PK 컬럼에 `pk: true`가 설정된다
   - system 컬럼(company_code 등)에 `system: true`, `visible: false`가 설정된다
   - `table_type_columns`에 커스텀 메타가 있으면 그것이 우선 적용된다
   - select 타입 필드에 `options[]`가 포함된다
   - entity 타입 필드에 `ref` 객체가 포함된다
3. ~~relations API는 Phase 1에서 안 만듦~~ (Phase 5에서 `table_relationships` 기반으로 구현)
4. 프론트엔드 `lib/api/meta.ts`에서 위 API들을 호출하는 함수가 있다
5. 실제 DB 테이블 (예: `order_management_test`, `user_info`)로 테스트하여 합리적인 FieldConfig가 생성됨을 확인

---

## 7. 다음 단계 연결

Phase 2는 이 API가 반환하는 `FieldConfig[]`을 받아서:
- **테이블 컴포넌트**: FieldConfig[]을 컬럼으로 렌더링
- **폼 컴포넌트**: FieldConfig[]을 입력 필드로 렌더링
- **검색 컴포넌트**: FieldConfig[]을 검색 조건으로 렌더링

Phase 2의 컴포넌트들은 `getTableFields(tableName)`을 호출하여 FieldConfig[]을 가져온 뒤 렌더링한다.
