F-LAB JAVA · 6주차 · Phase 5 · DataSource 인터페이스 (추상화의 진화)
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
추상화는 변경의 압력이 애플리케이션 코드까지 전파되지 않도록 차단하는 장치로, "커넥션을 얻는 방법" 자체를 인터페이스로 만들면 애플리케이션은 인터페이스에만 의존하고 구체 풀(HikariCP·DBCP2 등)은 외부에서 주입받게 되며, 이는 5주차 ConnectionMaker 와 정확히 같은 사상이다.
추상화가 필요한 이유는 — 풀 라이브러리 (HikariCP, DBCP2 등) 가 바뀔 때마다 애플리케이션의 DB 접근 코드가 모두 바뀌는 OCP 위반 문제 (Unit 5.1) 를 해결하기 위해서다.
5주차에서 본 OCP 정신과 동일한 해결책 — 인터페이스로 추상화 하여 변경의 압력을 인터페이스 경계에서 차단하면 애플리케이션은 인터페이스에만 의존하고 구체 구현이 바뀌어도 영향받지 않는다.
해결의 방향은 "커넥션을 얻는 방법" 자체를 인터페이스로 분리 하는 것 — 5주차의ConnectionMaker가 한 그 역할을 자바 표준으로 끌어올린 것이DataSource(다음 Unit) 다.
5주차의ConnectionMaker와 6주차의DataSource는 같은 사상의 다른 이름 으로, 5주차에 자체적으로 만든 추상화가 사실 자바 표준 라이브러리에 이미 존재했음을 깨닫는 지점이다.
추상화 = 표준 USB 포트:
문제 (Unit 5.1):
- 가전마다 다른 충전기 (HikariCP/DBCP2/...)
- 가전(애플리케이션) 이 충전기 모양에 강결합
- 충전기 바꾸면 가전도 바꿔야
추상화 (표준 USB):
- 표준 포트 (인터페이스)
- 가전은 포트만 알면 됨
- 충전기는 외부에서
변경 압력 차단:
- 새 충전기 나와도 (HikariCP → DBCP2)
- 표준 USB 호환이면
- 가전 그대로
5주차 ConnectionMaker = USB:
- 자체 표준 정의
6주차 DataSource = 자바 표준 USB:
- 업계 표준 (javax.sql)
DIP:
- 가전 → 표준 USB → 충전기
- 가전이 충전기 직접 의존 X
→ 추상화 = 변경 압력 차단 (인터페이스 경계), 5주차 ConnectionMaker = 6주차 DataSource.
1. 추상화의 필요성
2. 변경 압력 차단
3. 5주차 OCP 정신
4. 인터페이스 의존
5. "획득 방법 자체를 인터페이스로"
6. ConnectionMaker = DataSource
7. 추상화의 효과
8. DIP 적용
9. 면접 + 자기 점검
추상화 필요:
Unit 5.1 문제:
- 풀 변경 → 코드 변경
- OCP 위반
- 변경 광범위
→ 변경 압력 차단 필요
추상화의 본질:
세부 사항 숨김:
- "무엇" 만 노출
- "어떻게" 는 숨김
→ 의존을 줄임
변경의 격리:
변경 가능한 부분:
- 추상화 뒤로
- 인터페이스 뒤로
애플리케이션:
- 인터페이스만 의존
- 변경 모름
추상화 필요 (ILIC)
ILIC 현재 (가정, 풀 직접 의존):
- 102 테이블 DAO 가 HikariDataSource 직접
- HikariCP → DBCP2 변경 시 102 개 수정
추상화 (DataSource):
- DAO 는 DataSource (인터페이스) 의존
- HikariCP/DBCP2 외부 주입
- 풀 변경 시 주입만 다르게
- DAO 코드 그대로
→ 추상화로 변경 격리
추상화의 필요성은?
답:
1. 왜:
본질:
격리:
결과:
변경 압력:
외부 변경:
- 새 풀 라이브러리
- 라이브러리 업그레이드
- 요구사항 변경
→ 코드에 압력
차단 = 인터페이스:
인터페이스가 방벽:
- 변경: 인터페이스 뒤
- 애플리케이션: 인터페이스 앞
- 차단
→ 변경이 못 넘어옴
변경 압력 차단:
변경 ────→ |인터페이스| ┃ 애플리케이션
↑ ┃
┃ ← 영향 X
(방벽)
- 변경은 인터페이스 뒤 구현
- 인터페이스는 그대로
- 애플리케이션 영향 X
변경 가능 영역:
추상화 뒤:
- 자유롭게 바뀜
- 새 구현 추가
- 구현 교체
추상화 앞 (애플리케이션):
- 안정
- 인터페이스만 알면 됨
// 변경 압력 차단 (ILIC)
// 인터페이스 (방벽)
interface DataSource {
Connection getConnection() throws Exception;
}
// 애플리케이션 (변경 압력 받지 않음)
public class ShipmentDao {
private final DataSource dataSource; // 인터페이스만
public ShipmentDao(DataSource ds) {
this.dataSource = ds;
}
// → 풀 라이브러리 변경 무관
}
// 구현 (변경 가능 영역)
class HikariDataSource implements DataSource {
public Connection getConnection() { return null; }
}
class DbcpDataSource implements DataSource {
public Connection getConnection() { return null; }
}
// HikariDataSource → DbcpDataSource 교체:
// → ShipmentDao 그대로
// → 주입만 다르게
변경 압력 차단의 의미는?
답:
1. 변경 압력:
차단:
시각화:
영역:
5주차 OCP:
"확장 열림, 변경 닫힘"
- 확장: 새 구현 추가 (열림)
- 변경: 기존 코드 변경 X (닫힘)
→ 추상화로 달성
OCP 달성:
새 풀 추가 (확장 ✅):
- 새 DataSource 구현
- 애플리케이션 코드 변경 X (닫힘 ✅)
→ OCP 준수
5주차 패턴 재등장:
- OCP
- DIP
- 전략 패턴
- 인터페이스 추상화
→ 모두 적용
자바 표준의 OOP:
자바 라이브러리:
- OCP 정신 구현
- DataSource 가 그 예
- 5주차 정신을 표준화
→ 좋은 라이브러리 = 좋은 OOP
// 5주차 OCP 정신 (ILIC)
// 5주차 ConnectionMaker (OCP)
interface ConnectionMaker {
Connection makeConnection() throws Exception;
}
// 새 구현 추가 (확장)
class NewConnectionMaker implements ConnectionMaker {
public Connection makeConnection() { return null; }
}
// 6주차 DataSource (자바 표준, 같은 OCP)
interface DataSource {
Connection getConnection() throws Exception;
}
// 새 풀 추가 (확장)
class CustomDataSource implements DataSource {
public Connection getConnection() { return null; }
}
// 애플리케이션:
public class ShipmentDao {
private final DataSource ds;
public ShipmentDao(DataSource ds) { this.ds = ds; }
// OCP 준수: 새 DataSource 와도 호환
}
// → 5주차 정신 = 6주차 자바 표준
5주차 OCP 정신의 적용은?
답:
1. OCP:
달성:
재등장:
자바 표준:
애플리케이션 → 인터페이스:
구체 풀 의존 X:
- HikariCP/DBCP2 모름
인터페이스만:
- DataSource (다음 Unit)
- getConnection() 만 안다
구체는 모름:
애플리케이션:
- DataSource.getConnection()
- 반환된 Connection 사용
실제 구현:
- HikariCP? DBCP2? 알 필요 X
- 외부가 결정
외부에서 주입:
애플리케이션:
- 인터페이스만 의존
- 구체는 외부 (DI)
→ 5주차 DI
코드의 단순함:
애플리케이션 코드:
- 인터페이스 메서드만
- 짧고 명확
- 풀 설정 X (외부)
// 인터페이스 의존 (ILIC)
@Service
public class ShipmentService {
private final ShipmentDao shipmentDao;
public ShipmentService(ShipmentDao dao) {
this.shipmentDao = dao;
}
// ShipmentService 는 ShipmentDao 만 알면 됨
// ShipmentDao 가 어떤 풀 쓰는지 X
}
@Repository
public class ShipmentDao {
private final DataSource dataSource; // 인터페이스
public ShipmentDao(DataSource ds) {
this.dataSource = ds;
}
// ShipmentDao 는 DataSource 만 알면 됨
// 구체 풀 X (Spring 이 주입)
public Connection getConn() throws Exception {
return dataSource.getConnection(); // 인터페이스 메서드
}
}
// 구체 (외부, Spring Boot 자동)
// HikariDataSource 가 DataSource 구현
// → ShipmentDao 는 모름 (그저 DataSource)
interface DataSource {
Connection getConnection() throws Exception;
}
애플리케이션이 인터페이스에만 의존한다는 것은?
답:
1. 인터페이스 의존:
구체:
주입:
이점:
핵심 통찰:
"커넥션을 얻는 방법" 자체를
인터페이스로 분리.
- 행위 (얻기) 를 인터페이스
- 구현 (어떻게 얻나) 는 자유
행위의 추상화:
Connection 얻는 행위:
- getConnection()
- 단 하나의 메서드
- 모든 풀 공통
→ 공통 행위 추출
다양한 구현:
같은 행위 (getConnection):
- DriverManager 가 직접
- HikariCP 가 풀에서
- DBCP2 가 풀에서
- Mock 이 가짜로 (테스트)
→ 인터페이스 → N 개 구현
자바 표준화:
javax.sql.DataSource:
- 자바가 표준화
- 모든 풀 구현
- 다음 Unit 5.3 (★깊이)
// "획득 방법 자체를 인터페이스로" (ILIC)
// 인터페이스 (획득 방법 추상화)
interface DataSource {
Connection getConnection() throws Exception;
// 단 하나의 핵심 메서드
// "어떻게 얻든 상관없이, 얻어다오"
}
// 다양한 구현 (어떻게 얻는지 다름)
// 1. DriverManager 로 (풀 X)
class DriverManagerDataSource implements DataSource {
private final String url;
public DriverManagerDataSource(String url) { this.url = url; }
public Connection getConnection() throws Exception {
return DriverManager.getConnection(url);
}
}
// 2. HikariCP 로 (풀)
// HikariDataSource implements DataSource (Hikari 제공)
// 3. Mock (테스트)
class MockDataSource implements DataSource {
public Connection getConnection() {
return null; // 가짜
}
}
// 애플리케이션: 어느 구현이든 OK
ShipmentDao dao = new ShipmentDao(/* 어떤 DataSource든 */);
class ShipmentDao { ShipmentDao(DataSource ds) {} }
"커넥션 획득 방법 자체를 인터페이스로" 의 의미는?
답:
1. 핵심 통찰:
행위:
구현:
자바 표준:
ConnectionMaker = DataSource:
같은 사상의 다른 이름:
- 5주차 자체 정의
- 6주차 자바 표준
→ 같은 추상화
| 5주차 | 6주차 (자바 표준) |
|---|---|
| ConnectionMaker | DataSource |
| makeConnection() | getConnection() |
| NConnectionMaker | HikariDataSource |
| DConnectionMaker | DriverManagerDataSource |
깨달음:
5주차에서:
- 자체 ConnectionMaker 정의
6주차에서:
- 자바가 이미 DataSource 로
- 우리 추상화가 표준 그 자체
→ "내가 만든 게 사실 표준이었다"
표준의 가치:
자바 표준 (DataSource):
- 모든 풀이 구현
- 호환성
- 다른 라이브러리도 알아봄
자체 추상화:
- 한 프로젝트 안에서만
→ 표준 사용 권장
// 5주차 ConnectionMaker = 6주차 DataSource (ILIC)
// 5주차 (자체)
interface ConnectionMaker {
Connection makeConnection() throws Exception;
}
// 6주차 (자바 표준)
// import javax.sql.DataSource;
interface DataSource {
Connection getConnection() throws Exception;
}
// 메서드 이름만 다름, 사상 동일
// ILIC:
// - 5주차 정신을 6주차에선 자바 표준으로
// - HikariCP / DriverManagerDataSource 등 모두 DataSource 구현
// - ShipmentDao 는 DataSource 만 의존
// → 자체 추상화 학습이 자바 표준 이해의 기반
5주차 ConnectionMaker 와 6주차 DataSource 는 같은 사상?
답:
1. 같은 사상:
매핑:
깨달음:
권장:
추상화 효과:
1. 유연성 (풀 교체)
2. 테스트 (Mock)
3. 결합도 ↓
4. 확장성
5. 변경 격리
유연성:
풀 교체:
- 주입만 다르게
- 코드 그대로
환경별:
- prod: HikariCP
- test: H2 + DriverManagerDataSource
// 테스트 (Mock 주입)
@Test
void test() {
DataSource mockDs = mock(DataSource.class);
Connection mockConn = mock(Connection.class);
when(mockDs.getConnection()).thenReturn(mockConn);
ShipmentDao dao = new ShipmentDao(mockDs);
// 실제 DB 없이 테스트
}
// 추상화 덕분에 Mock 주입 가능
환경별 구현:
운영: HikariDataSource
테스트: 인메모리 DB (H2)
단위 테스트: Mock
→ 환경 따라 주입만 다르게
// 추상화 효과 (ILIC)
// 1. 환경별 (Spring 프로파일)
@Configuration
@Profile("prod")
class ProdDataSourceConfig {
@Bean DataSource dataSource() {
// HikariCP (운영)
return null;
}
}
@Configuration
@Profile("test")
class TestDataSourceConfig {
@Bean DataSource dataSource() {
// H2 (테스트)
return null;
}
}
// 2. 단위 테스트
class ShipmentDaoTest {
@Test
void test() {
DataSource mock = mock(DataSource.class); // Mock
ShipmentDao dao = new ShipmentDao(mock);
// DB 없이 테스트
}
}
// 3. 새 풀 도입 시
// - 새 DataSource 구현
// - 빈 등록만
// - 코드 변경 X
class ShipmentDao { ShipmentDao(DataSource ds) {} }
추상화의 효과 (유연성/테스트/교체) 는?
답:
1. 효과:
유연성:
테스트:
환경별:
DIP (의존 역전 원칙):
"고수준 모듈은 저수준 모듈에 의존 X
둘 다 추상에 의존"
→ 추상화 가운데
DIP 적용:
고수준: 애플리케이션 (ShipmentDao)
저수준: 구체 풀 (HikariDataSource)
추상: DataSource (인터페이스)
- ShipmentDao → DataSource ← HikariDataSource
의존 방향 역전:
전 (DIP 위반):
ShipmentDao → HikariDataSource
(고수준 → 저수준)
후 (DIP 준수):
ShipmentDao → DataSource ← HikariDataSource
(둘 다 추상 의존)
5주차 DIP 복습:
UserDao → ConnectionMaker ← NConnectionMaker
6주차 적용:
ShipmentDao → DataSource ← HikariDataSource
→ 같은 DIP, 다른 인터페이스 이름
// DIP 적용 (ILIC)
// 추상 (인터페이스, 가운데)
interface DataSource {
Connection getConnection() throws Exception;
}
// 고수준 (애플리케이션) — 추상 의존
public class ShipmentDao {
private final DataSource ds; // 추상
public ShipmentDao(DataSource ds) { this.ds = ds; }
}
// 저수준 (구체 풀) — 추상 구현
class HikariDataSource implements DataSource {
public Connection getConnection() { return null; }
}
class DbcpDataSource implements DataSource {
public Connection getConnection() { return null; }
}
// 의존 방향:
// ShipmentDao → DataSource ← HikariDataSource
// 둘 다 추상을 가리킴 (DIP)
// 의존 역전: 풀이 애플리케이션을 안 알아도 OK
// 풀이 인터페이스에 맞춤 (역전)
DIP (의존 역전) 적용은?
답:
1. DIP:
적용:
역전:
5주차:
| Q | 핵심 답변 |
|---|---|
| 추상화 필요? | 변경 압력 차단 |
| 변경 압력 차단? | 인터페이스 방벽 |
| 5주차 정신? | OCP, DIP |
| 인터페이스 의존? | 구체 모름 |
| 획득 방법 추상화? | getConnection 하나 |
| ConnectionMaker=DataSource? | 같은 사상 |
| 효과? | 유연/테스트/교체 |
| DIP? | 둘 다 추상 |
| 표준 가치? | 호환성 |
| 5주차 vs 6주차? | 자체 vs 표준 |
답:
답:
답:
답:
답:
1. 추상화 = 변경 압력 차단
2. 5주차 OCP 정신의 적용
3. ConnectionMaker = DataSource
이번 Unit에서 추상화 필요성을 봤다면, 다음은 DataSource 인터페이스 (★ 깊이 파기).
🔌 Phase 5 — DataSource 인터페이스
✅ Unit 5.1 다양한 커넥션 획득 방식
✅ Unit 5.2 추상화의 필요성 ← 여기
⏭ Unit 5.3 DataSource 인터페이스 ★깊이
⏭ Unit 5.4 DriverManagerDataSource
🧪 Part A (9 Unit) ✅
💾 Part B — DB 접근의 진화
✅ Phase 3 — JDBC (3)
✅ Phase 4 — Connection Pool (4)
🔌 Phase 5 — DataSource (2/4)
총: 18/28 Unit