Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -26,26 +26,44 @@ jobs:
with:
distribution: temurin
java-version: 21
cache: maven

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5
with:
gradle-home-cache-cleanup: true
# Configuration cache occasionally trips up the Vanniktech publish
# plugin's property-driven configuration — keep CI off it.
cache-read-only: ${{ github.ref != 'refs/heads/master' }}

- name: Build and test
run: ./mvnw -B verify
# `build` runs compileJava + compileTestJava + test on every subproject.
# `jacocoTestReport` is wired in each module's build.gradle.kts but
# repeating it here makes the dependency explicit for the next step.
run: ./gradlew build jacocoTestReport --no-configuration-cache --stacktrace

- name: Upload coverage to Codecov
if: success() && github.event_name == 'push'
uses: codecov/codecov-action@v6
with:
files: ./target/site/jacoco/jacoco.xml
# Glob across all subprojects so adding a future module (e.g.
# api-log-jdbc) picks up its coverage report without another edit.
files: |
./core/build/reports/jacoco/test/jacocoTestReport.xml
./jpa/build/reports/jacoco/test/jacocoTestReport.xml
./r2dbc/build/reports/jacoco/test/jacocoTestReport.xml
./mybatis/build/reports/jacoco/test/jacocoTestReport.xml
flags: unittests
fail_ci_if_error: false # don't break CI if Codecov upload glitches
token: ${{ secrets.CODECOV_TOKEN }} # optional for public repos
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}

- name: Upload test reports on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: test-reports
# `**` so any future module's reports are also captured.
path: |
target/surefire-reports/
target/failsafe-reports/
**/build/reports/tests/
**/build/test-results/
**/build/reports/jacoco/
retention-days: 7
38 changes: 23 additions & 15 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 30
environment: maven-central
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -22,33 +23,40 @@ jobs:
with:
distribution: temurin
java-version: 21
cache: maven
server-id: central # matches <id>central</id> in settings.xml below
server-username: MAVEN_CENTRAL_USERNAME
server-password: MAVEN_CENTRAL_PASSWORD
gpg-private-key: ${{ secrets.SIGNING_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE # maven-gpg-plugin 3.x convention

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5

- name: Derive version from tag
id: ver
run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"

- name: Set release version
run: ./mvnw -B versions:set -DnewVersion=${{ steps.ver.outputs.version }} -DgenerateBackupPoms=false
- name: Build and test
run: ./gradlew build --no-configuration-cache --stacktrace -PVERSION=${{ steps.ver.outputs.version }}

- name: Build, sign, and publish to Maven Central
- name: Publish to Maven Central
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: ./mvnw -B -P release deploy
# Sonatype Central Portal credentials (https://central.sonatype.com/account)
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
# ASCII-armored private key (`gpg --armor --export-secret-keys <key-id>`)
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: |
./gradlew publishAndReleaseToMavenCentral \
--no-configuration-cache \
--stacktrace \
-PVERSION=${{ steps.ver.outputs.version }}

- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
generate_release_notes: true
name: ${{ github.ref_name }}
fail_on_unmatched_files: false
# Glob across every module's build/libs/ so the release page picks up
# api-log-core / -jpa / -r2dbc / -mybatis without per-module entries.
files: |
target/*.jar
target/*.asc
**/build/libs/*.jar
**/build/libs/*.asc
66 changes: 45 additions & 21 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,44 +1,68 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
# Gradle
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
# IDE - IntelliJ
.idea/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

# IDE - Eclipse / STS
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
# IDE - VS Code
.vscode/

### NetBeans ###
# NetBeans
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/
# OS
.DS_Store
Thumbs.db

# Logs
*.log
logs/

### PostgreSQL data ###
# Secrets / signing
*.gpg
secring.*
local.properties
gradle-local.properties
*.env
.env*
!.env.example

# PostgreSQL data
data/

### Claude ###
# Claude
.claude/

### Application properties (root level duplicate) ###
# Application properties (root level duplicate; test fixtures are kept)
application.properties
# Test fixtures should be tracked though
!src/test/resources/application.properties

# Test results
/test-results/
/reports/
3 changes: 0 additions & 3 deletions .mvn/wrapper/maven-wrapper.properties

This file was deleted.

33 changes: 32 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@ The source of truth for the entries below is [docs/changelog.md](docs/changelog.

## [Unreleased]

## [0.6.0] — Multi-module split (Gradle), pluggable JPA / R2DBC / MyBatis backends

### Changed

- **The single `api-log-spring-boot-starter` artifact is split.** Consumers now add `kr.devslab:api-log-core` plus exactly one backend artifact: `api-log-jpa` (drop-in for v0.5.x), `api-log-r2dbc` (reactive), or `api-log-mybatis`.
- **Build system: Maven → Gradle 8.10** with Vanniktech maven-publish per module.
- **Package renames**: `model.dto` → `dto`, `model.ApiLogEntity` → `jpa.model.ApiLogEntity`, `service.ApiLogService` → `jpa.writer.JpaApiLogWriter`. Full mapping in [docs/changelog.md](docs/changelog.md#060--multi-module-split-gradle-pluggable-jpa--r2dbc--mybatis-backends).

### Added

- **`ApiLogWriter` SPI** — backend-agnostic three-method interface (`writeInitiated` / `writeSuccess` / `writeError`). Each backend artifact registers one implementation; the core listener routes events through it.
- **`api-log-r2dbc`** — reactive backend using R2DBC's `DatabaseClient`. Pure-reactive schema initializer; no JDBC pull-in.
- **`api-log-mybatis`** — MyBatis mapper backend with `::jsonb` cast on inserts.

### Fixed

- `V1.0__create_api_log.sql` now uses `IF NOT EXISTS` on both CREATE TABLE and CREATE INDEX — idempotent across boots under BUILTIN mode.

Full migration notes in [docs/changelog.md](docs/changelog.md#060--multi-module-split-gradle-pluggable-jpa--r2dbc--mybatis-backends).

## [0.5.2] — Fix bean registration in real consumer apps

### Fixed

- `RestApiClientUtil` + four `@Configuration` classes were never registered in consumer apps (relied on `@ComponentScan` reaching the starter's package). Fixed by splitting into three `@AutoConfiguration` classes registered via `META-INF/spring/.../AutoConfiguration.imports`.
- `spring-boot-starter-web` is now `<optional>true</optional>` — pure-WebFlux apps no longer get a Servlet stack forced onto their classpath.

Full notes in [docs/changelog.md](docs/changelog.md#052--fix-bean-registration-in-real-consumer-apps).

## [0.5.1] — Reactive (WebFlux) client + end-to-end HTTP tests

### Added
Expand Down Expand Up @@ -75,7 +104,9 @@ See [docs/changelog.md](docs/changelog.md#020--schema-management-opt-in) for the

First public release. See [docs/changelog.md](docs/changelog.md#010--initial-release) for details.

[Unreleased]: https://github.com/devslab-kr/api-log/compare/v0.5.1...HEAD
[Unreleased]: https://github.com/devslab-kr/api-log/compare/v0.6.0...HEAD
[0.6.0]: https://github.com/devslab-kr/api-log/releases/tag/v0.6.0
[0.5.2]: https://github.com/devslab-kr/api-log/releases/tag/v0.5.2
[0.5.1]: https://github.com/devslab-kr/api-log/releases/tag/v0.5.1
[0.5.0]: https://github.com/devslab-kr/api-log/releases/tag/v0.5.0
[0.4.0]: https://github.com/devslab-kr/api-log/releases/tag/v0.4.0
Expand Down
55 changes: 42 additions & 13 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

> Spring Boot용 이벤트 드리븐 API 호출 로깅. 비동기 이벤트 파이프라인 + PostgreSQL JSONB. 요청 경로를 막지 않고 외부 API 호출을 모두 기록합니다.

[![Maven Central](https://img.shields.io/maven-central/v/kr.devslab/api-log-spring-boot-starter.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/kr.devslab/api-log-spring-boot-starter)
[![Maven Central](https://img.shields.io/maven-central/v/kr.devslab/api-log-core.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/kr.devslab/api-log-core)
[![CI](https://github.com/devslab-kr/api-log/actions/workflows/ci.yml/badge.svg)](https://github.com/devslab-kr/api-log/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/devslab-kr/api-log/branch/master/graph/badge.svg)](https://codecov.io/gh/devslab-kr/api-log)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
Expand Down Expand Up @@ -57,35 +57,64 @@ public class UserService {
```
Caller code
RestApiClientUtil (또는 자체 HTTP 클라이언트)
RestApiClientUtil / ReactiveApiClientUtil (또는 자체 HTTP 클라이언트)
↓ publishEvent
ApplicationEventPublisher
↓ @EventListener (async)
ApiEventListener
ApiLogService
ApiLogRepository (JPA)
PostgreSQL (api_log · JSONB columns)
↓ @EventListener (virtual threads)
ApiEventListener (api-log-core)
ApiLogWriter (SPI)
├─ JpaApiLogWriter (api-log-jpa)
├─ R2dbcApiLogWriter (api-log-r2dbc)
└─ MybatisApiLogWriter (api-log-mybatis)
PostgreSQL (api_log · JSONB columns)
```

## 설치

v0.6.0부터 스타터가 4개 아티팩트로 분리됐습니다 — 백엔드 비종속 코어 1개 +
영속화 백엔드 1개. **`api-log-core` 1개 + 백엔드 1개**를 직접 골라 추가:

| 좌표 | 언제 쓰나 |
| --- | --- |
| `kr.devslab:api-log-jpa` | Servlet / JPA 앱 (v0.5.x 드롭인) |
| `kr.devslab:api-log-r2dbc` | WebFlux / R2DBC 앱 — JDBC 의존성 없음 |
| `kr.devslab:api-log-mybatis` | 이미 MyBatis를 쓰고, JPA를 원치 않을 때 |

백엔드 아티팩트 각각이 `api-log-core`를 transitive하게 가져오므로
좌표 하나만 추가하면 됩니다.

### Maven

```xml
<!-- JPA (가장 흔함 — v0.5.x 드롭인) -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-jpa</artifactId>
<version>0.6.0</version>
</dependency>

<!-- 또는 리액티브 앱에서 R2DBC -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-r2dbc</artifactId>
<version>0.6.0</version>
</dependency>

<!-- 또는 MyBatis -->
<dependency>
<groupId>kr.devslab</groupId>
<artifactId>api-log-spring-boot-starter</artifactId>
<version>0.5.1</version>
<artifactId>api-log-mybatis</artifactId>
<version>0.6.0</version>
</dependency>
```

### Gradle

```kotlin
implementation("kr.devslab:api-log-spring-boot-starter:0.5.1")
implementation("kr.devslab:api-log-jpa:0.6.0")
// 또는 "kr.devslab:api-log-r2dbc:0.6.0"
// 또는 "kr.devslab:api-log-mybatis:0.6.0"
```

## 설정
Expand Down
Loading