Git Submodule
Git Submodule 은 Git 레포지토리 내에 다른 레포지토리를 두는 것으로, 모듈화에 필요한 것들을 넣어두면 편하게 사용할 수 있다.
일단 파이파이, 메이븐 등에 안올려도 되고, Git 이여서 익숙하고 편하다. 그래서 선택했다.
개요
Submodule 에 엔티티 등 서브모듈들을 정의했는데, 변동사항이 많아서 인터페이스로 최소한으로 남겨두고 구현체에서 변동사항만 구현하고 나머지는 정의하고 싶었다.
// 서브모듈
@MappedSuperclass
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract BaseUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Coulumn(name = "name", nullable = false)
private String name;
}
// 메인
@Entity
@Table(name = "user")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseUser {
private String password;
}
예시다. 엔티티는 이런 형식으로 구현하면 된다.
하지만 문제가 있다.
예를 들어 각 프로젝트마다 다른 컨버터가 필요해서 인터페이스를 만들었다고 생각하자.
// 서브모듈
public interface Converter<T extends BaseUser> {
Map<String, Object> convertUserToMap(BaseUser user);
}
// 메인
@Component
public class ConverterImpl implements Converter<BaseUser> {
@Override
public Map<String, Object> convertUserToMap(BaseUser user) {
// 로직
}
}
이렇게 만들어 놓고 서브 모듈 서비스에서 사용한다.
// 서브 모듈
@Service
@RequiredArgsConstructor
public class UserService {
private final Converter<? extends BaseUser> converter;
// 로직
}
이렇게 하면 서브모듈 서비스가 converter 를 인식 못한다.
왜냐 converter 라는 Bean 이 없기 때문이다.
왜 없냐, Spring 의 컴포넌트 스캔 범위가 다르기 때문이다.
방법1. 서브모듈 스캔
// 메인 프로젝트의 Application 클래스
@SpringBootApplication
@ComponentScan(basePackages = {
"com.main.project", // 메인 프로젝트 패키지
"com.submodule.package" // 서브모듈 패키지
})
public class MainApplication {
// ...
}
// 메인 프로젝트
@Configuration
@ComponentScan(basePackages = "com.submodule.service") // 서브모듈 서비스 스캔
public class SubmoduleConfig {
@Bean
public Converter<BaseUser> converter() {
return new ConverterImpl();
}
}
// 서브모듈의 resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.submodule.config.SubmoduleAutoConfiguration
// 서브모듈의 AutoConfiguration
@Configuration
@ConditionalOnMissingBean(Converter.class)
public class SubmoduleAutoConfiguration {
// 기본 구현체 제공하거나 필요한 설정
}
// 서브모듈 - 더 유연한 설계
@Service
@RequiredArgsConstructor
public class UserService {
private final Converter<? extends BaseUser> converter;
// 또는 Optional로 처리
private final Optional<Converter<? extends BaseUser>> converter;
}
이런 형식으로 하면 되는데, 귀찮다.
방법 2. @Qualifier
// 메인 프로젝트
@Component
@Qualifier("userConverter") // 명시적 이름 지정
public class ConverterImpl implements Converter<BaseUser> {
@Override
public Map<String, Object> convertUserToMap(BaseUser user) {
// 로직
}
}
// 서브모듈
@Service
@RequiredArgsConstructor
public class UserService {
@Qualifier("userConverter") // 같은 이름으로 주입
private final Converter<? extends BaseUser> converter;
// 로직
}
이렇게 하면 Bean의 이름을 명확하게 알기 때문에 Spring이 그 빈을 찾아와준다.