Published on

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

Authors
  • avatar
    Name
    Hyo814
    Twitter

네임스페이스 옵션을 정적 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. 회고 — "거의 안 바뀜"의 진짜 의미

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

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

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

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

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

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

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

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

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


6. 교훈 정리

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

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