diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..22cf42a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,42 @@ + +name: Build Branch +on: + push: + branches-ignore: [ main ] + +permissions: read-all + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: SDKMAN Cache + uses: actions/cache@v4 + with: + path: ~/.sdkman + key: "${{ runner.os }}-sdkman-${{ hashFiles('.sdkmanrc') }}" + restore-keys: "${{ runner.os }}-sdkman-" + - name: Maven Cache + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: "${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}" + restore-keys: "${{ runner.os }}-maven-" + - name: Install SDKMAN + run: curl -s "https://get.sdkman.io?rcupdate=false" | bash + - name: Build Application + run: | + source "$HOME/.sdkman/bin/sdkman-init.sh" + sdk env install + + ./mvnw + - name: Build Client + run: | + export CLIENT_PATH='target/generated-sources/openapi' + export CONTROLLERS_PATH='com/github/jaguililla/appointments/http/controllers' + + rm -rf "${CLIENT_PATH}/src/main/java/${CONTROLLERS_PATH}" + mvn -f "${CLIENT_PATH}/pom.xml" clean install diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index d7c9c5f..0000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,25 +0,0 @@ - -on: push - -permissions: read-all - -jobs: - push: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/cache@v4 - with: - path: ~/.sdkman - key: "${{ runner.os }}-sdkman-${{ hashFiles('.sdkmanrc') }}" - restore-keys: "${{ runner.os }}-sdkman-" - - uses: actions/cache@v4 - with: - path: ~/.m2/repository - key: "${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}" - restore-keys: "${{ runner.os }}-maven-" - - run: curl -s "https://get.sdkman.io?rcupdate=false" | bash - - run: | - source "$HOME/.sdkman/bin/sdkman-init.sh" - sdk env install - ./mvnw diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..be17502 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,57 @@ + +name: Release Application +on: + push: + branches: [ main ] + +permissions: read-all + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: SDKMAN Cache + uses: actions/cache@v4 + with: + path: ~/.sdkman + key: "${{ runner.os }}-sdkman-${{ hashFiles('.sdkmanrc') }}" + restore-keys: "${{ runner.os }}-sdkman-" + - name: Maven Cache + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: "${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}" + restore-keys: "${{ runner.os }}-maven-" + - name: Install SDKMAN + run: curl -s "https://get.sdkman.io?rcupdate=false" | bash + # Required for publishing (Maven settings.xml repository) + - name: Java Setup + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: temurin + - name: Publish and Tag Application + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + source "$HOME/.sdkman/bin/sdkman-init.sh" + sdk env install + + ./mvnw -B -P release + - name: Publish Client + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + export CLIENT_PATH='target/generated-sources/openapi' + export CONTROLLERS_PATH='com/github/jaguililla/appointments/http/controllers' + export REPOSITORY='https://maven.pkg.github.com/jaguililla/spring_template' + export ALT_REPOSITORY="altDeploymentRepository=github::default::${REPOSITORY}" + + rm -rf "${CLIENT_PATH}/src/main/java/${CONTROLLERS_PATH}" + mvn -f "${CLIENT_PATH}/pom.xml" -B -D ${ALT_REPOSITORY} clean deploy diff --git a/.mvn/parent.xml b/.mvn/parent.xml index 8a915b3..0e4d745 100644 --- a/.mvn/parent.xml +++ b/.mvn/parent.xml @@ -29,6 +29,8 @@ ${project.groupId}.${project.artifactId}.http ${openapi.package}.controllers ${openapi.package}.client + ${project.groupId}/${project.artifactId} + verify undertow ui @@ -108,19 +110,30 @@ spring-boot-maven-plugin + ${image.registry}/${image.name}:${project.version} - ${project.groupId}/${project.artifactId}:${project.version} - ${project.groupId}/${project.artifactId}:latest - ${project.groupId}/${project.artifactId} + ${image.registry}/${image.name}:latest -XX:+AlwaysPreTouch -XX:+UseParallelGC -XX:+UseNUMA + true + true + true ${java.version} + + + image + + build-image + + verify + + org.apache.maven.plugins @@ -207,6 +220,9 @@ true ${openApiNullable} + ${project.groupId} + ${project.artifactId}-client + ${project.version} @@ -231,5 +247,83 @@ + + + release + + + ${release.goal} + + + + org.springframework.boot + spring-boot-maven-plugin + + + + ${env.GITHUB_ACTOR} + ${env.GITHUB_TOKEN} + + + true + + + + org.codehaus.mojo + exec-maven-plugin + 3.3.0 + + git + + + + + config + verify + + exec + + + + config + --global + user.name + ${env.GITHUB_ACTOR} + + + + + tag + verify + + exec + + + + tag + -m + Release ${project.version} + ${project.version} + + + + + push + verify + + exec + + + + push + --tags + + + + + + + + diff --git a/README.md b/README.md index 409c5d2..f8698ea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -> # ABOUT +> # ๐ŸŽฏ ABOUT > This is a 'best practices' template project. However, it is an opinionated take on that. > > DISCLAIMER: I'm by no means an expert on Spring Boot (it's not even my preferred tool), one reason @@ -16,15 +16,19 @@ > > Have fun! -# Appointments +# ๐Ÿ—“๏ธ Appointments Application to create appointments (REST API). Appointments are stored in a relational DB (Postgres), and their creation/deletion is published to a Kafka broker. -## Architecture -* Hexagonal Architecture +## ๐Ÿ“˜ Architecture +* [Hexagonal]/[Onion]/[Clean] Architecture * OpenAPI code generation (server and client) -## Stack +[Hexagonal]: https://alistair.cockburn.us/hexagonal-architecture +[Onion]: https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1 +[Clean]: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html + +## ๐Ÿงฐ Stack * Java 21 * Spring 3.3 (configurable server, 'undertow' by default) * Actuator (healthcheck, etc.) @@ -32,26 +36,26 @@ Application to create appointments (REST API). Appointments are stored in a rela * Postgres * Kafka -## Runtime +## ๐ŸŽ๏ธ Runtime * Cloud Native Buildpacks (building) * Docker Compose (local environment with the infrastructure) -## Test +## ๐Ÿงช Test * ArchUnit (preferred over Java modules: it allows naming checks, etc.) * Testcontainers (used to provide a test instance of Postgres and Kafka) -## Development +## โš’๏ธ Development * SDKMAN (allows to use simpler runners on CI) * Maven Wrapper (Maven can be provided by SDKMAN, however, Maven Wrapper has better IDE support) * Editorconfig (supported by a lot of editors, rules limited though) * CI pipelines for GitHub and GitLab -## Requirements +## ๐Ÿ“‘ Requirements * Docker Compose * JDK 21+ * SDKMAN (optional, recommended) -## Design +## ๐Ÿ“š Design * The REST API controller and client are generated from the OpenAPI spec at build time. * Hexagonal Architecture: domain, ports, and adapters. * Use cases are 'one responsibility services'. Start with services, split when they get bigger. @@ -77,7 +81,16 @@ Application to create appointments (REST API). Appointments are stored in a rela - **appointments.domain.model**: holds the business entities. These are the data structures used by the business logic. Follows the same access rules as its parent package. -## Design Decisions +## ๐Ÿ“– Terms +* UseCase/Case +* Service +* Adapter +* Port +* Domain +* Input/driver adapter +* Output/driven adapter + +## ๐Ÿค” Design Decisions * Minimal: don't use libraries to implement easy stuff (even if that's boring). * Prefer flat structure (avoid empty parent packages). * Less coupling with Spring (easier to migrate, to other frameworks/toolkits). @@ -92,10 +105,10 @@ Application to create appointments (REST API). Appointments are stored in a rela a container for this application. * Atomicity in notifiers (with outbox pattern) should be done with a different notifier adapter. -## Set up +## ๐ŸŽš๏ธ Set up * `sdk env install` -## Commands +## โ–ถ๏ธ Commands All commands assume a Unix like OS. The most important commands to operate the project are: @@ -110,7 +123,7 @@ To run or deploy the application: * Run JAR locally: `java -jar target/appointments-0.1.0.jar` * Run container: `docker-compose --profile local up` -## Service Management +## ๐Ÿค– Service Management * You can check the API spec using [Swagger UI](http://localhost:8080/swagger-ui/index.html). ### Docker diff --git a/docker-compose.yml b/docker-compose.yml index b3183ab..7cc21e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,7 +40,7 @@ services: depends_on: - postgres - kafka - image: com.github.jaguililla/appointments + image: ghcr.io/jaguililla/spring_template/com.github.jaguililla/appointments environment: GLOBAL_LOG_LEVEL: warn APPLICATION_LOG_LEVEL: info diff --git a/pom.xml b/pom.xml index 3e52e7a..d61dfa4 100644 --- a/pom.xml +++ b/pom.xml @@ -28,8 +28,17 @@ ${openapi.package}.client undertow ui + ghcr.io/jaguililla/spring_template + deploy + + + github + https://maven.pkg.github.com/jaguililla/spring_template + + + org.springframework.boot