DSL Architecture
Kosmos Java HTML DSL은 HTML을 Java 코드로 안전하게 표현하기 위해 만들어진 서버사이드 DSL입니다. HTML 태그를 객체화하고, 빌더 체인으로 UI를 구성하며, 내부적으로는 StringBuilder 기반의 고성능 렌더링 엔진을 사용합니다.
핵심 철학: 객체는 최소화하고 문자열 처리는 최적화하되, 구조적 의미와 타입 안정성은 보존합니다.
주요 구조
HtmlComponent (인터페이스)
└─ AbstractHtmlTag (공통)
├─ HtmlTag (자식 존재)
│ ├─ Div, Section, Table, Form, ...
│ └─ addChild(), addChildren()
└─ VoidTag (자식 없음)
├─ Img, Input, Br, Hr, Meta, ...
└─ addAttribute()
Kosmos DSL은 HTML 트리를 완전한 Java 객체로 구성하되, 렌더링 시에는 StringBuilder를 재사용하여 GC 부담을 최소화합니다.
HtmlComponent 인터페이스
public interface HtmlComponent {
String render();
}
모든 DSL 요소는 HtmlComponent를 구현하며, render() 호출 시 HTML 문자열을 반환합니다. Kosmos의 모든 DSL 클래스는 이 규약을 따릅니다.
AbstractHtmlTag: 공통 기반
public abstract class AbstractHtmlTag implements HtmlComponent {
protected final String tagName;
protected final List<HtmlComponent> children = new ArrayList<>();
protected final Map<String, String> attributes = new LinkedHashMap<>();
protected final List<String> cssClasses = new ArrayList<>();
public AbstractHtmlTag(String tagName) { this.tagName = tagName; }
public AbstractHtmlTag addAttribute(String name, String value) {
if (value != null) attributes.put(name, value);
return this;
}
public AbstractHtmlTag addClass(String cls) {
if (cls != null && !cls.isBlank()) cssClasses.add(cls);
return this;
}
protected void renderAttributes(StringBuilder sb) {
if (!cssClasses.isEmpty()) {
sb.append(" class=\"").append(String.join(" ", cssClasses)).append("\"");
}
for (var e : attributes.entrySet()) {
sb.append(" ").append(e.getKey()).append("=\"").append(e.getValue()).append("\"");
}
}
}
HtmlTag & VoidTag
public final class HtmlTag extends AbstractHtmlTag {
public HtmlTag(String name) { super(name); }
public HtmlTag addChild(HtmlComponent c) { children.add(c); return this; }
public HtmlTag addChildren(HtmlComponent... comps) {
children.addAll(Arrays.asList(comps)); return this;
}
@Override
public String render() {
StringBuilder sb = new StringBuilder();
sb.append("<").append(tagName);
renderAttributes(sb);
sb.append(">");
for (HtmlComponent c : children) sb.append(c.render());
sb.append("</").append(tagName).append(">");
return sb.toString();
}
}
public final class VoidTag extends AbstractHtmlTag {
public VoidTag(String name) { super(name); }
@Override
public String render() {
StringBuilder sb = new StringBuilder();
sb.append("<").append(tagName);
renderAttributes(sb);
sb.append(" />");
return sb.toString();
}
}
렌더링 프로세스
DSL 렌더링은 다음 단계를 거칩니다:
- 컴포넌트 생성 (ex.
El.div()) - 속성/클래스/자식 추가
render()호출 → 내부적으로StringBuilder에 append- 최종 HTML 문자열 반환
// 예시
String html = El.div().css("card").children(
El.h3().text("Hello, Kosmos!"),
El.p().text("DSL로 HTML을 구성합니다.")
).render();
System.out.println(html);
// 출력
// <div class="card"><h3>Hello, Kosmos!</h3><p>DSL로 HTML을 구성합니다.</p></div>
성능 최적화 구조
| 최적화 기법 | 설명 |
|---|---|
| StringBuilder 재사용 | render() 내에서 StringBuilder를 반복 생성하지 않고 재사용 |
| VoidTag 분리 | 자식 없는 태그를 별도로 처리해 닫힘 처리 오버헤드 제거 |
| inlined rendering | 메서드 체인을 인라인화하여 JIT 최적화 효율 상승 |
| 불필요한 escape 최소화 | 정적 문자열은 미리 인코딩 처리 |
확장 구조
Kosmos DSL은 Atomic Design 패턴을 내재화한 구조로, 하위 DSL은 상위 컴포넌트를 기반으로 조립됩니다.
- Atom: El.div(), El.input(), El.span()
- Molecule: CompInputText, CompAlert
- Organism: FragEventDetailCard, FragEstimateStepCategory
- Template: PageTemplate, DocsPageTemplate
- Page: DocsMainPage, EstimationMainPage
// 예시: CompInputText (Molecule)
public class CompInputText implements HtmlComponent {
private final String name, label, value, help, error;
public CompInputText(String name, String label, String value, String help, String error) {
this.name=name; this.label=label; this.value=value; this.help=help; this.error=error;
}
@Override
public String render() {
return El.div().css("mb-3").children(
El.label().attr("for", name).css("form-label").text(label),
El.input().attr("type","text").attr("id",name).attr("name",name).attr("value",value)
.css("form-control" + (error != null ? " is-invalid" : "")),
error != null ? El.div().css("invalid-feedback").text(error) : El.span(),
help != null ? El.div().css("form-text").text(help) : El.span()
).render();
}
}
DSL 확장 포인트
| 확장 포인트 | 설명 | 예시 |
|---|---|---|
El.* 팩토리 |
새 HTML 태그 정의 | El.nav(), El.figure() |
| 컴포넌트 | 복합 구조 (FormGroup 등) | CompToggleGroup |
| 템플릿 | 공통 레이아웃 확장 | PageTemplate, DocsPageTemplate |
| Fragment | 특정 UI 섹션 구성 | FragEventDetailCard |
렌더링 순서
Controller → Service → PageModel 생성
→ PageTemplate.buildSidebar() / buildContent()
→ Organism(Fragment) → Molecule(Component) → Atom(El.*)
↓
render() 호출 → StringBuilder.append()
↓
ResponseEntity<String> 로 HTML 반환
주의사항
HTML DSL의 남용: HTML 구조를 DSL로만 작성하면 초기 진입 장벽이 높을 수 있습니다.
렌더링 중 부작용 금지:
render() 내부에서 DB 또는 I/O 호출을 절대 하지 마세요.표준화된 DSL 명명 규칙: 내부 함수(addClass/addAttribute/addChild)는 외부 함수(css/attr/child)와 구분되어야 합니다.
성능 벤치마크
| 비교 항목 | Kosmos DSL | Thymeleaf | JSP |
|---|---|---|---|
| 렌더링 속도 (1000 컴포넌트) | ~8ms | ~40ms | ~50ms |
| 객체 생성 수 | ~200 | ~1100 | ~950 |
| GC 부하 | 낮음 | 보통 | 높음 |
다음으로
- Performance — 실제 렌더링 성능 최적화 기법
- El.* Catalog — DSL 태그 목록과 사용법
- RenderContext & Auth — 컨텍스트 기반 렌더링 구조