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