diff --git a/.github/workflows/sedr-library-release.yml b/.github/workflows/sedr-library-release.yml index b3d8f8b..ac47f3d 100644 --- a/.github/workflows/sedr-library-release.yml +++ b/.github/workflows/sedr-library-release.yml @@ -50,7 +50,6 @@ jobs: needs: Calculate-Version permissions: contents: read - packages: read runs-on: ubuntu-latest @@ -127,7 +126,6 @@ jobs: if: ${{ needs.Calculate-Version.outputs.next_version != '' }} permissions: contents: read - packages: write runs-on: ubuntu-latest @@ -164,18 +162,10 @@ jobs: - name: Publish SEDR Library run: | cd sedr-library - if echo "$MAVEN_OR_GITHUB" | grep -iq "^maven$"; then - echo "Publishing to Maven Central via jReleaser" - ./gradlew publishMavenPublicationToStagingDeployRepository - ./gradlew jreleaserDeploy - else - echo "Publishing to GitHub Packages" - ./gradlew publishMavenPublicationToGitHubPackagesRepository - fi + echo "Publishing to Maven Central via jReleaser" + ./gradlew publishMavenPublicationToStagingDeployRepository + ./gradlew jreleaserDeploy env: - MAVEN_OR_GITHUB: ${{ vars.MAVEN_OR_GITHUB }} - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TOKEN: ${{ secrets.SEDR_AUTOMATION_TOKEN }} JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} JRELEASER_GPG_SECRET_KEY: ${{ secrets.SIGNING_KEY }} @@ -202,18 +192,24 @@ jobs: ref: main token: ${{ secrets.SEDR_AUTOMATION_TOKEN }} - - name: Update version in README.adoc + - name: Update version in sedr-library README run: | - sed -i "s/|Latest version |.\`[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[a-zA-Z0-9.-]*\`/|Latest version |\`${{ env.VERSION }}\`/" README.adoc - sed -i "s/sedr-library:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[a-zA-Z0-9.-]*/sedr-library:${{ env.VERSION }}/g" README.adoc - sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[a-zA-Z0-9.-]*|${{ env.VERSION }}|" README.adoc + sed -i "s/|Latest version |.\`[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[a-zA-Z0-9.-]*\`/|Latest version |\`${{ env.VERSION }}\`/" sedr-library/README.adoc + sed -i "s/sedr-library:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[a-zA-Z0-9.-]*/sedr-library:${{ env.VERSION }}/g" sedr-library/README.adoc + sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[a-zA-Z0-9.-]*|${{ env.VERSION }}|" sedr-library/README.adoc + + - name: Upload updated sedr-library README + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: sedr-library-readme-${{ env.VERSION }} + path: sedr-library/README.adoc - - name: Commit and push updated README.adoc + - name: Commit and push updated sedr-library README run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add README.adoc - git diff --cached --quiet || git commit -m "docs(readme): update version to ${{ env.VERSION }} [skip ci]" + git add sedr-library/README.adoc + git diff --cached --quiet || git commit -m "docs(sedr-library): update README version to ${{ env.VERSION }} [skip ci]" git push Determine-Release-Type: diff --git a/.github/workflows/spring-rules-release.yml b/.github/workflows/spring-rules-release.yml index 98b8596..18e74ec 100644 --- a/.github/workflows/spring-rules-release.yml +++ b/.github/workflows/spring-rules-release.yml @@ -148,6 +148,43 @@ jobs: JRELEASER_GPG_PASSPHRASE: ${{ secrets.SIGNING_PASSWORD }} JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.SIGNING_PUBLIC_KEY }} + Update-README: + name: Update README version + needs: [Calculate-Version, Publish] + if: ${{ needs.Calculate-Version.outputs.next_version != '' }} + permissions: + contents: write + runs-on: ubuntu-latest + env: + VERSION: ${{ needs.Calculate-Version.outputs.next_version }} + + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: main + token: ${{ secrets.SEDR_AUTOMATION_TOKEN }} + + - name: Update version in hexagonal-spring-rules README + run: | + sed -i -E "s#^\|Latest version \|.*#|Latest version |\`${{ env.VERSION }}\`#" hexagonal-spring-rules/README.adoc + sed -i -E "s#(architecture-validator-hexagonal-spring-rules:)(|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[a-zA-Z0-9.-]*)#\1${{ env.VERSION }}#g" hexagonal-spring-rules/README.adoc + + - name: Upload updated hexagonal-spring-rules README + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: hexagonal-spring-rules-readme-${{ env.VERSION }} + path: hexagonal-spring-rules/README.adoc + + - name: Commit and push updated hexagonal-spring-rules README + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add hexagonal-spring-rules/README.adoc + git diff --cached --quiet || git commit -m "docs(hexagonal-spring-rules): update README version to ${{ env.VERSION }} [skip ci]" + git push + Determine-Release-Type: needs: [Calculate-Version] if: ${{ needs.Calculate-Version.outputs.next_version != '' }} @@ -155,7 +192,7 @@ jobs: Workflow-Summary: name: Workflow Summary - needs: [Build, Security-Scan, Publish, Release-Spring-Rules, Determine-Release-Type] + needs: [Build, Security-Scan, Publish, Update-README, Release-Spring-Rules, Determine-Release-Type] if: always() runs-on: ubuntu-latest steps: diff --git a/README.adoc b/README.adoc index 46e8190..0fdb12a 100644 --- a/README.adoc +++ b/README.adoc @@ -33,397 +33,21 @@ Run `./gradlew` from *within* a project's directory, not from the repository roo |=== == sedr-library - -Shared Java annotations and utilities for _Software Engineering Done Right_ projects. - -[cols="1,1"] -|=== -|Latest version |`0.4.6` -|Group ID |`com.arc-e-tect.sedr.utils` -|Artifact ID |`sedr-library` -|=== - -=== What's included - -[cols="1,2,3"] -|=== -|Class |Package |Description - -|`ExcludeFromJacocoGeneratedCodeCoverage` -|`com.arc_e_tect.sedr.utils.jacoco.marker` -|Marker annotation to document constructors, methods, or types that should be treated as excluded from JaCoCo code-coverage measurement, with an ArchUnit rule that enforces a justification. -|=== - -=== Adding the dependency - -The library is published to https://github.com/Arc-E-Tect/SoftwareEngineeringDoneRight-Library/packages/2968105[GitHub Packages]. -Unlike Maven Central, GitHub Packages requires authentication even for public packages. -You need a https://github.com/settings/tokens[GitHub Personal Access Token (PAT)] with the `read:packages` scope. - -==== 1. Store your credentials locally - -Add the following to `~/.gradle/gradle.properties` (create the file if it does not exist). -Never commit this file — it lives outside your project. - -[source,properties] ----- -gpr.user= -gpr.key= ----- - -==== 2. Declare the repository - -.Gradle (Kotlin DSL — `build.gradle.kts`) -[source,kotlin] ----- -repositories { - maven { - url = uri("https://maven.pkg.github.com/Arc-E-Tect/SoftwareEngineeringDoneRight-Library") - credentials { - username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") - password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") - } - } -} ----- - -.Gradle (Groovy DSL — `build.gradle`) -[source,groovy] ----- -repositories { - maven { - url = uri('https://maven.pkg.github.com/Arc-E-Tect/SoftwareEngineeringDoneRight-Library') - credentials { - username = project.findProperty('gpr.user') ?: System.getenv('GITHUB_ACTOR') - password = project.findProperty('gpr.key') ?: System.getenv('GITHUB_TOKEN') - } - } -} ----- - -.Maven (`pom.xml`) -[source,xml] ----- - - - github-sedr-library - https://maven.pkg.github.com/Arc-E-Tect/SoftwareEngineeringDoneRight-Library - - ----- - -For Maven, add your credentials to `~/.m2/settings.xml`: - -[source,xml] ----- - - - - github-sedr-library - ${env.GITHUB_ACTOR} - ${env.GITHUB_TOKEN} - - - ----- - -==== 3. Declare the dependency - -.Gradle (Kotlin DSL) -[source,kotlin] ----- -dependencies { - implementation("com.arc-e-tect.sedr.utils:sedr-library:0.4.6") -} ----- - -.Gradle (Groovy DSL) -[source,groovy] ----- -dependencies { - implementation 'com.arc-e-tect.sedr.utils:sedr-library:0.4.6' -} ----- - -.Maven -[source,xml] ----- - - com.arc-e-tect.sedr.utils - sedr-library - 0.4.6 - ----- - -==== Using in CI/CD - -In GitHub Actions, `GITHUB_ACTOR` and `GITHUB_TOKEN` are injected automatically — no extra secrets are needed when consuming the package from within a GitHub Actions workflow. - -[source,yaml] ----- -- name: Build - run: ./gradlew build - env: - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ----- - -=== Enforcing the justification convention with ArchUnit - -The library ships a `test-fixtures` artifact that contains the reusable ArchUnit rule `AbstractCoverageExclusionConventionsTest`. -It verifies that every use of `@ExcludeFromJacocoGeneratedCodeCoverage` in your production code carries a non-blank `justification`. -The Gradle builds in this repository use Gradle's native JaCoCo support, with the JaCoCo tool version managed through `refreshVersions` in each build. - -==== Add the test-fixtures dependency - -.Gradle (Kotlin DSL) -[source,kotlin] ----- -dependencies { - testImplementation(testFixtures("com.arc-e-tect.sedr.utils:sedr-library:0.4.6")) -} ----- - -.Gradle (Groovy DSL) -[source,groovy] ----- -dependencies { - testImplementation testFixtures('com.arc-e-tect.sedr.utils:sedr-library:0.4.6') -} ----- - -The test-fixtures artifact brings `archunit-junit5` and `junit-jupiter-api` in as transitive dependencies, so no extra entries are needed for those. -You do need to add the JUnit Jupiter engine for test execution: - -.Gradle (Kotlin DSL) -[source,kotlin] ----- -dependencies { - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.4") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") -} ----- - -.Gradle (Groovy DSL) -[source,groovy] ----- -dependencies { - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.4' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' -} ----- - -==== Write the convention test - -Extend `AbstractCoverageExclusionConventionsTest` and override `getBasePackage()` to scope the rule to your project's root package. - -[source,java] ----- -class CoverageExclusionConventionsTest extends AbstractCoverageExclusionConventionsTest { - - @Override - protected String getBasePackage() { - return "com.example.myapp"; - } -} ----- - -The inherited test `coverageExclusionAnnotationsMustHaveJustification()` runs automatically and fails if any class, constructor, or method in the scanned packages carries `@ExcludeFromJacocoGeneratedCodeCoverage` without a non-blank `justification`. - -==== Using the annotation correctly - -.Compliant — justification provided -[source,java] ----- -@ExcludeFromJacocoGeneratedCodeCoverage( - justification = "Spring Boot entry point – not unit-testable") -public static void main(String[] args) { - SpringApplication.run(MyApp.class, args); -} ----- - -.Non-compliant — ArchUnit rule will fail -[source,java] ----- -@ExcludeFromJacocoGeneratedCodeCoverage // missing justification -public static void main(String[] args) { - SpringApplication.run(MyApp.class, args); -} ----- - -=== Example project - -A self-contained example demonstrating both compliant and non-compliant usage is located at link:examples/sedr-library/jacoco-marker/[`examples/sedr-library/jacoco-marker/`]. - -[cols="1,2"] -|=== -|File |Purpose - -|`src/main/java/.../CompliantService.java` -|Uses `@ExcludeFromJacocoGeneratedCodeCoverage` with a justification — passes the rule. - -|`src/main/java/.../NonCompliantService.java` -|Uses `@ExcludeFromJacocoGeneratedCodeCoverage` without a justification — the ArchUnit test reports a violation for this class. - -|`src/test/java/.../CoverageExclusionConventionsTest.java` -|Extends `AbstractCoverageExclusionConventionsTest` targeting the example package. -|=== - -To run the example (requires the library to be published to your local Maven repository first): - -[source,bash] ----- -# 1. Publish the library locally -cd sedr-library && ./gradlew clean build publishToMavenLocal - -# 2. Run the example — expected to FAIL with one ArchUnit violation -cd ../examples/sedr-library/jacoco-marker && ./gradlew test ----- - -The test run reports exactly one violation: -`Method NonCompliantService.stop() uses @ExcludeFromJacocoGeneratedCodeCoverage without a justification`. -`CompliantService` is not mentioned. +include::sedr-library/README.adoc[tag=root,leveloffset=+1] == hexagonal-spring-rules - -Published Spring-focused external rule pack for the Architecture Validator Gradle plugin. -It enforces Hexagonal (ports and adapters) architectural boundaries — dependency direction, domain isolation, and port contracts — against Spring applications. - -See link:hexagonal-spring-rules/README.adoc[`hexagonal-spring-rules/README.adoc`] for build instructions and CI/release details, and link:hexagonal-spring-rules/CHANGELOG.md[CHANGELOG.md] for the release history and current version. - -[cols="1,1"] -|=== -|Group ID |`com.arc-e-tect.architecture-validator` -|Artifact ID |`architecture-validator-hexagonal-spring-rules` -|=== - -=== What's included - -The rule pack is a set of ArchUnit-based test classes that the Architecture Validator plugin discovers on the `testArchitecture` classpath and folds into the generated rule-pack suite. - -[cols="1,3"] -|=== -|Class |Checks - -|`SpringHexagonalArchitectureTest` -|Controllers only call in-ports, services do not access repositories directly (must go through an out-port), repositories are only reached through out-ports, and Spring components live in the expected Hexagonal layer. - -|`DomainIsolationTest` -|The domain model depends only on Java core and other domain classes; the application core carries no Spring, persistence, or validation framework dependencies; application services stay free of Spring stereotypes. - -|`DependencyDirectionTest` -|Dependencies flow inward only — the core never depends on adapters or adapter implementations, adapters depend only on ports (not concrete services), and only the configuration layer wires concrete services directly. - -|`PortContractTest` -|In-ports and out-ports are interfaces, and port signatures reference only Java core and domain model types — no framework leakage into port contracts. -|=== - -=== Consuming the rule pack - -Add the rule pack to `testArchitectureImplementation` in a project that already applies the Architecture Validator plugin: - -[source,groovy] ----- -dependencies { - testArchitectureImplementation 'com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules:' -} ----- - -The rules read package boundaries from system properties set by the Architecture Validator plugin's `architectureValidator` extension: - -[cols="1,3"] -|=== -|Property |Purpose - -|`architectureValidator.basePackage` -|Root package to validate. - -|`architectureValidator.inPorts` -|Comma-separated in-port packages. - -|`architectureValidator.outPorts` -|Comma-separated out-port packages. - -|`architectureValidator.domainModel` -|Comma-separated domain model packages. - -|`architectureValidator.adapters` -|Comma-separated adapter packages. - -|`architectureValidator.applicationServices` -|Comma-separated application service packages. -|=== - -=== Example project - -A self-contained example demonstrating a Spring Hexagonal architecture violation is located at link:examples/architecture-validator/single-module-spring/[`examples/architecture-validator/single-module-spring/`]. -See link:examples/architecture-validator/single-module-spring/README.adoc[its README] for full details. - -[cols="1,2"] -|=== -|File |Purpose - -|`.../application/service/OrderService.java` -|A Spring `@Service` that depends directly on `OrderRepository` — the rule violation under test. - -|`.../adapter/persistence/OrderRepository.java` -|Adapter-layer Spring repository reached directly by the service instead of through an out-port. - -|`.../adapter/web/OrderController.java` -|Inbound web adapter calling the use case. - -|`.../application/port/in/OrderUseCase.java` -|Inbound port interface. - -|`.../application/domain/model/Order.java` -|Domain model. -|=== - -To run the example — expected to FAIL because the sample is intentionally non-compliant: - -[source,bash] ----- -cd examples/architecture-validator/single-module-spring && ./gradlew testArchitecture ----- - -To fix the violation: introduce an outbound port in `application.port.out`, make `OrderService` depend on that port, and move the repository implementation behind an adapter that satisfies the port contract. +include::hexagonal-spring-rules/README.adoc[tag=root,leveloffset=+1] == nvd-cache-primer +include::nvd-cache-primer/README.adoc[tag=root,leveloffset=+1] -Internal utility Gradle project that primes and validates the OWASP Dependency-Check NVD (National Vulnerability Database) cache shared by this repository's security-scan workflows. -It is not published — its only purpose is running the `dependencyCheckUpdate` task in CI. - -=== Why it exists - -`sedr-library` and `hexagonal-spring-rules` each run OWASP Dependency-Check as part of their security-scan workflows. -Updating the NVD database from scratch is slow and can hit NVD API rate limits when run repeatedly. -`nvd-cache-primer` is a minimal project — it has no source code, just the `owaspDependencycheck` plugin — dedicated to warming a `~/.gradle/dependency-check-data` cache once a day. - -The scheduled workflow `.github/workflows/nvd-cache-refresh.yml` runs daily (`17 2 * * *` UTC), restores the previous cache, executes `dependencyCheckUpdate` from this project, and saves the refreshed cache under a date-stamped key. -The per-project `security-scan.yml` workflow then restores that shared cache before running `dependencyCheckAnalyze`, only falling back to its own update when the cache is missing. - -=== Configuration - -[source,groovy] ----- -dependencyCheck { - nvd { - apiKey = System.getenv('NVD_APIKEY_SEDR') ?: System.getenv('NVD_APIKEY') - } - failBuildOnCVSS = 9 - format = 'HTML' -} ----- +== examples/architecture-validator/single-module-spring -An https://nvd.nist.gov/developers/request-an-api-key[NVD API key] is required — set `NVD_APIKEY_SEDR` (or `NVD_APIKEY` as a fallback) as a repository secret or local environment variable. +include::examples/architecture-validator/single-module-spring/README.adoc[tag=root,leveloffset=+1] -=== Running locally +== examples/sedr-library/jacoco-marker -[source,bash] ----- -cd nvd-cache-primer -NVD_APIKEY_SEDR= ./gradlew dependencyCheckUpdate ----- +include::examples/sedr-library/jacoco-marker/README.adoc[tag=root,leveloffset=+1] == License diff --git a/examples/architecture-validator/single-module-spring/README.adoc b/examples/architecture-validator/single-module-spring/README.adoc index 5b836bd..58d25b2 100644 --- a/examples/architecture-validator/single-module-spring/README.adoc +++ b/examples/architecture-validator/single-module-spring/README.adoc @@ -1,9 +1,32 @@ = Single-Module Spring Example :toc: left +// tag::root[] This example enables the companion Spring rule pack. It demonstrates a direct dependency from a Spring service to a Spring repository in the adapter layer. +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|`.../application/service/OrderService.java` +|A Spring `@Service` that depends directly on `OrderRepository` and triggers the rule violation under test. + +|`.../adapter/persistence/OrderRepository.java` +|Adapter-layer Spring repository reached directly by the service instead of through an out-port. + +|`.../adapter/web/OrderController.java` +|Inbound web adapter calling the use case. + +|`.../application/port/in/OrderUseCase.java` +|Inbound port interface. + +|`.../application/domain/model/Order.java` +|Domain model. +|=== + == Run [source,bash] @@ -33,4 +56,5 @@ The XML report is under `build/reports/architecture-validator/xml/`. Introduce an outbound port in `application.port.out`. Make the service depend on that port. -Move the repository implementation behind an adapter that satisfies the port contract. \ No newline at end of file +Move the repository implementation behind an adapter that satisfies the port contract. +// end::root[] \ No newline at end of file diff --git a/examples/sedr-library/jacoco-marker/README.adoc b/examples/sedr-library/jacoco-marker/README.adoc new file mode 100644 index 0000000..68f0394 --- /dev/null +++ b/examples/sedr-library/jacoco-marker/README.adoc @@ -0,0 +1,47 @@ += JaCoCo Marker Example +:toc: left +:toc-title: Contents +:icons: font +:source-highlighter: rouge + +This self-contained example demonstrates compliant and non-compliant usage of `@ExcludeFromJacocoGeneratedCodeCoverage`. + +// tag::root[] +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|`src/main/java/.../CompliantService.java` +|Uses `@ExcludeFromJacocoGeneratedCodeCoverage` with a justification and passes the rule. + +|`src/main/java/.../NonCompliantService.java` +|Uses `@ExcludeFromJacocoGeneratedCodeCoverage` without a justification, so the ArchUnit test reports a violation. + +|`src/test/java/.../CoverageExclusionConventionsTest.java` +|Extends `AbstractCoverageExclusionConventionsTest` targeting the example package. +|=== + +== Running the Example + +This example requires `sedr-library` to be published to your local Maven repository first. + +[source,bash] +---- +# 1. Publish the library locally +cd sedr-library && ./gradlew clean build publishToMavenLocal + +# 2. Run the example - expected to FAIL with one ArchUnit violation +cd ../examples/sedr-library/jacoco-marker && ./gradlew test +---- + +The test run reports exactly one violation. +`Method NonCompliantService.stop() uses @ExcludeFromJacocoGeneratedCodeCoverage without a justification`. +`CompliantService` is not mentioned. +// end::root[] + +== License + +This example is part of the _Software Engineering Done Right_ repository. +See link:../../../LICENSE[LICENSE] for details. diff --git a/hexagonal-spring-rules/CONTRIB.adoc b/hexagonal-spring-rules/CONTRIB.adoc new file mode 100644 index 0000000..8a572d1 --- /dev/null +++ b/hexagonal-spring-rules/CONTRIB.adoc @@ -0,0 +1,16 @@ += Contributing to hexagonal-spring-rules +:toc: left +:icons: font + +== Release and CI + +This project has independent workflows and release configuration. + +* `.github/workflows/spring-rules-build.yml` +* `.github/workflows/spring-rules-release.yml` +* `.github/workflows/spring-rules-security-scan.yml` +* `release.config.js` +* `package.json` + +Build jobs use configuration cache where compatible. +Publish jobs keep `--no-configuration-cache` for publishing tasks. diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index 179832c..123b7a3 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -11,13 +11,15 @@ image:https://img.shields.io/badge/Gradle-9.x-02303A?logo=gradle[Gradle 9.x,link The Spring Rules project publishes the `architecture-validator-hexagonal-spring-rules` rule pack. It is consumed by the Architecture Validator plugin as an external rule pack dependency. +// tag::root[] == Artifact Coordinates -[source] ----- -groupId: com.arc-e-tect.architecture-validator -artifactId: architecture-validator-hexagonal-spring-rules ----- +[cols="1,1"] +|=== +|Latest version |_Not yet released_ +|Group ID |`com.arc-e-tect.architecture-validator` +|Artifact ID |`architecture-validator-hexagonal-spring-rules` +|=== == Requirements @@ -37,10 +39,20 @@ Run from this project directory. ./gradlew clean build ---- -== Use in a Consumer Project +== Adding the Dependency -Add the rule pack to `testArchitectureImplementation`. +The rule pack will be published to Maven Central; no repository declaration or authentication will be required. +Add it to `testArchitectureImplementation` in a project that already applies the Architecture Validator plugin. +.Gradle (Kotlin DSL) +[source,kotlin] +---- +dependencies { + testArchitectureImplementation("com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules:") +} +---- + +.Gradle (Groovy DSL) [source,groovy] ---- dependencies { @@ -54,25 +66,59 @@ The plugin discovers these tests on the classpath and includes them in the gener The default rules target Spring-specific architectural boundaries, including dependency direction and domain isolation. -Source test classes include: +[cols="1,3"] +|=== +|Class |Checks + +|`SpringHexagonalArchitectureTest` +|Controllers only call in-ports, services do not access repositories directly (must go through an out-port), repositories are only reached through out-ports, and Spring components live in the expected Hexagonal layer. + +|`DomainIsolationTest` +|The domain model depends only on Java core and other domain classes, the application core carries no Spring, persistence, or validation framework dependencies, and application services stay free of Spring stereotypes. + +|`DependencyDirectionTest` +|Dependencies flow inward only, the core never depends on adapters or adapter implementations, adapters depend only on ports (not concrete services), and only the configuration layer wires concrete services directly. -* `SpringHexagonalArchitectureTest` -* `DomainIsolationTest` -* `DependencyDirectionTest` -* `PortContractTest` +|`PortContractTest` +|In-ports and out-ports are interfaces, and port signatures reference only Java core and domain model types with no framework leakage into port contracts. +|=== + +== Architecture Validator Properties + +The rules read package boundaries from system properties set by the Architecture Validator plugin's `architectureValidator` extension. + +[cols="1,3"] +|=== +|Property |Purpose + +|`architectureValidator.basePackage` +|Root package to validate. + +|`architectureValidator.inPorts` +|Comma-separated in-port packages. + +|`architectureValidator.outPorts` +|Comma-separated out-port packages. + +|`architectureValidator.domainModel` +|Comma-separated domain model packages. + +|`architectureValidator.adapters` +|Comma-separated adapter packages. + +|`architectureValidator.applicationServices` +|Comma-separated application service packages. +|=== -== Release and CI +== Example Project -This project has independent workflows and release configuration. +A self-contained example demonstrating a Spring Hexagonal architecture violation is at link:../examples/architecture-validator/single-module-spring/[`examples/architecture-validator/single-module-spring/`]. +See link:../examples/architecture-validator/single-module-spring/README.adoc[its README] for full details. -* `.github/workflows/spring-rules-build.yml` -* `.github/workflows/spring-rules-release.yml` -* `.github/workflows/spring-rules-security-scan.yml` -* `release.config.js` -* `package.json` +== Contributing -Build jobs use configuration cache where compatible. -Publish jobs keep `--no-configuration-cache` for publishing tasks. +See link:CONTRIB.adoc[CONTRIB.adoc] for this project's release and CI workflow details. +// end::root[] == License diff --git a/nvd-cache-primer/README.adoc b/nvd-cache-primer/README.adoc new file mode 100644 index 0000000..64579e4 --- /dev/null +++ b/nvd-cache-primer/README.adoc @@ -0,0 +1,53 @@ += nvd-cache-primer +:toc: left +:toc-title: Contents +:icons: font +:source-highlighter: rouge + +Internal utility Gradle project that primes and validates the OWASP Dependency-Check NVD (National Vulnerability Database) cache shared by this repository's security-scan workflows. +It is not published. +Its only purpose is running the `dependencyCheckUpdate` task in CI. + +// tag::root[] +== Why It Exists + +`sedr-library` and `hexagonal-spring-rules` each run OWASP Dependency-Check as part of their security-scan workflows. +Updating the NVD database from scratch is slow and can hit NVD API rate limits when run repeatedly. +`nvd-cache-primer` is a minimal project. +It has no source code and only applies the `owaspDependencycheck` plugin. +It is dedicated to warming a `~/.gradle/dependency-check-data` cache once a day. + +The scheduled workflow `.github/workflows/nvd-cache-refresh.yml` runs daily (`17 2 * * *` UTC). +It restores the previous cache, executes `dependencyCheckUpdate` from this project, and saves the refreshed cache under a date-stamped key. +The per-project `security-scan.yml` workflow then restores that shared cache before running `dependencyCheckAnalyze`. +It only falls back to its own update when the cache is missing. + +== Configuration + +[source,groovy] +---- +dependencyCheck { + nvd { + apiKey = System.getenv('NVD_APIKEY_SEDR') ?: System.getenv('NVD_APIKEY') + } + failBuildOnCVSS = 9 + format = 'HTML' +} +---- + +An https://nvd.nist.gov/developers/request-an-api-key[NVD API key] is required. +Set `NVD_APIKEY_SEDR` (or `NVD_APIKEY` as a fallback) as a repository secret or local environment variable. + +== Running Locally + +[source,bash] +---- +cd nvd-cache-primer +NVD_APIKEY_SEDR= ./gradlew dependencyCheckUpdate +---- +// end::root[] + +== License + +This project is part of the _Software Engineering Done Right_ repository. +See link:../LICENSE[LICENSE] for details. diff --git a/sedr-library/CONTRIB.adoc b/sedr-library/CONTRIB.adoc new file mode 100644 index 0000000..0ca6e79 --- /dev/null +++ b/sedr-library/CONTRIB.adoc @@ -0,0 +1,15 @@ += Contributing to sedr-library +:toc: left +:icons: font + +== Release and CI + +This project has independent workflows and release configuration. + +* `.github/workflows/sedr-library-build.yml` +* `.github/workflows/sedr-library-release.yml` +* `release.config.js` +* `package.json` + +Configuration cache is disabled throughout. +`resolveVersion` calls `gh release list` at configuration time to determine the library version, which is not compatible with configuration cache. diff --git a/sedr-library/README.adoc b/sedr-library/README.adoc new file mode 100644 index 0000000..97c2bde --- /dev/null +++ b/sedr-library/README.adoc @@ -0,0 +1,168 @@ += sedr-library +:toc: left +:toc-title: Contents +:icons: font +:source-highlighter: rouge + +Shared Java annotations and utilities for _Software Engineering Done Right_ projects. + +// tag::root[] +== Artifact Coordinates + +[cols="1,1"] +|=== +|Latest version |`0.4.6` +|Group ID |`com.arc-e-tect.sedr.utils` +|Artifact ID |`sedr-library` +|=== + +== Requirements + +[cols="1,1",options="header"] +|=== +|Requirement |Version +|Java |21+ +|Gradle |9.x +|=== + +== What's Included + +[cols="1,2,3"] +|=== +|Class |Package |Description + +|`ExcludeFromJacocoGeneratedCodeCoverage` +|`com.arc_e_tect.sedr.utils.jacoco.marker` +|Marker annotation to document constructors, methods, or types that should be treated as excluded from JaCoCo code-coverage measurement, with an ArchUnit rule that enforces a justification. +|=== + +== Adding the Dependency + +The library is published to https://central.sonatype.com/artifact/com.arc-e-tect.sedr.utils/sedr-library[Maven Central]. +No repository declaration or authentication is required. + +.Gradle (Kotlin DSL) +[source,kotlin] +---- +dependencies { + implementation("com.arc-e-tect.sedr.utils:sedr-library:0.4.6") +} +---- + +.Gradle (Groovy DSL) +[source,groovy] +---- +dependencies { + implementation 'com.arc-e-tect.sedr.utils:sedr-library:0.4.6' +} +---- + +.Maven +[source,xml] +---- + + com.arc-e-tect.sedr.utils + sedr-library + 0.4.6 + +---- + +== Enforcing the Justification Convention with ArchUnit + +The library ships a `test-fixtures` artifact that contains the reusable ArchUnit rule `AbstractCoverageExclusionConventionsTest`. +It verifies that every use of `@ExcludeFromJacocoGeneratedCodeCoverage` in your production code carries a non-blank `justification`. +The Gradle builds in this repository use Gradle's native JaCoCo support, with the JaCoCo tool version managed through `refreshVersions` in each build. + +=== Add the test-fixtures dependency + +.Gradle (Kotlin DSL) +[source,kotlin] +---- +dependencies { + testImplementation(testFixtures("com.arc-e-tect.sedr.utils:sedr-library:0.4.6")) +} +---- + +.Gradle (Groovy DSL) +[source,groovy] +---- +dependencies { + testImplementation testFixtures('com.arc-e-tect.sedr.utils:sedr-library:0.4.6') +} +---- + +The test-fixtures artifact brings `archunit-junit5` and `junit-jupiter-api` in as transitive dependencies. +No extra entries are needed for those. +You do need to add the JUnit Jupiter engine for test execution. + +.Gradle (Kotlin DSL) +[source,kotlin] +---- +dependencies { + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.4") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} +---- + +.Gradle (Groovy DSL) +[source,groovy] +---- +dependencies { + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.4' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} +---- + +=== Write the Convention Test + +Extend `AbstractCoverageExclusionConventionsTest` and override `getBasePackage()` to scope the rule to your project's root package. + +[source,java] +---- +class CoverageExclusionConventionsTest extends AbstractCoverageExclusionConventionsTest { + + @Override + protected String getBasePackage() { + return "com.example.myapp"; + } +} +---- + +The inherited test `coverageExclusionAnnotationsMustHaveJustification()` runs automatically. +It fails if any class, constructor, or method in the scanned packages carries `@ExcludeFromJacocoGeneratedCodeCoverage` without a non-blank `justification`. + +=== Using the Annotation Correctly + +.Compliant - justification provided +[source,java] +---- +@ExcludeFromJacocoGeneratedCodeCoverage( + justification = "Spring Boot entry point - not unit-testable") +public static void main(String[] args) { + SpringApplication.run(MyApp.class, args); +} +---- + +.Non-compliant - ArchUnit rule will fail +[source,java] +---- +@ExcludeFromJacocoGeneratedCodeCoverage // missing justification +public static void main(String[] args) { + SpringApplication.run(MyApp.class, args); +} +---- + +== Example Project + +A self-contained example demonstrating both compliant and non-compliant usage is in link:../examples/sedr-library/jacoco-marker/[`examples/sedr-library/jacoco-marker/`]. +See link:../examples/sedr-library/jacoco-marker/README.adoc[its README] for full details. + +== Contributing + +See link:CONTRIB.adoc[CONTRIB.adoc] for this project's release and CI workflow details. +// end::root[] + +== License + +This project is part of the _Software Engineering Done Right_ repository. +See link:../LICENSE[LICENSE] for details. diff --git a/sedr-library/build.gradle b/sedr-library/build.gradle index 8067009..78d5dfd 100644 --- a/sedr-library/build.gradle +++ b/sedr-library/build.gradle @@ -101,14 +101,6 @@ publishing { } } repositories { - maven { - name = 'GitHubPackages' - url = uri('https://maven.pkg.github.com/Arc-E-Tect/SoftwareEngineeringDoneRight-Library') - credentials { - username = System.getenv('GITHUB_ACTOR') ?: project.findProperty('gpr.user') - password = System.getenv('GITHUB_TOKEN') ?: project.findProperty('gpr.key') - } - } maven { name = 'StagingDeploy' url = layout.buildDirectory.dir('staging-deploy')