优化
This commit is contained in:
@@ -3,72 +3,62 @@ import { readdir, readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import {
|
||||
ACCESS_MODES, CONTEXTS, IDEMPOTENCY, SYNCHRONOUS_CLIENTS, USER_ENTRIES,
|
||||
graphEdges, longestSimplePaths, stronglyConnectedComponents
|
||||
} from './synchronous-dependency-baseline.mjs'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
const serviceModules = [
|
||||
'auth', 'iam', 'order', 'firstleg', 'finance', 'file', 'warehouse',
|
||||
'integration-service', 'print', 'event-bridge-service', 'lastleg-service',
|
||||
'warehouse-overseas-service'
|
||||
'warehouse-overseas-service', 'overseas-security-service'
|
||||
]
|
||||
|
||||
const canonicalSource = new Map(serviceModules.map(name => [name, `${name}${name.endsWith('-service') ? '' : '-service'}`]))
|
||||
|
||||
const expectedFeignEdges = new Map(Object.entries({
|
||||
'auth-service->iam-service': 2,
|
||||
'file-service->finance-service': 1,
|
||||
'file-service->firstleg-service': 1,
|
||||
'file-service->iam-service': 2,
|
||||
'file-service->integration-service': 1,
|
||||
'file-service->order-service': 1,
|
||||
'file-service->warehouse-service': 1,
|
||||
'finance-service->file-service': 1,
|
||||
'finance-service->firstleg-service': 2,
|
||||
'finance-service->iam-service': 1,
|
||||
'finance-service->warehouse-service': 1,
|
||||
'firstleg-service->event-bridge-service': 1,
|
||||
'firstleg-service->file-service': 7,
|
||||
'firstleg-service->finance-service': 1,
|
||||
'firstleg-service->iam-service': 1,
|
||||
'firstleg-service->integration-service': 1,
|
||||
'firstleg-service->order-service': 2,
|
||||
'firstleg-service->print-service': 1,
|
||||
'firstleg-service->warehouse-service': 4,
|
||||
'iam-service->auth-service': 1,
|
||||
'iam-service->event-bridge-service': 1,
|
||||
'iam-service->firstleg-service': 1,
|
||||
'iam-service->warehouse-service': 1,
|
||||
'lastleg-service->event-bridge-service': 1,
|
||||
'lastleg-service->warehouse-service': 1,
|
||||
'order-service->file-service': 1,
|
||||
'order-service->firstleg-service': 3,
|
||||
'order-service->iam-service': 1,
|
||||
'order-service->warehouse-service': 1
|
||||
}))
|
||||
|
||||
const expectedRawHttpImplementations = [
|
||||
'common-audit/src/main/java/com/baiyue/common/audit/HttpAuditSink.java',
|
||||
'event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFirstlegEventClient.java',
|
||||
'event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java',
|
||||
'gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpPdaSessionValidationClient.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpUnloadPhotoFileGateway.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/palletops/gateway/HttpPalletPhysicalGateway.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/gateway/HttpRelabelPhotoFileGateway.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/EvergreenTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/HedeTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/MatsonTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/SmlTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/ZimTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/ocr/BaiduMeterClient.java'
|
||||
const expectedOnlineEdges = [
|
||||
'auth-service->iam-service',
|
||||
'file-service->iam-service',
|
||||
'file-service->integration-service',
|
||||
'finance-service->file-service',
|
||||
'finance-service->firstleg-service',
|
||||
'finance-service->iam-service',
|
||||
'finance-service->warehouse-service',
|
||||
'firstleg-service->file-service',
|
||||
'firstleg-service->iam-service',
|
||||
'firstleg-service->order-service',
|
||||
'firstleg-service->print-service',
|
||||
'firstleg-service->warehouse-service',
|
||||
'iam-service->auth-service',
|
||||
'iam-service->firstleg-service',
|
||||
'iam-service->warehouse-service',
|
||||
'lastleg-service->file-service',
|
||||
'lastleg-service->warehouse-overseas-service',
|
||||
'lastleg-service->warehouse-service',
|
||||
'order-service->file-service',
|
||||
'order-service->firstleg-service',
|
||||
'order-service->iam-service',
|
||||
'order-service->warehouse-service'
|
||||
]
|
||||
|
||||
const reviewedRawInternalEdges = [
|
||||
const expectedBackgroundEdges = [
|
||||
'event-bridge-service->firstleg-service',
|
||||
'event-bridge-service->lastleg-service',
|
||||
'gateway-overseas->auth-service',
|
||||
'lastleg-service->file-service',
|
||||
'lastleg-service->warehouse-overseas-service'
|
||||
'event-bridge-service->overseas-security-service',
|
||||
'file-service->finance-service',
|
||||
'file-service->firstleg-service',
|
||||
'file-service->iam-service',
|
||||
'file-service->order-service',
|
||||
'file-service->warehouse-service',
|
||||
'finance-service->firstleg-service',
|
||||
'firstleg-service->event-bridge-service',
|
||||
'firstleg-service->finance-service',
|
||||
'firstleg-service->integration-service',
|
||||
'iam-service->event-bridge-service',
|
||||
'lastleg-service->event-bridge-service',
|
||||
'order-service->firstleg-service'
|
||||
]
|
||||
|
||||
const rawHttpPattern = /\b(?:RestClient|WebClient|HttpClient)\b/
|
||||
@@ -87,7 +77,7 @@ async function javaFiles(directory) {
|
||||
}
|
||||
|
||||
async function discoverFeignEdges() {
|
||||
const edges = new Map()
|
||||
const clients = []
|
||||
for (const module of serviceModules) {
|
||||
const sourceRoot = path.join(root, module, 'src', 'main', 'java')
|
||||
for (const file of await javaFiles(sourceRoot)) {
|
||||
@@ -96,66 +86,24 @@ async function discoverFeignEdges() {
|
||||
const annotation = source.match(/@FeignClient\(([\s\S]*?)\)\s*(?:public\s+)?interface/)
|
||||
const target = annotation?.[1].match(/name\s*=\s*"([^"]+)"/)?.[1]
|
||||
assert.ok(target, `cannot resolve Feign target from ${path.relative(root, file)}`)
|
||||
const edge = `${canonicalSource.get(module)}->${target}`
|
||||
edges.set(edge, (edges.get(edge) ?? 0) + 1)
|
||||
clients.push({
|
||||
path: path.relative(root, file).replaceAll('\\', '/'),
|
||||
source: canonicalSource.get(module), target
|
||||
})
|
||||
}
|
||||
}
|
||||
return edges
|
||||
return clients.sort((left, right) => left.path.localeCompare(right.path))
|
||||
}
|
||||
|
||||
function sortedEntries(map) {
|
||||
return [...map.entries()].sort(([left], [right]) => left.localeCompare(right))
|
||||
}
|
||||
|
||||
function stronglyConnectedComponents(edgeNames) {
|
||||
const adjacency = new Map()
|
||||
for (const edge of edgeNames) {
|
||||
const [from, to] = edge.split('->')
|
||||
if (!adjacency.has(from)) adjacency.set(from, new Set())
|
||||
if (!adjacency.has(to)) adjacency.set(to, new Set())
|
||||
adjacency.get(from).add(to)
|
||||
}
|
||||
let index = 0
|
||||
const indexes = new Map()
|
||||
const lowLinks = new Map()
|
||||
const stack = []
|
||||
const onStack = new Set()
|
||||
const components = []
|
||||
|
||||
function connect(node) {
|
||||
indexes.set(node, index)
|
||||
lowLinks.set(node, index++)
|
||||
stack.push(node)
|
||||
onStack.add(node)
|
||||
for (const target of adjacency.get(node)) {
|
||||
if (!indexes.has(target)) {
|
||||
connect(target)
|
||||
lowLinks.set(node, Math.min(lowLinks.get(node), lowLinks.get(target)))
|
||||
} else if (onStack.has(target)) {
|
||||
lowLinks.set(node, Math.min(lowLinks.get(node), indexes.get(target)))
|
||||
}
|
||||
}
|
||||
if (lowLinks.get(node) !== indexes.get(node)) return
|
||||
const component = []
|
||||
let current
|
||||
do {
|
||||
current = stack.pop()
|
||||
onStack.delete(current)
|
||||
component.push(current)
|
||||
} while (current !== node)
|
||||
components.push(component.sort())
|
||||
}
|
||||
|
||||
for (const node of adjacency.keys()) if (!indexes.has(node)) connect(node)
|
||||
return components.filter(component => component.length > 1)
|
||||
.sort((left, right) => left.join().localeCompare(right.join()))
|
||||
}
|
||||
|
||||
test('all Feign clients remain covered by the reviewed synchronous edge baseline', async () => {
|
||||
assert.deepEqual(sortedEntries(await discoverFeignEdges()), sortedEntries(expectedFeignEdges))
|
||||
test('all 44 Feign clients have reviewed context, access and idempotency metadata', async () => {
|
||||
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'FEIGN')
|
||||
.map(({ path, source, target }) => ({ path, source, target }))
|
||||
.sort((left, right) => left.path.localeCompare(right.path))
|
||||
assert.equal(reviewed.length, 44)
|
||||
assert.deepEqual(await discoverFeignEdges(), reviewed)
|
||||
})
|
||||
|
||||
test('raw HTTP transports cannot be added without an explicit graph review', async () => {
|
||||
test('all 16 raw HTTP transports have reviewed context, access and idempotency metadata', async () => {
|
||||
const discovered = []
|
||||
for (const module of ['common-audit', 'event-bridge-service', 'gateway-overseas', 'lastleg-service', 'integration-service']) {
|
||||
for (const file of await javaFiles(path.join(root, module, 'src', 'main', 'java'))) {
|
||||
@@ -165,18 +113,127 @@ test('raw HTTP transports cannot be added without an explicit graph review', asy
|
||||
discovered.push(path.relative(root, file).replaceAll('\\', '/'))
|
||||
}
|
||||
}
|
||||
assert.deepEqual(discovered.sort(), [...expectedRawHttpImplementations].sort())
|
||||
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'HTTP')
|
||||
.map(client => client.path).sort()
|
||||
assert.equal(reviewed.length, 16)
|
||||
assert.deepEqual(discovered.sort(), reviewed)
|
||||
})
|
||||
|
||||
test('the current synchronous service graph keeps its reviewed strongly connected component visible', async () => {
|
||||
const feignEdges = (await discoverFeignEdges()).keys()
|
||||
const components = stronglyConnectedComponents([...feignEdges, ...reviewedRawInternalEdges])
|
||||
assert.deepEqual(components, [[
|
||||
test('every reviewed synchronous client has a complete and exclusive executable classification', () => {
|
||||
assert.equal(new Set(SYNCHRONOUS_CLIENTS.map(client => client.id)).size, SYNCHRONOUS_CLIENTS.length)
|
||||
for (const client of SYNCHRONOUS_CLIENTS) {
|
||||
assert.ok(client.path && client.source && client.target && client.note, `${client.id} metadata is incomplete`)
|
||||
assert.ok(client.contexts.length > 0, `${client.id} has no execution context`)
|
||||
assert.equal(new Set(client.contexts).size, client.contexts.length, `${client.id} repeats a context`)
|
||||
for (const context of client.contexts) assert.ok(CONTEXTS.includes(context), `${client.id} has invalid context ${context}`)
|
||||
assert.ok(ACCESS_MODES.includes(client.access), `${client.id} has invalid access ${client.access}`)
|
||||
assert.ok(IDEMPOTENCY.includes(client.idempotency), `${client.id} has invalid idempotency ${client.idempotency}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('all four Lastleg RestClient gateways use explicit bounded transports', async () => {
|
||||
const clients = new Map([
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpUnloadPhotoFileGateway.java', 'getUnloadPhotoFile'],
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/gateway/HttpRelabelPhotoFileGateway.java', 'getRelabelPhotoFile'],
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java', 'getWarehouseResource'],
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/palletops/gateway/HttpPalletPhysicalGateway.java', 'getPalletPhysical']
|
||||
])
|
||||
for (const [file, propertyAccessor] of clients) {
|
||||
const source = await readFile(path.join(root, file), 'utf8')
|
||||
assert.match(source, /restClientFactory\.build\(restClientBuilder, baseUrl,/)
|
||||
assert.match(source, new RegExp(`restClientProperties\\.${propertyAccessor}\\(\\)`))
|
||||
assert.doesNotMatch(source, /RetryTemplate|Retryer|@Retryable|\.retry\(|fallbackFactory|fallback\s*=/i)
|
||||
}
|
||||
|
||||
const factory = await readFile(path.join(root,
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/config/LastlegRestClientFactory.java'), 'utf8')
|
||||
assert.match(factory, /connectTimeout\(timeouts\.getConnectTimeout\(\)\)/)
|
||||
assert.match(factory, /setReadTimeout\(timeouts\.getReadTimeout\(\)\)/)
|
||||
assert.doesNotMatch(factory, /RetryTemplate|Retryer|@Retryable|\.retry\(|fallbackFactory|fallback\s*=/i)
|
||||
|
||||
for (const configFile of [
|
||||
'lastleg-service/src/main/resources/application.yml',
|
||||
'config/nacos/overseas/lastleg-service.yml'
|
||||
]) {
|
||||
const source = await readFile(path.join(root, configFile), 'utf8')
|
||||
for (const key of ['unload-photo-file', 'relabel-photo-file', 'warehouse-resource', 'pallet-physical']) {
|
||||
assert.match(source, new RegExp(
|
||||
`${key}:\\s*\\n\\s+connect-timeout:\\s*\\$\\{[^}]+:[1-9][0-9]*(?:ms|s)\\}`
|
||||
+ `\\s*\\n\\s+read-timeout:\\s*\\$\\{[^}]+:[1-9][0-9]*(?:ms|s)\\}`))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('REQUEST, BACKGROUND and full graphs remain separate executable baselines', () => {
|
||||
assert.deepEqual(graphEdges('REQUEST'), expectedOnlineEdges)
|
||||
assert.deepEqual(graphEdges('BACKGROUND'), expectedBackgroundEdges)
|
||||
assert.deepEqual(graphEdges('INFRASTRUCTURE'), [
|
||||
'common-audit->audit-service',
|
||||
'gateway-overseas->auth-service',
|
||||
'gateway-overseas->overseas-security-service'
|
||||
])
|
||||
assert.deepEqual(stronglyConnectedComponents(graphEdges('REQUEST')), [[
|
||||
'auth-service', 'file-service', 'firstleg-service', 'iam-service', 'order-service'
|
||||
]])
|
||||
assert.deepEqual(stronglyConnectedComponents(graphEdges('BACKGROUND')), [[
|
||||
'event-bridge-service', 'finance-service', 'firstleg-service', 'lastleg-service'
|
||||
]])
|
||||
assert.deepEqual(stronglyConnectedComponents(graphEdges('ALL')), [[
|
||||
'auth-service', 'event-bridge-service', 'file-service', 'finance-service',
|
||||
'firstleg-service', 'iam-service', 'lastleg-service', 'order-service'
|
||||
]])
|
||||
})
|
||||
|
||||
test('overseas security edges never become online business dependencies', () => {
|
||||
assert.ok(!graphEdges('REQUEST').some(edge => edge.includes('overseas-security-service')))
|
||||
assert.ok(graphEdges('INFRASTRUCTURE').includes('gateway-overseas->overseas-security-service'))
|
||||
assert.ok(graphEdges('BACKGROUND').includes('event-bridge-service->overseas-security-service'))
|
||||
})
|
||||
|
||||
test('known user entries expose every longest simple REQUEST path over the two-hop budget', () => {
|
||||
const summary = Object.fromEntries(USER_ENTRIES.map(entry => {
|
||||
const paths = longestSimplePaths(entry.service)
|
||||
return [entry.service, { hops: paths[0].length - 1, paths: paths.map(path => path.join('->')) }]
|
||||
}))
|
||||
assert.deepEqual(summary, {
|
||||
'auth-service': { hops: 5, paths: [
|
||||
'auth-service->iam-service->firstleg-service->order-service->file-service->integration-service'
|
||||
] },
|
||||
'iam-service': { hops: 4, paths: [
|
||||
'iam-service->firstleg-service->order-service->file-service->integration-service'
|
||||
] },
|
||||
'order-service': { hops: 4, paths: [
|
||||
'order-service->file-service->iam-service->firstleg-service->print-service',
|
||||
'order-service->file-service->iam-service->firstleg-service->warehouse-service',
|
||||
'order-service->firstleg-service->file-service->iam-service->auth-service',
|
||||
'order-service->firstleg-service->file-service->iam-service->warehouse-service',
|
||||
'order-service->iam-service->firstleg-service->file-service->integration-service'
|
||||
] },
|
||||
'firstleg-service': { hops: 4, paths: [
|
||||
'firstleg-service->order-service->file-service->iam-service->auth-service',
|
||||
'firstleg-service->order-service->file-service->iam-service->warehouse-service'
|
||||
] },
|
||||
'finance-service': { hops: 5, paths: [
|
||||
'finance-service->file-service->iam-service->firstleg-service->order-service->warehouse-service',
|
||||
'finance-service->firstleg-service->order-service->file-service->iam-service->auth-service',
|
||||
'finance-service->firstleg-service->order-service->file-service->iam-service->warehouse-service',
|
||||
'finance-service->iam-service->firstleg-service->order-service->file-service->integration-service'
|
||||
] },
|
||||
'file-service': { hops: 4, paths: [
|
||||
'file-service->iam-service->firstleg-service->order-service->warehouse-service'
|
||||
] },
|
||||
'lastleg-service': { hops: 5, paths: [
|
||||
'lastleg-service->file-service->iam-service->firstleg-service->order-service->warehouse-service'
|
||||
] },
|
||||
'warehouse-service': { hops: 0, paths: ['warehouse-service'] },
|
||||
'warehouse-overseas-service': { hops: 0, paths: ['warehouse-overseas-service'] }
|
||||
})
|
||||
for (const entry of USER_ENTRIES) {
|
||||
const result = summary[entry.service]
|
||||
if (result.hops > 2) assert.ok(result.paths.every(path => path.split('->').length > 3))
|
||||
}
|
||||
})
|
||||
|
||||
test('shared Feign timeout defaults and long-running export overrides do not drift silently', async () => {
|
||||
for (const profile of ['local', 'cn', 'overseas']) {
|
||||
const source = await readFile(path.join(root, 'config', 'nacos', profile, 'baiyue-common.yml'), 'utf8')
|
||||
|
||||
Reference in New Issue
Block a user