Docs 시스템 구현
Kosmos DSL과 GenericCrudController를 기반으로 한 UNeedSoft의 Docs 시스템은 메뉴 트리(DocsNode), 본문(DocsContent), 변경 이력(DocsChangelog)을 통합하여 SSR(서버사이드 렌더링)으로 문서를 제공합니다.
목표: “하나의 시스템으로 개발자 문서, API 명세, 변경 이력까지” Kosmos DSL의 성능과 일관성을 활용하여 구현합니다.
전체 구성
Docs 시스템은 4개 도메인으로 구성됩니다:
DocsNode— 좌측 사이드 트리(메뉴 구조)DocsContent— 각 메뉴의 HTML/Markdown 본문DocsChangelog— 버전별 변경 이력DocsMain— 렌더링 통합 모델 (Node + Content + Changelog)
/docs
├─ DocsNodeController / DocsNodeService / DocsNodeRepository
├─ DocsContentController / DocsContentService / DocsContentRepository
├─ DocsChangelogController / DocsChangelogService / DocsChangelogRepository
├─ DocsMainController / DocsMainService
└─ ui/
├─ DocsMainPage.java
├─ DocsMainPageTemplate.java
├─ FragDocsMainNavTree.java
├─ FragDocsMainContent.java
├─ FragDocsMainChangelog.java
└─ FragDocsMainNotFound.java
핵심 모델
| 클래스 | 역할 | 주요 필드 |
|---|---|---|
DocsMainModel |
문서 렌더링 통합 모델 | pathInfo, renderType, sideNavTree, content, changelogs |
DocsNodeTreeDto |
트리 구조 메뉴 | id, title, path, parentId, children |
DocsContentRespDto |
본문 내용 | title, subtitle, content, version, modifiedAt |
DocsChangelogRespDto |
버전별 변경 이력 | version, title, content, releaseDate, tag |
데이터 로딩 흐름
[사용자 요청] /docs/kosmos/getting-started
└ DocsMainController.handle(path)
├ DocsNodeService.findTree() → 좌측 트리 (DocsNodeTreeDto)
├ DocsContentService.findByPath(path) → 본문 (DocsContentRespDto)
├ DocsChangelogService.findByNode(nodeId) → 변경 이력 (DocsChangelogRespDto)
└ PageTemplate.render(DocsMainPageTemplate) → HTML 완성
DocsMainController 예시
@Controller
@RequestMapping("/docs")
public class DocsMainController extends BaseController {
private final DocsMainService service;
public DocsMainController(DocsMainService service) {
this.service = service;
}
@GetMapping("/{path:^(?!admin|api).+$}")
public ResponseEntity<String> page(@PathVariable String path, RenderContext ctx) {
DocsMainModel model = service.loadDocsPage(path, ctx);
HtmlComponent page = new DocsMainPage(model);
return ResponseEntity.ok(page.render(ctx));
}
}
DocsMainService 예시
@Service
public class DocsMainService {
@Autowired private DocsNodeService nodeService;
@Autowired private DocsContentService contentService;
@Autowired private DocsChangelogService changelogService;
public DocsMainModel loadDocsPage(String path, RenderContext ctx) {
DocsNodeTreeDto tree = nodeService.buildTree();
DocsContentRespDto content = contentService.findByPath(path);
List<DocsChangelogRespDto> changelogs = changelogService.findAllByNode(content.getId());
DocsMainRenderType type = (content != null)
? DocsMainRenderType.CONTENT
: DocsMainRenderType.NOT_FOUND;
return new DocsMainModel(new DocsMainPathInfo(content.getId(), path, content.getTitle()), type, tree, content, changelogs);
}
}
DSL 페이지 구성
public class DocsMainPage extends AbstractDocsPageTemplate {
private final DocsMainModel model;
public DocsMainPage(DocsMainModel model) {
this.model = model;
}
@Override
protected HtmlComponent buildContent(RenderContext ctx) {
if (model.getRenderType() == DocsMainRenderType.CONTENT) {
return new FragDocsMainContent(model.getContent());
} else if (model.getRenderType() == DocsMainRenderType.CHANGELOG) {
return new FragDocsMainChangelog(model.getChangelogs());
} else {
return new FragDocsMainNotFound();
}
}
@Override
protected HtmlComponent buildSidebar(RenderContext ctx) {
return new FragDocsMainNavTree(model.getSideNavTree());
}
}
UI 프래그먼트 구조
- FragDocsMainNavTree: DocsNodeTreeDto 기반 재귀 트리 렌더링
- FragDocsMainContent: DocsContentRespDto의 HTML을 그대로 삽입
- FragDocsMainChangelog: DocsChangelogRespDto 목록 렌더링
- FragDocsMainNotFound: 문서 없음 메시지
// FragDocsMainContent (예시)
public class FragDocsMainContent implements HtmlComponent {
private final DocsContentRespDto content;
public FragDocsMainContent(DocsContentRespDto content) { this.content = content; }
@Override
public String render(RenderContext ctx) {
return El.div().css("docs-content container")
.child(El.h1().css("mb-3").text(content.getTitle()))
.child(El.h5().css("text-muted mb-4").text(content.getSubtitle()))
.child(El.raw(content.getContent())) // HTML 그대로 출력
.render(ctx);
}
}
변경 이력 렌더링
DocsChangelog는 badge 색상으로 태그 유형을 구분합니다:
| Tag | 색상 | 의미 |
|---|---|---|
| New | New | 신규 추가 |
| Update | Update | 기능 개선 |
| Fixed | Fixed | 버그 수정 |
주의사항
문서 캐싱: DB 캐싱 없이 대량 문서를 직접 조회하면 렌더링 지연이 발생할 수 있습니다.
HTML 보안: Tinymce에서 입력된 HTML은 XSS 필터링 또는 CSP(Content-Security-Policy) 설정을 병행해야 합니다.
버전 관리: DocsChangelog의
version은 semver(v1.0.0) 규칙을 유지하세요.테스트 시나리오
- 네비게이션: 부모/자식 노드 클릭 시 올바른 contentId 로드
- 본문 렌더링: Tinymce HTML이 올바르게 표시되는지 확인
- 변경 이력: badge/tag/날짜 정렬 확인
- Not Found: 유효하지 않은 경로 시 FragDocsMainNotFound 표시
다음으로
- DSL Architecture — Kosmos의 내부 구조 이해
- El.* Catalog — DSL 요소 모음집
- Changelogs — 전체 버전 이력 보기