Skip to content

Commit

Permalink
Merge pull request #28 from jaguililla/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jaguililla authored Sep 5, 2024
2 parents 7a95f33 + 73e2e36 commit 39c3fdd
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 38 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

name: Nightly Build
on:
schedule:
- cron: "59 23 * * *"

permissions: read-all

jobs:
nightly:
name: Nightly
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
20 changes: 20 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -75,3 +77,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
46 changes: 46 additions & 0 deletions .mvn/parent.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
! Common settings for `pom.xml`, it could be moved to its own repository to be reused.
!-->
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Expand Down Expand Up @@ -253,5 +256,48 @@
</dependency>
</dependencies>
</profile>

<profile>
<id>pitest</id>

<build>
<plugins>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.16.3</version>
<dependencies>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
<configuration>
<timeoutConstant>30000</timeoutConstant>
<targetClasses>
<param>com.github.jaguililla.appointments.domain.*</param>
</targetClasses>
<targetTests>
<param>com.github.jaguililla.appointments.domain.*</param>
</targetTests>
<features>
<!-- Required to process Java records, generates false positives -->
<feature>-frecord</feature>
</features>
<verbose>true</verbose>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>mutationCoverage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
61 changes: 24 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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`

Expand Down
2 changes: 2 additions & 0 deletions doc/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</parent>

<artifactId>appointments</artifactId>
<version>0.3.4</version>
<version>0.3.5</version>

<name>Appointments</name>
<description>Application to create appointments (REST API)</description>
Expand Down

0 comments on commit 39c3fdd

Please sign in to comment.