본문으로 건너뛰기
2026. 6. 30
© WONKOOK LEE

백엔드 도구들, FE로 치면 무엇일까요?

칸칸이 도구가 담긴 도구 상자

이 글은 직무 통합 과정에서 백엔드 실무를 맡게 된 프론트엔드 개발자가 실제 작업을 부딪히며 배운 것들을 주제 단위로 정리하는 시리즈입니다. 낯선 개념을 이미 아는 개념에 대응시키는 방식으로 씁니다.

  1. 도메인 모델은 왜 DB를 몰라야 할까요?
  2. DB 조회는 왜 아웃바운드일까요?
  3. 그 로직은 어디에 살아야 할까요?
  4. 이 API는 누가 부르나요?
  5. Response는 모델을 닮아야 할까요?
  6. null은 비우기일까요, 건드리지 않기일까요?
  7. 컬럼일까요, JSON 컬럼일까요?
  8. DB 스키마에도 git이 필요할까요?
  9. 백엔드 도구들, FE로 치면 무엇일까요? — 이 글

첫 PR을 올리기까지 코드보다 도구와 싸운 시간이 길었습니다. 커밋하면 처음 보는 린터가 막아서고, 빌드 파일은 어디를 고쳐야 하는지 모르겠고, 테스트를 돌리려니 Docker를 켜라고 합니다. 그런데 하나씩 정체를 알고 보니 전부 프론트엔드에서 매일 쓰던 것들의 다른 이름이었습니다 — 몇 개의 예외만 빼고. 그 예외가 오히려 백엔드라는 직무의 정체를 알려줬습니다.

백엔드의 도구들은 FE의 무엇에 대응하고, 대응물이 없는 도구는 왜 없을까요?


이 글에서 다루는 내용

시리즈의 마지막 글입니다. 지금까지의 글이 개념의 대응이었다면 이번에는 도구의 대응입니다 — 코드 품질(ktlint·detekt), 빌드와 구조(Gradle 멀티모듈, 템플릿 레포, 도메인별 레포 분리), 테스트(Testcontainers)를 FE 대응물로 정리하고, 대응물이 없는 도구들이 왜 없는지를 짚습니다. 끝에는 시리즈 전체의 FE ↔ BE 대응표를 한 장으로 모았습니다.





1. 코드 품질 — ktlint와 detekt

커밋을 막아섰던 린터의 정체는 두 가지였습니다.

도구하는 일FE 대응물
ktlint포맷 검사와 자동 수정Prettier
detekt코드 스멜·복잡도 정적 분석 (긴 메서드, 복잡한 조건식 등)ESLint

역할 분담까지 똑같아서 설명이 거의 필요 없었습니다. 흥미로웠던 것은 baseline이라는 장치입니다. detekt를 기존 코드베이스에 도입하면 위반이 수백 개씩 나오는데, 그걸 다 고치는 대신 현재 위반 목록을 스냅샷으로 얼려두고 새로운 위반만 막습니다. 레거시 코드 곳곳의 eslint-disable 주석을 파일 하나에 모아둔 것과 같은 개념입니다.

여기서 한 가지 데인 것 — baseline 항목은 코드 위치가 아니라 코드 내용을 서명으로 기억합니다. 그래서 얼려둔 코드를 조금만 고쳐도 서명이 어긋나 "새 위반"으로 살아납니다. 처음엔 당황했는데 곱씹으니 설계 의도였습니다. 그 코드를 건드린 사람이 그 빚도 갚아라. 다만 갚는 방법은 구분해야 합니다 — 남의 빚이 깨어난 것이면 baseline을 갱신할 수도 있지만, 내가 새로 만든 위반은 baseline에 얼리는 게 아니라 리팩터링으로 없애는 것이 맞습니다.


Recap

ktlint는 Prettier, detekt는 ESLint에 대응하고, baseline은 레거시 위반을 얼려두는 eslint-disable 모음입니다. baseline은 코드 내용을 서명으로 기억하므로 건드리면 빚이 깨어나며, 내가 새로 만든 위반은 얼리는 게 아니라 갚는 것이 맞습니다.




2. 빌드와 구조 — Gradle, 템플릿, 레포 경계

구조를 만드는 도구들은 이 시리즈에서 이미 여러 번 등장했습니다. 이름표만 정리하면 이렇게 됩니다.

도구/관습하는 일FE 대응물
Gradle 멀티모듈 (settings.gradle)모듈 선언·의존 방향·빌드 오케스트레이션pnpm workspace + Turborepo
스켈레톤 템플릿 레포새 레포의 출발점 — 모든 레포가 같은 구조를 따름create-app 계열 보일러플레이트
도메인별 레포 분리도메인 = 레포. 담당과 맥락의 분리모노레포 패키지 경계, 마이크로프론트엔드

settings.gradle이 모듈들을 선언하고 모듈 간 의존이 빌드 설정에 명시된다는 것 — 1편2편에서 본 "빌드가 강제하는 의존 방향"의 물리적 실체가 이 파일들입니다. workspace 루트의 pnpm-workspace.yaml을 처음 열어봤을 때의 감각으로 읽으면 정확히 맞습니다.

템플릿 레포는 규모의 산물입니다. 도메인마다 레포가 늘어나는 구조에서는 "새 레포를 어떻게 시작하는가"가 반복 문제가 되고, 그 답이 모두가 따르는 출발 템플릿입니다. 모든 레포가 같은 구조라는 것은 처음 온 사람에게 큰 선물이기도 합니다 — 하나를 익히면 열을 읽을 수 있습니다.


Recap

Gradle 멀티모듈은 pnpm workspace + Turborepo, 템플릿 레포는 create-app 보일러플레이트, 도메인별 레포 분리는 모노레포 패키지 경계·마이크로프론트엔드에 대응합니다. 시리즈 내내 말한 "빌드가 강제하는 경계"의 물리적 실체가 이 빌드 설정 파일들입니다.




3. 테스트 — 진짜 DB를 띄우는 Testcontainers

테스트를 돌리려니 Docker를 켜라고 했던 이유가 이것입니다. Testcontainers는 테스트가 시작될 때 진짜 MySQL 같은 것을 Docker 컨테이너로 띄우고 끝나면 걷어 갑니다. 인메모리 가짜 DB로는 못 잡는 것들 — 실제 SQL 방언, 제약 조건, 트랜잭션 동작 — 을 실물로 검증하는 것입니다.

프론트엔드 대응물은 정확합니다 — jsdom으로 브라우저를 흉내 내는 대신 Playwright로 진짜 브라우저를 띄우는 통합 테스트. "모킹의 편함과 실물의 정직함" 사이에서 실물 쪽에 선 선택이라는 점, 대신 로컬 환경 요구사항(Docker)이 생긴다는 비용까지 같은 구조입니다.

1편에서 헥사고날 구조의 일상적 이점이 테스트 가능성이라고 했는데, 그 문장과 이 도구는 역할이 나뉩니다 — 코어의 업무 규칙은 DB 없이 빠르게 검증하고(구조가 주는 것), 어댑터와 SQL은 Testcontainers로 실물 검증합니다(도구가 주는 것). 순수한 안쪽은 가볍게, 바깥 경계는 정직하게.


Recap

Testcontainers는 테스트마다 진짜 DB를 Docker로 띄우는 도구로, jsdom 대신 Playwright로 진짜 브라우저를 띄우는 선택에 대응합니다. 코어는 구조 덕분에 DB 없이 검증하고 어댑터는 이 도구로 실물 검증하는 — 안쪽은 가볍게 바깥은 정직하게 — 역할 분담이 됩니다.




4. 대응물이 없는 도구들

정직하게 대응물이 없는 칸이 두 개 있습니다.

  • 마이그레이션 도구 (Liquibase 등)8편에서 본 대로, 프론트엔드에는 "살아남아서 움직여야 하는 저장된 데이터"가 없기 때문입니다. (IndexedDB의 버전·업그레이드 콜백이 예외적인 친척이지만, 기기 하나의 데이터를 다룬다는 점에서 무게가 다릅니다.)
  • JPA / Entity (ORM) — 객체와 테이블 사이의 매핑 계층입니다. 굳이 찾으면 API 응답 정규화 계층이 비슷한 자리에 있지만, 영속성이라는 상대가 없으니 본질적으로는 대응물이 없다고 보는 게 정확합니다.

돌아보면 이 "없음"이 백엔드라는 직무의 정체를 가장 잘 설명해 줍니다. 온보딩 내내 새로 배운 것 대부분은 사실 이미 아는 개념의 다른 이름이었습니다 — 낯선 것은 개념이 아니라 어휘였습니다. 그리고 진짜로 새로웠던 소수는 전부 한 지점으로 수렴했습니다. 살아 있는 데이터. 데이터가 남고, 움직여야 하고, 그 이력에 답해야 한다는 것. 프론트엔드와 백엔드를 가르는 선은 언어도 프레임워크도 아니고 그 책임이었습니다.


Recap

마이그레이션 도구와 ORM은 FE 대응물이 없고, 그 이유는 하나로 수렴합니다 — 프론트엔드에는 살아남아 움직여야 하는 데이터가 없습니다. 온보딩에서 낯선 것 대부분은 어휘였고, 진짜 새로운 것은 살아 있는 데이터에 대한 책임이었습니다.




시리즈 한 장 요약 — FE ↔ BE 대응표

아홉 편에 걸쳐 쌓아온 대응표의 전체판입니다.

BE 개념FE에서 가장 가까운 것대응의 핵심다룬 글
도메인 모델React 코어 / 헤드리스 로직순수한 중심, 바깥을 모름1편
어댑터렌더러 (react-dom 등)갈아끼울 수 있는 바깥 부품1편
포트 (인터페이스)스토리지 인터페이스 계약계약은 안쪽이 소유한다1편
인바운드 어댑터이벤트 리스너세상이 나를 부른다2편
아웃바운드 어댑터fetch / API 클라이언트내가 세상을 부른다2편
의존성 규칙모노레포 import 방향덜 바뀌는 쪽으로만 의존2편
의존성 역전 (DIP)props 콜백안쪽이 선언, 바깥이 구현2편
도메인 서비스순수 도메인 util프레임워크를 모르는 업무 규칙3편
애플리케이션 서비스오케스트레이션 훅조율만, 로직은 위임3편
인가의 자리라우트 가드모델 밖, 흐름의 관문에서3편
인터널 APIBFF의 서버 간 조립클라 대신 서버가 합친다4편
모델 ↔ Response 정합리소스 REST vs 뷰모델 논쟁같은 축의 서버판5편
DTO 레이어링응답 타입·뷰모델·폼 상태 어댑터경계마다 자기 표현5편
필드 마스크 / permit폼의 dirty fields의도를 값이 아니라 목록에6편
컬럼 vs JSON 컬럼atom 쪼개기 vs 상태 객체그 단위로 일하는 쪽이 누구인가7편
마이그레이션(대응물 없음)살아 있는 데이터를 옮기는 일8편
ktlint / detektPrettier / ESLintbaseline은 얼려둔 빚이 글
Gradle 멀티모듈pnpm workspace + Turborepo빌드가 강제하는 경계이 글
TestcontainersPlaywright 통합 테스트흉내 대신 실물이 글

긴 시리즈를 함께 읽어주셔서 감사합니다. 언젠가 반대 방향의 온보딩 — 백엔드 개발자를 위한 프론트엔드 개념 지도 — 을 쓰는 분이 있다면, 이 표가 거울처럼 쓰일 수 있기를 바랍니다.




References

도구 공식 문서

  1. ktlint — An anti-bikeshedding Kotlin linter
  2. detekt — Static code analysis for Kotlin
  3. Gradle — Structuring Projects with Gradle (multi-project builds)
  4. Testcontainers


좋은 사람들과 재미있는 일을 하며 열정적이고 즐겁게 살고 싶은 개발자