142 lines
3.1 KiB
Markdown
142 lines
3.1 KiB
Markdown
# interface 생성 예시
|
|
|
|
## 좋은 예시 1: application output port
|
|
|
|
```java
|
|
public interface UserReader {
|
|
Optional<User> findByEmail(UserEmail email);
|
|
Optional<User> findById(UserId userId);
|
|
}
|
|
|
|
@Repository
|
|
public class JpaUserReader implements UserReader {
|
|
...
|
|
}
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- application이 persistence 구현을 모른다
|
|
- 바깥 구현 교체와 테스트 대역 주입이 쉽다
|
|
- 레이어 경계가 분명하다
|
|
|
|
## 좋은 예시 2: 외부 시스템 client contract
|
|
|
|
```java
|
|
public interface VaultTransitClient {
|
|
String sign(String keyName, byte[] input);
|
|
PublicKey readPublicKey(String keyName);
|
|
}
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- 외부 연동 경계가 분명하다
|
|
- HttpClient/WebClient/Jackson 세부가 계약에 새지 않는다
|
|
- fake/stub 구현으로 테스트하기 쉽다
|
|
|
|
## 좋은 예시 3: 교체 가능한 정책 객체
|
|
|
|
```java
|
|
public interface PasswordHasher {
|
|
String hash(String rawPassword);
|
|
boolean matches(String rawPassword, String encodedPassword);
|
|
}
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- 알고리즘 교체 가능성이 실제로 있다
|
|
- application/domain이 구체 해시 구현을 모른다
|
|
|
|
## 좋은 예시 4: 인터페이스 없이 concrete class 유지
|
|
|
|
```java
|
|
@Component
|
|
public class LoginResponseAssembler {
|
|
public LoginResponse toResponse(User user, TokenPair tokenPair) {
|
|
...
|
|
}
|
|
}
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- 내부 presentation helper일 뿐 계약 경계가 아니다
|
|
- 구현체 1개, 교체 가치 낮음, 인터페이스 이득 작음
|
|
- 불필요한 LoginResponseAssemblerImpl을 만들지 않는다
|
|
|
|
## 나쁜 예시 1: 의미 없는 Service/Impl 쌍
|
|
|
|
```java
|
|
public interface UserService {
|
|
User create(CreateUserCommand command);
|
|
}
|
|
|
|
@Service
|
|
public class UserServiceImpl implements UserService {
|
|
...
|
|
}
|
|
```
|
|
|
|
**문제:**
|
|
|
|
- 실제 경계/교체/테스트 seam 의미가 약하다
|
|
- 타입만 늘고 추상화 이득이 거의 없다
|
|
- “관성적인 인터페이스”에 가깝다
|
|
|
|
## 나쁜 예시 2: 기술 세부를 계약에 노출
|
|
|
|
```java
|
|
public interface UserClient {
|
|
ResponseEntity<String> getUser(String id);
|
|
}
|
|
```
|
|
|
|
**문제:**
|
|
|
|
- Spring Web 타입이 계약에 박힌다
|
|
- 호출자가 구현 기술에 묶인다
|
|
|
|
**개선:**
|
|
|
|
- 도메인/애플리케이션에 더 맞는 결과 타입으로 계약 정의
|
|
|
|
## 나쁜 예시 3: 여러 책임을 한 인터페이스에 몰아넣기
|
|
|
|
```java
|
|
public interface UserManager {
|
|
User findUser(...);
|
|
User saveUser(...);
|
|
void sendEmail(...);
|
|
String issueToken(...);
|
|
}
|
|
```
|
|
|
|
**문제:**
|
|
|
|
- 하나의 역할이 아니다
|
|
- 호출자마다 일부만 필요할 가능성이 높다
|
|
- 응집도가 낮다
|
|
|
|
## 나쁜 예시 4: 조기 추상화
|
|
|
|
```java
|
|
public interface DeadlineService {
|
|
void setDeadline(...);
|
|
}
|
|
|
|
public class TaskDeadlineService implements DeadlineService { ... }
|
|
|
|
public class PaymentDeadlineService implements DeadlineService { ... }
|
|
```
|
|
|
|
**문제:**
|
|
|
|
- 지금은 비슷해 보여도 미래에 다르게 진화할 수 있다
|
|
- 아직 공통 계약이 자연스러운지 검증되지 않았다
|
|
|
|
**개선 방향:**
|
|
|
|
- 충분한 공통성/경계 필요가 생길 때까지 분리된 concrete class 유지
|