diff --git a/README.md b/README.md index 5937339..2b03846 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,106 @@ Reports are stored in "$pwd/build/reports/diktat-report.html". Утилиты для тестирования репозиториев +## codestyle-archunit + +Набор правил для поддержки оформления архитектуры. + +#### Список доступных правил + - `ru.touchin.codestyle.archunit.rules.ClassNamingArchRules` + - `ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules` + + +#### Gradle plugin +Настройка и применение совместно с [ArchUnit Gradle Plugin](https://github.com/societe-generale/arch-unit-gradle-plugin). + +Действие `checkRules` для проверки соответствие правилам запускается при операциях сборки, по умолчанию. +Вручную можно вызвать командой ``gradle :checkRules`` для нужного модуля. +Добавить его можно следующим образом на примере установки в рутовый gradle.build проекта: + +Groovy DSL: +```groovy +buildscript { + dependencies { + classpath "com.societegenerale.commons:arch-unit-gradle-plugin:3.0.0" + } +} +subprojects { + dependencyManagement { + dependencies { + dependency "com.tngtech.archunit:archunit:1.0.1" + } + } + + apply plugin: "java" + apply plugin: "com.societegenerale.commons.arch-unit-gradle-plugin" + + archUnit { + mainScopePath = "/classes/kotlin/main" // or "/classes/java/main" + testScopePath = "/classes/kotlin/test" // or "/classes/java/test" + + var applyType = applyOn("ru.touchin", "main") + configurableRules = [ + configurableRule( + "ru.touchin.codestyle.archunit.rules.ClassNamingArchRules", + applyType, + ), + configurableRule( + "ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules", + applyType, + ), + ] + } + + dependencies { + archUnitExtraLib "ru.touchin:codestyle-archunit" // or archUnitExtraLib project(":codestyle-archunit") + } +} +``` + +Kotlin DSL: +```kotlin +plugins { + id("com.societegenerale.commons.arch-unit-gradle-plugin") version "3.0.0" +} +subprojects { + configure { + dependencies { + dependency("com.tngtech.archunit:archunit:1.0.1") + } + } + + apply(plugin = "java") + apply(plugin = "com.societegenerale.commons.arch-unit-gradle-plugin") + + archUnit { + mainScopePath = "/classes/kotlin/main" // or "/classes/java/main" + testScopePath = "/classes/kotlin/test" // or "/classes/java/test" + + configurableRules = listOf( + "ru.touchin.codestyle.archunit.rules.ClassNamingArchRules", + "ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules" + ).map { package -> + configurableRule( + package, + applyOn("ru.touchin", "main") + ) + } + } + + dependencies { + archUnitExtraLib("ru.touchin:codestyle-archunit") // or archUnitExtraLib(project(":codestyle-archunit")) + } +} +``` + +Отключить проверки на таске помимо конфигурирования `configurableRule` можно также таким образом: +```kotlin +// clear action launch for root project to avoid exception +tasks.checkRules.configure { + actions.clear() +} +``` + ## logger Основные компоненты логирования: diff --git a/build.gradle.kts b/build.gradle.kts index 01becba..3cb9bce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.cli.common.toBooleanLenient plugins { kotlin("jvm") - id ("org.springframework.boot") apply false + id("org.springframework.boot") apply false // IntelliJ idea @@ -59,7 +59,6 @@ diktat { debug = false } - subprojects { println("Enabling Kotlin JVM plugin in project ${project.name}...") apply(plugin = "org.jetbrains.kotlin.jvm") @@ -82,7 +81,7 @@ subprojects { reports { txt.enabled = false xml.enabled = false - html{ + html { enabled = true destination = file("${project.buildDir}/reports/kotlin-detekt-${project.name}.html") } @@ -107,6 +106,8 @@ subprojects { dependency("org.junit.jupiter:junit-jupiter-params:5.4.2") dependency("org.junit.jupiter:junit-jupiter-engine:5.4.2") + dependency("com.tngtech.archunit:archunit:1.0.1") + dependency("org.liquibase:liquibase-core:4.4.0") dependency("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") @@ -132,7 +133,7 @@ subprojects { dependencies { // use for @ConstructorBinding implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation ("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") } diff --git a/codestyle-archunit/build.gradle.kts b/codestyle-archunit/build.gradle.kts new file mode 100644 index 0000000..920175b --- /dev/null +++ b/codestyle-archunit/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("kotlin") +} + +dependencies { + implementation(project(":common")) + implementation("com.tngtech.archunit:archunit") +} diff --git a/codestyle-archunit/src/main/kotlin/ru/touchin/codestyle/archunit/rules/ClassNamingArchRules.kt b/codestyle-archunit/src/main/kotlin/ru/touchin/codestyle/archunit/rules/ClassNamingArchRules.kt new file mode 100644 index 0000000..bf4e256 --- /dev/null +++ b/codestyle-archunit/src/main/kotlin/ru/touchin/codestyle/archunit/rules/ClassNamingArchRules.kt @@ -0,0 +1,53 @@ +package ru.touchin.codestyle.archunit.rules + +import com.tngtech.archunit.lang.ArchRule +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods +import ru.touchin.common.exceptions.CommonException + +/** + * Set of rules that defines constraint for classes naming. + */ +@Suppress("unused") +object ClassNamingArchRules { + + + val CLASSES_WHICH_HAVE_SCHEDULED_METHODS_MUST_HAVE_SUFFIX: ArchRule = methods() + .that() + .areAnnotatedWith("org.springframework.scheduling.annotation.Scheduled") + .should().beDeclaredInClassesThat().haveSimpleNameEndingWith("Job") + .allowEmptyShould(true) + .because("Classes that use 'Scheduled' annotation must have name with 'Job' suffix") + + val ANNOTATED_SERVICE_CLASSES_MUST_HAVE_SUFFIX: ArchRule = methods() + .that().areAnnotatedWith("org.springframework.stereotype.Service") + .should().beDeclaredInClassesThat().haveSimpleNameContaining("Service") + .allowEmptyShould(true) + .because("Classes that use 'Service' annotation are assignable to class with `Service` suffix") + + val ANNOTATED_ENTITY_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes() + .that().areAnnotatedWith("javax.persistence.Entity") + .should().haveSimpleNameEndingWith("Entity") + .allowEmptyShould(true) + .because("Hibernate 'Entity' classes must have name with 'Entity' suffix") + + val EXCEPTION_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes() + .that().areAssignableTo(Exception::class.java) + .or().areAssignableTo(CommonException::class.java) + .should().haveSimpleNameEndingWith("Exception") + .allowEmptyShould(true) + .because("'Exception' classes must have name with 'Exception' suffix") + + val JPAREPOSITORY_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes() + .that().areAssignableTo("org.springframework.data.jpa.repository.JpaRepository") + .should().haveSimpleNameEndingWith("Repository") + .allowEmptyShould(true) + .because("Classes that use 'JpaRepository' must have name with 'Repository' suffix") + + val WEBCLIENT_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes() + .that().haveFullyQualifiedName("org.springframework.web.reactive.valction.client.WebClient") + .should().onlyBeAccessed().byClassesThat().haveSimpleNameEndingWith("WebClient") + .allowEmptyShould(true) + .because("Classes that use Spring 'WebClient' must have name with 'WebClient' suffix") + +} diff --git a/codestyle-archunit/src/main/kotlin/ru/touchin/codestyle/archunit/rules/ClassPackagingArchRules.kt b/codestyle-archunit/src/main/kotlin/ru/touchin/codestyle/archunit/rules/ClassPackagingArchRules.kt new file mode 100644 index 0000000..8ec8a3a --- /dev/null +++ b/codestyle-archunit/src/main/kotlin/ru/touchin/codestyle/archunit/rules/ClassPackagingArchRules.kt @@ -0,0 +1,56 @@ +package ru.touchin.codestyle.archunit.rules + +import com.tngtech.archunit.lang.ArchRule +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes + +/** + * Set of rules that defines constraint for classes placement. + */ +@Suppress("unused") +object ClassPackagingArchRules { + + + val IMPL_CLASSES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes() + .that().haveSimpleNameEndingWith("Impl") + .should().resideInAPackage("..impl..") + .allowEmptyShould(true) + .because("Implementations of interfaces must reside in package 'impl'") + + + val ENTITIES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes() + .that().haveSimpleNameEndingWith("Entity") + .should().resideInAPackage("..models..") + .allowEmptyShould(true) + .because("'Entity' must reside in package 'models'") + + val REPOSITORIES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes() + .that().haveSimpleNameEndingWith("Repository") + .should().resideInAPackage("..repositories..") + .allowEmptyShould(true) + .because("'Repository' must reside in package 'repositories'") + + val CORE_SERVICES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes() + .that().haveSimpleNameEndingWith("CoreService") + .should().resideInAPackage("..services..") + .allowEmptyShould(true) + .because("'CoreService' must reside in package 'services'") + + val CONVERTERS_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes() + .that().haveSimpleNameEndingWith("Converter") + .should().resideInAPackage("..converters..") + .allowEmptyShould(true) + .because("'Converter' must reside in package 'converters'") + + val EXCEPTIONS_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes() + .that().haveSimpleNameEndingWith("Exception") + .should().resideInAPackage("..exceptions..") + .allowEmptyShould(true) + .because("'Exception' must reside in package 'exceptions'") + + val ENUM_CLASSES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes() + .that().areEnums() + .should().resideInAPackage("..enums..") + .allowEmptyShould(true) + .because("'Enum' must reside in package 'enums'") + +} diff --git a/detekt-config.yml b/detekt-config.yml index 95bcf79..903dea1 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -295,7 +295,7 @@ style: conversionFunctionPrefix: 'to' DestructuringDeclarationWithTooManyEntries: active: true - maxDestructuringEntries: 1 + maxDestructuringEntries: 5 EqualsNullCall: active: true ExplicitItLambdaParameter: diff --git a/settings.gradle.kts b/settings.gradle.kts index 8fba89c..3851799 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,6 +39,7 @@ include("common-geo") include("common-geo-spatial4j-spring") include("common-messaging") include("captcha") +include("codestyle-archunit") include("logger") include("logger-spring") include("logger-spring-web")