Skip to content

Commit 7013a99

Browse files
authored
Merge pull request #3 from unit-mesh/javaServiceAnalyser
Java service analyser
2 parents c239191 + f50ceff commit 7013a99

File tree

5 files changed

+138
-3
lines changed

5 files changed

+138
-3
lines changed

code-quality/src/main/kotlin/cc/unitmesh/quality/extension/JavaServiceAnalyser.kt

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
package cc.unitmesh.quality.extension
22

33
import cc.unitmesh.quality.QualityAnalyser
4+
import cc.unitmesh.quality.extension.rule.TooManyRepositoryDependenciesRule
45
import chapi.domain.core.CodeDataStruct
5-
import org.archguard.linter.rule.webapi.WebApiRuleSetProvider
66
import org.archguard.rule.core.Issue
7+
import org.archguard.rule.core.IssuePosition
8+
import org.archguard.rule.core.Rule
9+
import org.archguard.rule.core.RuleType
710

811
class JavaServiceAnalyser(thresholds: Map<String, Int> = mapOf()) : QualityAnalyser {
9-
private val webApiRuleSetProvider = WebApiRuleSetProvider()
10-
1112
override fun analysis(nodes: List<CodeDataStruct>): List<Issue> {
13+
val serviceNodes = nodes.filter { it.filterAnnotations("Service").isNotEmpty() }
14+
if (serviceNodes.isNotEmpty()) {
15+
val results: MutableList<Issue> = mutableListOf()
16+
TooManyRepositoryDependenciesRule().visitRoot(serviceNodes, fun(rule: Rule, position: IssuePosition) {
17+
results += Issue(
18+
position,
19+
ruleId = rule.key,
20+
name = rule.name,
21+
detail = rule.description,
22+
ruleType = RuleType.SERVICE_SMELL
23+
)
24+
})
25+
return results;
26+
}
27+
//todo rule3: 调用的repository之间的关联度 -- ER关系上,关联度越强越好
28+
//todo rule4: 不依赖controller层相关概念,如:request/response -- 层与层之间依赖关系清晰
29+
//todo rule5: public filed数量 -- service应该都是private filed
30+
//pending rule6: 不被外界访问的public method -- 应归属到bad smell
31+
//pending rule1: service 长度小于x00行 -- bad smell large class
32+
1233
// 检查 Service 长度,调用的 repository 数量,
1334
return listOf()
1435
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package cc.unitmesh.quality.extension.rule
2+
3+
import chapi.domain.core.CodeDataStruct
4+
import org.archguard.rule.core.IssueEmit
5+
import org.archguard.rule.core.Rule
6+
7+
open class ServiceRule : Rule() {
8+
open fun visitRoot(rootNode: List<CodeDataStruct>, callback: IssueEmit) {}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package cc.unitmesh.quality.extension.rule
2+
3+
import chapi.domain.core.CodeDataStruct
4+
import org.archguard.rule.core.IssueEmit
5+
import org.archguard.rule.core.IssuePosition
6+
import org.archguard.rule.core.Severity
7+
8+
/**
9+
* Service should not dependent more than 5 repositories.
10+
*/
11+
const val LIMIT = 5
12+
13+
class TooManyRepositoryDependenciesRule : ServiceRule() {
14+
init {
15+
this.id = "too-many-repository-dependencies"
16+
this.name = "TooManyRepositoryDependencies"
17+
this.key = this.javaClass.name
18+
this.severity = Severity.WARN
19+
this.description = "Service should not dependent more than 5 repositories."
20+
}
21+
22+
override fun visitRoot(rootNodes: List<CodeDataStruct>, callback: IssueEmit) {
23+
rootNodes.forEach {
24+
val repositoryCount = it.Fields.filter { it.TypeType.contains("Repository", true) }.count()
25+
if (repositoryCount > LIMIT) {
26+
callback(this, IssuePosition())
27+
}
28+
}
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cc.unitmesh.quality;
2+
3+
import cc.unitmesh.quality.extension.JavaServiceAnalyser
4+
import chapi.ast.javaast.JavaAnalyser
5+
import chapi.domain.core.CodeDataStruct
6+
import kotlinx.serialization.json.Json
7+
import org.archguard.rule.core.RuleType
8+
import org.junit.jupiter.api.Assertions
9+
import org.junit.jupiter.api.Test
10+
import java.io.File
11+
import java.nio.file.Paths
12+
import kotlin.test.assertEquals
13+
14+
class JavaServiceAnalyserTest {
15+
private fun loadNodes(source: String): List<CodeDataStruct> {
16+
return Json { ignoreUnknownKeys = true }.decodeFromString(
17+
File(this.javaClass.classLoader.getResource(source)!!.file).readText()
18+
)
19+
}
20+
21+
@Test
22+
fun `should return empty list of issues when node is not a service`() {
23+
val nodes = loadNodes("java/structs_HelloController.json")
24+
val issues = JavaServiceAnalyser().analysis(nodes)
25+
26+
assertEquals(0, issues.size)
27+
}
28+
29+
@Test
30+
fun `should identify too many repository dependencies`() {
31+
val path = getAbsolutePath("java/ServiceWithSixRepositories.java")
32+
val data = JavaAnalyser().analysis(File(path).readText(), "ServiceWithSixRepositories.java").DataStructures
33+
val issues = JavaServiceAnalyser().analysis(data)
34+
35+
Assertions.assertEquals(1, issues.size)
36+
Assertions.assertEquals("TooManyRepositoryDependencies", issues[0].name)
37+
Assertions.assertEquals("Service should not dependent more than 5 repositories.", issues[0].detail)
38+
Assertions.assertEquals(RuleType.SERVICE_SMELL, issues[0].ruleType)
39+
}
40+
41+
private fun getAbsolutePath(path: String): String {
42+
val resource = this.javaClass.classLoader.getResource(path)
43+
return Paths.get(resource!!.toURI()).toFile().absolutePath
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.afs.restapi.service;
2+
3+
import com.afs.restapi.repository.CompanyRepository;
4+
import com.afs.restapi.repository.EmployeeRepository;
5+
import com.afs.restapi.repository.DepartmentRepository;
6+
import com.afs.restapi.repository.TeamRepository;
7+
import com.afs.restapi.repository.GroupRepository;
8+
import com.afs.restapi.repository.CommunitRepository;
9+
import org.springframework.stereotype.Service;
10+
11+
@Service
12+
public class Example {
13+
private CompanyRepository companyRepository;
14+
private EmployeeRepository employeeRepository;
15+
private DepartmentRepository departmentRepository;
16+
private TeamRepository teamRepository;
17+
private GroupRepository groupRepository;
18+
private CommunitRepository communitRepository;
19+
20+
public Example(CompanyRepository companyRepository, EmployeeRepository employeeRepository,
21+
DepartmentRepository departmentRepository, TeamRepository teamRepository,
22+
GroupRepository groupRepository, CommunitRepository communitRepository) {
23+
this.companyRepository = companyRepository;
24+
this.employeeRepository = employeeRepository;
25+
this.departmentRepository = departmentRepository;
26+
this.teamRepository = teamRepository;
27+
this.groupRepository = groupRepository;
28+
this.communitRepository = communitRepository;
29+
}
30+
}

0 commit comments

Comments
 (0)