messaging-superpowers-package 설계서/계획서 기반 구현. registry를 19 → 43 leaf로 확장하고 src/messaging 아래 24개 leaf를 등록. - core-api: M1 publish/consume + M2 batch·delayed·pause-resume - policy/transport-spi: 재시도 결정, DLQ orchestration, admission control, lifecycle - kafka·rabbit(Stable): contiguous commit, confirm/return 상관, 배치, 보안 설정 - pulsar·nats(Experimental): 기본 비활성, live 인증 없음을 코드로 기록 - outbox/inbox/claim-check: 트랜잭션 결합, lease, 무결성 검증 - admin: plan → approve → execute를 타입으로 강제 - 문서 9종, infra compose 7종, JMH 벤치마크 3종 검증: 아키텍처 게이트 3종 통과, 24개 leaf 전부 check 통과, messaging 테스트 604개 통과/0 실패. 미완: 계획서가 요구한 실 브로커 IT 40개 중 7개만 작성. Rabbit 13 / Outbox 6 / Inbox 4 / NATS·Pulsar·Share 5 / testkit 2 / starter·admin 3, 그리고 TLS·ACL 2개가 남음.
184 lines
8.1 KiB
Groovy
184 lines
8.1 KiB
Groovy
import groovy.json.JsonSlurper
|
|
|
|
plugins {
|
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
|
}
|
|
|
|
rootProject.name = 'ca-skeleton'
|
|
|
|
File repositoryRoot = settingsDir.parentFile.canonicalFile
|
|
File moduleRegistryFile = new File(settingsDir, 'config/architecture/modules.json')
|
|
if (!moduleRegistryFile.isFile()) {
|
|
throw new GradleException("Missing module registry: ${moduleRegistryFile}")
|
|
}
|
|
|
|
def moduleRegistry = new JsonSlurper().parse(moduleRegistryFile)
|
|
if (!(moduleRegistry instanceof Map)) {
|
|
throw new GradleException("Module registry root must be a JSON object: ${moduleRegistryFile}")
|
|
}
|
|
Set<String> expectedRootFields = ['runtime_compositions', 'modules'] as Set
|
|
if (moduleRegistry.keySet().collect { it as String }.toSet() != expectedRootFields) {
|
|
throw new GradleException(
|
|
"Module registry root fields must be exactly ${expectedRootFields}: ${moduleRegistryFile}")
|
|
}
|
|
if (!(moduleRegistry.modules instanceof List) || moduleRegistry.modules.isEmpty()) {
|
|
throw new GradleException("Module registry has no modules: ${moduleRegistryFile}")
|
|
}
|
|
Set<String> expectedRuntimeCompositions = ['app-bootstrap', 'sample-portfolio'] as Set
|
|
if (!(moduleRegistry.runtime_compositions instanceof List) ||
|
|
moduleRegistry.runtime_compositions.collect { it as String }.toSet() !=
|
|
expectedRuntimeCompositions ||
|
|
moduleRegistry.runtime_compositions.size() != expectedRuntimeCompositions.size()) {
|
|
throw new GradleException(
|
|
"Module registry runtime_compositions must be exactly ${expectedRuntimeCompositions}: " +
|
|
moduleRegistryFile)
|
|
}
|
|
|
|
int expectedModuleCount = 43
|
|
if (moduleRegistry.modules.size() != expectedModuleCount) {
|
|
throw new GradleException(
|
|
"Module registry must contain exactly ${expectedModuleCount} modules, " +
|
|
"but found ${moduleRegistry.modules.size()}: ${moduleRegistryFile}")
|
|
}
|
|
|
|
Set<String> moduleIds = new LinkedHashSet<>()
|
|
Set<String> gradlePaths = new LinkedHashSet<>()
|
|
Set<String> sourceDirectoryPaths = new LinkedHashSet<>()
|
|
String repositoryRootPrefix = repositoryRoot.path + File.separator
|
|
|
|
List<Map<String, Object>> validatedModules = moduleRegistry.modules.withIndex().collect { rawModule, index ->
|
|
if (!(rawModule instanceof Map)) {
|
|
throw new GradleException("Module registry entry ${index} must be a JSON object.")
|
|
}
|
|
|
|
Map<String, Object> module = rawModule as Map<String, Object>
|
|
Set<String> expectedModuleFields = [
|
|
'id',
|
|
'gradle_path',
|
|
'source_path',
|
|
'allowed_dependencies',
|
|
'runtime_memberships'
|
|
] as Set
|
|
if (module.keySet().collect { it as String }.toSet() != expectedModuleFields) {
|
|
throw new GradleException(
|
|
"Module registry entry ${index} fields must be exactly ${expectedModuleFields}.")
|
|
}
|
|
['id', 'gradle_path', 'source_path'].each { field ->
|
|
if (!(module[field] instanceof String) || (module[field] as String).isBlank()) {
|
|
throw new GradleException(
|
|
"Module registry entry ${index} needs a nonblank string '${field}'.")
|
|
}
|
|
}
|
|
if (!(module.allowed_dependencies instanceof List)) {
|
|
throw new GradleException(
|
|
"Module registry entry '${module.id}' needs an 'allowed_dependencies' list.")
|
|
}
|
|
if (!(module.runtime_memberships instanceof List)) {
|
|
throw new GradleException(
|
|
"Module registry entry '${module.id}' needs a 'runtime_memberships' list.")
|
|
}
|
|
|
|
String id = module.id as String
|
|
String gradlePath = module.gradle_path as String
|
|
String sourcePath = module.source_path as String
|
|
List<String> allowedDependencies = module.allowed_dependencies.withIndex().collect {
|
|
dependencyId, dependencyIndex ->
|
|
if (!(dependencyId instanceof String) || (dependencyId as String).isBlank()) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' has a non-string or blank allowed dependency " +
|
|
"at index ${dependencyIndex}.")
|
|
}
|
|
dependencyId as String
|
|
}
|
|
List<String> runtimeMemberships = module.runtime_memberships.withIndex().collect {
|
|
membership, membershipIndex ->
|
|
if (!(membership instanceof String) || (membership as String).isBlank()) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' has a non-string or blank runtime membership " +
|
|
"at index ${membershipIndex}.")
|
|
}
|
|
membership as String
|
|
}
|
|
if (runtimeMemberships.toSet().size() != runtimeMemberships.size()) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' contains duplicate runtime memberships.")
|
|
}
|
|
Set<String> unknownRuntimeMemberships = runtimeMemberships.toSet() - expectedRuntimeCompositions
|
|
if (!unknownRuntimeMemberships.isEmpty()) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' references unknown runtime memberships " +
|
|
"${unknownRuntimeMemberships.toSorted()}.")
|
|
}
|
|
|
|
if (!moduleIds.add(id)) {
|
|
throw new GradleException("Module registry contains duplicate module id '${id}'.")
|
|
}
|
|
if (!gradlePath.startsWith(':')) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' has Gradle path '${gradlePath}' that does not start with ':'.")
|
|
}
|
|
if (!gradlePaths.add(gradlePath)) {
|
|
throw new GradleException("Module registry contains duplicate Gradle path '${gradlePath}'.")
|
|
}
|
|
if (new File(sourcePath).isAbsolute()) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' source path must be repository-root-relative: '${sourcePath}'.")
|
|
}
|
|
|
|
File sourceDirectory = new File(repositoryRoot, sourcePath).canonicalFile
|
|
if (!sourceDirectory.path.startsWith(repositoryRootPrefix)) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' source path escapes the repository root: '${sourcePath}'.")
|
|
}
|
|
if (!sourceDirectory.isDirectory()) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' source path is not an existing directory: ${sourceDirectory}")
|
|
}
|
|
if (!sourceDirectoryPaths.add(sourceDirectory.path)) {
|
|
throw new GradleException(
|
|
"Module registry entry '${id}' resolves to duplicate or aliased canonical source directory: " +
|
|
"${sourceDirectory}")
|
|
}
|
|
|
|
[
|
|
id : id,
|
|
gradle_path : gradlePath,
|
|
source_directory : sourceDirectory,
|
|
allowed_dependencies: allowedDependencies,
|
|
runtime_memberships : runtimeMemberships
|
|
]
|
|
}
|
|
|
|
expectedRuntimeCompositions.each { compositionId ->
|
|
Map<String, Object> composition = validatedModules.find { it.id == compositionId }
|
|
if (composition == null || !(composition.runtime_memberships as List).contains(compositionId)) {
|
|
throw new GradleException(
|
|
"Runtime composition '${compositionId}' must be registered and include itself in " +
|
|
'runtime_memberships.')
|
|
}
|
|
}
|
|
|
|
validatedModules.each { module ->
|
|
module.allowed_dependencies.each { dependencyId ->
|
|
if (dependencyId == module.id) {
|
|
throw new GradleException(
|
|
"Module registry entry '${module.id}' must not depend on itself.")
|
|
}
|
|
if (module.id != 'sample-portfolio' && dependencyId == 'sample-portfolio') {
|
|
throw new GradleException(
|
|
"Production module registry entry '${module.id}' must not allow a dependency on " +
|
|
"'sample-portfolio'.")
|
|
}
|
|
if (!moduleIds.contains(dependencyId)) {
|
|
throw new GradleException(
|
|
"Module registry entry '${module.id}' references unknown allowed dependency id " +
|
|
"'${dependencyId}'.")
|
|
}
|
|
}
|
|
}
|
|
|
|
validatedModules.each { module ->
|
|
include module.gradle_path
|
|
project(module.gradle_path).projectDir = module.source_directory
|
|
}
|