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

- Name
- Hyo814
네임스페이스 옵션을 정적 JSON에서 DB 조회로 옮긴 이유
용어(Term) 등록 화면의 네임스페이스 셀렉트박스가 namespaces.json 정적 파일을 읽어 채워지고 있었습니다. 관리자가 DB에서 네임스페이스를 새로 추가했는데도 셀렉트박스에는 안 보이는 사용자 제보가 들어와, DB 직접 조회로 옮긴 작업입니다.
1. 시작 — "관리자가 추가한 네임스페이스가 안 보여요"
증상은 단순했습니다:
- 관리자가
/web-admin/namespaces/에서 새 네임스페이스geo를 추가 - 사용자(용어 등록자)가 용어 등록 화면에서 네임스페이스 셀렉트박스 열기
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으로 남기지 말기.
- 사용자가 모르는 동작 차이를 만드는 게 가장 위험. "추가했는데 안 보임"은 신뢰를 가장 빠르게 갉아먹는 패턴.
작은 수정이었지만 "왜 처음엔 정적이었고, 왜 옮겨야 했나" 를 들여다본 게 더 가치 있는 작업이었어요.