프로젝트 개요 : 이것이 취업을 위한 백엔드 개발이다 클론코딩 - 상품관리 애플리케이션
프로젝트 환경 : IntelliJ, SpringBoot, MySQL
프로젝트 코드 : https://github.com/smkim9202/ProductManagement
애플리케이션 실행 환경
각 상황에 따른 의존성 주입
애플리케이션 로컬 개발 환경(테스트 환경) : ListProductRepository 사용
애플리케이션 서버 환경(서비스 환경) : DatabaseProductRepository 사용
인터페이스 의존
코드 변경
ProductRepository.java : 인터페이스 정의
package kr.co.api.product.management.domain;
import java.util.List;
public interface ProductRepository {
Product add(Product product);
Product findById(Long id);
List<Product> findAll();
List<Product> findByNameContaining(String name);
Product update(Product product);
void delete(Long id);
}
도메인 계층에 위치
다른 모든 계층은 인프라스트럭처 계층에 의존하면 안된다.
인터페이스를 infrastructure에 위치시키면 Service에서 인프라스트럭처 계층 방향으로 의존성이 생기기 때문에 domain 계층에 위치 시켜야 한다.
메서드
두 클래스가 가진 public 메서드를 인터페이스가 가지고 있어야 한다.
~Repository.java : 인터페이스 implements
@Repository
public class DatabaseProductRepository implements ProductRepository {
@Repository
public class ListProductRepository implements ProductRepository {
SimpleProductService.java : 상품 리포지토리 클래스에서 상품 인터페이스에 의존하도록 변경
//private ListProductRepository listProductRepository;
//private DatabaseProductRepository databaseProductRepository;
private ProductRepository productRepository;
DatabaseProductRepository 주석처리 후 databaseProductRepository 부분 productRepository 변경
애플리케이션 실행
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in kr.co.api.product.management.application.SimpleProductService required a single bean, but 2 were found:
- databaseProductRepository: defined in file [/Users/smkim/devcode/springPrj/product.management/target/classes/kr/co/api/product/management/infrastructure/DatabaseProductRepository.class]
- listProductRepository: defined in file [/Users/smkim/devcode/springPrj/product.management/target/classes/kr/co/api/product/management/infrastructure/ListProductRepository.class]
This may be due to missing parameter name information
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
Ensure that your compiler is configured to use the '-parameters' flag.
You may need to update both your build tool settings as well as your IDE.
(See https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#parameter-name-retention)
Process finished with exit code 1
에러 원인
SimpleProductService 생성자의 0번째 Parameter에는 하나의 Bean이 필요한데, 2개가 들어갈 수 있어서 어떤 Bean을 넣어야 할지 알 수 없다.
둘 중 하나의 빈에 @Primary를 붙이던가, 여러 개의 빈을 주입받을 수 있도록 변경하던가, @Qualifier를 붙여서 어떤 빈이 주입되록 할지 지정하는 방법들로 해결해야 한다.
의존 방향
구체적인 존재가 아닌 추상적인 존재에 의존하도록 함으로써 애플리케이션의 동작을 코드 변경 없이 Runtime에 결정할 수 있도록 설계한다.
Service가 구체화 된 Repository에 직접 의존 => Service와 구체화 된 Repository가 추상화 된 Repository에 의존하도록 한다.
인터페이스에 의존하는 코드를 추상화에 의존하는 코드라고 표현한다.
변경 전
변경 후
의존성 주입과 @Profile
Spring Profiles 적용
~Repository.java 클래스 : @Profile 적용
@Repository
@Profile("test")
public class ListProductRepository implements ProductRepository {
@Repository
@Profile("prod")
public class DatabaseProductRepository implements ProductRepository {
@Profile 지정
ListProductRepository : 로컬 개발환경에서 기능 테스트시 사용하는 환경이라 test
DatabaseProductRepository : 사용자에게 서비스가 제공된다는 의미에서 production환경(운영환경, 사용환경)의 prod
applicaion.properties : 실행할 Profile 지정
spring.profiles.active=test
spring.datasource.url=jdbc:mysql://localhost:3306/스키마이름
spring.datasource.username=계정아이디
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Profile 우선 순위
애플리케이션 실행 시점에 명령어에 Profile을 명시하면 설정된 Profile보다 우선순위가 높다
java -jar -Dspring.profiles.active=test application.jar
or
java -jar --spring.profiles.active=test application.jar
DB 없을 경우 실행 여부
test 환경을 우선순위로 설정 했지만, DB가 실행되어 있지 않을 경우 실행시 CommunicationsException 에러 발생한다.
데이터베이스를 사용하기 위해 추가해 준 의존성인 spring-boot-starter-jdbc에서 데이터 베이스 연결과 자동 설정(Auto Configuration)을 진행하기 때문이다.
test Profile로 애플리케이션이 실행될 때는 데이터베이스 연결과 관련된 자동 설정이 작동하지 않도록 만들어야 한다.
applicaion.properties : 3개의 properties 파일로 나누기
스프링 부트에서는 Profile에 따라 설정 파일을 자동으로 매핑해 준다.
형식 : applicaion-{profile}.properies
applicaion.properties
모든 애플리케이션 환경에서 공통적으로 사용할 설정
- 실행 할 profiles 지정
spring.profiles.active=test
application-test.properties
test로 실행되었을 경우 적용되는 설정
- spring-boot-starter-jdbc에 의해 진행되는 데이터베이스 연결 자동 설정이 작동하지 않도록 제외시키는 설정
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
application-prod.properties
prod로 실행되었을 경우 적용되는 설정
- DB 연결 정보
spring.datasource.url=jdbc:mysql://localhost:3306/스키마이름
spring.datasource.username=계정아이디
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Application.java : Profile에 따라 빈 생성
@Bean
@Profile("prod")
public ApplicationRunner runner(DataSource dataSource) {
ApplicationRunner 빈을 생성하는 코드에 Profile 붙이기
test 모드 일 경우 dataSource 타입의 빈이 없어 DB를 실행하지 않는 상황에서 애플리케이션 실행시 에러가 발생된다.
빈을 생성하는 코드에 @Profile("prod")를 붙이면 해당 환경에서만 커넥션 풀을 생성하기 위한 ApplicationRunner가 실행된다.
의존성 주입(DI)과 의존성 역전 원칙(DIP)
의존성 주입(Dependency Injection)
- 의존성 주입 : 의존성이 주입 되는 행위 자체
- 의존성 주입 패턴 : 의존성 주입 될 수 있는 코드 설계를 지칭
- Service가 정상적으로 작동하기 위해서는 Repository에 필요한 의존성이 주입되어야 함
- 의존성 주입해 주는 코드를 작성하면 스프링 프레임워크에 의해 실행 됨
- 의존성을 주입받지 않고 직접 생성 해 줄 경우 의존성 방향이 애매해지고 추상화의 이점이 없어짐
- 의존성 주입하여 사용해야 실행 시점에 의존성을 바꿔서 유연하게 사용 가능
의존성 역전 원칙(Dependency Inversion Principle)
- 고수준 컴포넌트가 저수준 컴포넌트에 의존하지 말아야한다는 의미
- 고수준 : 비교 대상되는 두 컴포넌트 중 도메인 정책에 가까울 수록 고수준
- 저수준 : 애플리케이션 외부에 가까울수록 저수준
- 즉 추상화에 의존해야 한다는 뜻 : 의존성 역적 원칙을 지키는 코드는 의존성 방향이 인터페이스 쪽으로 모임
- Repository인터페이스를 두지 않을 경우 Class Service(고수준) -> Class Repository(저수준) 방향으로 의존성 역전 원칙에 반대되는 쪽으로 의존성이 생김
- 인터페이스를 둘 경우 Class Service(고수준) -> Interface Repository(고수준 추상적) <- Class Repository(저수준) 방향으로 저수준 컴포넌트에 의존하던 의존성 방향이 역전 됨
'프로젝트 > clone coding' 카테고리의 다른 글
[백엔드 개발 : ProductManagement] 리팩토링과 테스트 코드 (0) | 2025.04.11 |
---|---|
[백엔드 개발 : ProductManagement] 상품 관리 애플리케이션 기능 구현(MySQL) (0) | 2025.04.05 |
[백엔드 개발 : ProductManagement] 상품 관리 애플리케이션 DB 연동(MySQL) (0) | 2025.04.04 |
[백엔드 개발 : ProductManagement] 전역 예외 핸들러 추가 (0) | 2025.03.27 |
[백엔드 개발 : ProductManagement] 유효성 검사 추가 (0) | 2025.03.20 |