Skip to content

Commit

Permalink
Smell detection aligned with latest definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
Agomik committed Apr 5, 2024
1 parent 7f925cd commit 525df4d
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 33 deletions.
11 changes: 11 additions & 0 deletions data/tests/test_sniffer_sbc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ topology_template:
type: micro.nodes.Service
requirements:
- interaction: dbt4
st5:
type: micro.nodes.Service
requirements:
- interaction: dbt5
dbt5:
type: micro.nodes.Datastore
groups:
Edge:
type: micro.groups.Edge
Expand Down Expand Up @@ -77,6 +83,11 @@ topology_template:
type: micro.groups.Team
members:
- s2t4
tt5:
type: micro.groups.Team
members:
- st5
- dbt5
relationship_templates:
t:
type: micro.relationships.InteractsWith
Expand Down
54 changes: 54 additions & 0 deletions data/tests/test_sniffer_slt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,34 @@ topology_template:
- interaction: mr2t8
mr2t8:
type: micro.nodes.MessageRouter
st9:
type: micro.nodes.Service
requirements:
- interaction: d2t9
d1t9:
type: micro.nodes.Datastore
d2t9:
type: micro.nodes.Datastore
mr1t10:
type: micro.nodes.MessageRouter
mr2t10:
type: micro.nodes.MessageRouter
st10:
type: micro.nodes.Service
requirements:
- interaction: mr2t10
st11:
type: micro.nodes.Service
requirements:
- interaction: mb2t11
mb1t11:
type: micro.nodes.MessageBroker
mb2t11:
type: micro.nodes.MessageBroker
groups:
Edge:
type: micro.groups.Edge
members: []
t1t1:
type: micro.groups.Team
members:
Expand Down Expand Up @@ -133,6 +160,33 @@ topology_template:
members:
- mr1t8
- mr2t8
t1t9:
type: micro.groups.Team
members:
- d1t9
- st9
t2t9:
type: micro.groups.Team
members:
- d2t9
t1t11:
type: micro.groups.Team
members:
- mb1t11
- st11
t2t11:
type: micro.groups.Team
members:
- mb2t11
t1t10:
type: micro.groups.Team
members:
- mr1t10
- st10
t2t10:
type: micro.groups.Team
members:
- mr2t10
relationship_templates:
t:
type: micro.relationships.InteractsWith
Expand Down
4 changes: 2 additions & 2 deletions microfreshener/core/analyser/costants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SMELLS_NAME = SMELL_ENDPOINT_BASED_SERVICE_INTERACTION, SMELL_WOBBLY_SERVICE_INTERACTION_SMELL, SMELL_SHARED_PERSISTENCY, SMELL_NO_API_GATEWAY, SMELL_SINGLE_LAYER_TEAMS, SMELL_MULTIPLE_SERVICES_IN_ONE_CONTAINER, SMELL_ESB_MISUSE, SMELL_TIGHTLY_COUPLED_TEAMS, SMELL_SHARED_BOUNDED_CONTEXT =\
"Endpoint-based-service-interaction", "Wobbly-service-interaction", "Shared-persistency", "No-api-gateway", "Single-layer-teams", "Multiple-services-in-one-container", "ESB-misuse", "Tightly-coupled-teams", "Shared-bounded-context"

REFACTORING_NAMES = REFACTORING_ADD_SERVICE_DISCOVERY, REFACTORING_ADD_MESSAGE_ROUTER, REFACTORING_ADD_MESSAGE_BROKER, REFACTORING_ADD_CIRCUIT_BREAKER, REFACTORING_USE_TIMEOUT, REFACTORING_MERGE_SERVICES, REFACTORING_SPLIT_DATABASE, REFACTORING_ADD_DATA_MANAGER, REFACTORING_ADD_API_GATEWAY, REFACTORING_SPLIT_SERVICES, REFACTORING_SPLIT_TEAMS_BY_SERVICE, REFACTORING_SPLIT_TEAMS_BY_COUPLING, REFACTORING_MERGE_TEAMS =\
"Add-service-discovery", "Add-message-router", "Add-message-broker", "Add-circuit-breaker", "Use-timeout", "Merge-service", "Split-Datastore", "Add-data-manager", "Add-api-gateway", "Split-service-in-two-pods", "Split-teams-by-service", "Split-teams-by-coupling", "Merge-teams"
REFACTORING_NAMES = REFACTORING_ADD_SERVICE_DISCOVERY, REFACTORING_ADD_MESSAGE_ROUTER, REFACTORING_ADD_MESSAGE_BROKER, REFACTORING_ADD_CIRCUIT_BREAKER, REFACTORING_USE_TIMEOUT, REFACTORING_MERGE_SERVICES, REFACTORING_SPLIT_DATABASE, REFACTORING_ADD_DATA_MANAGER, REFACTORING_ADD_API_GATEWAY, REFACTORING_SPLIT_SERVICES, REFACTORING_SPLIT_TEAMS_BY_MICROSERVICE, REFACTORING_SPLIT_TEAMS_BY_COUPLING, REFACTORING_REORGANIZE_TEAMS_BY_BOUNDED_CONTEXT, REFACTORING_SPLIT_BOUNDED_CONTEXT_BY_TEAMS =\
"Add-service-discovery", "Add-message-router", "Add-message-broker", "Add-circuit-breaker", "Use-timeout", "Merge-service", "Split-Datastore", "Add-data-manager", "Add-api-gateway", "Split-service-in-two-pods", "Split-teams-by-microservice", "Split-teams-by-coupling", "Reorganize-teams-by-bounded-contexts", "Split-bounded-context-by-teams"
8 changes: 4 additions & 4 deletions microfreshener/core/analyser/smell.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ..model import Relationship
from ..model import nodes
from .costants import SMELL_ENDPOINT_BASED_SERVICE_INTERACTION, SMELL_NO_API_GATEWAY, SMELL_SHARED_PERSISTENCY, SMELL_WOBBLY_SERVICE_INTERACTION_SMELL, SMELL_SINGLE_LAYER_TEAMS, SMELL_MULTIPLE_SERVICES_IN_ONE_CONTAINER, SMELL_TIGHTLY_COUPLED_TEAMS, SMELL_SHARED_BOUNDED_CONTEXT
from .costants import REFACTORING_ADD_SERVICE_DISCOVERY, REFACTORING_ADD_MESSAGE_ROUTER, REFACTORING_ADD_MESSAGE_BROKER, REFACTORING_ADD_CIRCUIT_BREAKER, REFACTORING_USE_TIMEOUT, REFACTORING_MERGE_SERVICES, REFACTORING_SPLIT_DATABASE, REFACTORING_ADD_DATA_MANAGER, REFACTORING_ADD_API_GATEWAY, REFACTORING_SPLIT_SERVICES, REFACTORING_SPLIT_TEAMS_BY_SERVICE, REFACTORING_SPLIT_TEAMS_BY_COUPLING, REFACTORING_MERGE_TEAMS
from .costants import REFACTORING_ADD_SERVICE_DISCOVERY, REFACTORING_ADD_MESSAGE_ROUTER, REFACTORING_ADD_MESSAGE_BROKER, REFACTORING_ADD_CIRCUIT_BREAKER, REFACTORING_USE_TIMEOUT, REFACTORING_MERGE_SERVICES, REFACTORING_SPLIT_DATABASE, REFACTORING_ADD_DATA_MANAGER, REFACTORING_ADD_API_GATEWAY, REFACTORING_SPLIT_SERVICES, REFACTORING_SPLIT_TEAMS_BY_MICROSERVICE, REFACTORING_SPLIT_TEAMS_BY_COUPLING, REFACTORING_REORGANIZE_TEAMS_BY_BOUNDED_CONTEXT, REFACTORING_SPLIT_BOUNDED_CONTEXT_BY_TEAMS
class Smell(object):

def __init__(self, name):
Expand Down Expand Up @@ -143,7 +143,7 @@ def __str__(self):
def to_dict(self):
sup_dict = super(SingleLayerTeamsSmell, self).to_dict()
return {**sup_dict, **{"refactorings": [
{"name": REFACTORING_SPLIT_TEAMS_BY_SERVICE, "description": "Split the teams by service."},
{"name": REFACTORING_SPLIT_TEAMS_BY_MICROSERVICE, "description": "Split the teams by microservice."},
]}}

class MultipleServicesInOneContainerSmell(NodeSmell):
Expand Down Expand Up @@ -188,6 +188,6 @@ def __str__(self):
def to_dict(self):
sup_dict = super(SharedBoundedContextSmell, self).to_dict()
return {**sup_dict, **{"refactorings": [
{"name": REFACTORING_SPLIT_DATABASE, "description": "Split the database among the user teams."},
{"name": REFACTORING_MERGE_TEAMS, "description": "Merge the teams."},
{"name": REFACTORING_REORGANIZE_TEAMS_BY_BOUNDED_CONTEXT, "description": "Move the bounded context inside team borders."},
{"name": REFACTORING_SPLIT_BOUNDED_CONTEXT_BY_TEAMS, "description": "Split bounded context among teams."},
]}}
31 changes: 15 additions & 16 deletions microfreshener/core/analyser/sniffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ class SingleLayerTeamsSmellSniffer(GroupSmellSniffer):
def __str__(self):
return 'SingleLayerTeamsSmellSniffer({})'.format(super(GroupSmellSniffer, self).__str__())

def _team_not_containing_type(self, group: Team, type):
for node in group.members:
if isinstance(node, type):
return False
return True

def _not_internally_linked_to_service(self, node):
same_squad = self.micro_model.squad_of(node)
for link in (node.interactions + node.incoming_interactions):
Expand All @@ -116,7 +122,6 @@ def _not_internally_linked_to_service(self, node):
return False
return True


@visitor(Team)
def snif(self, group: Team) -> SingleLayerTeamsSmell:
smell = SingleLayerTeamsSmell(group)
Expand All @@ -126,11 +131,8 @@ def snif(self, group: Team) -> SingleLayerTeamsSmell:
target_node = relationship.target
source_squad = self.micro_model.squad_of(source_node)
target_squad = self.micro_model.squad_of(target_node)
if (source_squad is not None and
target_squad is not None and
source_squad != target_squad and
isinstance(source_node, Service) and
not isinstance(target_node, Service) and
if ((source_squad is not None and target_squad is not None and source_squad != target_squad) and
(isinstance(source_node, Service) and not isinstance(target_node, Service) and self._team_not_containing_type(source_squad, type(target_node))) and
(isinstance(target_node, Datastore) or self._not_internally_linked_to_service(target_node))):
smell.addLinkCause(relationship)
return smell
Expand Down Expand Up @@ -197,23 +199,20 @@ class SharedBoundedContextSmellSniffer(GroupSmellSniffer):
def __str__(self):
return 'SharedBoundedContextSmellSniffer({})'.format(super(GroupSmellSniffer, self).__str__())

def _is_datastore_linked_to_other_squads(self, node, allowedSquad):
for link in node.incoming_interactions:
source_squad = self.micro_model.squad_of(link.source)
if source_squad is not None and source_squad is not allowedSquad:
return True
return False

@visitor(Team)
def snif(self, group: Team) -> SharedBoundedContextSmell:
smell = SharedBoundedContextSmell(group)
for node in group.members:
if(isinstance(node, Datastore)):
if self._is_datastore_linked_to_other_squads(node, 2):
smell.addLinkCause(relationship)
for relationship in node.incoming_interactions:
source_node = relationship.source
source_squad = self.micro_model.squad_of(source_node)
if (source_squad is not None and source_squad is not group):
smell.addLinkCause(relationship)
elif(isinstance(node, Service)):
for relationship in node.interactions:
target_node = relationship.target
if isinstance(target_node, Datastore) and self._is_datastore_linked_to_other_squads(target_node, group):
target_squad = self.micro_model.squad_of(target_node)
if (isinstance(target_node, Datastore) and target_squad is not group):
smell.addLinkCause(relationship)
return smell
27 changes: 19 additions & 8 deletions tests/test_analyser/test_sniffer_sbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from microfreshener.core.analyser.sniffer import SharedBoundedContextSmellSniffer
from microfreshener.core.analyser.smell import SharedBoundedContextSmell

import os

class TestSharedBoundedContextSmell(TestCase):

@classmethod
def setUpClass(self):
file = 'data/tests/test_sniffer_sbc.yml'
file = os.getcwd() + '/data/tests/test_sniffer_sbc.yml'
loader = YMLImporter()
self.micro_model = loader.Import(file)
self.sbcSniffer = SharedBoundedContextSmellSniffer(self.micro_model)
Expand All @@ -21,23 +22,28 @@ def test_yes_sbc(self):
self.assertFalse(smell1.isEmpty())
team2 = self.micro_model.get_group("t2t1")
smell2 = self.sbcSniffer.snif(team2)
self.assertTrue(smell2.isEmpty())
self.assertIsInstance(smell2, SharedBoundedContextSmell)
self.assertFalse(smell2.isEmpty())

def test_no_sbc_with_external_service(self):
def test_yes_sbc_with_external_service(self):
team1 = self.micro_model.get_group("t1t2")
smell1 = self.sbcSniffer.snif(team1)
self.assertTrue(smell1.isEmpty())
self.assertIsInstance(smell1, SharedBoundedContextSmell)
self.assertFalse(smell1.isEmpty())
team2 = self.micro_model.get_group("t2t2")
smell2 = self.sbcSniffer.snif(team2)
self.assertTrue(smell2.isEmpty())
self.assertIsInstance(smell2, SharedBoundedContextSmell)
self.assertFalse(smell2.isEmpty())

def test_no_sbc_if_no_other_db_user(self):
def test_yes_sbc_if_no_other_db_user(self):
team1 = self.micro_model.get_group("t1t3")
smell1 = self.sbcSniffer.snif(team1)
self.assertTrue(smell1.isEmpty())
self.assertIsInstance(smell1, SharedBoundedContextSmell)
self.assertFalse(smell1.isEmpty())
team2 = self.micro_model.get_group("t2t3")
smell2 = self.sbcSniffer.snif(team2)
self.assertTrue(smell2.isEmpty())
self.assertIsInstance(smell2, SharedBoundedContextSmell)
self.assertFalse(smell2.isEmpty())

def test_yes_sbc_db_without_team(self):
team1 = self.micro_model.get_group("t1t4")
Expand All @@ -48,3 +54,8 @@ def test_yes_sbc_db_without_team(self):
smell2 = self.sbcSniffer.snif(team2)
self.assertIsInstance(smell2, SharedBoundedContextSmell)
self.assertFalse(smell2.isEmpty())

def test_no_sbc_same_team(self):
team1 = self.micro_model.get_group("tt5")
smell1 = self.sbcSniffer.snif(team1)
self.assertTrue(smell1.isEmpty())
20 changes: 18 additions & 2 deletions tests/test_analyser/test_sniffer_slt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from microfreshener.core.analyser.sniffer import SingleLayerTeamsSmellSniffer
from microfreshener.core.analyser.smell import SingleLayerTeamsSmell

import os

class TestSingleLayerTeamsSmell(TestCase):

@classmethod
def setUpClass(self):
file = 'data/tests/test_sniffer_slt.yml'
file = os.getcwd() + '/data/tests/test_sniffer_slt.yml'
loader = YMLImporter()
self.micro_model = loader.Import(file)
self.sltSniffer = SingleLayerTeamsSmellSniffer(self.micro_model)
Expand Down Expand Up @@ -78,4 +79,19 @@ def test_yes_slt_with_mr_internally_linked_to_mr(self):
self.assertFalse(smell1.isEmpty())
team2 = self.micro_model.get_group("t2t8")
smell2 = self.sltSniffer.snif(team2)
self.assertTrue(smell2.isEmpty())
self.assertTrue(smell2.isEmpty())

def test_no_slt_with_owned_db(self):
team1 = self.micro_model.get_group("t1t9")
smell1 = self.sltSniffer.snif(team1)
self.assertTrue(smell1.isEmpty())

def test_no_slt_with_owned_mr(self):
team1 = self.micro_model.get_group("t1t10")
smell1 = self.sltSniffer.snif(team1)
self.assertTrue(smell1.isEmpty())

def test_no_slt_with_owned_mb(self):
team1 = self.micro_model.get_group("t1t11")
smell1 = self.sltSniffer.snif(team1)
self.assertTrue(smell1.isEmpty())
3 changes: 2 additions & 1 deletion tests/test_analyser/test_sniffer_tct.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from microfreshener.core.analyser.sniffer import TightlyCoupledTeamsSmellSniffer
from microfreshener.core.analyser.smell import TightlyCoupledTeamsSmell

import os

class TestTightlyCoupledTeamsSmell(TestCase):

@classmethod
def setUpClass(self):
file = 'data/tests/test_sniffer_tct.yml'
file = os.getcwd() + '/data/tests/test_sniffer_tct.yml'
loader = YMLImporter()
self.micro_model = loader.Import(file)
self.tctSniffer = TightlyCoupledTeamsSmellSniffer(self.micro_model)
Expand Down

0 comments on commit 525df4d

Please sign in to comment.