DI(의존성 주입, Dependency Injection) - 유지보수성과 확장성이 좋아지는 이유

백엔드&인프라 추종자·2025년 2월 26일

스프링 공부

목록 보기
16/35

🚀 DI(의존성 주입, Dependency Injection) - 유지보수성과 확장성이 좋아지는 이유


DI 적용 전 - 직접 객체 생성 (강한 결합, High Coupling)

🔹 문제점: 클래스가 특정 구현체에 직접 의존

  • UserServiceUserRepository의 구체적인 구현체(JdbcUserRepository)를 직접 생성
  • 구현체를 변경할 경우, UserService 내부 코드를 직접 수정해야 함
@Service
public class UserService {
    private final UserRepository userRepository = new JdbcUserRepository(); // 직접 객체 생성

    public User getUser(Long id) {
        return userRepository.findUserById(id);
    }
}

문제점

  1. 유지보수성 저하

    • UserRepository의 다른 구현체(MongoUserRepository)를 사용하려면 직접 수정해야 함
    private final UserRepository userRepository = new MongoUserRepository(); // 직접 변경 필요
    • 수정할 곳이 많아질수록 유지보수가 어려움
  2. 확장성 저하

    • 새로운 구현체를 추가하려면 기존 코드 수정이 필수적
    • OCP(Open-Closed Principle) 위배: 새 기능 추가 시 기존 코드 수정 필요

DI(의존성 주입) 적용 - 유지보수성과 확장성 향상

🔹 해결: 인터페이스 기반으로 느슨한 결합 (Low Coupling)

  • UserServiceUserRepository 인터페이스만 알고 있으며, 구체적인 구현체는 외부에서 주입

1. 인터페이스 정의 (추상화)

public interface UserRepository {
    User findUserById(Long id);
}

2. 구현체 생성

@Repository
public class JdbcUserRepository implements UserRepository {
    @Override
    public User findUserById(Long id) {
        // JDBC를 사용한 데이터 조회 로직
        return new User(id, "JDBC_User");
    }
}
@Repository
public class MongoUserRepository implements UserRepository {
    @Override
    public User findUserById(Long id) {
        // MongoDB를 사용한 데이터 조회 로직
        return new User(id, "Mongo_User");
    }
}

3. DI를 통한 UserService 구현

@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired  // 생성자 주입
    public UserService(UserRepository userRepository) {  
        this.userRepository = userRepository;
    }

    public User getUser(Long id) {
        return userRepository.findUserById(id);
    }
}

🎯 DI 적용 후 개선점

1. 유지보수성 향상

  • UserService 내부 코드를 변경할 필요 없이 외부에서 원하는 구현체를 주입 가능
  • UserRepository의 변경이 필요해도 UserService 수정 없이 변경 가능
// JdbcUserRepository 주입
UserRepository userRepository = new JdbcUserRepository(); 
UserService userService = new UserService(userRepository);

2. 확장성 향상

  • MongoUserRepository, JdbcUserRepository 같은 새로운 구현체가 추가되더라도 기존 코드 수정 없이 사용 가능
// MongoUserRepository 주입
UserRepository userRepository = new MongoUserRepository();  
UserService userService = new UserService(userRepository);
  • OCP(Open-Closed Principle) 준수: 기존 코드 수정 없이 새로운 기능 추가 가능
  • Spring이 관리하는 @Service, @Repository와 함께 사용하면 자동으로 구현체가 주입됨
@Configuration
public class AppConfig {
    @Bean
    public UserRepository userRepository() {
        return new JdbcUserRepository();  // 원하는 구현체로 변경 가능
    }
}

Spring 자동 주입 방식

Spring에서는 @Autowired를 사용하면 자동으로 적절한 구현체가 주입됨

@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {  
        this.userRepository = userRepository;
    }
}
  • @ComponentScan에 의해 @Repository가 자동 등록됨
  • @Autowired가 있는 생성자를 보고 Spring이 적절한 구현체를 자동으로 주입

🎯 비교 정리

DI 미적용 (직접 생성)DI 적용 (의존성 주입)
유지보수성구현체 변경 시 UserService 코드 수정 필요구현체 변경 시 UserService 수정 불필요
확장성새로운 구현체 추가 시 기존 코드 수정 필요새로운 구현체 추가해도 기존 코드 유지
결합도강한 결합 (High Coupling)느슨한 결합 (Low Coupling)
OCP (개방-폐쇄 원칙)기능 확장 시 기존 코드 수정 필요기존 코드 수정 없이 확장 가능
테스트 용이성실제 구현체를 사용해야 함 (Mocking 어려움)Mock 객체를 주입하여 테스트 가능

결론

  • DI를 사용하면 유지보수성이 향상되고, 새로운 기능 추가 시 코드 변경 없이 확장 가능
  • 느슨한 결합을 통해 코드 변경을 최소화하여 OCP(개방-폐쇄 원칙)를 준수
  • Mock 객체 주입을 통해 단위 테스트가 쉬워짐

💡 즉, DI를 사용하면 변경에 유연하고 확장성이 뛰어난 코드가 만들어짐! 🚀

profile
AI 답변 글을 주로 올립니다.

0개의 댓글