F-LAB JAVA · 5주차 · Phase 4 · 관심사의 분리
🌱 Phase 4 시작 — ★ 깊이 파기 (Spring 이해의 출발점)
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
관심사의 분리 (Separation of Concerns) 는 "관심이 같은 것끼리는 하나로 모으고, 관심이 다른 것은 떨어뜨려라" 는 리팩토링의 가장 기본 원리로, 여기서 관심사 (Concern) 는 코드가 다루는 하나의 주제·책임이며 변경의 이유와 일치한다.
관심사 는 코드가 신경 쓰는 하나의 주제로, 전통 DAO 에서는 "DB 연결", "SQL 실행", "예외 처리" 등이 각각의 관심사다.
"같은 것끼리 모으고 다른 것은 떨어뜨려라" 는 같은 관심사 (예: 모든 연결 생성 코드) 는 한 곳에 모아 관리하고, 다른 관심사 (연결과 SQL) 는 분리하라는 뜻이다.
관심사는 변경의 이유와 일치 하므로 (DB 변경 시 연결 관심사만, 스키마 변경 시 SQL 관심사만 바뀜), 관심사 분리는 곧 SRP (단일 책임 원칙) 와 직결된다.
처음엔 모든 코드를 한 메서드에 모으는 게 편하지만, 시스템이 커지면 분리하지 않으면 변경이 어려워져 살아남을 수 없으므로, DAO 에서 가장 먼저 분리할 관심사는 반복되는 DB 연결 생성 이다.
관심사의 분리 = 주방 정리:
같은 것끼리 모으기:
- 칼은 칼꽂이에
- 양념은 양념통에
- 그릇은 그릇장에
다른 것은 떨어뜨리기:
- 칼과 양념 섞지 않기
- 각자 자리
처음엔 (분리 X):
- 다 한 서랍에 (편함)
- 작을 땐 OK
커지면 (분리 필요):
- 뭐가 어디 있는지 모름
- 칼 찾다 베임
- 정리해야 살아남음
관심사 = 주방 도구의 종류:
- 자르는 도구 (관심사 1)
- 양념 (관심사 2)
- 변경 이유 다름
→ 관심사의 분리 = 같은 것 모으고 다른 것 분리, 관심사 = 변경의 이유.
1. 관심사의 분리 원칙
2. 관심사(Concern)의 의미
3. 모으고 떨어뜨리기
4. 관심사와 책임
5. 변경의 이유와 관심사
6. DAO의 첫 관심사
7. 왜 분리하는가
8. SRP와의 관계
9. 면접 + 자기 점검
관심사의 분리 (Separation of Concerns):
"관심이 같은 것끼리는 하나로 모으고,
관심이 다른 것은 떨어뜨려라"
리팩토링의 가장 기본 원리
핵심 아이디어:
코드를 관심사 단위로:
- 같은 관심사 → 모음
- 다른 관심사 → 분리
→ 각 관심사 독립 관리
전통 DAO 문제 (관심사 관점):
한 메서드에:
- 연결 관심사
- SQL 관심사
- 예외 관심사
여러 관심사 섞임
→ 분리 필요
분리 방향:
1단계: 메서드 추출 (같은 관심사 모음)
2단계: 클래스 분리 (다른 관심사 분리)
3단계: 인터페이스 (추상화)
→ 점진적 분리
// 관심사 분리 전 (혼재)
public class ShipmentDaoBefore {
public void add(Shipment s) throws Exception {
// [연결 관심사] + [SQL 관심사] 혼재
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(...);
PreparedStatement ps = c.prepareStatement("insert ...");
ps.executeUpdate();
ps.close(); c.close();
}
}
// 관심사 분리 후 (방향)
public class ShipmentDaoAfter {
// [SQL 관심사] 만 남김
public void add(Shipment s) throws Exception {
Connection c = getConnection(); // [연결 관심사] 분리
PreparedStatement ps = c.prepareStatement("insert ...");
ps.executeUpdate();
ps.close(); c.close();
}
// [연결 관심사] 한 곳에 모음
private Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(...);
}
}
관심사의 분리 원칙은?
답:
1. 원칙:
아이디어:
DAO 문제:
방향:
관심사 (Concern):
코드가 다루는 하나의 주제·책임.
- 코드가 신경 쓰는 것
- 변경의 이유
관심사 예시 (DAO):
- "DB 에 어떻게 연결하나" (연결)
- "어떤 SQL 을 실행하나" (SQL)
- "예외를 어떻게 처리하나" (예외)
- "자원을 어떻게 해제하나" (해제)
각각이 관심사
관심사 식별:
"이 코드가 다루는 주제는?"
- 연결 생성 → 연결 관심사
- 쿼리 실행 → SQL 관심사
"이 코드가 바뀌는 이유는?"
- DB 변경 → 연결 관심사
관심사 크기:
너무 작으면:
- 과도한 분리
- 복잡
너무 크면:
- 여러 책임
- 혼재
→ 적절한 단위
// ILIC DAO 의 관심사들
public class ShipmentDao {
public void add(Shipment s) throws Exception {
// 관심사 1: DB 연결 (어떻게 연결?)
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(...);
// 관심사 2: SQL 실행 (무엇을 저장?)
PreparedStatement ps = c.prepareStatement(
"insert into shipments(id, bl_no, weight) values(?, ?, ?)");
ps.setLong(1, s.getId());
ps.executeUpdate();
// 관심사 3: 자원 해제 (어떻게 정리?)
ps.close();
c.close();
// 관심사 4: 예외 처리 (throws)
}
// 4가지 관심사 식별 → 분리 대상
}
관심사(Concern)의 의미는?
답:
1. 관심사:
예시:
식별:
크기:
같은 관심사 모으기:
흩어진 같은 관심사:
- 메서드마다 연결 코드
→ 한 곳으로 (getConnection)
→ 중복 제거
→ 한 곳 관리
다른 관심사 분리:
섞인 다른 관심사:
- 연결 + SQL 한 메서드
→ 분리 (연결은 별도)
→ 각자 독립
→ 변경 격리
// 흩어진 연결 코드 (같은 관심사)
public void add(...) {
Connection c = DriverManager.getConnection(...); // 여기
}
public void get(...) {
Connection c = DriverManager.getConnection(...); // 여기
}
// 모으기 (한 곳)
private Connection getConnection() {
return DriverManager.getConnection(...); // 한 곳
}
public void add(...) {
Connection c = getConnection(); // 사용
}
public void get(...) {
Connection c = getConnection(); // 사용
}
// 연결 관심사 분리 (다른 클래스로)
class ConnectionMaker { // 연결 관심사만
Connection makeConnection() { ... }
}
class ShipmentDao { // SQL 관심사만
private ConnectionMaker connectionMaker;
// 연결은 ConnectionMaker 에 위임
}
// 두 관심사 떨어뜨림
// 모으기 + 떨어뜨리기
// 1단계: 모으기 (메서드 추출)
public class ShipmentDao {
private Connection getConnection() throws Exception {
// 흩어진 연결 코드 모음
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
}
public void add(Shipment s) throws Exception {
Connection c = getConnection(); // 모은 것 사용
// SQL...
}
}
// 2단계: 떨어뜨리기 (클래스 분리, Phase 6)
// ConnectionMaker (연결) + ShipmentDao (SQL)
"관심이 같은 것끼리 모으고 다른 것은 떨어뜨려라" 의 뜻은?
답:
1. 같은 것 모으기:
다른 것 분리:
모으기:
떨어뜨리기:
관심사와 책임:
거의 같은 의미:
- 관심사 (Concern)
- 책임 (Responsibility)
맥락 차이:
- 관심사: 코드가 다루는 주제
- 책임: 클래스/메서드가 해야 할 일
미묘한 차이:
관심사:
- 더 넓은 개념
- 횡단 관심사 (cross-cutting)
예: 로깅, 트랜잭션
책임:
- 객체/클래스 단위
- SRP 의 책임
횡단 관심사 (Cross-cutting Concern):
여러 모듈에 걸친 관심사:
- 로깅
- 보안
- 트랜잭션
→ AOP 로 분리 (나중에)
실무에서:
관심사 ≈ 책임:
- 보통 같이 사용
- "이 코드의 관심사/책임은?"
→ SRP: 하나의 책임
→ SoC: 관심사 분리
// 관심사 = 책임 (보통)
public class ShipmentDao {
// 책임/관심사: 배송 데이터 접근
public void add(Shipment s) { }
public Shipment get(Long id) { return null; }
}
// 횡단 관심사 예시 (AOP)
@Aspect
public class LoggingAspect {
// 로깅: 여러 클래스에 걸친 횡단 관심사
@Around("execution(* com.ilic.dao.*.*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
log.info("DAO 호출: {}", pjp.getSignature());
return pjp.proceed();
// 모든 DAO 에 로깅 (횡단 관심사 분리)
}
}
관심사와 책임(Responsibility)의 관계는?
답:
1. 관계:
차이:
횡단 관심사:
실무:
관심사 = 변경의 이유:
관심사가 다르면:
- 변경 이유 다름
연결 관심사:
- DB 변경 시
SQL 관심사:
- 스키마 변경 시
변경 이유로 분리:
"이 코드가 바뀌는 이유는?"
- 여러 이유 → 여러 관심사
- 분리
한 클래스 = 한 변경 이유
DAO 변경 이유:
- DB 종류 변경 → 연결 관심사
- 접속 정보 변경 → 연결 관심사
- 스키마 변경 → SQL 관심사
- 비즈니스 규칙 → 서비스
→ 변경 이유별 분리
격리 효과:
관심사 분리하면:
- DB 변경 → 연결 코드만
- 스키마 변경 → SQL 만
- 서로 영향 X
→ 변경 격리
// 변경 이유별 분리
// 연결 관심사 (DB 변경 이유)
class ConnectionMaker {
Connection makeConnection() {
// DB 종류/접속 정보 변경 → 여기만
return null;
}
}
// SQL 관심사 (스키마 변경 이유)
class ShipmentDao {
private final ConnectionMaker connectionMaker;
public void add(Shipment s) {
Connection c = connectionMaker.makeConnection();
// 스키마 변경 → 여기 SQL 만
// 연결 변경 → 여기 안 건드림 (격리)
}
}
// 비즈니스 관심사 (규칙 변경 이유)
class ShipmentService {
private final ShipmentDao dao;
// 비즈니스 규칙 변경 → 여기만
}
변경의 이유와 관심사의 연결은?
답:
1. 관심사 = 변경 이유:
분리:
예시:
격리:
DAO 첫 분리 대상:
반복되는 DB 연결 생성:
- 모든 메서드에 중복
- 가장 명백한 관심사
→ getConnection 분리 (Phase 4.2)
왜 연결부터:
- 가장 많이 중복
- 변경 잦음 (DB)
- 분리 명확
- 효과 큼
→ 첫 분리로 적합
연결 관심사:
- 드라이버 로딩
- URL/계정/비밀번호
- Connection 생성
→ 한 곳으로 (getConnection)
분리 후 변화:
전:
add, get, update ... 연결 코드 중복
후:
getConnection (한 곳)
add, get ... getConnection 사용
→ DB 변경 = getConnection 만
// DAO 첫 관심사 = 연결 생성
// 분리 전 (중복)
public class ShipmentDaoBefore {
public void add(Shipment s) throws Exception {
Class.forName("com.mysql.jdbc.Driver"); // 연결 (중복)
Connection c = DriverManager.getConnection(...);
}
public Shipment get(Long id) throws Exception {
Class.forName("com.mysql.jdbc.Driver"); // 연결 (중복)
Connection c = DriverManager.getConnection(...);
return null;
}
}
// 분리 후 (한 곳)
public class ShipmentDaoAfter {
// 연결 관심사 한 곳에 모음
private Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost/ilic", "root", "password");
}
public void add(Shipment s) throws Exception {
Connection c = getConnection(); // 사용
}
public Shipment get(Long id) throws Exception {
Connection c = getConnection(); // 사용
return null;
}
// DB 변경 → getConnection 만 수정
}
DAO에서 분리해야 할 첫 번째 관심사는?
답:
1. 첫 대상:
왜:
연결 관심사:
분리 후:
처음엔 한데 모은 게 편함:
작은 시스템:
- 한 곳에 다 (편함)
- 분리 오버헤드 X
- 빠른 개발
초기엔 OK
커지면 문제:
시스템 성장:
- 코드 비대
- 변경 어려움
- 중복 누적
- 유지보수 ↓
→ 분리 안 하면 살아남기 어려움
분리 효과:
1. 변경 용이
- 관심사별 격리
2. 재사용
- 분리된 관심사
3. 테스트
- 독립 테스트
4. 이해
- 명확한 구조
트레이드오프:
분리:
+ 변경/재사용/테스트
- 초기 복잡도
안 분리:
+ 초기 단순
- 성장 시 부담
→ 시스템 규모에 따라
// ILIC 성장 시나리오
// 초기 (작음) - 한데 모아도 OK
// public void add(Shipment s) { /* 다 한 곳 */ }
// 성장 (102 테이블, 431 API)
// → 분리 필수
public class ShipmentDao {
private final ConnectionMaker connectionMaker; // 연결 분리
private final SqlExecutor sqlExecutor; // 실행 분리
// 각 관심사 분리 → 102 테이블 관리 가능
// 분리 안 했으면 → 유지보수 불가
}
interface ConnectionMaker { Connection makeConnection(); }
interface SqlExecutor { void execute(String sql); }
처음엔 한데 모은 게 편한데 분리하는 이유는?
답:
1. 처음엔 편함:
커지면 문제:
효과:
트레이드오프:
SRP (Single Responsibility Principle):
"클래스는 단 하나의 책임만"
- 변경 이유 하나
= 관심사 분리의 클래스 버전
SoC vs SRP:
SoC (관심사 분리):
- 더 넓은 원칙
- 모든 수준 (시스템/모듈/클래스)
SRP (단일 책임):
- SoC 의 클래스 적용
- 하나의 책임
함께 작동:
관심사 분리:
- 관심사별로 나눔
SRP:
- 각 클래스 = 하나의 관심사/책임
→ 관심사 분리 = SRP 달성
적용:
전통 DAO:
- 여러 관심사 (SoC 위반)
- 여러 책임 (SRP 위반)
분리 후:
- ConnectionMaker (연결 책임)
- ShipmentDao (SQL 책임)
- 각자 SRP
// SoC + SRP 적용
// 연결 관심사 = 연결 책임 (SRP)
interface ConnectionMaker {
Connection makeConnection();
}
// 변경 이유: DB 연결 방식
// SQL 관심사 = 데이터 접근 책임 (SRP)
class ShipmentDao {
private final ConnectionMaker connectionMaker;
public void add(Shipment s) { }
}
// 변경 이유: 배송 데이터 접근
// 비즈니스 관심사 = 비즈니스 책임 (SRP)
class ShipmentService {
private final ShipmentDao dao;
public void processBooking(Shipment s) { }
}
// 변경 이유: 비즈니스 규칙
// 각 클래스: 하나의 관심사 = 하나의 책임
SRP와 관심사 분리의 관계는?
답:
1. SRP:
SoC vs SRP:
함께:
적용:
| Q | 핵심 답변 |
|---|---|
| 관심사 분리? | 같은 것 모음, 다른 것 분리 |
| 관심사? | 코드가 다루는 주제 (변경 이유) |
| 모으고 떨어뜨리기? | 중복 제거, 변경 격리 |
| 관심사 vs 책임? | 거의 같음 (SoC vs SRP) |
| 변경 이유? | 관심사 = 변경 이유 |
| 첫 관심사? | DB 연결 생성 |
| 왜 분리? | 성장 시 살아남기 |
| 효과? | 변경/재사용/테스트 |
| SRP 관계? | 관심사 분리 = SRP |
| 횡단 관심사? | 로깅/트랜잭션 (AOP) |
답:
답:
답:
답:
답:
1. 관심사의 분리
2. 관심사 = 변경의 이유
3. DAO 첫 분리
이번 Unit에서 관심사 분리 개념을 봤다면, 다음은 메서드 추출 (실제 리팩토링).
🌱 Phase 4 — 관심사의 분리 (★ 깊이)
✅ Unit 4.1 관심사 분리 개념 ← 여기
⏭ Unit 4.2 1단계: 메서드 추출
⏭ Unit 4.3 2단계: 추상클래스로 확장
✅ Part A — 동시성 마무리 (7 Unit)
🌱 Part B — 토비의 스프링
✅ Phase 3 — 전통 DAO (3 Unit)
🌱 Phase 4 — 관심사의 분리 (1/3 진행)
총: 11/26 Unit
🌱 Phase 4 시작 — ★ 깊이 파기