Design Docs: 소프트웨어 개발의 필수 설계 문서


 " "First, solve the problem. Then, write the code." — John Johnson

 

물음표와 함께 있는 사람

들어가며

개발자들은 주로 코드를 작성하며 비즈니스 가치를 만들어내고, 현실 세계의 문제를 해결하며 일을 합니다.

문제를 해결하기 위해선 문제인식을 공유해야 하고 이때 작게는 바로 옆의 팀원이나 팀장님, 크게는 조직이나 회사 차원의 설득이 필요할 때가 있습니다.
내가 해결해야 한다고 생각했던 문제가 다른 사람은 동의하지 않을 때가 생각보다 자주 있으며 그럴때는 근거에 기반한 합리적인 설득 과정이 필요하게 됩니다. 이러한 문제를 해결하기 위해 Google, Amazon, Uber 등 세계적인 기술 기업들이 공통적으로 사용하고 있는 도구가 바로 Design Docs(설계 문서)입니다.

Design Docs는 단순한 기술 문서가 아닙니다. 이는 프로젝트의 성공과 실패를 가르는 핵심 도구이며, 개발팀의 규모가 커질수록 그 중요성은 더욱 증대됩니다.

다른 사람들을 설득하는 사람

Design Docs란 무엇인가?

정의와 핵심 개념


Design Docs(또는 Technical Spec, RFC)는 코드를 작성하기 전에 작성하는 경량화된 계획 문서입니다. 이 문서는 특정 문제를 어떻게 해결할 것인지를 기술하며, 팀의 피드백을 수집하고 합의를 도출하며 향후 참고할 수 있는 문서화를 제공합니다.

 

핵심 특징

- 비공식적이지만 구조화된 문서: 엄격한 형식보다는 문제 해결에 초점
- 협업 중심: 작성자 혼자만의 문서가 아닌 팀 전체의 지혜가 담긴 결과물
- 결정의 기록: 왜 이런 선택을 했는지에 대한 트레이드오프와 의사결정 과정을 문서화
- 미래를 위한 투자: 단기적 시간 투자로 장기적 개발 효율성 확보

역사적 배경과 진화

RFC의 기원 (1960년대)

Design Docs의 뿌리는 1960년대 IETF(Internet Engineering Task Force)에서 시작된 **RFC(Request For Comments)** 프로세스에서 찾을 수 있습니다. 초기 인터넷 개발자들은 복잡한 기술적 결정을 내리기 위해 공개적으로 아이디어를 제안하고 커뮤니티의 피드백을 받는 방식을 도입했습니다.

현대 기업으로의 전파

1990년대와 2000년대를 거치며 소프트웨어의 복잡성이 급격히 증가했습니다. 이에 따라 대형 기술 기업들은 각자의 방식으로 Design Docs 문화를 발전시켰습니다:

- Google: 2000년대 초부터 Design Docs 문화를 정착시켜 현재까지 핵심 개발 프로세스로 유지
- Amazon: Working Backwards 문서와 함께 기술 설계 검토 프로세스 도입
- Uber: 수십 명에서 수천 명으로 성장하면서 RFC 프로세스를 체계화하여 급속한 확장 지원

원격 근무 시대의 재조명

COVID-19 팬데믹 이후 원격 근무가 일반화되면서 Design Docs의 중요성이 더욱 부각되었습니다. 비동기 협업과 명확한 의사소통의 필요성이 증대되면서, 많은 기업들이 이 방법론을 적극적으로 도입하고 있습니다.

Design Docs의 핵심 구성 요소

1. 헤더 정보

```
제목: [RFC-001] 사용자 인증 시스템 마이크로서비스 전환
작성자: 김개발 (kihttp://m.dev@company.com)
검토자: 이아키텍트, 박시니어, 최리드
상태: Draft | Under Review | Approved | In Progress | Completed
최종 수정일: 2025-07-08
```

2. 개요 (Overview)

모든 팀원이 이해할 수 있는 고수준 요약입니다. 3문단 이내로 작성하며, 다음을 포함합니다:
- 해결하려는 문제
- 제안하는 솔루션의 핵심
- 예상되는 영향과 이익

3. 배경 (Background)

프로젝트가 필요한 이유와 현재 상황을 설명합니다:
- 비즈니스 요구사항
- 기술적 제약사항
- 기존 시스템의 한계점

4. 목표와 비목표 (Goals & Non-Goals)

명확한 범위 설정을 위해 무엇을 할 것인지, 무엇을 하지 않을 것인지를 구분합니다.
목표:
- 인증 서비스의 99.9% 가용성 확보
- 응답 시간 100ms 이하 달성
- 일일 100만 요청 처리 능력
비목표:
- 기존 사용자 데이터 마이그레이션 (별도 프로젝트)
- OAuth 2.0 외 다른 인증 방식 지원

5. 제안된 솔루션 (Proposed Solution)

기술적 세부사항을 포함한 구체적인 해결책을 제시합니다:
- 시스템 아키텍처 다이어그램
- API 설계
- 데이터베이스 스키마
- 보안 고려사항

6. 대안 고려사항 (Alternatives Considered)

왜 다른 방법이 아닌 이 방법을 선택했는지 설명합니다:
- 고려했던 다른 아키텍처
- 각 대안의 장단점 분석
- 비용-효과 분석

7. 구현 계획 (Implementation Plan)

프로젝트 실행을 위한 구체적인 로드맵입니다:
- 마일스톤별 작업 분할
- 타임라인과 담당자
- 위험 요소와 완화 방안

언제 Design Docs를 작성해야 할까?

작성이 필요한 경우

1. 1 엔지니어-월 이상의 프로젝트: 상당한 시간 투자가 필요한 모든 프로젝트
2. 크로스 팀 영향: 여러 팀이나 시스템에 영향을 미치는 변경사항
3. 아키텍처 변경: 기존 시스템의 구조적 변화가 필요한 경우
4. 새로운 기술 도입: 팀에서 처음 사용하는 기술이나 도구 적용 시
5. 외부 의존*: 서드파티 서비스나 라이브러리와의 복잡한 통합

실제 적용 예시

Git 이미지

Git 전환 방안

상태: Under review, 날짜: 2025/07/13, 담당: 개발팀 다메카솔 (damecasol@damecasol.com)

Objective

현재 프로젝트의 소스 코드 관리 시스템(SCM)으로 사용 중인 SVN(Subversion)은 중앙 집중식 버전 관리 시스템으로, 최신 개발 환경의 요구사항을 충족하기에는 여러 한계가 있습니다. 특히 분산형 버전 관리 시스템(DVCS)인 Git에 비해 협업 효율성, 브랜치/머지(Branch/Merge)의 유연성, 오프라인 작업의 용이성, 그리고 개발자 생산성 측면에서 개선이 필요합니다. 따라서 본 문서에서는 이러한 문제점들을 해결하고 개발 프로세스의 효율성을 높이기 위해 Git으로의 전환을 제안합니다.

Proposal

1. Git 도입
Git은 현재 소프트웨어 개발 업계에서 가장 널리 사용되는 분산형 버전 관리 시스템입니다. 각 개발자가 독립적인 저장소 복사본을 가지므로, 네트워크 연결 없이도 작업이 가능하며, 빠르고 유연한 브랜치 및 머지 기능을 제공하여 병렬 개발 및 기능별 개발에 최적화되어 있습니다.
향상된 협업: 각 개발자가 로컬 저장소에서 독립적으로 작업하고, 필요한 시점에 중앙 저장소와 동기화함으로써 충돌을 최소화하고 협업 효율성을 높일 수 있습니다.
유연한 브랜치 전략: Git의 가볍고 빠른 브랜치 기능은 다양한 개발 워크플로우(예: Git Flow, GitHub Flow)를 적용할 수 있게 하여, 안정적인 릴리즈 관리와 신속한 기능 개발을 가능하게 합니다.
성능: SVN에 비해 커밋, 브랜치 생성, 머지 등의 작업 속도가 훨씬 빠릅니다.

2. GitHub 또는 GitLab 도입
Git 저장소를 호스팅하고 팀 협업 기능을 제공하는 플랫폼으로 GitHub 또는 GitLab을 도입하는 것을 제안합니다. 이 플랫폼들은 코드 리뷰, 이슈 트래킹, CI/CD 연동 등 개발 생명주기 전반을 지원하는 강력한 기능들을 제공합니다.
코드 리뷰: 풀 리퀘스트(Pull Request) 또는 머지 리퀘스트(Merge Request) 기능을 통해 효율적인 코드 리뷰 프로세스를 구축할 수 있습니다.
이슈 트래킹 및 프로젝트 관리: 내장된 이슈 관리 시스템을 활용하여 개발 진행 상황을 투명하게 관리하고, 프로젝트 계획을 효율적으로 수립할 수 있습니다.
CI/CD 연동: Jenkins, CircleCI, GitLab CI/CD 등 다양한 CI/CD 도구와 연동하여 자동화된 테스트 및 배포 파이프라인을 구축할 수 있습니다.

3. 전환 전략 (선택)
SVN에서 Git으로의 전환은 여러 가지 방식으로 진행될 수 있습니다. 현재 프로젝트의 규모와 이력을 고려하여 가장 적합한 방식을 선택해야 합니다.
히스토리 전체 마이그레이션: SVN의 모든 커밋 히스토리를 Git으로 가져오는 방식입니다. 히스토리 보존이 중요할 때 적합합니다. git svn clone 등의 도구를 사용할 수 있습니다.
새로운 Git 저장소 시작: 기존 SVN 히스토리는 보존하고, 특정 시점부터 새로운 Git 저장소에서 작업을 시작하는 방식입니다. 빠르고 간단하지만, 이전 히스토리는 SVN에서만 확인할 수 있습니다.

Check Points

개발자 교육: Git은 SVN과 사용 방식이 다르므로, 개발자들이 Git에 익숙해질 수 있도록 충분한 교육과 연습 시간이 필요합니다.
기존 CI/CD 파이프라인 변경: SVN 기반으로 구축된 기존 CI/CD 파이프라인이 있다면 Git 저장소와 연동되도록 수정해야 합니다.
대규모 저장소 히스토리 마이그레이션: 프로젝트의 SVN 히스토리가 매우 크다면, Git으로의 마이그레이션에 시간이 오래 걸리거나 추가적인 설정이 필요할 수 있습니다.

Limits

초기 학습 곡선: Git의 강력한 기능만큼이나 처음 접하는 사용자에게는 학습 곡선이 존재할 수 있습니다. 이는 교육을 통해 완화해야 합니다.
전환 기간 동안의 혼란: 전환 과정에서 일시적으로 개발자들의 혼란이나 생산성 저하가 발생할 수 있습니다. 이를 최소화하기 위한 명확한 전환 계획과 지원이 필요합니다.

Alternatives

Mercurial (Hg): Git과 유사한 분산형 버전 관리 시스템이지만, Git만큼 보편적으로 사용되지는 않습니다.
Perforce (Helix Core): 대규모 프로젝트와 바이너리 파일 관리에 강점이 있는 중앙 집중식 시스템이지만, 라이센스 비용이 발생할 수 있습니다.
기존 SVN 유지: 현재 시스템을 유지하는 방안도 있지만, 앞서 언급된 문제점들(협업 효율성, 유연성 등)은 해결되지 않을 것입니다.

Reference

Git 공식 홈페이지: https://git-scm.com/
GitHub: https://github.com/
GitLab: https://about.gitlab.com/

 

주의 아이콘


작성 시 주의사항

DO: 해야 할 것들
- 간결하고 명확한 언어 사용
- 시각적 다이어그램 적극 활용
- 트레이드오프와 의사결정 근거 명시
- 구체적인 예시와 시나리오 제공
- 정기적인 업데이트와 버전 관리


DON'T: 하지 말아야 할 것들
- 과도하게 세부적인 구현 코드 포함
- 불확실한 추측이나 가정에 의존
- 일방적인 결정 강요
- 완벽한 문서 작성에 과도한 시간 투자
- 작성 후 방치하여 실제와 다른 내용 유지

조직 차원의 도입 전략

화살표와 오브젝트로 구성된 전략을 표현하는 이미지

점진적 도입 방법

1단계: 파일럿 프로젝트 (1-2개월)
- 소규모 팀에서 시범 적용
- 간단한 템플릿으로 시작
- 초기 저항 최소화에 집중

2단계: 프로세스 정립 (2-3개월)
- 파일럿 결과를 바탕으로 프로세스 개선
- 팀별 특성에 맞는 템플릿 커스터마이징
- 리뷰 프로세스와 승인 기준 수립

3단계: 전사 확산 (6개월+)
- 성공 사례 공유와 교육 프로그램 실시
- 도구와 인프라 개선
- 문화 정착을 위한 지속적 노력

마무리: Design Docs로 만드는 더 나은 개발 문화

Design Docs는 조직의 기술적 의사결정 능력을 향상시키고 개발팀의 협업 문화를 발전시키는 강력한 도구입니다.

성공적인 Design Docs 문화 정착의 핵심은 완벽한 문서를 만드는 것이 아니라, 문제를 함께 해결하고 지식을 공유하는 과정에 있습니다. 오늘부터 작은 프로젝트 하나에 Design Docs를 적용해보세요. 그 작은 시작이 여러분의 팀과 조직에 큰 변화를 가져올 것입니다.

📚 도메인 주도 개발 시작하기 - 최범균

도메인 주도 개발 시작하기 표지

📋 기본 정보

- 제목: 도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지
- 저자: 최범균
- 출판사: 한빛미디어
- 출판일: 2022년 3월 21일
- ISBN: 9791162245385
- 링크: 교보문고, 알라딘 , 예스24

⏱️ 독서 데이터

- 독서기간: 2025.06.20 - 2025.07.05
- 독서시간: 18H
- 평점: ⭐⭐⭐⭐⭐
- 한줄평: "DDD 입문자에게 최고의 가이드북, 이론과 실무의 완벽한 조화"

 

🎯 독서 목표

- 배경지식: Spring Boot, JPA 기반 웹 개발 경험, 계층형 아키텍처에 대한 이해
- 해결과제: 복잡한 비즈니스 로직을 계층형 아키텍처가 아닌 다른 방법으로 효과적으로 설계하고 구현하는 방법론 습득
- 활용계획: 현재 진행 중인 프로젝트에 DDD 패턴을 점진적으로 적용하여 프로젝트 리팩토링
- 기대효과: 도메인 중심의 설계를 통한 코드 품질 향상과 유지보수성 증대

💡 주요 인사이트

1. 도메인 모델 패턴의 핵심은 비즈니스 규칙을 코드로 구현하는 것
    - 단순한 데이터 저장소가 아닌 비즈니스 로직을 담는 객체로서의 도메인 모델
    - 엔티티와 밸류 타입의 명확한 구분을 통한 개념적 명확성 확보
    - 실제 적용 예시: 주문(Order) 엔티티에서 배송지 변경 가능 여부를 주문 상태에 따라 판단하는 로직을 도메인 모델 내부에 구현

 

2. 애그리거트를 통한 일관성 경계 설정
    - 연관된 엔티티와 밸류 객체들을 하나의 군으로 묶어 일관성을 관리
    - 애그리거트 루트를 통한 외부 접근 제어로 데이터 무결성 보장
    - 실제 적용 예시: 주문 애그리거트에서 주문항목(OrderLine) 추가/삭제를 주문(Order) 루트를 통해서만 가능하도록 설계

 

3. DIP(의존역전원칙)를 통한 인프라스트럭처 의존성 해결
    - 고수준 모듈이 저수준 모듈에 의존하지 않도록 하는 설계 원칙
    - 리포지터리 인터페이스를 도메인 영역에 정의하고 구현을 인프라 영역에 배치
    - 실제 적용 예시: OrderRepository 인터페이스를 도메인에 정의하고 JpaOrderRepository 구현체를 인프라 계층에 분리

 

📝 인상적인 구절

도메인에서 사용하는 용어를 코드에 반영하여, 코드만 읽어도 자연스럽게 이해하는 방향으로 가야한다.



업무에서 기획자와 개발자들이 서로 다른 용어를 사용하여 의사소통 비용이 증가하는 경험을 자주 하는데, 유비쿼터스 언어의 중요성을 강조하며, 개발자와 도메인 전문가 간의 소통 단절을 해결하는 DDD의 핵심 철학을 간결하게 표현하였습니다.

🔄 실무 적용 포인트

- 현재 업무에 적용할 수 있는 점: 기존 Service 계층에 흩어진 비즈니스 로직을 도메인 모델로 이동시켜 응집도 향상
- 앞으로 개선할 수 있는 영역: 애그리거트 단위로 트랜잭션 경계를 재설정하여 데이터 일관성 문제 해결
- 새롭게 시도해볼 아이디어: 도메인 이벤트를 활용한 마이크로서비스 간 느슨한 결합 구현

 

🏆 결론 및 추천

도메인 주도 개발은 저에겐 헥사고날 아키텍처나 클린 아키텍처와 유사하게 느껴졌었습니다.

요즘 관련 서적들을 쭈욱 읽어나가며 어떤 차이점이 있는지 조금씩 보이기 시작하지만, 모든 개발 서적의 근간이 되는 아이디어는 항상 유사한 것 같습니다.

"결합도를 낮추고, 응집도를 높이며 읽기 쉽고 유지보수 하기 쉬운 소프트웨어를 개발하여 적은 비용으로 비즈니스 가치를 만들어 내는 것"

 

이 책의 장점은 추상적인 개념 설명에 그치지 않고 Spring Boot와 JPA를 활용한 구체적인 구현 예제를 제공하여 개발자로서 코드를 읽으며 실제 상황에 어떻게 적용할 지 구체적인 예제를 확인할 수 있다는 점입니다. 또한 초보자가 놓치기 쉬운 함정들과 주의사항을 적절히 언급하여 시행착오를 줄여주었습니다.

DDD를 처음 접하는 개발자, 복잡한 도메인을 다루는 시니어 개발자, 그리고 아키텍처 설계에 관심 있는 모든 개발자에게 도움이 될 것 같습니다.

🔍 연관 도서

- [도메인 주도 설계 - 에릭 에반스]- DDD의 원류이자 바이블
- [도메인 주도 설계 핵심 - 반 버논] - DDD 핵심 개념의 간결한 정리
- [클린 아키텍처 - 로버트 C. 마틴] - 아키텍처 설계 원칙의 현대적 접근

HMAC 인증 방식은 해시 기반 메시지 인증 코드(HMAC)를 사용하여 메시지 무결성과 인증을 보장하는 보안 기술입니다.

목차

- 소개
- HMAC 인증의 기본 원리
- HMAC 인증 구현하기
- 보안 고려사항과 모범 사례
- HMAC vs 다른 인증 방식 비교
- 결론
- 참고 자료

소개

API 보안은 현대 웹 서비스에서 가장 중요한 요소 중 하나입니다. 다양한 인증 방식 중에서 HMAC(Hash-based Message Authentication Code) 인증 방식은 메시지의 무결성과 신원 확인을 동시에 보장하는 강력한 메커니즘을 제공합니다. 이 글에서는 HMAC 인증 방식의 원리부터 실제 구현까지 상세히 알아보겠습니다.

이 글에서 배울 내용:

  • HMAC 알고리즘의 작동 원리와 암호학적 기초
  • 다양한 프로그래밍 언어에서 HMAC 인증 구현 방법
  • HMAC 인증을 적용할 때의 보안 모범 사례

HMAC 인증의 기본 원리

HMAC이란 무엇인가?

HMAC(Hash-based Message Authentication Code)는 메시지 인증을 위한 암호화 기법으로, 비밀 키와 해시 함수를 조합하여 메시지의 무결성과 신원을 검증합니다. HMAC은 단순한 해시 함수와 달리 비밀 키를 사용하기 때문에, 중간자 공격(man-in-the-middle attack)이나 재전송 공격(replay attack)에 강한 보안성을 제공합니다.

wiki Hmac 이미지



HMAC 알고리즘은 다음과 같은 수식으로 표현됩니다:

HMAC(K,m) = H((K' ⊕ opad) || H((K' ⊕ ipad) || m))


여기서:

  • K는 비밀 키
  • m은 메시지
  • H는 해시 함수(SHA-256, SHA-512 등)
  • K'는 해시 함수의 블록 크기에 맞게 조정된 키
  • opad와 ipad는 내부 패딩과 외부 패딩 상수
  • ⊕는 XOR 연산, ||는 연결 연산자

java

// HMAC 계산의 기본 원리 (의사 코드)
public static byte[] hmac(byte[] key, byte[] message, MessageDigest hashFunction) throws Exception {
  int blockSize = 64; // SHA-256의 블록 크기
  
  // 키가 해시 함수의 블록 크기보다 크면 해싱하여 줄임
  if (key.length > blockSize) {
    key = hashFunction.digest(key);
  }
  
  // 키가 짧으면 패딩으로 블록 크기까지 늘림
  if (key.length < blockSize) {
    byte[] newKey = new byte[blockSize];
    System.arraycopy(key, 0, newKey, 0, key.length);
    key = newKey;
  }
  
  // 내부/외부 패딩 생성
  byte[] ipad = new byte[blockSize];
  byte[] opad = new byte[blockSize];
  
  for (int i = 0; i < blockSize; i++) {
    ipad[i] = 0x36;
    opad[i] = 0x5c;
  }
  
  // XOR 연산 및 해싱
  byte[] keyXorIpad = new byte[blockSize];
  byte[] keyXorOpad = new byte[blockSize];
  
  for (int i = 0; i < blockSize; i++) {
    keyXorIpad[i] = (byte) (key[i] ^ ipad[i]);
    keyXorOpad[i] = (byte) (key[i] ^ opad[i]);
  }
  
  // 내부 해싱
  hashFunction.reset();
  hashFunction.update(keyXorIpad);
  hashFunction.update(message);
  byte[] innerHash = hashFunction.digest();
  
  // 외부 해싱
  hashFunction.reset();
  hashFunction.update(keyXorOpad);
  hashFunction.update(innerHash);
  byte[] outerHash = hashFunction.digest();
  
  return outerHash;
}

 

HMAC 인증 프로세스

HMAC 기반 API 인증의 일반적인 흐름은 다음과 같습니다:

1. 클라이언트와 서버가 사전에 비밀 키를 공유합니다.
2. 클라이언트가 요청을 보낼 때:
   - 요청 데이터(URL, 메서드, 헤더, 본문 등)를 기반으로 메시지 문자열을 생성합니다.
   - 공유된 비밀 키를 사용하여 메시지의 HMAC을 계산합니다.
   - HMAC 서명을 요청 헤더에 포함시켜 서버로 전송합니다.
3. 서버가 요청을 받으면:
   - 동일한 방식으로 요청 데이터로부터 메시지 문자열을 생성합니다.
   - 동일한 비밀 키로 HMAC을 계산합니다.
   - 계산된 HMAC과 클라이언트가 보낸 서명을 비교합니다.
   - 일치하면 요청을 처리하고, 불일치하면 거부합니다.

HMAC 인증 절차 (출처 : https://cyberhoot.com/cybrary/hmac-authentication/)

💡 팁: 타임스탬프를 메시지에 포함시켜 재전송 공격을 방지할 수 있습니다. 서버는 일정 시간(보통 5-15분) 이상 지난 요청을 거부하는 방식으로 구현합니다.

HMAC 인증 구현하기

java

// 서버 측 HMAC 검증 필터 (Spring Framework)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.stream.Collectors;
import org.apache.commons.codec.binary.Hex;
import java.nio.charset.StandardCharsets;
import java.time.Instant;

public class HmacAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 
            throws ServletException, IOException {
        try {
            // 요청에서 인증 헤더 가져오기
            String apiKey = request.getHeader("X-API-Key");
            String hmacSignature = request.getHeader("X-HMAC-Signature");
            String timestamp = request.getHeader("X-Timestamp");
            
            if (apiKey == null || hmacSignature == null || timestamp == null) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("{\"error\":\"Missing authentication headers\"}");
                return;
            }
            
            // API 키로 클라이언트 비밀 키 조회 (실제로는 DB에서 조회)
            String secretKey = getClientSecretKey(apiKey);
            
            if (secretKey == null) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("{\"error\":\"Invalid API Key\"}");
                return;
            }
            
            // 현재 시간과 타임스탬프의 차이 확인 (재전송 공격 방지)
            long currentTime = Instant.now().getEpochSecond();
            long requestTime = Long.parseLong(timestamp);
            
            if (Math.abs(currentTime - requestTime) > 300) { // 5분 허용
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("{\"error\":\"Timestamp expired\"}");
                return;
            }
            
            // 서명할 메시지 생성
            String method = request.getMethod();
            String path = request.getRequestURI();
            
            // 요청 본문 읽기
            String body = request.getReader().lines().collect(Collectors.joining());
            
            String message = method + path + timestamp + body;
            
            // HMAC 계산
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKeySpec);
            
            byte[] hmacBytes = sha256Hmac.doFinal(message.getBytes(StandardCharsets.UTF_8));
            String calculatedSignature = Hex.encodeHexString(hmacBytes);
            
            // 서명 비교
            if (calculatedSignature.equals(hmacSignature)) {
                // 인증 성공, 요청 처리 계속
                filterChain.doFilter(request, response);
            } else {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("{\"error\":\"Invalid signature\"}");
            }
        } catch (Exception e) {
            logger.error("HMAC 인증 중 오류 발생", e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.getWriter().write("{\"error\":\"Authentication error\"}");
        }
    }
    
    private String getClientSecretKey(String apiKey) {
        // 실제 구현에서는 DB나 캐시에서 API 키에 해당하는 비밀 키를 조회
        // 여기서는 예시로 하드코딩
        if ("test-api-key".equals(apiKey)) {
            return "test-secret-key";
        }
        return null;
    }
}

 

클라이언트 측 구현 (JavaScript)

클라이언트에서는 요청을 보내기 전에 HMAC 서명을 계산하여 헤더에 포함시켜야 합니다.
java


// 클라이언트 측 HMAC 서명 생성 (Java)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.apache.commons.codec.binary.Hex;

public class HmacClient {
    private final String apiKey;
    private final String secretKey;
    private final HttpClient httpClient;
    
    public HmacClient(String apiKey, String secretKey) {
        this.apiKey = apiKey;
        this.secretKey = secretKey;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();
    }
    
    public HttpResponse<String> sendRequest(String method, String url, String body) throws Exception {
        // 타임스탬프 생성 (Unix 시간)
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        
        // 서명할 메시지 생성
        String message = method + url + timestamp + (body != null ? body : "");
        
        // HMAC 서명 계산
        Mac sha256Hmac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(
            secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        sha256Hmac.init(secretKeySpec);
        
        byte[] hmacBytes = sha256Hmac.doFinal(message.getBytes(StandardCharsets.UTF_8));
        String signature = Hex.encodeHexString(hmacBytes);
        
        // HTTP 요청 생성
        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Content-Type", "application/json")
            .header("X-API-Key", apiKey)
            .header("X-HMAC-Signature", signature)
            .header("X-Timestamp", timestamp);
        
        HttpRequest request;
        
        switch (method.toUpperCase()) {
            case "GET":
                request = requestBuilder.GET().build();
                break;
            case "POST":
                request = requestBuilder.POST(HttpRequest.BodyPublishers.ofString(body != null ? body : ""))
                    .build();
                break;
            case "PUT":
                request = requestBuilder.PUT(HttpRequest.BodyPublishers.ofString(body != null ? body : ""))
                    .build();
                break;
            case "DELETE":
                request = requestBuilder.DELETE().build();
                break;
            default:
                throw new IllegalArgumentException("Unsupported HTTP method: " + method);
        }
        
        // 요청 전송 및 응답 반환
        return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    }
    
    // 사용 예시
    public static void main(String[] args) {
        try {
            HmacClient client = new HmacClient("test-api-key", "test-secret-key");
            HttpResponse<String> response = client.sendRequest(
                "GET", 
                "
https://api.example.com/data
", 
                null
            );
            
            System.out.println("Status code: " + response.statusCode());
            System.out.println("Response body: " + response.body());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

보안 고려사항과 모범 사례

키 관리 전략

HMAC 인증의 보안성은 비밀 키 관리에 크게 의존합니다. 효과적인 키 관리를 위한 모범 사례는 다음과 같습니다:
주요 장점:

  • 비밀 키는 충분히 길고 무작위적이어야 합니다 (최소 256비트 길이 권장).
  • 키는 안전한 저장소(HSM, KMS 등)에 보관하고, 평문으로 코드나 설정 파일에 저장하지 마세요.
  • 주기적인 키 교체 메커니즘을 구현하여 키 노출 시 피해를 최소화하세요.

타임스탬프와 논스(Nonce) 활용

재전송 공격을 방지하기 위한 추가적인 보안 조치로 타임스탬프와 논스를 활용할 수 있습니다.
java


// 타임스탬프와 논스를 포함한 HMAC 서명 생성
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import org.apache.commons.codec.binary.Hex;

public class HmacWithNonce {
    
    public static HmacSignature createHmacWithNonce(String method, String url, String body, String secretKey) 
            throws Exception {
        // 타임스탬프 생성
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        
        // 무작위 논스 생성 (16바이트)
        byte[] nonceBytes = new byte[16];
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.nextBytes(nonceBytes);
        String nonce = Hex.encodeHexString(nonceBytes);
        
        // 메시지 생성
        String message = method + url + timestamp + nonce + (body != null ? body : "");
        
        // HMAC 계산
        Mac sha256Hmac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(
            secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        sha256Hmac.init(secretKeySpec);
        
        byte[] hmacBytes = sha256Hmac.doFinal(message.getBytes(StandardCharsets.UTF_8));
        String signature = Hex.encodeHexString(hmacBytes);
        
        return new HmacSignature(signature, timestamp, nonce);
    }
    
    // 서명 결과를 담는 클래스
    public static class HmacSignature {
        private final String signature;
        private final String timestamp;
        private final String nonce;
        
        public HmacSignature(String signature, String timestamp, String nonce) {
            this.signature = signature;
            this.timestamp = timestamp;
            this.nonce = nonce;
        }
        
        public String getSignature() {
            return signature;
        }
        
        public String getTimestamp() {
            return timestamp;
        }
        
        public String getNonce() {
            return nonce;
        }
    }
}

HMAC vs 다른 인증 방식 비교

인증 방식별 특징

다양한 API 인증 방식의 장단점을 비교해보겠습니다.

 

인증 방식 주요 기능 보안 수준 구현 복잡성
API 키 단일 토큰 기반 인증 낮음 매우 간단
HMAC 메시지 서명 기반 인증 높음 보통
OAuth 2.0 위임 액세스 프로토콜 높음 복잡함
JWT Self-contained 토큰 중간-높음 보통
mTLS 상호 TLS 인증 매우 높음 복잡함

 

HMAC의 장단점

HMAC 인증 방식의 주요 장단점은 다음과 같습니다:

장점:

  • 메시지 무결성과 신원 확인을 동시에 보장
  • 비밀 키가 네트워크로 전송되지 않음
  • 타임스탬프와 함께 사용 시 재전송 공격에 강함
  • 서버 측에서 세션 상태를 유지할 필요가 없음


단점:

  • 클라이언트와 서버 간 시간 동기화 필요
  • 구현이 OAuth나 API 키보다 복잡함
  • 비밀 키 관리에 주의가 필요함

HMAC 인증의 고급 적용 사례

캐노니컬 요청 형식

대규모 API 시스템에서는 요청의 모든 부분(쿼리 파라미터, 헤더 등)을 일관된 형식으로 정규화하여 서명하는 것이 중요합니다. AWS 서명 버전 4와 유사한 캐노니컬 형식의 예시입니다.
java


// 캐노니컬 요청 형식 생성 (AWS Signature V4 스타일)
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class CanonicalRequest {
    
    public static String createCanonicalRequest(String method, String url,
                                               Map<String, String> headers,
                                               Map<String, String> queryParams,
                                               String body) throws Exception {
        // URL 경로 추출
        URI uri = new URI(url);
        String path = uri.getPath();
        
        // 쿼리 파라미터 정렬 및 인코딩
        TreeMap<String, String> sortedQueryParams = new TreeMap<>(queryParams);
        String canonicalQueryString = sortedQueryParams.entrySet().stream()
            .map(entry -> {
                try {
                    return URLEncoder.encode(entry.getKey(), "UTF-8") + "=" +
                           URLEncoder.encode(entry.getValue(), "UTF-8");
                } catch (Exception e) {
                    throw new RuntimeException("URL 인코딩 실패", e);
                }
            })
            .collect(Collectors.joining("&"));
        
        // 헤더 정렬 및 소문자로 변환
        TreeMap<String, String> sortedHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        sortedHeaders.putAll(headers);
        
        StringBuilder canonicalHeaders = new StringBuilder();
        for (Map.Entry<String, String> header : sortedHeaders.entrySet()) {
            canonicalHeaders.append(header.getKey().toLowerCase())
                           .append(":")
                           .append(header.getValue().trim())
                           .append("\n");
        }
        
        // 서명에 포함된 헤더 이름들
        String signedHeaders = sortedHeaders.keySet().stream()
            .map(String::toLowerCase)
            .collect(Collectors.joining(";"));
        
        // 요청 본문 해시 생성
        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
        byte[] bodyHash = sha256.digest((body != null ? body : "").getBytes(StandardCharsets.UTF_8));
        String hexBodyHash = bytesToHex(bodyHash);
        
        // 캐노니컬 요청 형식 조합
        return method + "\n" +
               path + "\n" +
               canonicalQueryString + "\n" +
               canonicalHeaders + "\n" +
               signedHeaders + "\n" +
               hexBodyHash;
    }
    
    private static String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }
    
    // 사용 예시
    public static void main(String[] args) {
        try {
            // 요청 정보
            String method = "POST";
            String url = "
https://api.example.com/resource/path?queryParam1=value1
";
            
            // 헤더 생성
            Map<String, String> headers = new HashMap<>();
            headers.put("Host", "api.example.com");
            headers.put("Content-Type", "application/json");
            headers.put("X-Amz-Date", "20250518T120000Z");
            
            // 쿼리 파라미터
            Map<String, String> queryParams = new HashMap<>();
            queryParams.put("queryParam1", "value1");
            
            // 요청 본문
            String body = "{\"key\":\"value\"}";
            
            String canonicalRequest = createCanonicalRequest(method, url, headers, queryParams, body);
            System.out.println("Canonical Request:");
            System.out.println(canonicalRequest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


다층 보안을 위한 HMAC 활용

HMAC은 다른 보안 메커니즘과 함께 사용하여 다층 방어(defense in depth)를 구현할 수 있습니다.

적용 예시

java

// JWT와 HMAC을 결합한 보안 강화 예시
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.Map;
import org.apache.commons.codec.binary.Hex;

public class SecureTokenGenerator {
    
    public static class SecureToken {
        private final String token;
        private final String signature;
        
        public SecureToken(String token, String signature) {
            this.token = token;
            this.signature = signature;
        }
        
        public String getToken() {
            return token;
        }
        
        public String getSignature() {
            return signature;
        }
    }
    
    public static SecureToken createSecureToken(Map<String, Object> payload, String jwtSecret, String hmacSecret) {
        // JWT 토큰 생성
        Key key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
        
        String token = Jwts.builder()
            .setClaims(payload)
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1시간 유효
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();
        
        // JWT 토큰에 대한 HMAC 서명 추가
        try {
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(
                hmacSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKeySpec);
            
            byte[] hmacBytes = sha256Hmac.doFinal(token.getBytes(StandardCharsets.UTF_8));
            String signature = Hex.encodeHexString(hmacBytes);
            
            return new SecureToken(token, signature);
        } catch (Exception e) {
            throw new RuntimeException("HMAC 서명 생성 실패", e);
        }
    }
    
    // 서버 측 검증
    public static Map<String, Object> verifySecureToken(String token, String signature, 
                                                      String jwtSecret, String hmacSecret) {
        // HMAC 서명 검증
        try {
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(
                hmacSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKeySpec);
            
            byte[] hmacBytes = sha256Hmac.doFinal(token.getBytes(StandardCharsets.UTF_8));
            String calculatedSignature = Hex.encodeHexString(hmacBytes);
            
            if (!calculatedSignature.equals(signature)) {
                throw new RuntimeException("유효하지 않은 HMAC 서명");
            }
            
            // JWT 토큰 검증
            Key key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
            Map<String, Object> claims = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();
            
            return claims;
        } catch (Exception e) {
            throw new RuntimeException("토큰 검증 실패: " + e.getMessage(), e);
        }
    }
    
    // 사용 예시
    public static void main(String[] args) {
        try {
            // 페이로드 생성
            Map<String, Object> payload = new HashMap<>();
            payload.put("userId", 123);
            
            // 보안 토큰 생성
            SecureToken secureToken = createSecureToken(
                payload, "jwt-secret-key", "hmac-secret-key");
            
            System.out.println("Token: " + secureToken.getToken());
            System.out.println("HMAC Signature: " + secureToken.getSignature());
            
            // 토큰 검증
            Map<String, Object> claims = verifySecureToken(
                secureToken.getToken(), secureToken.getSignature(),
                "jwt-secret-key", "hmac-secret-key");
            
            System.out.println("Verified claims: " + claims);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

HMAC vs 다른 인증 방식 비교

실제 서비스에서의 선택 가이드

어떤 인증 방식을 선택해야 할지는 서비스의 특성과 보안 요구사항에 따라 달라집니다.

 

도구 주요 기능 링크
HMAC 인증 높은 보안성, 메시지 무결성 보장 https://tools.ietf.org/html/rfc2104
OAuth 2.0 사용자 인증 위임, 서드파티 접근 제어 https://oauth.net/2/
JWT Self-contained 토큰, 무상태 인증 https://jwt.io/

 

보안 강화를 위한 추가 조치

HMAC 인증과 함께 사용할 수 있는 추가적인 보안 조치들입니다:

  • TLS(HTTPS) 사용 강제
  • 속도 제한(Rate Limiting) 구현
  • IP 주소 기반 필터링 추가
  • 사용자별 API 키 권한 세분화

결론

HMAC 인증 방식은 API 보안을 위한 강력하고 효과적인 솔루션입니다. 비밀 키를 네트워크로 전송하지 않으면서도 메시지 무결성과 신원 확인을 동시에 보장할 수 있어, 다양한 보안 요구사항을 충족시킵니다. 올바르게 구현하고 모범 사례를 따른다면, HMAC은 중간자 공격이나 재전송 공격과 같은 일반적인 API 보안 위협으로부터 시스템을 보호하는 데 큰 도움이 됩니다.

핵심 요약:

  • HMAC은 해시 함수와 비밀 키를 조합한 메시지 인증 코드로, API 인증에 효과적입니다.
  • 올바른 구현을 위해서는 타임스탬프, 논스, 캐노니컬 요청 형식 등의 추가 요소가 중요합니다.
  • 비밀 키 관리는 HMAC 보안의 핵심이므로, 안전한 저장과 주기적인 교체가 필수적입니다.

참고 자료

 

 

 

 

'IT' 카테고리의 다른 글

[IT] SHA-1, SHA-2, SHA-256 해시 알고리즘의 차이점  (2) 2023.12.02

📚 만들면서 배우는 클린 아키텍처 - 톰 홈버그

만들면서 배우는 클린 아키텍처 표지

https://product.kyobobook.co.kr/detail/S000001766486

 

클린 아키텍처 | 톰 홈버그 - 교보문고

클린 아키텍처 | 우리 모두는 낮은 개발 비용으로 유연하고 적응이 쉬운 소프트웨어 아키텍처를 구축하고자 한다. 그러나 불합리한 기한과 쉬워보이는 지름길은 이러한 아키텍처를 구축하는 것

product.kyobobook.co.kr

 

📋 기본 정보

  • 제목: 만들면서 배우는 클린 아키텍처
  • 저자: 톰 홈버그
  • 출판사: 위키북스
  • 출판일: 2021-11-26
  • ISBN: 1158392753

⏱️ 독서 데이터

  • 독서기간: 2025.05.13 - 2025.05.16
  • 독서시간: 5H
  • 평점: ⭐⭐⭐⭐⭐
  • 한줄평: 클린 아키텍처 자습서

🎯 독서 목표

 

[독서/IT 교양] 클린 아키텍처

안녕하세요. 다메카솔🐿️ 입니다. 이번에 읽은 책은 최근 관심이 많아진 아키텍처에 관련된 책입니다. https://product.kyobobook.co.kr/detail/S000001033082 클린 아키텍처: 소프트웨어 구조와 설계의 원칙

damecasol.tistory.com

  • 해결과제: 실제 코딩 시 적용할 수 있는 예제 인사이트
  • 활용계획: 출근시간에 독서 후, 사이드 프로젝트 리팩토링
  • 기대효과: 사이드 프로젝트 헥사고날 아키텍처로 리팩토링
  1.  

🏆 결론 및 추천

클린 아키텍처 책을 먼저 읽어도 좋고, 이 책을 먼저 읽어도 좋을 것 같습니다.

C.마틴의 책은 다양한 사례와 옆에서 이야기해주는 것 같은 자세한 설명으로 이해가 쉽게 되지만, 실무에 적용하기엔 추상적인 내용이라 읽고나서 활용하기가 조금 어려움이 있었습니다.

톰 홈버그의 책은 얇아서 읽기 쉬우며, 예제도 있어서 개발자들이 무언가를 가장 빠르게 방법인 따라해보며 배우기가 가능하여 쉽게 적용할 수 있었습니다.

🔍 연관 도서

https://damecasol.tistory.com/101

 

[독서/IT 교양] 클린 아키텍처

안녕하세요. 다메카솔🐿️ 입니다. 이번에 읽은 책은 최근 관심이 많아진 아키텍처에 관련된 책입니다. https://product.kyobobook.co.kr/detail/S000001033082 클린 아키텍처: 소프트웨어 구조와 설계의 원칙

damecasol.tistory.com

 

AWS Summit Seoul 2025 배너

 

👋 들어가며

안녕하세요, 다메카솔입니다. 2025-05-14 ~ 15일 개최된 AWS Summit Seoul 2025에 다녀왔습니다. 개발자라면 모두 가고싶어하는 AWS Summit에 당첨되어 좋아했는데, 업무로 인해 2일차만 참석할 수 있었습니다 ㅠㅠ. 클라우드 기술에 관심 있는 개발자, 기술 담당자, 그리고 비즈니스 리더들이 모인 이번 행사에서 AI가 개발시장을 뒤흔드는 시점에서 최신 트렌드 인사이트를 얻고 왔습니다. 이 글에서는 제가 참여한 세션들과 전체적인 행사 분위기, 그리고 개인적인 소감을 공유하고자 합니다.

행사장 입구

📍 행사 개요 및 현장 분위기

행사명 : AWS Summit Seoul 2025
일시 : 2025-05-14 ~ 15
장소 : 코엑스 컨벤션 센터
참가 인원 : 약 4만 명(기조연설에서 언급)
https://aws.amazon.com/ko/events/summits/seoul/

 

AWS Summit Seoul

기술 혁신의 최전선에 있는 AWS 서비스와 생성형 AI를 중심으로 변화하는 시대를 조망하는 기조연설을 준비했습니다. 비즈니스의 새로운 혁신을 가져올 수 있는 주요 산업별 성공 사례와 신기술

aws.amazon.com

9호선에 많은 직장인들이 타고있었지만 봉은사역에 가니까 개발자같은 분들의 비중이 눈에띄게 높아졌습니다. 오전엔 비가 조금 내렸지만 행사장에 도착하니 사람들이 모여서 줄서있고 대화나누느라 분위기가 뜨거웠습니다 . 체크인 하는데 줄이 너무 길어 공항 출국하는 느낌을 받았습니다. 

AWS Seoul Summit 체크인 대기자
AWS 기조연설 세션 입구
기조연설 끝난 뒤 체크인

🚀 기조 연설 하이라이트

AWS 기조연설 참가 등록자 4만명

 

기조연설 장표

 

AI 에이전트

 

기조연설



이번 AWS Summit의 2일차 기조 연설의 핵심 주제는 단순화, 에이전틱 AI, AI Creative 였습니다. IT의 최신 트렌드를 관통하는 주제라고 생각되었는데 AWS 부사장도 강조하는 바이브코딩에 급변하는 개발 트렌드를 느낄 수 있었습니다. 특히 생성형 AI 코딩시연이 끝나고 기조연설이 끝나고 나가면서 다른 개발자분들이 이제 개발자 일 정말 줄어드는거 아니냐는 말을 하셨는데, 저도 어느정돈 걱정되면서, 코딩의 시간이 줄어들면서 더 많은 일을 할 수 있게 되니 어떻게 될지는 지켜봐야 될 것 같습니다.

💡 참석한 주요 세션 리뷰

1. 나의 완벽한 개발비서 Amazon Q와 함께 SDLC 이븐하게 가속하기

발표자:
이재은 딜리버리 컨설턴트, AWS
조은혜 딜리버리 컨설턴트, AWS

개발비서 Q 프로필

 

Q Developer CLI 장표

주요 내용:

- Amazon Q 소개
- SDLC 단계별 사용사례
- 데모

배운 점 & 인사이트:

최근 커서AI도 사용하고, 클로드도 사용하고 있는데 Amazon Q에서도 에이전트AI 서비스를 출시하였습니다.
요구사항 분석부터 개발, 테스트코드 작성, 배포, 문서 작성까지 모든 것을 바이브 코딩으로 진행하는 데모였습니다. 이제 정말 바이브코딩을 배워서 새로운 시대로 넘어가느냐, 남겨지느냐의 기로에 선 것 같습니다.
고급 모델들은 월 호출 회수가 제한이 있는데, 워낙 많은 기업들에서 출시하고 있고, 모두 뛰어난 성능을 보여주니 돌아가면서 이용하면 꼭 유료모델을 사용하지 않아도 될 것 같습니다.

 2. 인터넷 안되는 하이브리드 환경에서 살아남기

발표자:
최영준 소프트웨어 엔지니어, 두산에너빌리티
이재영 MLOps 엔지니어, 무신사

최영준 소프트웨어 엔지니어님과 발표 장표


주요 내용:

- 에어갭 환경에서 하이브리드 환경 전환 여정
- Eks 오토모드 / 하이브리드 모드 구축기

배운 점 & 인사이트:

금융권에 있다보니 인터넷이 안된다는 주제에 꽂혀 참석한 세션이였습니다.
제 상황과 유사하지는 않은 사례였지만 두 주제 모두 정말 시야가 넓어지는 주제였습니다.
첫번째 사례는 에어갭 환경에서 사업확대로  퍼블릭 인프라 구축으로 확대하면서 IaC 적용으로  같은 환경을 구성한 사례였고
두번째 사례는 작년 11월 출시한 EKS의 최신 기능을 실무에 적용하면서 생겼던 이슈들을 트러블슈팅하는 과정을 발표한 사례였습니다.
모두 각자의 환경에서 주어진 문제를 해결하는 능력에 영감을 많이 얻었습니다.

 3. 변화하는 RDB 데이터를 놓치지 않는 실시간 스트리밍 아키텍처 이야기

발표자:
엄준영 소프트웨어 엔지니어, 무신사
박상운 개발리드, 리콘랩스

Connector 설정 장표
리콘랩스의 박상운 개발자님


주요 내용:

- 무신사의 CDC 기반 서비스 성공 사례
- 리콘랩스의 데이터베이스 스트림 사례

배운 점 & 인사이트:

두 세션 모두 데이터 스트림에 대한 인사이트를 주었습니다.
지금 진행중인 업무에서 Kafka를 다룰 일이 많아 추가적으로 공부하고 있었는데 실무적용 사례로 커넥터나 스트림을 적극 활용해봐야겠다는 영감을 얻었고, 리콘랩스의 사진을 찍으면 365도 카메라 촬영처럼 시각화하는 서비스는 활용 분야가 정말 넓을 것 같았고, 기술 리더님도 바이브코딩을 적극 활용하시는 것 같았습니다.

🎁 AWS 부스 및 파트너 전시관 탐방

이번 서밋에서는 다양한 AWS 서비스 부스와 파트너사 전시관이 마련되어 있었습니다. 다른 IT 행사에 참여하면 굿즈 사냥으로 열심히 돌아다니는게 낙이었는데, 워낙 세션들이 알차서 그리 많이 돌아다니지는 않고 평소 관심있던 부스들 가서 굿즈 몇개 받고 구경하고 다시 세션에 참석하러 이동하였습니다.
월드 IT쇼 같은 경우엔 기념품만 노리러 오신 것 같은 분들이 많이 보였는데, AWS는 추첨제 행사라 그런지 그런 분들은 안보였던 것 같습니다.

🍽️ 행사장 식사 &  AWS Certification Lounge

행사장 식사


행사 중 제공된 식사는 정말 기대 이상으로 맛있게 먹었습니다. 샌드위치와 스낵렙, 과일과 디저트, 음료수까지 당 충전을 잘 해서 세션을 더 열심히 들을 수 있었습니다. 
전에 SAA를 취득해서 AWS Certification Lounge에 입장할 수 있게 되어 다녀왔습니다 ㅎㅎ.
조용히 휴대폰 충전하면서 쉴수 있는 공간이었는데 AWS 자격증을 캐릭터화한 스티커가 있어서 동료분들 몇개 나눠주려고 챙겼습니다 ㅎㅎ

라운지에서 내려다본 1층

💭 종합적인 소감 및 앞으로의 계획

이번 AWS Summit Seoul을 통해 최신 개발 트렌드와 다양한 인사이트를 얻을 수 있었습니다. 특히 Amazon Q는 앞으로 제 업무에 큰 도움이 될 것 같습니다. 취미로 조금씩만 사용하는 수준이었는데, 적극적으로 도입하고 회사 업무에도 적용할 수 있게 공유를 할 예정입니다

🙋‍♀️ 마치며

AWS Summit Seoul 2025는 정말 연차쓰고 간 것이 후회되지 않는 행사었습니다. 클라우드 기술에 관심이 있거나 AWS 서비스를 활용 중인 분들에게 내년 행사는 꼭 참석해보시길 추천드립니다.

혹시 이번 AWS Summit Seoul 2025에 참석하셨던 분들, 어떤 세션이 가장 유익하셨나요? 댓글로 여러분의 경험도 공유해주세요!

Gradle은 유연성과 성능을 갖춘 오픈소스 빌드 자동화 도구로, Java 기반 프로젝트부터 다중 언어 개발까지 효율적인 빌드 환경을 제공합니다.

Gradle 로고

 

목차

  • 소개
  • Gradle의 핵심 개념
  • Gradle vs Maven
  • 실전 Gradle 사용법
  • 성능 최적화 기법
  • 결론
  • 참고 자료

개요

안녕하세요. 다메카솔🐿️ 입니다.

백엔드 개발을 하면서 항상 사용하는 Gradle에 대해 따로 시간을 내서 찾아본 적이 없는 것 같아 궁금증이 생겨 내용을 정리해보았습니다. 자주 쓰는  도구이지만 공식문서를 참고하면서 내용을 구조화하니 좀 더 이해가 깊어진 것 같습니다.

소개

소프트웨어 개발에서 빌드 자동화는 코드를 컴파일하고, 테스트를 실행하며, 배포 가능한 아티팩트를 생성하는 중요한 과정입니다. Gradle은 2009년 처음 등장한 이후 Java, Kotlin, Groovy, Android 등 다양한 생태계에서 사실상의 표준(de facto standard)으로 자리잡았습니다.

이 글에서 정리한 내용:

  • Gradle의 기본 아키텍처와 핵심 개념
  • Maven과 Gradle의 주요 차이점과 장단점
  • 프로젝트에 Gradle을 효과적으로 적용하는 방법
  • 대규모 프로젝트에서 빌드 성능을 최적화하는 기법

Gradle의 핵심 개념

빌드 스크립트와 DSL

Gradle은 Groovy 또는 Kotlin DSL(Domain Specific Language)을 사용하여 빌드 스크립트를 작성합니다. 이는 XML 기반의 Maven과 달리 프로그래밍 언어의 모든 기능을 활용할 수 있어 유연하고 강력한 빌드 로직을 구현할 수 있습니다.

 
groovy
// build.gradle 기본 예제
plugins {
    id 'java'
    id 'application'
}

group = 'com.example'
version = '1.0-SNAPSHOT'

sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.slf4j:slf4j-api:1.7.32'
    testImplementation 'junit:junit:4.13.2'
}

application {
    mainClassName = 'com.example.Main'
}

태스크 기반 모델

Gradle Docs Task Image

Gradle의 핵심은 태스크(Task) 기반 모델입니다. 각 태스크는 특정 작업을 수행하며, 태스크 간의 의존성을 설정할 수 있습니다.

💡 팁: Gradle은 빌드 수행 시 필요한 태스크만 실행하는 증분 빌드를 지원하여 빌드 시간을 크게 단축시킵니다.

 

 
groovy
// 커스텀 태스크 예제
task customTask {
    doLast {
        println 'This is a custom task'
    }
}

// 태스크 의존성 설정
task dependentTask(dependsOn: customTask) {
    doLast {
        println 'This task depends on customTask'
    }
}

Gradle vs Maven

구문과 유연성

Maven은 XML 기반 선언적 접근 방식을 사용하는 반면, Gradle은 프로그래밍 언어를 사용하여 빌드 스크립트를 작성합니다.

주요 장점:

  • Gradle은 조건문, 반복문 등 프로그래밍 구성을 사용할 수 있어 복잡한 빌드 로직을 표현하기 용이함
  • 플러그인 시스템을 통해 쉽게 확장 가능
  • 빌드 스크립트가 더 간결하고 가독성이 높음

성능 비교

Gradle은 증분 빌드, 빌드 캐시, 병렬 실행 등 다양한 성능 최적화 기능을 제공합니다.

이미지 표시 대규모 프로젝트에서 Gradle은 Maven보다 최대 100배 빠른 빌드 성능을 보여줄 수 있습니다.

실전 Gradle 사용법

멀티 프로젝트 구성

대규모 애플리케이션은 여러 모듈로 구성되는 경우가 많습니다. Gradle은 멀티 프로젝트 구성을 쉽게 관리할 수 있습니다.

 
groovy
// settings.gradle
rootProject.name = 'my-application'
include 'api', 'service', 'repository', 'common'

// root build.gradle
allprojects {
    repositories {
        mavenCentral()
    }
}

subprojects {
    apply plugin: 'java'
    
    dependencies {
        testImplementation 'junit:junit:4.13.2'
    }
}

의존성 관리

Gradle은 유연한 의존성 관리 기능을 제공합니다. 특히 의존성 버전 충돌 해결과 전이적 의존성(transitive dependencies) 관리가 뛰어납니다.

의존성 잠금(Dependency Locking)

 
groovy
// 의존성 버전 잠금 예시
dependencyLocking {
    lockAllConfigurations()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

실행 결과:

 
Generated lock file 'dependencies.lockfile'

성능 최적화 기법

빌드 캐시 활용

Gradle의 빌드 캐시는 이전 빌드의 결과물을 재사용하여 빌드 시간을 단축합니다.

병렬 실행

Gradle은 기본적으로 병렬 실행을 지원하여 멀티코어 환경에서 빌드 성능을 극대화합니다.

 
groovy
// gradle.properties
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m

최적화 기법주요 기능적용 방법

병렬 빌드 독립적인 태스크를 동시에 실행 org.gradle.parallel=true
빌드 캐시 이전 빌드 결과 재사용 org.gradle.caching=true
데몬 프로세스 JVM 시작 시간 절약 기본 활성화됨
증분 컴파일 변경된 파일만 컴파일 기본 지원됨

결론

Gradle은 단순한 빌드 도구를 넘어 프로젝트 자동화를 위한 강력한 플랫폼으로 발전했습니다. 프로그래밍 언어 기반의 유연한 DSL, 성능 최적화 기능, 다양한 플러그인 생태계를 통해 복잡한 빌드 요구사항을 효과적으로 해결할 수 있습니다.

핵심 요약:

  • Gradle은 Groovy 또는 Kotlin DSL을 사용하여 유연하고 강력한 빌드 스크립트를 작성할 수 있습니다.
  • 증분 빌드, 캐싱, 병렬 실행 등 다양한 성능 최적화 기능을 제공합니다.
  • Maven에 비해 복잡한 빌드 로직과 대규모 멀티 프로젝트 구성에 더 적합합니다.
  • Android, Spring Boot 등 주요 프레임워크와의 통합이 뛰어납니다.

참고 자료

+ Recent posts