실무자가 알려주는 헥사고날 아키텍처 (Hexagonal architecture)
나는 왜 프로젝트에 헥사고날 아키텍처 (Hexagonal architecture)를 도입해서 개발했나?
프롤로그
실제로 회사 프로젝트에 헥사고날 아키텍처를 도입했던 경험을 바탕으로 이 글을 작성하려고 합니다. 왜 이 아키텍처를 선택했는지, 팀원들의 반대 의견은 무엇이었는지, 그리고 최종적으로 프로젝트가 어떤 성과를 거두었는지를 담아 제 생각을 공유하고자 합니다.
Hexagonal Architecture 란?
The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation.
wikipedia - Hexagonal architecture (software)
헥사고날 아키텍처(또는 포트와 어댑터 아키텍처)는 소프트웨어 설계에서 사용되는 아키텍처 패턴입니다. 이 패턴의 목적은 느슨하게 결합된 애플리케이션 구성 요소를 만드는 것이며, 이 구성 요소들은 포트와 어댑터를 통해 소프트웨어 환경과 쉽게 연결될 수 있습니다. 이를 통해 구성 요소는 어떤 수준에서도 교체가 가능하며 테스트 자동화를 용이하게 합니다.
헥사고날 아키텍처를 쉽게 설명하자면, "다목적 전기 플러그" 에 비유할 수 있어요. 생각해보세요. 여러분 집에 다양한 전자기기들이 있는데, 그 기기들이 모두 다른 전원을 필요로 하더라도, 각 기기에 맞는 어댑터(변환기)만 연결하면 하나의 콘센트에서 전기를 공급받을 수 있죠.
그 어댑터는 전기(핵심 기능)를 각 기기(다양한 소프트웨어 환경)에 맞게 변환하는 역할을 합니다.
헥사고날 아키텍처에서도 마찬가지로, 애플리케이션의 핵심 로직은 그대로 유지되면서 포트와 어댑터를 사용해 외부 시스템이나 인터페이스와 연결
됩니다. 이렇게 하면 애플리케이션의 핵심 로직이 어떤 환경에서든 재사용
될 수 있고, 특정 시스템에 의존하지 않으므로 유지보수가 쉬워지고 유연성
이 높아집니다. 그래서 전자기기에 다양한 어댑터를 연결하듯, 소프트웨어에서도 필요한 인터페이스를 쉽게 교체할 수 있는 구조라고 생각하면 됩니다.
헥사고날이 적용된 핸드폰?
이런 개쩌는 설계를 코드에도 도입이 가능하다고?? 그럼그럼 그것은 사실이야
헥사고날 접근 방식의 최고 장점
변경 허용 (Change-tolerant)
기술 변화는 빠른 속도
로 일어나고 있으며 소프트웨어가 이러한 변경을 수용할 준비가 되어 있지 않다면 소프트웨어 아키텍처가 변화에 대한 내성이 없기 때문에 회사는 대규모 리팩터링에 많은 돈과 시간을 소비
해야 할 수 있다. 따라서 헥사고날 아키텍처의 포트와 어댑터라는 특성
은 마찰이 적고 기술 변화를 흡수할 준비가 되어있는 이점
을 제공합니다.
Hexagonal 을 구성하는 세 가지 헥사곤
소프트웨어 아키텍처 디자인 패턴으로, 소프트웨어 시스템의 각 부분을 명확하게 분리하고 관리
하기 위해 사용됩니다.
- 도메인 헥사곤 (Domain Hexagon)
- 도메인 헥사곤은 소프트웨어 시스템의
핵심 비즈니스 로직 또는 도메인 로직을 담당
하는 부분을 나타냅니다. - 이 영역은 주로
도메인 모델, 엔터티, 값 객체, 비즈니스 규칙 및 도메인 서비스
를 포함하며, 시스템의 핵심 기능을 정의하고 구현합니다. - 도메인 헥사곤은 시스템의 핵심이며,
외부 요소에 대한 의존성을 최소화하기 위해 외부와의 상호 작용을 최소화하는 것을 목표
로 합니다.
- 도메인 헥사곤은 소프트웨어 시스템의
- 어플리케이션 헥사곤 (Application Hexagon)
- 어플리케이션 헥사곤은 도메인 헥사곤을 감싸고 있는 부분으로,
비즈니스 로직을 실행하고 외부 요청을 처리하는 역할
을 합니다. - 주로
애플리케이션 서비스, 유스 케이스(Use Case), 컨트롤러 등을 포함
하며, 외부 요청을 받아 도메인 헥사곤에게 전달하고 결과를 반환합니다. - 어플리케이션 헥사곤은 도메인 헥사곤과
외부 요소 간의 중간 역할을 수행하며, 비즈니스 로직의 실행 흐름을 조정
합니다.
- 어플리케이션 헥사곤은 도메인 헥사곤을 감싸고 있는 부분으로,
- 프레임워크 헥사곤 (Framework Hexagon)
- 프레임워크 헥사곤은
외부 기술과 프레임워크를 통합하고 사용하는 부분
을 나타냅니다. - 주로
데이터베이스 연결, 외부 서비스 연동, 보안, 로깅 등과 관련된 코드를 포함하며, 외부 기술과의 상호 작용
을 처리합니다. - 프레임워크 헥사곤은
어플리케이션 헥사곤과 도메인 헥사곤을 지원
하고, 외부 시스템과의 통합을 관리합니다. 입력 및 출력 어댑터를 구현
합니다.
- 프레임워크 헥사곤은
드라이빙(Driving), 드리븐(Driven) 오퍼레이션
- 드라이빙(Driving) 오퍼레이션
소프트웨어에서 수행되어야 하는 동작을 요청
하는 것이다.- 주로 컨트롤러(Controller) 또는
비즈니스 로직의 흐름을 제어
합니다. - 시스템의 주도자 역할을 하며, 다른
컴포넌트나 모듈을 호출하고 흐름을 제어
합니다.
- 드리븐(Driven) 오퍼레이션
- 주로 서비스(Service) 또는
데이터 액세스 계층에서 발생
합니다. - 시스템의 주도를 받는 역할을 하며, 주로
데이터베이스 액세스, 비즈니스 로직 실행 또는 객체-데이터베이스 매핑과 관련된 작업을 수행
합니다.
- 주로 서비스(Service) 또는
이러한 분리는 소프트웨어의 모듈화를 촉진하고 코드의 의존성을 줄여서 유지보수성을 향상시키며, 각 컴포넌트가 자신의 역할에 집중
할 수 있도록 돕습니다.
어플리케이션 헥사곤은 시스템의 외부 인터페이스와 관련이 있으며, 드라이빙 오퍼레이션은 내부 비즈니스 로직을 주도하고 흐름을 제어하는 데 관련이 있습니다. 즉, 어플리케이션 헥사곤은 시스템의 전반적인 아키텍처를 나타내는 것이고, 드라이빙 오퍼레이션은 그 중 일부분에서 발생하는 동작을 설명하는 개념입니다.
포트와 유스케이스를 통한 동작 처리
유스케이스(Use Case) 란?
유스케이스는 주로 요구사항 분석, 시스템 설계, 테스트, 문서화 등 소프트웨어 개발 프로세스의 다양한 단계에서 활용됩니다. 이를 통해 시스템이 사용자 또는 다른 시스템과 어떻게 상호 작용해야 하는지를 명확하게 이해하고 설계할 수 있습니다.
액터(Actor)
: 액터는 유스케이스와 상호 작용하는 주체 또는 역할을 나타냅니다. 일반적으로 사용자, 다른 시스템, 외부 엔터티 등이 액터로 정의됩니다. 액터는 시스템 외부에서 유스케이스를 트리거하거나 시스템으로부터 결과를 받는 역할을 합니다.목표(Objective)
: 유스케이스의 목표는 시스템이 완료해야 하는 주요 작업 또는 목적을 나타냅니다. 간단한 문장으로 유스케이스의 핵심 목적을 요약적으로 설명합니다.범위(Scope)
: 범위는 유스케이스가 어떤 부분 또는 기능을 다루는지를 정의합니다. 시스템의 전체 기능을 다루는지, 특정 하위 시스템 또는 모듈과 관련이 있는지 등을 명시합니다.트리거(Trigger)
: 트리거는 유스케이스가 시작되는 이벤트나 조건을 나타냅니다. 트리거는 액터나 외부 시스템에 의해 발생할 수 있으며, 유스케이스를 시작하는 원인을 설명합니다.입력 데이터(Input Data)
: 입력 데이터는 유스케이스가 실행될 때 필요한 정보나 데이터를 나타냅니다. 이 데이터는 주로 액터나 외부 시스템으로부터 제공되며, 유스케이스가 이를 기반으로 동작합니다.액션(Action)
: 액션은 유스케이스가 수행하는 주요 활동이나 단계를 설명합니다. 각 액션은 사용자 또는 시스템의 관점에서 어떤 일이 발생하는지를 나타냅니다.
간단한 예를 들어보겠습니다:
유스케이스
: 주문 생성
액터
: 고객목표
: 주문을 생성하고 결제 정보를 입력하기범위
: 주문 관리 시스템트리거
: 고객이 “주문 생성” 버튼을 클릭입력 데이터
: 주문 상품 목록, 배송 주소액션
:- 시스템은 주문 화면을 표시
- 고객은 주문 상품 목록과 배송 주소를 입력
- 고객은 “주문 완료” 버튼을 클릭
- 시스템은 주문을 생성하고 결제 정보를 입력하는 단계로 진행
이러한 요소들은 유스케이스를 효과적으로 문서화하고 이해관계자들 간의 의사소통을 개선하는 데 도움이 됩니다.
유스케이스의 목적?
요구사항 분석
: 유스케이스를 사용하여 시스템이 어떻게 동작해야 하는지를 상세하게 문서화하고 요구사항을 수집합니다. 이는 개발자, 시스템 설계자, 프로젝트 관리자, 고객 등 모든 이해관계자 간에 요구사항을 명확하게 정의하는 데 도움이 됩니다.기능 설계
: 유스케이스는 시스템의 기능을 사용자의 관점에서 정의하고 설계하는 데 사용됩니다. 사용자가 시스템과 어떻게 상호 작용해야 하는지를 나타내므로 기능을 개발 및 구현할 때 유용한 지침을 제공합니다.테스트 계획
: 유스케이스는 시스템을 테스트하기 위한 테스트 케이스를 작성하는 데 사용됩니다. 각 유스케이스 시나리오는 시스템이 예상대로 동작하는지 확인하기 위한 기준으로 사용됩니다.요구사항 추적
: 유스케이스는 각 요구사항이 어떤 유스케이스와 연관되어 있는지 추적하는 데 사용됩니다. 이를 통해 요구사항이 어떻게 구현되었는지를 추적하고 변경 사항에 대한 영향을 파악할 수 있습니다.
요약하면, 유스케이스는 소프트웨어 개발 프로젝트에서 요구사항을 정의하고 문서화하며, 기능을 설계하고 테스트하는 데 중요한 역할을 합니다. 또한 사용자와 개발자 간에 원활한 의사소통을 가능하게 하며, 시스템의 사용법을 이해하는 데 도움이 됩니다.
입력 포트와 출력 포트
입력 포트는 유스케이스를 구현
한다. 헥사고날 아키텍처에서 유스케이스는 소프트웨어 기능을 기술
하는 인터페이스이기 때문이다. 즉 입력 포트는 소프트웨어 기능을 가능하게 하는 동작을 설명
한다.
출력 포트는 외부 시스템과 상호작용이 필요
한 경우 출력 포트는 입력 포트 내부
에 나타난다.
드라이빙 오퍼레이션 허용을 위한 입력 어댑터
컴퓨터 리소스는 더 저렴해지고 모든 사람이 쉽게 접근할 수 있게 진화하고 있습니다. 이러한 접근성이 더 많은 사람이 소프트웨어 개발에 참여하고 협력할 수 있음을 의미합니다.
이처럼 증가하는 협업의 결과는 사람들의 문제를 더 좋고 현대적인 솔류션으로 해결하려는 창의적인 노력을 지원하는 새로운 프로그래밍 언어와 도구 개발 프레임워크로 나타납니다.
이러한 상황에서 소프트웨어 개발할 때 발생하는 우려 사항 중 하나는 어떻게 시스템이 계속되는 기술적인 변화를 맞이하면서도 유의마하고 수익성을 유지하느냐
비즈니스 규칙과 기술적 세부사항이 연결되도록 시스템이 설계된 경우 새로운 기술의 통합은 쉽지 않을것
이다.
복잡성 증가
: 비즈니스 규칙과 기술적 세부사항이 강하게 결합된 시스템은 복잡성이 높아집니다. 새로운 기술을 통합하려면 기존 시스템의 많은 부분을 변경해야 할 수 있으며, 이로 인해 전체 시스템의 복잡성이 증가하게 됩니다.유지보수 어려움
: 비즈니스 규칙과 기술적 구현이 혼재되어 있으면 유지보수가 어려워집니다. 새로운 기술을 통합하려면 기존 코드를 변경해야 하며, 이는 버그를 발생시키거나 예상치 못한 부작용을 초래할 수 있습니다.코드 의존성
: 기술적 세부사항과 비즈니스 규칙이 강하게 결합되면 코드 간의 의존성이 높아집니다. 따라서 하나의 모듈을 변경하면 다른 모듈에도 영향을 미칠 수 있으며, 이는 예상치 못한 문제를 발생시킬 수 있습니다.테스트 어려움
: 기술적 세부사항과 비즈니스 규칙이 혼재된 코드는 테스트하기 어렵습니다. 새로운 기술을 통합할 때 기존 테스트 케이스를 변경해야 할 수 있으며, 테스트의 안정성과 일관성이 저하될 수 있습니다.기술 갱신의 어려움
: 비즈니스 규칙과 기술적 세부사항이 강하게 결합된 시스템은 기술 스택을 업데이트하기 어려울 수 있습니다. 새로운 기술을 도입하려면 기존 시스템을 많이 변경해야 하므로 시간과 비용이 많이 소요됩니다.확장 어려움
: 새로운 기술을 통합하려면 기존 시스템을 변경해야 하므로 시스템의 확장이 어려워집니다. 새로운 요구사항을 수용하거나 새로운 기능을 추가하기 어려울 수 있습니다.
헥사고날 아키텍처에서 입력 어댑터는 소프트웨어를 다른 기술들과 호환되게 만드는 요소
이다.
실제 코드를 보며 이해 해보자
Hexagonal Architecture Git Repository 바로가기
일반적인 MVC 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
com.example.myapp
├── controller
│ └── UserController.java
├── entity
│ └── UserEntity.java
├── service
│ └── UserService.java
├── repository
│ └── UserRepository.java
├── dto
│ └── UserDTO.java
├── view
└── └── UserView.jsp (또는 .html)
위 경로는 예시 경로입니다. git 저장소 코드와 다릅니다.
MySQL 로 DB 를 저장하고 조회하는 간단한 프로젝트입니다.
만약 MySQL보다 MongoDB의 성능이 크게 향상되거나, MySQL을 제공하는 기업이 사라진다면 어떻게 될까요? 혹은 완전히 새로운 고성능의 R2DBC 데이터베이스가 출시되어 교체가 필요한 상황이 온다면?
내가 개발자라면, 어디를 교체해야 할까요? model, service 계층뿐만 아니라, 경우에 따라서는 controller 까지 수정해야 할지도 모릅니다. 왜 그럴까요?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "USER")
public class UserEntity {
@Id
@GeneratedValue
private Long id;
private String username;
}
위 코드는 JPA Entity 입니다. MongoDB Entity 로 바꿔야 해요.
1
2
3
4
5
6
7
8
9
10
11
12
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "BATTERY")
public class UserEntity {
@Id
private String id;
private String username;
}
이렇게 MongoDB Entity 로 변경되면서, Repository 추가해야 하고, JPA와 연결된 Service 코드 역시 수정해야 합니다.
만약 MySQL이 다시 성장해 최고의 DB 자리에 올라 교체가 필요해진다면, 또다시 JPA 리포지토리를 서비스 계층에서 변경해야 하는 번거로움이 발생할 수 있습니다.
하지만 헥사고날 아키텍처는?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
com.example.myapp
├── application
│ └── exception
│ └── port
│ └── in
│ └── UserInPort.java
│ └── out
│ └── UserOutPort.java
│ └── usecase
│ └── UserUseCase.java
├── domain
│ └── User.java
├── framework
│ └── adapter
│ └── in
│ └── web
│ └── UserController.java
│ └── out
│ └── mysql
│ └── entity
│ └── UserJpaEntity.java
│ └── repository
│ └── UserJpaRepository.java
│ └── UserJpaAdapter.java
│ └── mongodb
│ └── entity
│ └── UserMongoEntity.java
│ └── repository
│ └── UserMongoRepository.java
│ └── UserMongoAdapter.java
│ └── vault
│ └── redis
│ └── security
│ └── kafka
│ └── ...
│ └── exception
처음 봤을 때 바로 느낄 수 있었다. 구조가 너무 복잡했다.
이 복잡한 구조 때문에 팀원들을 설득하는 데 많은 어려움이 있었다. 헥사고날 아키텍처를 충분히 이해하지 못한 채 개발에 들어가면, 개발자들의 혼란만 커질 것이 분명했다.
그 당시 주니어 개발자들뿐만 아니라 시니어 개발자들까지도 이 복잡한 구조에 대해 많은 질문을 던졌고, 왜 굳이 헥사고날 아키텍처를 도입해야 하는지에 대한 의문을 자주 제기했다.
MVC 패턴으로 개발하면 더 쉽게 해결될 수 있는데, 왜 이 복잡한 구조를 선택해야 하는지 설득하는 과정은 쉽지 않았다. 이를 설득하기 위해 나는 코드 예제와 함께 나만의 설명을 이어갔다.
패키지는 복잡해지지만 정해진 규칙에서 잘 수행한다면 클린한 코드를 구현할 수 있다는 의견입니다.
깃 저장소의 코드를 보면, UserOutPort
인터페이스에 연결된 UserJpaAdapter
와 UserMongoAdapter
가 있습니다. 내가 연결하려는 어댑터에 @Primary
어노테이션을 추가함으로써 서비스 코드를 수정하지 않고도 데이터베이스를 변경
할 수 있었습니다. 어댑터에 필요한 기술만 정의되어 있으면, 쉽게 교체할 수 있는 구조를 확인할 수 있습니다.
1
2
3
4
5
6
//@Primary
@PersistenceAdapter
@RequiredArgsConstructor
public class UserJpaAdapter implements UserOutPort {
//...
}
1
2
3
4
5
6
@Primary
@PersistenceAdapter
@RequiredArgsConstructor
public class UserMongoAdapter implements UserOutPort {
//...
}
실제 회사에서 챗봇 로그를 저장하는 프로젝트를 담당하게 되었습니다. (프로젝트 이름은 WaveLens)
이 프로젝트는 짧은 기간 내에 완료해야 하는 긴급한 상황이었고, 당시 엘라스틱 서치에 대한 경험이 없었습니다. 하지만 챗봇 로그를 JSON 형태로 비동기적으로 저장해야 했기 때문에, 제가 알고 있는 MongoDB를 우선 사용하여 개발을 완료한 후, 유지보수 단계에서 MongoDB를 엘라스틱 서치로 변경하기로 결정했습니다.
MongoDB로 개발한 프로젝트는 월 평균 20만 건의 로그를 처리할 수 있게 되었고, 안정화된 후 MongoDB에서 엘라스틱 서치로의 교체 작업을 진행했습니다. 이 작업은 단 2일 만에 완료되었습니다. 코드 이슈 없이 어떻게 가능했을까요? 헥사고날 아키텍처의 특징 덕분에 서비스 로직을 수정하지 않고
, MongoDB와 동일한 로그 데이터를 저장하고 조회하는 엘라스틱 서치 어댑터만 추가하여 교체를 진행
할 수 있었습니다.
Out Port 이름 규칙
application - port - out
에 정의된 클래스 이름은 특정 프레임워크에 종속되어서는 안 됩니다. 예를 들어 Vault 를 사용한다고 할 때, out 포트는 광범위한 이름인 SecretOutPort
로 정의하고, 프레임워크 어댑터만 VaultAdapter
로 만들어 포트에 연결해 사용해야 합니다. 그래야 SecretOutPort
를 보고 해당 포트가 키 보안 관리를 위한 것임을 쉽게 유추하고 사용할 수 있기 때문입니다. 우리는 평생 Vault by HashiCorp 업체의 암호화 관리 시스템을 이용한다는 보장이 없기 때문에 항상 out port 는 종속되지 않는 이름으로 해야합니다.
개발 순서는 어떻게 될까?
헥사고날 아키텍처를 기반으로 코드 순서를 설명하자면 다음과 같다.
- 먼저 기획서를 확인하고 요구사항 분석 후 UseCase 를 작성합니다.
- 작성한 유스케이스 를 기반으로 Application - In - Port 서비스 로직을 구현합니다.
- 외부 통신이 필요한 경우 Application - Out - Port 기능을 작성합니다.
- Framework - Adapter - Out 의 어뎁터에 기능을 구현합니다.
여기서 중요한것은 서비스 로직에는 어뎁터의 엔티티를 가지고 있으면 안되고 도메인 모델을 가지고 있어야 합니다.
아키텍처가 중요한이유
모든 소프트웨어가 간단하게 시작해서 기반 코드가 커짐에 따라 기술 부채가 누적되고 유지보수가 더 어려워지는 이유
중 하나로 여겨진다.
나는 헥사고날 아키텍처가 무조건 도입되어야 한다고 생각하지 않습니다.
내가 강조하는 것은 개발에는 설계, 즉 규칙이 필요하다는 점입니다. 규칙 없이 1인 개발자의 마음대로 진행된 프로젝트는 다음에 인수인계를 받는 사람이나 함께 일하는 동료에게 얼마나 많은 시간이 소요될까요?
경력자라면 알 겁니다. 신규 입사 시 퇴사자의 대규모 프로젝트를 인수받았을 때 규칙이 없는 프로젝트 덩어리를 맞닥뜨리면 그 복잡함에 당황한 기억이요. 예를 들어, 나의 경험담에서는 service 패키지 안에 ServiceService.java
와 ServiceRepository.java
라는 클래스가 존재했는데, 이들 클래스의 기능이 무엇인지 감도 오지 않았습니다. (당연히 서비스 관련 로직이 있을 줄 알았는데.. 서비스 라는 도메인 이었다.)
제 생각에 우리가 작업하는 개발은 마치 건축과 비슷하다고 생각
합니다. 그래서 아키텍처라는 설계 도면이 꼭 필요하고, 그 도면에 맞춰 뼈대부터 탄탄하게 개발해야 완성된 프로젝트가 시간이 지나도 튼튼하게 유지
될 수 있습니다. 이를 통해 유지보수 비용도 줄어들어, 절감된 자원을 다른 기능 개발에 투자할 수 있다고 생각합니다.
직접 아키텍처를 잘 만들 자신이 없다면, 인터넷에 잘 알려진 클린 아키텍처나 헥사고날 아키텍처와 같은 유명한 설계를 도입해 프로젝트를 설계해보는 것이 좋습니다.
팀에서 규칙만 잘 만들고 지켜진다면 그것또한 아키텍처라고 생각하시면 됩니다.
결국 우리가 회사에서 하는것 - 유지보수
코딩을 자세히 들여다보면 목적은 더 정확히 버그를 수정하고 빠르게 성능을 개선하고 더 많이 기능을 추가
하는 작업입니다.
회사에서는 결국 유지보수를 최종적으로 자주하게 됩니다. 코드를 읽고, 이해하고, 코드를 수정/추가 하는것
프로젝트의 끝은 항상 유지보수라는 점을 명심하자!
백엔드 개발에서 성과를 어떻게 증명할 수 있을까요? 내가 만든 프로젝트에 유지보수 투입이 적거나 전혀 없다면, 그것이 바로 성과입니다. 유지보수 비용이 들지 않으니 말이죠. 이는 상사에게 성과를 증명할 때도 도움이 될 것입니다.
에필로그
만들면서 배우는 헥사고날 아키텍처 설계와 구현 교보문구
책을 구매해 읽으면서 글을 작성하는 데 큰 도움을 받았습니다.
만약 헥사고날 아키텍처 도입을 고민하고 있는 개발자라면, 이 책을 꼭 구매해서 읽어보기를 적극 추천합니다.