@@ -2,11 +2,16 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.springdoc.core.models.GroupedOpenApi;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.stereotype.Controller;
 
 import io.swagger.v3.oas.models.Components;
 import io.swagger.v3.oas.models.OpenAPI;
@@ -18,6 +23,8 @@
 @Configuration
 public class OpenApiConfig {
 
+	private static final String BASE_PACKAGE = "org.example.commerce_site.representation";
+
 	@Value("${springdoc.server-url}")
 	private String serverUrl;
 
@@ -46,56 +53,34 @@ public OpenAPI openAPI() {
 	}
 
 	@Bean
-	public GroupedOpenApi userOpenApi() {
-		String[] paths = {"/users/**"};
-		return GroupedOpenApi.builder().group("USER API").pathsToMatch(paths).build();
-	}
+	public List<GroupedOpenApi> groupedOpenApisByPackage() {
+		List<GroupedOpenApi> groupedApis = new ArrayList<>();
 
-	@Bean
-	public GroupedOpenApi partnerOpenApi() {
-		String[] paths = {"/partners/**"};
-		return GroupedOpenApi.builder().group("PARTNER API").pathsToMatch(paths).build();
-	}
+		Set<String> subPackages = discoverControllerGroups(BASE_PACKAGE);
 
-	@Bean
-	public GroupedOpenApi productOpenApi() {
-		String[] paths = {"/products/**"};
-		return GroupedOpenApi.builder().group("PRODUCT API").pathsToMatch(paths).build();
-	}
+		for (String pkg : subPackages) {
+			String groupName = pkg.substring(pkg.lastIndexOf('.') + 1).toUpperCase() + " API";
 
-	@Bean
-	public GroupedOpenApi categoryOpenApi() {
-		String[] paths = {"/categories/**"};
-		return GroupedOpenApi.builder().group("CATEGORY API").pathsToMatch(paths).build();
-	}
+			groupedApis.add(GroupedOpenApi.builder()
+				.group(groupName)
+				.packagesToScan(pkg)
+				.build());
+		}
 
-	@Bean
-	public GroupedOpenApi addressOpenApi() {
-		String[] paths = {"/addresses/**"};
-		return GroupedOpenApi.builder().group("ADDRESS API").pathsToMatch(paths).build();
+		return groupedApis;
 	}
 
-	@Bean
-	public GroupedOpenApi cartOpenApi() {
-		String[] paths = {"/carts/**"};
-		return GroupedOpenApi.builder().group("CART API").pathsToMatch(paths).build();
-	}
-
-	@Bean
-	public GroupedOpenApi orderOpenApi() {
-		String[] paths = {"/orders/**"};
-		return GroupedOpenApi.builder().group("ORDER API").pathsToMatch(paths).build();
-	}
+	private Set<String> discoverControllerGroups(String basePackage) {
+		ClassPathScanningCandidateComponentProvider scanner =
+			new ClassPathScanningCandidateComponentProvider(false);
 
-	@Bean
-	public GroupedOpenApi paymentsOpenApi() {
-		String[] paths = {"/payments/**"};
-		return GroupedOpenApi.builder().group("PAYMENT API").pathsToMatch(paths).build();
-	}
+		scanner.addIncludeFilter(new AnnotationTypeFilter(Controller.class));
 
-	@Bean
-	public GroupedOpenApi shipmentsOpenApi() {
-		String[] paths = {"/shipments/**"};
-		return GroupedOpenApi.builder().group("SHIPMENT API").pathsToMatch(paths).build();
+		return scanner.findCandidateComponents(basePackage).stream()
+			.map(beanDefinition -> {
+				String className = beanDefinition.getBeanClassName();
+				return className.substring(0, className.lastIndexOf('.'));
+			})
+			.collect(Collectors.toSet());
 	}
 }

리뷰: sub package를 구하고 등록하는 형태가 불안정한 것 같아요. package 네이밍 규칙 변경 등, 여러 요소에 따라 깨지기 쉬운 것 같은데, 이를 좀 더 안정적으로 관리할 수 있는 방법은 없을까요?