


The idea of Hexagonal Architecture is to put inputs and outputs at the edges of our design. Business logic should not depend on whether we expose a REST or a GraphQL API, and it should not depend on where we get data from — a database, a microservice API exposed via gRPC or REST, or just a simple CSV file.
1. Inside: 애플리케이션 핵심 (Core)
Domain: 비즈니스 규칙의 정수. Java의 순수 객체(POJO)로 작성되며 외부 라이브러리에 의존하지 않음Use Case: 애플리케이션의 핵심 비즈니스 로직을 수행하는 최소 단위의 실행 흐름을 의미. 입력 포트를 구현하여 비즈니스 흐름을 제어2. Boundary: 포트 (Ports)
3. Outside: 어댑터 (Adapters)
코드 예시
// 1. Domain (순수 java 객체)
public class User {
private Long id;
private String name;
public User(String name) {
this.name = name;
}
// Getter, 비즈니스 로직 등...
}
// 2. Usecase & Port (interface)
// Inbound Port: 외부(Controller)에서 내부로 들어오는 관문
public interface RegisterUserUseCase {
void register(String name);
}
// Outbound Port: 내부에서 외부(DB)로 나가는 관문
public interface SaveUserPort {
void save(User user);
}
// Usecase Imolementaion (service)
@Service
@RequiredArgsConstructor
public class RegisterUserService implements RegisterUserUseCase {
private final SaveUserPort saveUserPort; // Outbound Port 주입
@Override
public void register(String name) {
User user = new User(name);
// 비즈니스 검증 로직 등이 위치함
saveUserPort.save(user);
}
}
// Adapters
// 3. Outbound Adapter (Persistence)'
@Component
@RequiredArgsConstructor
public class UserPersistenceAdapter implements SaveUserPort {
private final UserJpaRepository repository; // 실제 Spring Data JPA 사용
@Override
public void save(User user) {
// Domain 객체를 Entity로 변환하여 저장
repository.save(new UserEntity(user.getName()));
}
}
// DB Entity
package com.example.adapter.out.persistence;
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class UserJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// JPA를 위한 기본 생성자 및 매핑용 메서드들...
public User toDomain() { // DB 엔티티를 도메인 객체로 변환
return new User(this.id, this.name);
}
}
// 4. Inbound Adapter (web)
@RestController
@RequiredArgsConstructor
public class UserController {
private final RegisterUserUseCase registerUserUseCase; // Port 호출
@PostMapping("/users")
public void register(@RequestParam String name) {
registerUserUseCase.register(name);
}
}
전체 경로
src
└── main
└── java
└── com.example
├── domain (핵심 비즈니스)
│ └── User.java <-- [순수 Domain Entity]
│
├── application (비즈니스 로직 조립)
│ ├── port
│ │ ├── in
│ │ │ └── RegisterUserUseCase.java <-- [Interface]
│ │ └── out
│ │ └── SaveUserPort.java <-- [Interface]
│ └── service
│ └── RegisterUserService.java <-- [UseCase 구현체]
│
└── adapter (외부 기술 연결)
├── in
│ └── web
│ └── UserController.java <-- [Spring MVC Controller]
└── out
└── persistence
├── UserJpaEntity.java <-- [DB Entity (JPA)]
├── UserJpaRepository.java <-- [Spring Data JPA]
└── UserPersistenceAdapter.java <-- [Port 구현체]
// 간략
src
└── main
└── java
└── com.example
├── domain
├── application
│ ├── port
│ │ ├── in <-- UseCase 인터페이스 위치
│ │ └── out <-- Persistence Port 인터페이스 위치
│ └── service <-- [UseCase Impl]
└── adapter
├── in
│ └── web <-- Controller 위치
└── out
└── persistence <-- DB Entity, JPA Repository 위치