📘 클린 아키텍처 북스터디 정리입니다
📚 도서: 로버트 C. 마틴 《Clean Architecture》
🧑💻 목적: 올바른 설계에 대한 감각과 습관을 익히기 위해
🗓️ 진행 기간: 2025년 7월 ~ 매주 2장
우리가 의존하지 않도록 피하고자 하는 것은 바로 변동성이 큰 구체적인 요소다
public class BillingService {
private final MySQLBillingRepository repository = new MySQLBillingRepository(); // 구체 의존
}
구체 → 추상으로 역전시킴BillingServiceBillingRepository, BillingRepositoryFactoryMySQLBillingRepository, MySQLBillingRepositoryFactory// 1. 인터페이스 정의
public interface BillingRepository {
void save(Invoice invoice);
}
// 2. 구체 클래스
public class MySQLBillingRepository implements BillingRepository {
public void save(Invoice invoice) {
// 실제 MySQL DB 저장
}
}
// 3. 추상 팩토리
// BillingRepositoryFactory는 BillingService가 어떤 구현체를 사용하는지 몰라도 되게 해줌
public interface BillingRepositoryFactory {
BillingRepository create();
}
// 4. 구체 팩토리
public class MySQLBillingRepositoryFactory implements BillingRepositoryFactory {
public BillingRepository create() {
return new MySQLBillingRepository();
}
}
// 5. 클라이언트
public class BillingService {
private final BillingRepository repository;
public BillingService(BillingRepositoryFactory factory) {
this.repository = factory.create(); // 어떤 구현체인지 모름
}
public void bill(Invoice invoice) {
repository.save(invoice);
}
}
| 항목 | 추상 팩토리 패턴 | Spring DI (@Autowired, @Bean 등) |
|---|---|---|
| 객체 생성 책임 | 개발자가 팩토리 클래스 직접 작성 | Spring이 객체 생성 및 주입 관리 |
| 구성 방식 | 명시적으로 Factory.create() 호출 | 설정 클래스를 통해 주입 방식 정의 |
| 사용 예시 | 객체 생성 위치를 코드로 분리 | 설정 파일(@Configuration) 또는 자동 주입 |
| 복잡도 | 복잡도 ↑ (팩토리 계층 필요) | 간단함, 생산성 ↑ |
// 1. 인터페이스 정의
public interface BillingRepository {
void save(Invoice invoice);
}
// 2. 구체 구현체
public class MySQLBillingRepository implements BillingRepository {
public void save(Invoice invoice) {
System.out.println("Saving invoice to MySQL");
}
}
public class OracleBillingRepository implements BillingRepository {
public void save(Invoice invoice) {
System.out.println("Saving invoice to Oracle");
}
}
// 3. 비즈니스 로직 클래스
@Service
public class BillingService {
private final BillingRepository billingRepository;
public BillingService(BillingRepository billingRepository) {
this.billingRepository = billingRepository;
}
public void bill(Invoice invoice) {
billingRepository.save(invoice);
}
}
// 4. 명시적인 객체 생성을 담당하는 구성 클래스
@Configuration
public class BillingConfiguration {
@Bean
public BillingRepository mySQLBillingRepository() {
return new MySQLBillingRepository(); // 추상 → 구체 주입
}
@Bean
public BillingRepository oracleBillingRepository() {
return new OracleBillingRepository();
}
@Bean
public BillingService billingService(
@Qualifier("mySQLBillingRepository") BillingRepository repository
) {
return new BillingService(repository);
}
}
의존성 역전 원칙에서 말하는 '유연성이 극대화된 시스템'이란
소스 코드 의존성이 추상에 의존하며 구체에는 의존하지 않는 시스템이다
이 문장은 의존성 역전 원칙(DIP)의 핵심 정신을 잘 담고 있다.
단순히 "구현체에 의존하지 말자"는 메시지를 넘어서,
좋은 소프트웨어 설계를 위해 지향해야 할 방향성을 제시한다.
특히 책에서 설명한 추상 팩토리 패턴을 통한 DIP 실현 예시는
어떻게 의존성의 방향을 '구체 → 추상'으로 역전시킬 수 있는지를 구체적으로 보여준다.
이번 장을 읽으며,
내가 현재 진행 중인 프로젝트에서 사용 중인 Spring 프레임워크에서도
@Bean, @Configuration, @Autowired와 같은 의존성 주입 기법을 통해
DIP 원칙이 자연스럽게 적용되고 있다는 점을 실제 코드와 연관지어 확인할 수 있었다.
앞으로는 클래스 간 의존 구조를 설계할 때,
의존성의 방향성과 추상화 수준을 보다 의식적으로 고려해야겠다는 생각이 들었다.