Skip to content

Antipattern #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions de.fhdo.lemma.analyzer.lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ dependencies {
implementation("de.fhdo.lemma.intermediate:de.fhdo.lemma.data.intermediate.metamodel:$lemmaEclipsePluginsVersion")
implementation("de.fhdo.lemma.intermediate:de.fhdo.lemma.service.intermediate.metamodel:" +
lemmaEclipsePluginsVersion)
implementation("de.fhdo.lemma.intermediate:de.fhdo.lemma.operation.intermediate.metamodel:" +
lemmaEclipsePluginsVersion)
implementation("de.fhdo.lemma.model_processing.utils:de.fhdo.lemma.model_processing.utils:" +
lemmaEclipsePluginsVersion)
implementation("de.fhdo.lemma.technology.technologydsl:de.fhdo.lemma.technology.technologydsl.metamodel:" +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.fhdo.lemma.analyzer.lib

import de.fhdo.lemma.analyzer.lib.impl.antipattern.AntipatternOperationAnalyzer
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AntipatternServiceAnalyzer
import de.fhdo.lemma.analyzer.lib.impl.domain.basic.DomainBasicAnalyzer
import de.fhdo.lemma.analyzer.lib.impl.service.basic.ServiceBasicAnalyzer
import de.fhdo.lemma.analyzer.lib.impl.service.metrics.athanasopoulos.AthanasopoulosMetricsAnalyzer
Expand Down Expand Up @@ -42,7 +44,9 @@ enum class Analyzers(val analyzerName: String) {
SERVICE_METRICS_ATHANASOPOULOS("athanasopoulos"),
SERVICE_METRICS_ENGEL("engel"),
SERVICE_METRICS_HAUPT("haupt"),
SERVICE_METRICS_HIRZALLA("hirzalla")
SERVICE_METRICS_HIRZALLA("hirzalla"),
OPERATION_ANTIPATTERN("operation-antipattern"),
SERVICE_ANTIPATTERN("service-antipattern"),
}

/**
Expand All @@ -60,4 +64,6 @@ fun Analyzers.createAnalyzer() : AnalyzerI
Analyzers.SERVICE_METRICS_ENGEL -> EngelMetricsAnalyzer()
Analyzers.SERVICE_METRICS_HAUPT -> HauptMetricsAnalyzer()
Analyzers.SERVICE_METRICS_HIRZALLA -> HirzallaMetricsAnalyzer()
Analyzers.OPERATION_ANTIPATTERN -> AntipatternOperationAnalyzer()
Analyzers.SERVICE_ANTIPATTERN -> AntipatternServiceAnalyzer()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.fhdo.lemma.analyzer.lib.analyzers

import de.fhdo.lemma.analyzer.lib.AnalyzerI
import de.fhdo.lemma.analyzer.lib.impl.antipattern.Antipattern

interface AntipatternOperationAnalyzerI : AnalyzerI{
fun checkExistingAntipattern(): Collection<Antipattern>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.fhdo.lemma.analyzer.lib.analyzers

import de.fhdo.lemma.analyzer.lib.AnalyzerI
import de.fhdo.lemma.analyzer.lib.impl.antipattern.Antipattern

interface AntipatternServiceAnalyzerI : AnalyzerI {
fun checkExistingAntipattern(): Collection<Antipattern>
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import de.fhdo.lemma.data.intermediate.IntermediateComplexType
import de.fhdo.lemma.data.intermediate.IntermediateDataModel
import de.fhdo.lemma.data.intermediate.IntermediateImportedComplexType
import de.fhdo.lemma.model_processing.utils.asXmiResource
import de.fhdo.lemma.operation.intermediate.IntermediateOperationModel
import de.fhdo.lemma.service.intermediate.IntermediateMicroservice
import de.fhdo.lemma.service.intermediate.IntermediateOperation
import de.fhdo.lemma.service.intermediate.IntermediateReferredOperation
Expand All @@ -37,12 +38,15 @@ internal data class CacheId(val id: String, val modelClazz: Class<*>) {

internal val DOMAIN_MODEL_CLASS = IntermediateDataModel::class.java
internal val SERVICE_MODEL_CLASS = IntermediateServiceModel::class.java
internal val OPERATION_MODEL_CLASS = IntermediateOperationModel::class.java

/* Global, reusable CacheIds */
internal val ALL_CONTEXTS_CACHE_ID = CacheId("allContexts", DOMAIN_MODEL_CLASS)
internal val ALL_INTERFACES_CACHE_ID = CacheId("allInterfaces", SERVICE_MODEL_CLASS)
internal val ALL_MICROSERVICES_CACHE_ID = CacheId("allMicroservices", SERVICE_MODEL_CLASS)
internal val ALL_OPERATIONS_CACHE_ID = CacheId("allOperations", SERVICE_MODEL_CLASS)
internal val ALL_CONTAINERS_CACHE_ID = CacheId("allContainers", OPERATION_MODEL_CLASS)
internal val ALL_INFRASTRUCTURE_NODES_CACHE_ID = CacheId("allInfrastructureNodes", OPERATION_MODEL_CLASS)
internal val ALL_EFFECTIVE_PROTOCOLS_CACHE_ID = CacheId("allEffectiveProtocols", SERVICE_MODEL_CLASS)
internal val ALL_REFERRED_OPERATIONS_CACHE_ID = CacheId("allReferredOperations", SERVICE_MODEL_CLASS)
internal val MICROSERVICE_DEPENDENCIES_CACHE_ID = CacheId("microserviceDependencies", SERVICE_MODEL_CLASS)
Expand Down Expand Up @@ -86,6 +90,17 @@ internal object Cache : HashMap<CacheId, Any?>() {
allModelsOfType(DOMAIN_MODEL_CLASS).map { it.contexts }.flatten()
}

/**
* returns all OperationNodes
*/
internal fun allContainer() = addOrGetCacheIterable(ALL_CONTAINERS_CACHE_ID) {
allModelsOfType(OPERATION_MODEL_CLASS).map{it.containers}.flatten()
}

internal fun allInfrastuctureNodes() = addOrGetCacheIterable(ALL_INFRASTRUCTURE_NODES_CACHE_ID) {
allModelsOfType(OPERATION_MODEL_CLASS).map{it.infrastructureNodes}.flatten()
}

/**
* Convenience method to add the [Iterable] returned by the [getCollection] lambda to the [Cache] under the given
* [CacheId]. In case the [id] is already in the [Cache], this method will return the existing entry.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern

enum class AntipatternType(val displayName: String) {
API_VERSIONING("API-Versioning"),
CYCLIC_DEPENDENCY("Cyclic Dependencies"),
USAGE_ESB("Usage Enterprise Service Bus"),
HARDCODED_ENDPOINTS("Hardcoded Endpoints"),
NO_API_GATEWAY("No API-Gateway"),
SHARED_PERSISTENCE("Shared Persistence"),
LOCAL_LOGGING("Local Logging"),
NO_MONITORING("Missing Monitoring"),
}

data class Antipattern(val type: AntipatternType, val message: String)

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern

import de.fhdo.lemma.analyzer.lib.analyzers.AntipatternOperationAnalyzerI
import de.fhdo.lemma.analyzer.lib.impl.AbstractSingleModelTypeAnalyzer
import de.fhdo.lemma.analyzer.lib.impl.Cache
import de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.operation.*
import de.fhdo.lemma.operation.intermediate.IntermediateOperationModel
import de.fhdo.lemma.operation.intermediate.IntermediateOperationNode

internal class AntipatternOperationAnalyzer : AbstractSingleModelTypeAnalyzer<IntermediateOperationModel>(IntermediateOperationModel::class.java), AntipatternOperationAnalyzerI {
private var strategies = setOf(
CircleStrategy(),
WrongAspectStrategy("ESB", AntipatternType.USAGE_ESB, "Using an 'Enterprise Service Bus' can make a solution too complex"),
MissingAspectStrategy("ServiceDiscovery", AntipatternType.HARDCODED_ENDPOINTS, "A service discovery should be used"),
AspectUsageStrategy("Database", AntipatternType.SHARED_PERSISTENCE, "Only one Service should access a data storage. In case of data separation, like different tables this can be ignored"),
MissingAspectStrategy("LogServer", AntipatternType.LOCAL_LOGGING, "Log-Data should be stored central"),
MissingAspectStrategy("Monitoring", AntipatternType.NO_MONITORING, "Services should be monitored central"),
MissingAspectStrategy("API-Gateway", AntipatternType.NO_API_GATEWAY, "Although the service has endpoints no API-Gateway is connected", true)
)

override fun checkExistingAntipattern(): Collection<Antipattern>{
val allInfrastuctureNodes = Cache.allInfrastuctureNodes()
val allContainer = Cache.allContainer()
val operationNodes = mutableListOf<IntermediateOperationNode>()
operationNodes.addAll(allContainer)
operationNodes.addAll(allInfrastuctureNodes)
val result = mutableListOf<Antipattern>()
strategies.forEach { result.addAll(it.analyzeOperationNodes(operationNodes)) }
return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern

import de.fhdo.lemma.analyzer.lib.analyzers.AntipatternServiceAnalyzerI
import de.fhdo.lemma.analyzer.lib.impl.AbstractSingleModelTypeAnalyzer
import de.fhdo.lemma.analyzer.lib.impl.Cache
import de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.service.APIVersionStrategy
import de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.service.AntipatternServiceAnalyzerStrategy
import de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.service.CircleStrategy
import de.fhdo.lemma.service.intermediate.IntermediateServiceModel

internal class AntipatternServiceAnalyzer : AbstractSingleModelTypeAnalyzer<IntermediateServiceModel>(
IntermediateServiceModel::class.java), AntipatternServiceAnalyzerI {

private var strategies = listOf<AntipatternServiceAnalyzerStrategy>(CircleStrategy(), APIVersionStrategy())

override fun checkExistingAntipattern(): Collection<Antipattern> {
val microservices = Cache.allMicroservices()
val result = mutableListOf<Antipattern>()
strategies.forEach { result.addAll(it.analyzeOperationNodes(microservices)) }
return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern

import de.fhdo.lemma.data.intermediate.IntermediateImportedAspect
import org.eclipse.emf.common.util.EList

/**
* Encapsulated logic for information extraction in aspects
* Created in context of antipattern analysis
*/
object AspectExtractor {
fun isArchitecturePatternDefined(aspects: EList<IntermediateImportedAspect>, architectureName: String): Boolean {
val names = aspects.filter { it.name.equals("ArchitecturePattern") }.flatMap { it.propertyValues }
.filter { it.property.name.equals("name") }
for (name in names) {
if (architectureName.equals(name.value)) {
return true
}
}
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.operation

import de.fhdo.lemma.analyzer.lib.impl.antipattern.Antipattern
import de.fhdo.lemma.operation.intermediate.IntermediateOperationNode

interface AntipatternOperationAnalyzerStrategy {
fun analyzeOperationNodes(allOperationNodes: Iterable<IntermediateOperationNode>): Collection<Antipattern>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.operation

import de.fhdo.lemma.analyzer.lib.impl.antipattern.Antipattern
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AntipatternType
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AspectExtractor
import de.fhdo.lemma.operation.intermediate.IntermediateOperationNode

class AspectUsageStrategy(val architecturePatternName: String, val antipatternType: AntipatternType, val description: String) : AntipatternOperationAnalyzerStrategy {
override fun analyzeOperationNodes(allOperationNodes: Iterable<IntermediateOperationNode>): Collection<Antipattern> {
val antipatterns = mutableListOf<Antipattern>()
for (node in allOperationNodes) {
if(AspectExtractor.isArchitecturePatternDefined(node.aspects,architecturePatternName)){
val tooManyDependent = getConnectedElements(node, allOperationNodes)
if(tooManyDependent){
antipatterns.add(Antipattern(antipatternType, "$description. Too many Nodes are dependend on ${node.name} with ArchitecturePattern $architecturePatternName."))
}
}
}
return antipatterns
}

private fun getConnectedElements(node: IntermediateOperationNode, allOperationNodes: Iterable<IntermediateOperationNode>):Boolean {
var count=node.usedByNodes.size;
for (operationNode in allOperationNodes) {
val dependonnodes = operationNode.dependsOnNodes
for (dependonnode in dependonnodes) {
if(dependonnode.name.equals(node.name)){
count++
}
}
}
val dependendOnMoreThan1 = count > 1
return dependendOnMoreThan1

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.operation

import de.fhdo.lemma.analyzer.lib.impl.antipattern.Antipattern
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AntipatternType
import de.fhdo.lemma.operation.intermediate.IntermediateOperationNode

class CircleStrategy : AntipatternOperationAnalyzerStrategy {
override fun analyzeOperationNodes(allOperationNodes: Iterable<IntermediateOperationNode>): Collection<Antipattern> {
val operationNodeMap = getOperationNodeMap(allOperationNodes)
val setOfOperationNodeLists = mutableSetOf<List<String>>() //Hier sind die Loops drin
for (key in operationNodeMap.keys) {
recursiveCall(key, key, listOf(key), setOfOperationNodeLists, mutableSetOf(),operationNodeMap)
}
val result = mutableSetOf<Antipattern>()
setOfOperationNodeLists.forEach{result.add(Antipattern(AntipatternType.CYCLIC_DEPENDENCY, "A cyclic dependency was detected. The cyclic dependency consists of $it"))}
return result
}

private fun recursiveCall(
parentKey: String,
checkedKey: String,
viewedElements: List<String>,
setOfLoopLists: MutableSet<List<String>>,
alreadyViewedElements: MutableSet<String>,
operationNodeMap: MutableMap<String, IntermediateOperationNode>
) {
val operationNode = operationNodeMap[checkedKey]
for (requiredNode in operationNode!!.dependsOnNodes) {//TODO hier kombinieren depends on und requiredBy
if(alreadyViewedElements.contains(requiredNode.name)){
continue
}
if(requiredNode.name.equals(parentKey)){
viewedElementsAddToSetIfNoEquivalentInSet(setOfLoopLists, viewedElements)
continue
}
alreadyViewedElements.add(requiredNode.name)
val list = viewedElements + listOf(requiredNode.name)
recursiveCall(parentKey, requiredNode.name, list, setOfLoopLists, alreadyViewedElements, operationNodeMap)
}
}

private fun viewedElementsAddToSetIfNoEquivalentInSet(
setOfLoopLists: MutableSet<List<String>>,
viewedElements: List<String>
) {
for (existingList in setOfLoopLists) {
if(existingList.size != viewedElements.size){
continue
}
var cont = false
for (viewedElement in viewedElements) {
if(!existingList.contains(viewedElement)){
cont=true
}
}
if(cont){
continue
}
return
}
setOfLoopLists.add(viewedElements)
}

private fun getOperationNodeMap(operationNodes: Iterable<IntermediateOperationNode>): MutableMap<String, IntermediateOperationNode> {
val map = mutableMapOf<String, IntermediateOperationNode>()
operationNodes.forEach { map[it.name] = it }
return map
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.operation

import de.fhdo.lemma.analyzer.lib.impl.antipattern.Antipattern
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AntipatternType
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AspectExtractor
import de.fhdo.lemma.operation.intermediate.IntermediateOperationNode
import de.fhdo.lemma.operation.intermediate.IntermediateOperationNodeReference

class MissingAspectStrategy(val architecturePatternName: String, val antipatternType: AntipatternType, val description: String, val endpointsRelevant: Boolean = false) : AntipatternOperationAnalyzerStrategy {
override fun analyzeOperationNodes(allOperationNodes: Iterable<IntermediateOperationNode>): Collection<Antipattern> {
val result = mutableSetOf<Antipattern>()
for (node in allOperationNodes) {
var skip = false
if(endpointsRelevant && node.endpoints.size == 0){
continue
}
if(AspectExtractor.isArchitecturePatternDefined(node.aspects, architecturePatternName)){
continue
}

for(dependendOnNodeReference in node.dependsOnNodes) {
val analyzedNode = getNodeWithName(dependendOnNodeReference, allOperationNodes)
if(AspectExtractor.isArchitecturePatternDefined(analyzedNode.aspects, architecturePatternName)){
skip = true
}
}
if (skip)continue
for(usedByNodeReference in node.usedByNodes) {
val analyzedNode = getNodeWithName(usedByNodeReference, allOperationNodes)
if(AspectExtractor.isArchitecturePatternDefined(analyzedNode.aspects,architecturePatternName)){
skip=true
}
}
if (skip)continue
result.add(Antipattern(antipatternType, "$description. Object ${node.name} has no connection to a Operation-Node with ArchitecturePattern-Aspect and name '$architecturePatternName'"))
}
return result
}

private fun getNodeWithName(
nodeReference: IntermediateOperationNodeReference,
allOperationNodes: Iterable<IntermediateOperationNode>
): IntermediateOperationNode{
for (node in allOperationNodes) {
if(node.name.equals(nodeReference.name)){
return node
}
}
throw RuntimeException("This should never happen")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.fhdo.lemma.analyzer.lib.impl.antipattern.strategies.operation

import de.fhdo.lemma.analyzer.lib.impl.antipattern.Antipattern
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AntipatternType
import de.fhdo.lemma.analyzer.lib.impl.antipattern.AspectExtractor
import de.fhdo.lemma.operation.intermediate.IntermediateOperationNode

/**
* This aspect describes a usage
*/
class WrongAspectStrategy(val architecturePatternName: String, val antipatternType: AntipatternType, val description: String) : AntipatternOperationAnalyzerStrategy {
override fun analyzeOperationNodes(allOperationNodes: Iterable<IntermediateOperationNode>): Collection<Antipattern> {
val result = mutableListOf<Antipattern>()
for (operationNode in allOperationNodes) {
if(AspectExtractor.isArchitecturePatternDefined(operationNode.aspects,architecturePatternName)){
result.add(Antipattern(antipatternType, "${description}. The service with the aspect is ${operationNode.name}"))
}
}
return result
}
}
Loading