발행일

네임스페이스 옵션을 정적 JSON에서 DB 조회로 옮긴 이유

네임스페이스 옵션을 정적 JSON에서 DB 조회로 옮긴 이유

용어(Term) 등록 화면의 네임스페이스 셀렉트박스가 namespaces.json 정적 파일을 읽어 채워지고 있었습니다. 관리자가 DB에서 네임스페이스를 새로 추가했는데도 셀렉트박스에는 안 보이는 사용자 제보가 들어와, DB 직접 조회로 옮긴 작업입니다.


1. 시작 — "관리자가 추가한 네임스페이스가 안 보여요"

증상은 단순했습니다:

  1. 관리자가 /web-admin/namespaces/ 에서 새 네임스페이스 geo를 추가
  2. 사용자(용어 등록자)가 용어 등록 화면에서 네임스페이스 셀렉트박스 열기
  3. geo가 옵션에 없음

새 네임스페이스는 DB에 정상적으로 들어가 있었고, 다른 곳(예: 관리자 화면의 네임스페이스 리스트)에서는 잘 보였습니다. 용어 등록 화면만 안 보였어요.

원인을 따라가니 static/data/namespaces.json 이라는 정적 파일이 셀렉트박스의 옵션 원본이었습니다.

[
  { "prefix": "rdf", "uri": "http://www.w3.org/1999/02/22-rdf-syntax-ns#" },
  { "prefix": "rdfs", "uri": "http://www.w3.org/2000/01/rdf-schema#" },
  { "prefix": "dc", "uri": "http://purl.org/dc/elements/1.1/" }
]

2. 왜 처음에는 JSON이었을까

git log를 거슬러 올라가 보니, 이 파일은 mockup으로 시작된 흔적이 있었습니다. 백엔드 모델이 아직 없을 때 "일단 화면에 보여줄 수 있어야 하니까" 프론트에서 정적 데이터로 둔 거예요.

문제는 백엔드 모델이 추가된 후에도 프론트 쪽 셀렉트박스가 그 정적 파일을 계속 보고 있었다는 점이었습니다. 모델은 만들어졌고, 관리자 화면도 만들어졌지만, "사용자 입력 화면의 셀렉트박스를 DB로 옮기는 작업"이 누락돼 있었어요.


3. "거의 안 바뀔 것"이라는 가정의 함정

처음 JSON으로 둔 사람의 가정은 합리적이었습니다. RDF/RDFS/DC 같은 표준 네임스페이스는 정말 거의 안 바뀌니까요. 그런데:

  • 표준 네임스페이스 외에 사내 커스텀 네임스페이스가 운영 도중 추가됨
  • 도메인이 확장되면서 (예: 위치/지리 데이터) 새 네임스페이스 필요성이 늘어남
  • "한 번에 다 정의하고 그 다음엔 안 바꿀 것"이라는 가정이 도메인 성장 속도와 맞지 않았음

운영을 시작하고 약 1년이 지난 시점에서 보면, 네임스페이스는 거의 매달 한두 개씩 추가되고 있었어요. "거의 안 바뀜" 의 정의가 빗나간 거죠.


4. 변경 — Namespace DB에서 직접 조회

수정은 단순합니다. 셀렉트박스 옵션을 채우는 함수만 갈아끼우면 돼요.

4.1 백엔드 view 보강

용어 등록 화면의 컨텍스트에 네임스페이스 쿼리셋을 추가했습니다.

# web_admin/views/dataset/term.py
class AdminTermListView(TemplateView):
    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['namespaces'] = (
            Namespace.objects.filter(is_active=True)
            .order_by('prefix')
            .values('id', 'prefix', 'uri')
        )
        return ctx

is_active 필터를 둔 이유는, 관리자가 "이전 네임스페이스를 더 이상 안 쓰지만 기존 용어 호환을 위해 모델은 남겨둘 때" 가 있어서입니다. 그런 건 옵션에서 빼야 했어요.

4.2 템플릿/JS에서 옵션 채우기

기존에는 JS가 namespaces.json을 fetch해서 셀렉트박스를 채웠는데, 이걸 컨텍스트로 받은 데이터를 인라인으로 렌더하는 식으로 바꿨습니다.

<select id="namespace-select" name="namespace_id">
  <option value="">선택</option>
  {% for ns in namespaces %}
    <option value="{{ ns.id }}">{{ ns.prefix }} ({{ ns.uri }})</option>
  {% endfor %}
</select>

JS는 한 번 더 fetch할 필요가 없으니 더 단순해졌고, 화면 로드 시점에 옵션이 이미 채워져 있어서 "옵션 잠깐 비었다가 채워지는" 깜빡임도 사라졌어요.

4.3 namespaces.json 자체는 어떻게?

완전히 제거했습니다. fallback으로 두지 않은 이유:

  • DB 조회 실패 시 fallback이 발동하면, 사용자가 "안 보이는데 왜 안 보이는지 모르는" 상태가 됨. 차라리 에러 메시지를 보여주는 게 명확.
  • 두 데이터 소스가 공존하면 "어느 게 진짜야?" 의 문제가 다시 생김

남겨두기보다 들어내는 게 안전했어요.


5. 검증

항목결과
관리자가 추가한 네임스페이스용어 등록 화면 셀렉트박스에 노출
비활성(is_active=False)옵션에서 제외
정렬prefix 오름차순
깜빡임화면 로드 시점에 옵션이 이미 채워짐 (fetch 대기 없음)
namespaces.json파일 제거, 참조 코드 잔재 0건

6. 남은 구멍 두 개

이 수정으로 원래 증상은 사라졌지만, 지금 다시 보니 같은 계열의 문제가 두 개 남아 있습니다.

① 옵션이 페이지 로드 시점에 고정됩니다.

인라인 렌더라 깜빡임은 없어졌는데, 대신 화면을 열어둔 채로 관리자가 네임스페이스를 추가하면 여전히 안 보입니다. 새로고침해야 나와요.

원래 문제가 "추가했는데 영영 안 보임" 이었다면 지금은 "추가했는데 새로고침 전엔 안 보임" 입니다. 심각도는 크게 줄었지만 같은 종류의 문제가 약하게 남아 있는 셈이에요. 용어 등록 화면을 오래 열어두고 작업하는 사용 패턴이라면 이것도 제보로 올라올 수 있습니다.

② 비활성 네임스페이스를 쓰던 기존 용어를 편집하면?

is_active=True만 옵션에 넣기로 한 건 맞는 결정이었는데, 여기서 한 가지를 확인했어야 했습니다.

이미 geo(현재 비활성)로 등록된 용어를 편집 화면에서 열면, 셀렉트박스에 그 값이 없습니다.

옵션에 없는 값은 선택 상태로 표시될 수 없으니, 사용자가 네임스페이스를 건드리지 않고 저장해도 다른 값으로 바뀌거나 비워질 수 있습니다. 4.1절에서 is_active 필터를 둔 이유로 "기존 용어 호환을 위해 모델은 남겨둘 때" 를 들었는데, 정작 그 기존 용어를 편집하는 경로는 안 본 거예요.

이건 비동기 피커에서 prefill 값이 소실되던 문제와 정확히 같은 계열입니다. 그쪽은 조회가 늦어서 값이 비었고, 이쪽은 옵션 목록에서 제외돼서 값이 비어요. 둘 다 화면이 표현할 수 없는 값은 저장할 때 사라진다는 같은 함정입니다.

해결도 같은 모양이 됩니다 — 목록을 is_active=True로 두되, 현재 값은 비활성이라도 옵션에 포함하는 것. "신규 선택 후보"와 "현재 값"은 다른 집합이니까요.


7. 회고 — "거의 안 바뀜"의 진짜 의미

처음 정적 JSON으로 둔 사람을 비난할 의도는 없습니다. 그 시점에서 모델이 없는 채로 일단 굴려야 했고, 정적 파일은 합리적인 선택이었어요. 다만 다음 번에는 다르게 갈 만한 룰이 보였습니다.

7.1 "거의 안 바뀜" 가정의 만료일

정적으로 두는 데이터에는 "언제까지 정적인가?" 의 만료일을 적어두면 좋습니다.

[
  { "prefix": "rdf", "uri": "..." }
]
// TODO: 2026Q2 이전에 Namespace DB로 이전 (백엔드 모델 추가 후)

이런 주석 한 줄이 있었다면 누락이 발생하지 않았을 거예요.

7.2 정적/동적 두 곳에 두지 않기

만약 mockup 단계에서 JSON으로 두었다면, 백엔드 모델이 추가되는 PR에서 JSON을 같이 제거하는 게 안전합니다. 두 곳에 데이터가 있는 상태가 길어지면 진짜 데이터가 어디인지 흐려져요.

7.3 셀렉트박스 옵션이 정적인지 동적인지 한눈에 알 수 있게

마크업/JS에서 "이 옵션은 어디서 왔는가" 를 한눈에 알 수 있는 패턴을 두는 것도 도움이 됐습니다. 예를 들어 인라인으로 렌더된 옵션과 fetch로 채워진 옵션은 코드 상 명백히 달라야 해요. 우리 코드는 그 경계가 흐려서 *"이게 정적 파일을 보고 있다"*는 사실이 한참 뒤에 발견됐습니다.


8. 교훈 정리

  • "거의 안 바뀜" 가정은 자주 틀린다. 정적 데이터로 시작하더라도 동적 전환 시점을 함께 기록해 두자.
  • mockup 데이터를 DB로 옮길 때, 옛 mockup 파일도 같이 제거. fallback으로 남기지 말기.
  • 사용자가 모르는 동작 차이를 만드는 게 가장 위험. "추가했는데 안 보임"은 신뢰를 가장 빠르게 갉아먹는 패턴.

작은 수정이었지만 "왜 처음엔 정적이었고, 왜 옮겨야 했나" 를 들여다본 게 더 가치 있는 작업이었어요.