Files

128 lines
2.7 KiB
Markdown

# port abstraction 예시
## 좋은 예시 1: outbound port를 application이 소유
```java
public interface UserReader {
Optional<User> findByEmail(UserEmail email);
Optional<User> findById(UserId userId);
}
@Repository
public class JpaUserReader implements UserReader {
...
}
```
**왜 좋은가:**
- application이 persistence 기술을 모른다
- 코어가 필요한 조회 능력만 계약으로 드러난다
- adapter만 JPA를 안다
## 좋은 예시 2: inbound port를 use case 계약으로 사용
```java
public interface LoginUseCase {
LoginResult login(LoginCommand command);
}
@RestController
class LoginController {
private final LoginUseCase loginUseCase;
...
}
```
**왜 좋은가:**
- controller가 구현체보다 use case 계약에 의존한다
- HTTP 세부와 비즈니스 흐름이 분리된다
## 좋은 예시 3: external API 경계 포트
```java
public interface TokenSigner {
Signature sign(SigningRequest request);
}
public class VaultTokenSigner implements TokenSigner {
...
}
```
**왜 좋은가:**
- 포트는 “서명한다”는 능력만 표현한다
- HTTP, JSON, Vault path/header는 adapter 구현으로 숨긴다
## 좋은 예시 4: 하나의 포트에 여러 adapter 가능
```java
public interface RateRepository {
BigDecimal findDiscountRate(Money amount);
}
public class InMemoryRateRepository implements RateRepository { ... }
public class JdbcRateRepository implements RateRepository { ... }
```
**왜 좋은가:**
- 테스트와 운영 구현이 같은 계약을 공유한다
- 포트는 기술 수와 무관하게 같은 대화를 표현한다
## 나쁜 예시 1: 기술 타입이 새는 포트
```java
public interface UserApiPort {
ResponseEntity<String> getUser(String id);
}
```
**문제:**
- HTTP 세부가 코어 계약으로 올라온다
- 비즈니스 의미가 아니라 transport 형식이 중심이 된다
## 나쁜 예시 2: adapter 편의 중심 포트
```java
public interface DatabasePort {
String query(String sql);
}
```
**문제:**
- 코어가 SQL/DB 기술 세부를 알게 된다
- “무엇을 원하나”가 아니라 “어떻게 하냐”를 말한다
## 나쁜 예시 3: 너무 범용적인 outbound port
```java
public interface ExternalSystemPort {
Object execute(Object input);
}
```
**문제:**
- 역할이 불명확하다
- 타입 안정성과 계약 의미가 없다
- 여러 외부 시스템 책임을 한 곳에 섞기 쉽다
## 나쁜 예시 4: 내부 helper까지 포트화
```java
public interface EmailNormalizerPort {
String normalize(String raw);
}
```
**문제:**
- 외부 경계가 아니라 내부 로직 detail이다
- 포트 추상화 비용이 이득보다 크다