From f3d47096bc24b564f7822e14e50113f8f67a2f5d Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 5 Sep 2024 18:20:26 +0200 Subject: [PATCH 1/4] Improve documentation --- .mvn/parent.xml | 46 +++++++++++++++++++++++++++++++++ README.md | 61 +++++++++++++++++--------------------------- doc/architecture.svg | 2 ++ pom.xml | 2 +- 4 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 doc/architecture.svg diff --git a/.mvn/parent.xml b/.mvn/parent.xml index 6cc9a6f..4368c91 100644 --- a/.mvn/parent.xml +++ b/.mvn/parent.xml @@ -1,5 +1,8 @@ + + + + pitest + + + + + org.pitest + pitest-maven + 1.16.3 + + + org.pitest + pitest-junit5-plugin + 1.2.1 + + + + 30000 + + com.github.jaguililla.appointments.domain.* + + + com.github.jaguililla.appointments.domain.* + + + + -frecord + + true + + + + verify + + mutationCoverage + + + + + + + diff --git a/README.md b/README.md index e5176ad..c7283f7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ > # 🎯 ABOUT -> This is a 'best practices' template project. However, it is an opinionated take on that. +> This is a 'best practices' template project for Spring Boot with Hexagonal Architecture. However, +> it is an opinionated take on that. > > DISCLAIMER: I'm by no means an expert on Spring Boot, one reason to do this is to learn it. > Opinions are welcomed (with proper reasoning), check the [contributing] section to share your @@ -14,7 +15,7 @@ [contributing]: https://github.com/jaguililla/hexagonal_spring/contribute # 🗓️ Appointments -Application to create appointments (REST API). Appointments are stored in a relational DB +Example 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 @@ -52,7 +53,22 @@ Application to create appointments (REST API). Appointments are stored in a rela * JDK 21+ * SDKMAN (optional, recommended) -## 📖 Terms +## 🤔 Design Decisions +* Simplicity and pragmatism over Hexagonal Architectures 'by the book'. +* Minimal: don't use libraries to implement easy stuff (even if that's boring). +* Start small, split and refactor when features are added (I.e.: split services into use cases). +* Prefer flat structure (avoid empty parent packages if possible). +* Small coupling with Spring (easier to migrate, to other frameworks/toolkits). +* Not use Spring integrations if a library can be used directly. +* No Spring profiles (settings are loaded from the environment). +* Split API spec in different files for future modularity. +* Prefer service independence over code reuse (sharing libraries among microservices). +* Docker Compose profiles are used to separate infrastructure from a complete local environment. +* Atomicity in notifiers (with outbox pattern) should be done with a different notifier adapter. +* No input ports: they don't need to be decoupled, they just use the domain (and that's acceptable). + +## 📖 Architecture +![Architecture Diagram](doc/architecture.svg) * **Port:** interface to set a boundary between application logic and implementation details. * **Adapter:**: port implementation to connect the application domain with the system's context. * **Domain:**: application logic and model entities. @@ -61,49 +77,19 @@ Application to create appointments (REST API). Appointments are stored in a rela * **Output/Driven Adapter:**: implementation of ports called from the domain. * **Input/Driver Adapter:**: commands that call application logic (don't require a port). -## 🤔 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). -* Not use Spring integrations if a library can be used directly. -* No Spring profiles (settings are loaded from the environment). -* Split API spec in different files for future modularity. -* Prefer service independence over code reuse (sharing libraries among microservices), less - coupling foster evolution among services and favor scalability when more teams/services are added. -* Take out the common (general) part of the `pom.xml` to `parent.xml`, however, it should not be - moved to another repository (because of avoid coupling rule above). -* Docker Compose profiles are used to separate infrastructure from a complete environment including - a container for this application. -* Atomicity in notifiers (with outbox pattern) should be done with a different notifier adapter. - ## 📚 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. * `domain` holds business logic (services and/or use cases) and driven ports (interfaces). * `domain.model` keeps the structures relevant to the application's domain. The more logic added to an entity, the better (it could be easily accessed by many different services, or use cases). * `output.{notifiers,repositories}` driven adapters (implementations of driven ports). * `input.controllers` driver adapter (adapters without interface). -* There are no 'input/driver ports', as they don't need to be decoupled from anything they just use - the domain (and that's acceptable). * Subpackages can be created for different adapter implementations (to isolate their code). -* Code structure and access rules: - - **appointments**: holds the Spring configuration (dependency injection) and contains the - starting class for the application. - - **appointments.output.{notifiers,repositories}**: contains domain ports' actual implementations. - These are implementation details and must not be used directly (except DI and tests). - - **appointments.input.controllers**: contains the REST controllers of the application (driver - adapter). Classes on this package cannot use any other application layer apart from domain. - - **appointments.domain**: contains the business rules. Must not reference implementation details - (storage, frameworks, etc.) directly, these features should be accessed by abstract - interchangeable interfaces. It's not a problem to reference this package from Controllers or - Repositories. - - **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. +* More information about each package rules can be found on their Javadoc documentation. ## 🎚️ Set up -* `sdk env install` +* With SDKMAN: `sdk env install` +* If SDKMAN is not available, JDK 21+ must be installed. ## ▶️ Commands All commands assume a Unix like OS. @@ -113,10 +99,11 @@ The most important commands to operate the project are: * Build: `./mvnw package` * Documentation: `./mvnw site` * Run: `./mvnw spring-boot:run` -* Build image: `./mvnw spring-boot:build-image` +* Build image: `./mvnw verify` or `./mvnw spring-boot:build-image` To run or deploy the application: +* Start infrastructure (for running locally): `docker-compose up -d` * Run JAR locally: `java -jar target/appointments-0.1.0.jar` * Run container: `docker-compose --profile local up` diff --git a/doc/architecture.svg b/doc/architecture.svg new file mode 100644 index 0000000..e55ef35 --- /dev/null +++ b/doc/architecture.svg @@ -0,0 +1,2 @@ + diff --git a/pom.xml b/pom.xml index 4c34388..baf1d64 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ appointments - 0.3.4 + 0.3.5 Appointments Application to create appointments (REST API) From 13b6effdb5c57eeb7a1fd221bb322b4a57aa7c4d Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 5 Sep 2024 19:54:07 +0200 Subject: [PATCH 2/4] Add nightly build to :CI --- .github/workflows/nightly.yml | 43 +++++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 18 +++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 .github/workflows/nightly.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..95c9f79 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,43 @@ + +name: Nightly Build +on: + push: +# schedule: +# - cron: "59 23 * * *" + +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 -P pitest + - 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/.gitlab-ci.yml b/.gitlab-ci.yml index d2e8f33..85edb28 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -75,3 +75,21 @@ release: release: tag_name: ${POM_VERSION} description: Release ${POM_VERSION} + +nightly: + stage: build + image: ubuntu:24.04 + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + cache: + key: build-cache + paths: + - .m2/repository + before_script: + - apt update + - apt -y install curl zip unzip + - curl -s "https://get.sdkman.io?rcupdate=false" | bash + - source "$HOME/.sdkman/bin/sdkman-init.sh" + script: + - sdk env install + - ./mvnw -P pitest From b3ab6ee2117c61492d165bc26618cbf48309029c Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 5 Sep 2024 19:58:27 +0200 Subject: [PATCH 3/4] Add nightly build to :CI --- .github/workflows/nightly.yml | 4 ++-- .gitlab-ci.yml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 95c9f79..f8729de 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,8 +8,8 @@ on: permissions: read-all jobs: - build: - name: Build + nightly: + name: Nightly runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 85edb28..f628487 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,7 @@ build: image: ubuntu:24.04 rules: - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH + - if: $CI_PIPELINE_SOURCE == "push" cache: key: build-cache paths: @@ -37,6 +38,7 @@ publish: image: maven:3 rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_PIPELINE_SOURCE == "push" before_script: - apt update - apt -y install curl zip unzip From 73e2e369634abe3d89d47a34ffb3d7f1b8079be3 Mon Sep 17 00:00:00 2001 From: jaguililla Date: Thu, 5 Sep 2024 20:00:33 +0200 Subject: [PATCH 4/4] Add nightly build to :CI --- .github/workflows/nightly.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f8729de..535212b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,9 +1,8 @@ name: Nightly Build on: - push: -# schedule: -# - cron: "59 23 * * *" + schedule: + - cron: "59 23 * * *" permissions: read-all