Schichtenarchitektur überwachen mit Spring und JUnit

Die Schichtenarchitektur einer Anwendung kann über die Projektstruktur in der IDE überwacht werden. Dies hat allerdings den Nachteil, dass unter Umständen eine Vielzahl von IDE-Projekten verwaltet werden müssen. Da dies nicht immer gewünscht ist, möchte ich eine Alternative zu diesem Vorgehen vorstellen.

Wir nutzen einen Spring-Container, um die Abhängigkeiten zwischen den Komponenten der verschiedenen Schichten zu verwalten. Im Beispiel gibt es eine Controller- und eine Service-Schicht.
Die Komponenten lassen wir automatisch durch den Container per Namenskonvention finden. Alle Controller tragen das Suffix "Controller" und alle Service-Implementierungen das Suffix "ServiceImpl". Eine entsprechende Konfiguration mit Spring Boot sieht wie folgt aus:

@SpringBootApplication
@ComponentScan(
  includeFilters = @Filter(
    type = FilterType.REGEX, 
    pattern = { ".*ServiceImpl", ".*Controller" }), 
  basePackages = { "de.oio.architecture.demo" }
)
public class DepencencyTesterApplication {
  public static void main(String[] args) {
    SpringApplication.run(DepencencyTesterApplication.class, args);
  }
}

Durch diese Konfiguration können wir gleichzeitig sicherstellen, dass sich alle Entwickler an die Namenskonvention der beiden Schichten halten müssen, da die Klassen ansonsten nicht als Komponenten in den Container aufgenommen werden.

Nun können wir uns einen einfachen JUnit-Test schreiben, der die Abhängigkeiten zwischen den einzelnen Komponenten unter Annahme der oben beschriebenen Namenskonvention prüft. Dies ist möglich, da der Spring-Container in der Lage ist, uns alle Komponenten und die zugehörigen Abhängigkeiten zu liefern:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DepencencyTesterApplication.class)
public class DepencencyTesterApplicationTests {

  @Autowired
  private DefaultListableBeanFactory beanFactory;

  @Test
  public void checkDependencies() {
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      for (String depName : beanFactory.getDependenciesForBean(beanName)) {
        checkNotAllowedConnection(beanName, depName, ".*ServiceImpl", ".*Controller");
      }
    }
  }

  private void checkNotAllowedConnection(String beanName, String depName, String beanNamePattern, String dependencyNamePattern) {
    String msg = "Connection not allowed: " + beanName + " -> " + depName;
    assertFalse(msg, beanName.matches(beanNamePattern) && depName.matches(dependencyNamePattern));
  }
}

Ein vollständiges Beispiel-Projekt befindet sich auf GitHub.

Short URL for this post: http://wp.me/p4nxik-2nU
This entry was posted in Build, config and deploy, Java and Quality, Spring Universe and tagged , , , , , . Bookmark the permalink.

Leave a Reply