Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
774e80d9d5
|
||
|
|
505cfe38f0
|
||
|
|
85bcdcadb8
|
||
|
|
e67c475c38
|
||
|
|
07983dc5b0
|
||
|
|
5e9587b93f
|
||
|
|
fd62f2574d
|
||
|
|
828bd0639e
|
||
|
|
da13b0d2b8
|
||
|
|
d632dc0f6b
|
||
|
|
b60e4aa457
|
||
|
|
e262f35b27
|
||
|
|
77e4cab8ad
|
||
|
|
7291419c9f
|
||
|
|
b2b8fcf92f
|
||
|
|
4e4b307275
|
||
|
|
e87a36caa5
|
No files matched your search
@@ -0,0 +1,26 @@
|
||||
# Version control
|
||||
.git
|
||||
.github
|
||||
|
||||
# Gradle build outputs / caches (must always be rebuilt fresh inside the image)
|
||||
.gradle
|
||||
build/
|
||||
.kotlin
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
.run
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
|
||||
# Docker-only local files (secrets/env must never be baked into the image)
|
||||
docker/.env
|
||||
docker/*.env.docker
|
||||
docker/*.secret
|
||||
|
||||
# Misc
|
||||
*.hprof
|
||||
.gradle-docker-cache/
|
||||
@@ -0,0 +1,7 @@
|
||||
* text=auto
|
||||
* eol=lf
|
||||
*.sh text eol=lf
|
||||
*.png binary
|
||||
*.jar binary
|
||||
gradlew.bat eol=crlf
|
||||
gradlew text eol=lf
|
||||
+49
-33
@@ -18,10 +18,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
run: echo "key=gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -48,16 +48,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Restore Gradle cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -70,58 +70,74 @@ jobs:
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Run lint
|
||||
run: ./gradlew ktlintCheck
|
||||
# Path scoped to :backend on purpose: :composeApp applies the Android Gradle plugin,
|
||||
# which needs an Android SDK to even configure. Keeping every gradlew invocation on a
|
||||
# fully-qualified project path (with org.gradle.configureondemand=true) lets CI skip
|
||||
# configuring :composeApp entirely, so no Android SDK setup is needed on this runner.
|
||||
run: ./gradlew :backend:ktlintCheck
|
||||
|
||||
- name: Publish ktlint report
|
||||
uses: yutailang0119/action-ktlint@v4
|
||||
uses: yutailang0119/action-ktlint@v5
|
||||
if: always()
|
||||
with:
|
||||
report-path: build/reports/ktlint/**/*.xml
|
||||
report-path: backend/build/reports/ktlint/**/*.xml
|
||||
continue-on-error: false
|
||||
|
||||
test:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GRADLE_CACHE_DIR: ${{ github.workspace }}/.gradle-docker-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
- name: Install a pinned Docker Compose version
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
curl -fSL https://github.com/docker/compose/releases/download/v5.1.4/docker-compose-linux-x86_64 \
|
||||
-o ~/.docker/cli-plugins/docker-compose
|
||||
chmod +x ~/.docker/cli-plugins/docker-compose
|
||||
docker compose version
|
||||
|
||||
- name: Restore Gradle cache
|
||||
uses: actions/cache@v3
|
||||
- name: Prepare docker secrets
|
||||
run: |
|
||||
[ -f docker/postgresql.secret ] || echo -n "changeit" > docker/postgresql.secret
|
||||
|
||||
- name: Generate cache key
|
||||
id: cache-key-generator
|
||||
run: echo "key=gradle-docker-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore Gradle cache (Docker)
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ needs.build.outputs.cache-key }}
|
||||
path: ${{ env.GRADLE_CACHE_DIR }}
|
||||
key: ${{ steps.cache-key-generator.outputs.key }}
|
||||
restore-keys: |
|
||||
gradle-${{ runner.os }}-
|
||||
gradle-docker-${{ runner.os }}-
|
||||
|
||||
- name: Grant execute permission to Gradle wrapper
|
||||
run: chmod +x gradlew
|
||||
- name: Prepare cache directory permissions
|
||||
run: |
|
||||
mkdir -p "$GRADLE_CACHE_DIR"
|
||||
chmod -R 777 "$GRADLE_CACHE_DIR"
|
||||
|
||||
- name: Start CI Docker Compose services
|
||||
run: ./gradlew composeUp -Pci
|
||||
- name: Run tests in Docker
|
||||
run: docker compose -f docker/docker-compose-test.yaml run tests
|
||||
|
||||
- name: Run tests
|
||||
run: ./gradlew test -x composeUp --no-daemon
|
||||
- name: Shut down Docker services
|
||||
if: always()
|
||||
run: docker compose -f docker/docker-compose-test.yaml down -v
|
||||
|
||||
- name: Upload test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-results
|
||||
path: build/reports/tests/test
|
||||
path: backend/build/reports/tests/test
|
||||
|
||||
- name: Publish Test Report
|
||||
uses: dorny/test-reporter@v1
|
||||
uses: dorny/test-reporter@v3
|
||||
if: always()
|
||||
with:
|
||||
name: JUnit Tests
|
||||
path: build/test-results/test/TEST-*.xml
|
||||
path: backend/build/test-results/test/TEST-*.xml
|
||||
reporter: java-junit
|
||||
@@ -1,9 +1,15 @@
|
||||
.gradle
|
||||
.kotlin/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### Android / KMP ###
|
||||
local.properties
|
||||
captures/
|
||||
.cxx/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
@@ -37,3 +43,4 @@ out/
|
||||
/docker/.env
|
||||
/docker/*.secret
|
||||
*.hprof
|
||||
/.gradle-docker-cache/
|
||||
@@ -0,0 +1,133 @@
|
||||
# CLAUDE.md — event-demo
|
||||
|
||||
Ce fichier donne le contexte du projet pour toute session Claude Code future sur ce dépôt.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
`event-demo` est un projet démo personnel (Fabrice Lecomte) qui illustre plusieurs patterns
|
||||
d'architecture backend :
|
||||
|
||||
- Event Sourcing
|
||||
- Event-Driven (bus d'événements asynchrone)
|
||||
- CQRS (séparation commandes / projections en lecture)
|
||||
- Architecture Hexagonale (ports & adapters), un dossier par *bounded context*
|
||||
|
||||
Le cas d'usage servant de support est un jeu de cartes façon UNO (créer une partie, rejoindre,
|
||||
jouer une carte, piocher, etc.), avec authentification des joueurs.
|
||||
|
||||
Dépôts distants configurés : `gitea` (auto-hébergé, git.gogn.synology.me — remote historique)
|
||||
et `github` (`flecomte/event-demo`, miroir). Vérifier vers lequel pousser selon le contexte.
|
||||
|
||||
## Stack technique
|
||||
|
||||
- **Langage** : Kotlin 2.1.21, JDK 21 (toolchain Gradle)
|
||||
- **Framework serveur** : Ktor 3.5.1 (Netty), DI via Koin 4.2.1
|
||||
- **Sérialisation** : kotlinx.serialization (JSON)
|
||||
- **Persistance** :
|
||||
- PostgreSQL (event store, via HikariCP) + migrations Flyway (`migrations/events/`)
|
||||
- RabbitMQ (bus d'événements / bus de commandes, via amqp-client)
|
||||
- **Auth** : JWT (ktor-server-auth-jwt), hash de mot de passe via password4j
|
||||
- **Infra dev/prod** : Docker Compose (fichiers `docker/docker-compose-{dev,test,prod}.yaml`
|
||||
incluant des « parts » réutilisables dans `docker/parts/`), reverse proxy Træfik
|
||||
- **Tests** : Kotest (runner JUnit5), MockK, kotest-extensions-koin, ArchUnit (test d'architecture)
|
||||
- **Qualité** : ktlint (`ktlint_official`, standard + experimental activés), reporting checkstyle
|
||||
- **CI** : GitHub Actions (`.github/workflows/tests.yml`) — build/cache Gradle, `ktlintCheck`,
|
||||
puis tests exécutés **dans Docker** (`docker compose -f docker/docker-compose-test.yaml run tests`)
|
||||
- **API** : documentée en OpenAPI (`resources/openapi/documentation.yaml`)
|
||||
|
||||
## Architecture
|
||||
|
||||
Un dossier par *bounded context* sous `src/main/kotlin/eventDemo/contexts/<context>/`, chacun
|
||||
strictement découpé en 3 couches :
|
||||
|
||||
- `domain/` — aucune dépendance vers les autres couches
|
||||
- `application/` — ne dépend que de `domain`
|
||||
- `infrastructure/` — dépend de `domain` et `application`
|
||||
|
||||
Contexts actuels :
|
||||
|
||||
- **`auth`** : `User`, création de compte, login JWT, event store dédié (Postgresql),
|
||||
projection utilisateur.
|
||||
- **`game`** : cœur du jeu — `Card`, `DrawPile`/`DiscardPile`, `Player`, `GameId`, commandes
|
||||
(`JoinTheGameCommand`, `PlayCardCommand`, `ReadyToPlayCommand`, `TakeCartFromDrawPileCommand`),
|
||||
state machine du jeu via `sealed interface Game` (`GameInit` → `GameCreated` → `GameStarted` →
|
||||
`GameEnded`), notifications, projections (liste de parties), listeners/réactions.
|
||||
|
||||
Libs transverses dans `libs/` (indépendantes de tout contexte) :
|
||||
|
||||
- `bus/` — abstraction `Bus<E>` avec implémentations in-memory et RabbitMQ (fanout exchange)
|
||||
- `command/` — `Command`, `CommandUnicityChecker` (empêche la double exécution d'une commande,
|
||||
cache glissant de 10 min par défaut)
|
||||
- `eventSource/` — `Event`, `EventStream` (append/lecture par version, gestion de
|
||||
`VersionConflictException`), `EventStore` in-memory / Postgresql
|
||||
- `helpers/`, `serializer/` — utilitaires (conversion de frames WebSocket, sérialiseurs UUID, etc.)
|
||||
|
||||
## Patterns notables dans le code
|
||||
|
||||
- **Event sourcing** : `Game.loadFromHistory(events)` reconstruit l'état en repliant
|
||||
(`fold`) les événements sur une state machine scellée, en utilisant la syntaxe Kotlin 2.1
|
||||
`when` avec garde `if` (ex. `is GameCreatedEvent if this is GameInit -> applyEvent(event)`).
|
||||
- **CQRS** : écriture via les command handlers (`application/command/handlers`), lecture via des
|
||||
projections dédiées (`application/projections`), propagées via le bus RabbitMQ, pas de couplage
|
||||
direct avec l'écriture.
|
||||
- **Event-driven** : réactions asynchrones (`ReactionListener`, `EventToNotificationSubscriber`)
|
||||
déclenchées par le bus RabbitMQ (exchange fanout, une queue par abonné).
|
||||
- **Exceptions métier** : hiérarchie `GameException` / `IllegalActionException` dans
|
||||
`domain/game/errors`, une exception par règle métier violée (ex.
|
||||
`NeedMorePlayersToStartGameException`, `ItsNotTheTurnException`).
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
```shell
|
||||
./gradlew build # build complet
|
||||
./gradlew test # tests (JUnit5 via Kotest)
|
||||
./gradlew ktlintCheck # lint
|
||||
./gradlew ktlintFormat # auto-format
|
||||
./gradlew buildFatJar # jar exécutable "all-in-one" (utilisé par le Dockerfile prod)
|
||||
|
||||
# Dépendances seules (Postgres, RabbitMQ, Træfik, pgAdmin...) pour lancer l'app en local hors docker
|
||||
docker compose -f docker/docker-compose-dev.yaml up -d
|
||||
|
||||
# Stack de test façon CI
|
||||
docker compose -f docker/docker-compose-test.yaml up -d
|
||||
# ou directement (comme en CI) :
|
||||
docker compose -f docker/docker-compose-test.yaml run tests
|
||||
|
||||
# Stack complète en prod
|
||||
docker compose -f docker/docker-compose-prod.yaml -p event-demo up -d
|
||||
```
|
||||
|
||||
URLs en dev (voir `doc/installation.md`, nécessite Træfik + résolution des `*.traefik.me`) :
|
||||
API sur `http://api.traefik.me/`, dashboard
|
||||
Træfik, pgAdmin et RabbitMQ management exposés via des sous-domaines `traefik.me`.
|
||||
|
||||
## Conventions de code
|
||||
|
||||
- ktlint en mode `ktlint_official` + règles `standard` et `experimental` activées
|
||||
(voir `.editorconfig`), indentation **2 espaces**, virgules finales (*trailing commas*)
|
||||
systématiques, wrapping forcé des expressions/signatures multi-lignes.
|
||||
- Fins de ligne forcées en **LF** (`.gitattributes`), sauf `gradlew.bat` en CRLF.
|
||||
- Code et identifiants en anglais.
|
||||
- Style Kotlin idiomatique/fonctionnel : `fold`, `let`, `apply`, `when` exhaustifs, classes/interfaces
|
||||
scellées (`sealed class`/`sealed interface`) pour modéliser états et événements plutôt que des enums
|
||||
avec des champs optionnels.
|
||||
|
||||
## Pièges connus / choses à savoir avant de toucher au build ou à la CI
|
||||
|
||||
- **MockK/ByteBuddy en Docker** : l'auto-attach dynamique de MockK échoue dans les conteneurs
|
||||
(le handshake SIGQUIT de l'AttachListener JVM time-out). Le `build.gradle.kts` charge donc
|
||||
l'agent `byte-buddy-agent` de façon statique via `-javaagent` pour les tâches `Test`, afin
|
||||
que MockK détecte l'instrumentation déjà présente et saute l'attach dynamique. Ne pas retirer
|
||||
ce bloc sans repenser l'exécution des tests en Docker.
|
||||
- **Secret Postgres en CI** : `docker/postgresql.secret` est généré à la volée par le workflow
|
||||
GitHub Actions s'il n'existe pas (`echo -n "changeit" > docker/postgresql.secret`) — normal,
|
||||
pas un fichier à committer.
|
||||
- Les tests « officiels » de la CI tournent **dans Docker**, pas directement via `./gradlew test`
|
||||
sur l'hôte — en cas de comportement différent entre local et CI, vérifier d'abord les
|
||||
variables d'environnement/versions du `docker-compose-test.yaml`.
|
||||
|
||||
## Historique récent (pour contexte)
|
||||
|
||||
Le projet a connu un « Massive refactor to build the V2 » (commit `e2d7942`) : passage d'une
|
||||
architecture par couches techniques plates (`adapter/presenter/domain`) à l'organisation actuelle
|
||||
par bounded context (`auth`/`game`) avec 3 couches hexagonales chacune.
|
||||
@@ -2,7 +2,6 @@ Event Demo
|
||||
==========
|
||||
- [Installation](./doc/installation.md)
|
||||
- [What's the demo for ?](#whats-the-demo-for-)
|
||||
- [What's in this demo](#whats-in-this-demo)
|
||||
- [The stack](#the-stack)
|
||||
- [Architecture](./doc/architecture.md)
|
||||
|
||||
@@ -18,31 +17,6 @@ of different patterns and architectures.
|
||||
- The CQRS pattern.
|
||||
- With the Hexagonal architecture.
|
||||
|
||||
|
||||
What's in this demo
|
||||
-------------------
|
||||
|
||||
- The **event sourcing** pattern.
|
||||
- The **event driven** pattern.
|
||||
- The **CQRS** pattern with **command** and **query**.
|
||||
- A fully **asynchronous** architecture.Concurently process.
|
||||
- A **pure Kotlin** implementation of **readmodel**/**projection**.
|
||||
- A **Redis** implementation of **readmodel**/**projection**.
|
||||
- A **pure Kotlin** implementation of **Event Store**.
|
||||
- A **Postgresql** implementation of **Event Store**.
|
||||
- A **pure Kotlin** implementation of **Event Bus**.
|
||||
- A **RabbitMQ** implementation of **Event Bus**.
|
||||
- A **Hexagonal** architecture.
|
||||
- Use of **Web Sockets**.
|
||||
- Use of the classic **Rest** route.
|
||||
- Simple usage of the **JWT**.
|
||||
- The **Ktor** framework.
|
||||
- The **Koin** Dependency Injection framework
|
||||
- Concurrently process.
|
||||
- Use of coroutines.
|
||||
- Using **docker compose** for the stack with **traefik**.
|
||||
- Use of **flyway** to migrate the postgresql schema.
|
||||
|
||||
The stack
|
||||
---------
|
||||
|
||||
@@ -51,11 +25,11 @@ Language
|
||||
|
||||
Framework
|
||||
- Ktor
|
||||
- with Koin for Dependency Injection
|
||||
|
||||
Database
|
||||
- Postgresql
|
||||
- with Flyway
|
||||
- Redis
|
||||
- RabbitMQ
|
||||
|
||||
Infra
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import org.jlleitschuh.gradle.ktlint.KtlintExtension
|
||||
|
||||
val ktorVersion: Provider<String> = providers.gradleProperty("ktor_version")
|
||||
val kotlinVersion: Provider<String> = providers.gradleProperty("kotlin_version")
|
||||
val kotlinSerializationVersion: Provider<String> = providers.gradleProperty("kotlin_serialization_version")
|
||||
val logbackVersion: Provider<String> = providers.gradleProperty("logback_version")
|
||||
val koinVersion: Provider<String> = providers.gradleProperty("koin_version")
|
||||
val kotlinLoggingVersion: Provider<String> = providers.gradleProperty("kotlin_logging_version")
|
||||
val kotestVersion: Provider<String> = providers.gradleProperty("kotest_version")
|
||||
|
||||
plugins {
|
||||
application
|
||||
kotlin("jvm")
|
||||
id("io.ktor.plugin") version "3.5.1"
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("org.jlleitschuh.gradle.ktlint") version "14.2.0"
|
||||
}
|
||||
|
||||
group = "io.github.flecomte"
|
||||
|
||||
application {
|
||||
mainClass.set("eventDemo.ApplicationKt")
|
||||
|
||||
val isDevelopment: Boolean = project.ext.has("development")
|
||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
||||
}
|
||||
|
||||
configure<KtlintExtension> {
|
||||
version.set("1.8.0")
|
||||
}
|
||||
ktlint {
|
||||
reporters {
|
||||
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.uuid.ExperimentalUuidApi")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
jvmArgs("-Djdk.attach.allowAttachSelf=true", "-XX:+EnableDynamicAgentLoading")
|
||||
// Dynamic self-attach (used by MockK/ByteBuddy) times out in Docker containers because the
|
||||
// SIGQUIT-triggered AttachListener handshake never completes there. Loading the byte-buddy
|
||||
// agent jar statically via -javaagent avoids the attach handshake entirely: MockK detects the
|
||||
// pre-installed Instrumentation instance and skips dynamic attach.
|
||||
doFirst {
|
||||
val agentJar =
|
||||
classpath.files.firstOrNull { it.name.startsWith("byte-buddy-agent") }
|
||||
?: error("byte-buddy-agent jar not found on test classpath")
|
||||
jvmArgs("-javaagent:$agentJar")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
implementation("io.ktor:ktor-server-core-jvm")
|
||||
implementation("io.ktor:ktor-server-auth-jvm")
|
||||
implementation("io.ktor:ktor-server-auth-jwt-jvm")
|
||||
implementation("io.ktor:ktor-server-auto-head-response-jvm")
|
||||
implementation("io.ktor:ktor-server-resources")
|
||||
implementation("io.ktor:ktor-server-content-negotiation-jvm")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
|
||||
implementation("io.ktor:ktor-server-websockets-jvm")
|
||||
implementation("io.ktor:ktor-server-cors-jvm")
|
||||
implementation("io.ktor:ktor-server-host-common-jvm")
|
||||
implementation("io.ktor:ktor-server-status-pages-jvm")
|
||||
implementation("io.ktor:ktor-server-netty-jvm")
|
||||
implementation("io.ktor:ktor-server-data-conversion")
|
||||
implementation("io.ktor:ktor-client-content-negotiation")
|
||||
implementation("io.ktor:ktor-client-auth")
|
||||
implementation("ch.qos.logback:logback-classic:${logbackVersion.get()}")
|
||||
implementation("io.insert-koin:koin-ktor:${koinVersion.get()}")
|
||||
implementation("io.insert-koin:koin-logger-slf4j:${koinVersion.get()}")
|
||||
implementation("io.github.oshai:kotlin-logging-jvm:${kotlinLoggingVersion.get()}")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:${kotlinSerializationVersion.get()}")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.2")
|
||||
implementation("org.postgresql:postgresql:42.7.13")
|
||||
implementation("com.zaxxer:HikariCP:6.3.0")
|
||||
implementation("com.rabbitmq:amqp-client:5.25.0")
|
||||
implementation("com.password4j:password4j:1.8.4")
|
||||
|
||||
// Force version of sub library (for security)
|
||||
implementation("commons-codec:commons-codec:1.13")
|
||||
|
||||
testImplementation("io.kotest:kotest-extensions-koin:${kotestVersion.get()}")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${kotlinVersion.get()}")
|
||||
testImplementation("io.ktor:ktor-server-test-host-jvm:${ktorVersion.get()}")
|
||||
testImplementation("io.kotest:kotest-runner-junit5:${kotestVersion.get()}")
|
||||
testImplementation("io.mockk:mockk:1.14.11")
|
||||
testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0")
|
||||
}
|
||||
File renamed without changes.
-2
@@ -3,7 +3,6 @@ package eventDemo.configuration
|
||||
import io.ktor.server.config.ApplicationConfig
|
||||
|
||||
data class Configuration(
|
||||
val redisUrl: String,
|
||||
val jwtSecret: String,
|
||||
val postgresql: Postgresql,
|
||||
val rabbitmq: RabbitMQ,
|
||||
@@ -25,7 +24,6 @@ data class Configuration(
|
||||
val ApplicationConfig.configuration
|
||||
get() =
|
||||
Configuration(
|
||||
redisUrl = getProperty("redis.url"),
|
||||
jwtSecret = getProperty("jwt.secret"),
|
||||
postgresql =
|
||||
Configuration.Postgresql(
|
||||
File renamed without changes.
-7
@@ -7,8 +7,6 @@ import org.koin.core.module.Module
|
||||
import org.koin.core.scope.Scope
|
||||
import org.koin.core.scope.ScopeCallback
|
||||
import org.koin.dsl.bind
|
||||
import redis.clients.jedis.JedisPooled
|
||||
import redis.clients.jedis.UnifiedJedis
|
||||
import javax.sql.DataSource
|
||||
|
||||
fun Module.configureDIDataSource(config: Configuration) {
|
||||
@@ -26,11 +24,6 @@ fun Module.configureDIDataSource(config: Configuration) {
|
||||
}
|
||||
} bind DataSource::class
|
||||
|
||||
// Redis (for Projections)
|
||||
single {
|
||||
JedisPooled(config.redisUrl)
|
||||
} bind UnifiedJedis::class
|
||||
|
||||
// RabbitMQ (for EventBus)
|
||||
factory {
|
||||
ConnectionFactory().apply {
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -2,7 +2,7 @@ package eventDemo.contexts.auth.application.eventStores
|
||||
|
||||
import eventDemo.contexts.auth.application.ports.UserEventStore
|
||||
import eventDemo.contexts.auth.domain.User
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
|
||||
class UserEventStoreRepository(
|
||||
val eventStore: UserEventStore,
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.contexts.auth.application.eventStores
|
||||
|
||||
import eventDemo.contexts.auth.domain.User
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
|
||||
interface UserRepository {
|
||||
fun get(id: UserId): User?
|
||||
+1
-1
@@ -2,6 +2,6 @@ package eventDemo.contexts.auth.application.ports
|
||||
|
||||
import eventDemo.contexts.auth.domain.events.UserEvent
|
||||
import eventDemo.libs.eventSource.eventStore.EventStore
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
|
||||
interface UserEventStore : EventStore<UserEvent, UserId>
|
||||
File renamed without changes.
+1
-1
@@ -2,7 +2,7 @@ package eventDemo.contexts.auth.domain
|
||||
|
||||
import eventDemo.contexts.auth.domain.events.NewUserCreatedEvent
|
||||
import eventDemo.contexts.auth.domain.events.UserEvent
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package eventDemo.contexts.auth.domain.events
|
||||
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.UserId
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.contexts.auth.domain.events
|
||||
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -5,7 +5,7 @@ import com.auth0.jwt.algorithms.Algorithm
|
||||
import eventDemo.configuration.configuration
|
||||
import eventDemo.contexts.auth.domain.User
|
||||
import eventDemo.contexts.auth.infrastructure.persistence.projection.UserProjection
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.auth.authentication
|
||||
+1
-1
@@ -4,7 +4,7 @@ import eventDemo.contexts.auth.application.ports.UserEventStore
|
||||
import eventDemo.contexts.auth.domain.events.UserEvent
|
||||
import eventDemo.libs.eventSource.eventStore.EventStore
|
||||
import eventDemo.libs.eventSource.eventStore.EventStoreInMemory
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
|
||||
/**
|
||||
* A stream to publish and read the user events.
|
||||
+1
-1
@@ -4,7 +4,7 @@ import eventDemo.contexts.auth.application.ports.UserEventStore
|
||||
import eventDemo.contexts.auth.domain.events.UserEvent
|
||||
import eventDemo.libs.eventSource.eventStore.EventStore
|
||||
import eventDemo.libs.eventSource.eventStore.EventStoreInPostgresql
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.sql.DataSource
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package eventDemo.contexts.auth.infrastructure.persistence.projection
|
||||
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.ids.UserId
|
||||
|
||||
data class UserProjection(
|
||||
val id: UserId,
|
||||
+5
-4
@@ -3,9 +3,10 @@ package eventDemo.contexts.auth.infrastructure.persistence.projection
|
||||
import eventDemo.contexts.auth.application.ports.UserProjectionRepository
|
||||
import eventDemo.contexts.auth.infrastructure.checkPassword
|
||||
import eventDemo.contexts.auth.infrastructure.hashPassword
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import java.util.UUID
|
||||
import eventDemo.shared.ids.UserId
|
||||
import javax.sql.DataSource
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlin.uuid.toJavaUuid
|
||||
|
||||
class UserProjectionRepositoryInPostgresql(
|
||||
val dataSource: DataSource,
|
||||
@@ -24,7 +25,7 @@ class UserProjectionRepositoryInPostgresql(
|
||||
}.use { resultSet ->
|
||||
if (resultSet.next()) {
|
||||
UserProjection(
|
||||
id = UserId(UUID.fromString(resultSet.getString("id"))),
|
||||
id = UserId(Uuid.parse(resultSet.getString("id"))),
|
||||
username = resultSet.getString("username"),
|
||||
password = resultSet.getString("password"),
|
||||
)
|
||||
@@ -42,7 +43,7 @@ class UserProjectionRepositoryInPostgresql(
|
||||
values (?, ?)
|
||||
""".trimIndent(),
|
||||
).use {
|
||||
it.setObject(1, user.id)
|
||||
it.setObject(1, user.id.id.toJavaUuid())
|
||||
it.setString(2, user.username)
|
||||
it.executeUpdate()
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+4
-4
@@ -1,11 +1,11 @@
|
||||
package eventDemo.contexts.game.application.channels
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.GameCommand
|
||||
import eventDemo.contexts.game.application.notification.CommandSubscriber
|
||||
import eventDemo.contexts.game.application.notification.EventToNotificationSubscriber
|
||||
import eventDemo.contexts.game.application.notification.models.Notification
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.game.command.GameCommand
|
||||
import eventDemo.shared.game.notification.Notification
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.ids.UserId
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
File renamed without changes.
+6
-6
@@ -1,11 +1,11 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.GameCommand
|
||||
import eventDemo.contexts.game.application.command.models.JoinTheGameCommand
|
||||
import eventDemo.contexts.game.application.command.models.PlayCardCommand
|
||||
import eventDemo.contexts.game.application.command.models.ReadyToPlayCommand
|
||||
import eventDemo.contexts.game.application.command.models.TakeCartFromDrawPileCommand
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.shared.game.command.GameCommand
|
||||
import eventDemo.shared.game.command.JoinTheGameCommand
|
||||
import eventDemo.shared.game.command.PlayCardCommand
|
||||
import eventDemo.shared.game.command.ReadyToPlayCommand
|
||||
import eventDemo.shared.game.command.TakeCartFromDrawPileCommand
|
||||
import eventDemo.shared.ids.GameId
|
||||
import java.util.Collections
|
||||
|
||||
class GameCommandHandlerDispatcher(
|
||||
+4
-12
@@ -1,12 +1,12 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.GameCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||
import eventDemo.libs.command.Command
|
||||
import eventDemo.libs.eventSource.eventStore.VersionConflictException
|
||||
import eventDemo.shared.command.Command
|
||||
import eventDemo.shared.game.command.GameCommand
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@@ -37,16 +37,8 @@ abstract class GameEventManager(
|
||||
return this
|
||||
}
|
||||
|
||||
protected fun <G : Game> Game.isStatusOrFail(
|
||||
kClass: KClass<G>,
|
||||
message: String,
|
||||
): G {
|
||||
if (kClass.isInstance(this)) {
|
||||
return this as G
|
||||
} else {
|
||||
throw CommandException(message)
|
||||
}
|
||||
}
|
||||
protected inline fun <reified G : Game> Game.isStatusOrFail(message: String): G =
|
||||
this as? G ?: throw CommandException(message)
|
||||
|
||||
protected fun <T> retry(
|
||||
mapAttempts: Int = 5,
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.auth.application.eventStores.UserRepository
|
||||
import eventDemo.contexts.game.application.command.models.JoinTheGameCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameCreated
|
||||
import eventDemo.shared.game.command.JoinTheGameCommand
|
||||
|
||||
/**
|
||||
* A command to perform an action to play a new card
|
||||
@@ -20,7 +20,7 @@ class JoinTheGameHandler(
|
||||
retry {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameCreated::class, "The game is started")
|
||||
.isStatusOrFail<GameCreated>("The game is started")
|
||||
.userJoinTheGame(
|
||||
userId = command.userId,
|
||||
name = user.username,
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.PlayCardCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||
import eventDemo.shared.game.command.PlayCardCommand
|
||||
|
||||
/**
|
||||
* A command to perform an action to play a new card
|
||||
@@ -16,7 +16,7 @@ class PlayCardHandler(
|
||||
override fun handle(command: PlayCardCommand) {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameStarted::class, "The game is not started")
|
||||
.isStatusOrFail<GameStarted>("The game is not started")
|
||||
.playTheCard(
|
||||
card = command.payload.card,
|
||||
playerId = command.payload.playerId,
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.ReadyToPlayCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameCreated
|
||||
import eventDemo.shared.game.command.ReadyToPlayCommand
|
||||
|
||||
/**
|
||||
* A command to set as ready to play
|
||||
@@ -16,7 +16,7 @@ class ReadyToPlayHandler(
|
||||
override fun handle(command: ReadyToPlayCommand) {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameCreated::class, "The game is started")
|
||||
.isStatusOrFail<GameCreated>("The game is started")
|
||||
.setReadyPlayer(command.payload.playerId)
|
||||
.saveEvents()
|
||||
.publishEvents()
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.TakeCartFromDrawPileCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||
import eventDemo.shared.game.command.TakeCartFromDrawPileCommand
|
||||
|
||||
/**
|
||||
* A command to draw card on draw pile.
|
||||
@@ -18,7 +18,7 @@ class TakeCartFromDrawPileHandler(
|
||||
override fun handle(command: TakeCartFromDrawPileCommand) {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameStarted::class, "The game is not started")
|
||||
.isStatusOrFail<GameStarted>("The game is not started")
|
||||
.playerTakeCartFromDrawPile(command.payload.playerId, 1)
|
||||
.saveEvents()
|
||||
.publishEvents()
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
package eventDemo.contexts.game.application.eventStores
|
||||
|
||||
import eventDemo.contexts.game.application.ports.GameEventStore
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||
import eventDemo.libs.eventSource.eventStore.VersionConflictException
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
class GameEventStoreRepository(
|
||||
val eventStore: GameEventStore,
|
||||
+1
-1
@@ -1,10 +1,10 @@
|
||||
package eventDemo.contexts.game.application.eventStores
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameCreated
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameInit
|
||||
import eventDemo.libs.eventSource.eventStore.VersionConflictException
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
interface GameRepository {
|
||||
fun get(id: GameId): Game?
|
||||
File renamed without changes.
File renamed without changes.
+12
-12
@@ -1,16 +1,5 @@
|
||||
package eventDemo.contexts.game.application.notification
|
||||
|
||||
import eventDemo.contexts.game.application.notification.models.ItsTheTurnOfNotification
|
||||
import eventDemo.contexts.game.application.notification.models.Notification
|
||||
import eventDemo.contexts.game.application.notification.models.PilesShuffledNotification
|
||||
import eventDemo.contexts.game.application.notification.models.PlayerAsJoinTheGameNotification
|
||||
import eventDemo.contexts.game.application.notification.models.PlayerAsPlayACardNotification
|
||||
import eventDemo.contexts.game.application.notification.models.PlayerHavePassNotification
|
||||
import eventDemo.contexts.game.application.notification.models.PlayerWasReadyNotification
|
||||
import eventDemo.contexts.game.application.notification.models.PlayerWinNotification
|
||||
import eventDemo.contexts.game.application.notification.models.TheGameWasStartedNotification
|
||||
import eventDemo.contexts.game.application.notification.models.WelcomeToTheGameNotification
|
||||
import eventDemo.contexts.game.application.notification.models.YourNewCardNotification
|
||||
import eventDemo.contexts.game.domain.events.CardIsPlayedEvent
|
||||
import eventDemo.contexts.game.domain.events.DrawFilledWithDiscardEvent
|
||||
import eventDemo.contexts.game.domain.events.GameCreatedEvent
|
||||
@@ -23,7 +12,18 @@ import eventDemo.contexts.game.domain.events.PlayerReadyEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerWinEvent
|
||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.game.notification.ItsTheTurnOfNotification
|
||||
import eventDemo.shared.game.notification.Notification
|
||||
import eventDemo.shared.game.notification.PilesShuffledNotification
|
||||
import eventDemo.shared.game.notification.PlayerAsJoinTheGameNotification
|
||||
import eventDemo.shared.game.notification.PlayerAsPlayACardNotification
|
||||
import eventDemo.shared.game.notification.PlayerHavePassNotification
|
||||
import eventDemo.shared.game.notification.PlayerWasReadyNotification
|
||||
import eventDemo.shared.game.notification.PlayerWinNotification
|
||||
import eventDemo.shared.game.notification.TheGameWasStartedNotification
|
||||
import eventDemo.shared.game.notification.WelcomeToTheGameNotification
|
||||
import eventDemo.shared.game.notification.YourNewCardNotification
|
||||
import eventDemo.shared.ids.UserId
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.github.oshai.kotlinlogging.withLoggingContext
|
||||
|
||||
+4
-4
@@ -1,7 +1,6 @@
|
||||
package eventDemo.contexts.game.application.notification
|
||||
|
||||
import eventDemo.contexts.game.application.command.handlers.GameCommandHandlerDispatcher
|
||||
import eventDemo.contexts.game.application.command.models.GameCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.logging.LoggingContextKeys.Command
|
||||
import eventDemo.contexts.game.application.logging.LoggingContextKeys.CurrentUserId
|
||||
@@ -9,12 +8,13 @@ import eventDemo.contexts.game.application.logging.LoggingContextKeys.Event
|
||||
import eventDemo.contexts.game.application.logging.LoggingContextKeys.Game
|
||||
import eventDemo.contexts.game.application.logging.LoggingContextKeys.Notification
|
||||
import eventDemo.contexts.game.application.logging.withLoggingContext
|
||||
import eventDemo.contexts.game.application.notification.models.Notification
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.libs.bus.Bus
|
||||
import eventDemo.libs.command.CommandUnicityChecker
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.game.command.GameCommand
|
||||
import eventDemo.shared.game.notification.Notification
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.ids.UserId
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
File renamed without changes.
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.contexts.game.application.ports
|
||||
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.libs.eventSource.eventStore.EventStore
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
interface GameEventStore : EventStore<GameEvent, GameId>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package eventDemo.domain.event.projection
|
||||
|
||||
import eventDemo.contexts.game.infrastructure.persistence.projections.models.GameList
|
||||
import eventDemo.shared.game.projection.GameList
|
||||
|
||||
interface GameListRepository {
|
||||
fun getList(
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package eventDemo.contexts.game.application.ports
|
||||
|
||||
import eventDemo.contexts.game.infrastructure.persistence.projections.models.GameProjection
|
||||
import eventDemo.libs.bus.Bus
|
||||
import eventDemo.shared.game.projection.GameProjection
|
||||
|
||||
interface GameProjectionBus : Bus<GameProjection>
|
||||
+1
-1
@@ -9,7 +9,7 @@ import eventDemo.contexts.game.domain.events.NewPlayerEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerHaveDrawCardEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerReadyEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerWinEvent
|
||||
import eventDemo.contexts.game.infrastructure.persistence.projections.models.GameList
|
||||
import eventDemo.shared.game.projection.GameList
|
||||
|
||||
fun GameList.applyEvent(event: GameEvent): GameList =
|
||||
when (event) {
|
||||
File renamed without changes.
+8
-8
@@ -1,15 +1,15 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.Card
|
||||
import eventDemo.contexts.game.domain.game.Card.Color
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.game.Card
|
||||
import eventDemo.shared.game.Card.Color
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* An [GameEvent] to represent a played card.
|
||||
@@ -24,7 +24,7 @@ data class CardIsPlayedEvent(
|
||||
) : GameEvent,
|
||||
PlayerActionEvent {
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
|
||||
val theColorCard get() = if (card is Card.CardWithColor) card.color else chosenColor
|
||||
+7
-7
@@ -1,14 +1,14 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.DiscardPile
|
||||
import eventDemo.contexts.game.domain.game.DrawPile
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.game.DiscardPile
|
||||
import eventDemo.shared.game.DrawPile
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* When the Pile are shuffled after the draw pille was empty
|
||||
@@ -21,6 +21,6 @@ class DrawFilledWithDiscardEvent(
|
||||
override val version: Int,
|
||||
) : GameEvent {
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
}
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* This [GameEvent] is sent when all players are ready.
|
||||
@@ -17,6 +17,6 @@ data class GameCreatedEvent(
|
||||
override val version: Int,
|
||||
) : GameEvent {
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
}
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import eventDemo.shared.serializers.GameIdSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
+10
-10
@@ -1,17 +1,17 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.DiscardPile
|
||||
import eventDemo.contexts.game.domain.game.DrawPile
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.domain.game.PlayerHand
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.game.DiscardPile
|
||||
import eventDemo.shared.game.DrawPile
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.game.PlayerHand
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import eventDemo.shared.serializers.PlayerIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* This [GameEvent] is sent when all players are ready.
|
||||
@@ -27,6 +27,6 @@ data class GameStartedEvent(
|
||||
override val version: Int,
|
||||
) : GameEvent {
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
}
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* An [GameEvent] to represent a new player joining the game.
|
||||
@@ -22,6 +22,6 @@ data class NewPlayerEvent(
|
||||
override val playerId: Player.PlayerId get() = player.id
|
||||
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.shared.game.Player
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
+8
-8
@@ -1,15 +1,15 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.Card
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.game.Card
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import eventDemo.shared.serializers.PlayerIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* This [GameEvent] is sent when a player can play.
|
||||
@@ -24,6 +24,6 @@ data class PlayerHaveDrawCardEvent(
|
||||
) : GameEvent,
|
||||
PlayerActionEvent {
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
}
|
||||
+7
-7
@@ -1,14 +1,14 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import eventDemo.shared.serializers.PlayerIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* This [GameEvent] is sent when a player is ready.
|
||||
@@ -22,6 +22,6 @@ data class PlayerReadyEvent(
|
||||
) : GameEvent,
|
||||
PlayerActionEvent {
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
}
|
||||
+7
-7
@@ -1,14 +1,14 @@
|
||||
package eventDemo.contexts.game.domain.events
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import eventDemo.shared.serializers.PlayerIdSerializer
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* This [GameEvent] is sent when a player is ready.
|
||||
@@ -22,6 +22,6 @@ data class PlayerWinEvent(
|
||||
) : GameEvent,
|
||||
PlayerActionEvent {
|
||||
@Serializable(with = EventIdSerializer::class)
|
||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||
override val eventId: EventId = EventId(Uuid.random())
|
||||
override val createdAt: Instant = Clock.System.now()
|
||||
}
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
package eventDemo.contexts.game.domain.game.errors
|
||||
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.Card
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.domain.game.PlayerList
|
||||
import eventDemo.contexts.game.domain.game.gameState.Deck
|
||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||
import eventDemo.shared.game.Card
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.game.PlayerList
|
||||
|
||||
abstract class GameException(
|
||||
message: String,
|
||||
+2
-2
@@ -9,10 +9,10 @@ import eventDemo.contexts.game.domain.events.NewPlayerEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerHaveDrawCardEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerReadyEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerWinEvent
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.PlayerList
|
||||
import eventDemo.contexts.game.domain.game.errors.GameException
|
||||
import eventDemo.contexts.game.domain.game.errors.InconsistentEventVersionException
|
||||
import eventDemo.shared.game.PlayerList
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
sealed interface Game {
|
||||
val aggregateId: GameId
|
||||
+8
-8
@@ -4,18 +4,18 @@ import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.events.GameStartedEvent
|
||||
import eventDemo.contexts.game.domain.events.NewPlayerEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerReadyEvent
|
||||
import eventDemo.contexts.game.domain.game.Card
|
||||
import eventDemo.contexts.game.domain.game.DiscardPile
|
||||
import eventDemo.contexts.game.domain.game.DrawPile
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.domain.game.PlayerHand
|
||||
import eventDemo.contexts.game.domain.game.PlayerList
|
||||
import eventDemo.contexts.game.domain.game.errors.AllPlayerNotReadyException
|
||||
import eventDemo.contexts.game.domain.game.errors.DeckMissingCardsException
|
||||
import eventDemo.contexts.game.domain.game.errors.NeedMorePlayersToStartGameException
|
||||
import eventDemo.contexts.game.domain.game.errors.ThePlayerIsNotInTheGameException
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import eventDemo.shared.game.Card
|
||||
import eventDemo.shared.game.DiscardPile
|
||||
import eventDemo.shared.game.DrawPile
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.game.PlayerHand
|
||||
import eventDemo.shared.game.PlayerList
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.ids.UserId
|
||||
|
||||
data class GameCreated(
|
||||
override val aggregateId: GameId,
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
package eventDemo.contexts.game.domain.game.gameState
|
||||
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.domain.game.PlayerList
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.game.PlayerList
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
data class GameEnded(
|
||||
override val aggregateId: GameId,
|
||||
+2
-2
@@ -2,8 +2,8 @@ package eventDemo.contexts.game.domain.game.gameState
|
||||
|
||||
import eventDemo.contexts.game.domain.events.GameCreatedEvent
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.PlayerList
|
||||
import eventDemo.shared.game.PlayerList
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
data class GameInit(
|
||||
override val aggregateId: GameId,
|
||||
+7
-7
@@ -6,13 +6,6 @@ import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerActionEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerHaveDrawCardEvent
|
||||
import eventDemo.contexts.game.domain.events.PlayerWinEvent
|
||||
import eventDemo.contexts.game.domain.game.Card
|
||||
import eventDemo.contexts.game.domain.game.Card.Color
|
||||
import eventDemo.contexts.game.domain.game.DiscardPile
|
||||
import eventDemo.contexts.game.domain.game.DrawPile
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.domain.game.PlayerList
|
||||
import eventDemo.contexts.game.domain.game.errors.InconsistentGameException
|
||||
import eventDemo.contexts.game.domain.game.errors.ItsNotTheTurnException
|
||||
import eventDemo.contexts.game.domain.game.errors.TheCardHasNoColorException
|
||||
@@ -22,6 +15,13 @@ import eventDemo.contexts.game.domain.game.errors.ThePlayerHasRemainingCardsExce
|
||||
import eventDemo.contexts.game.domain.game.errors.ThePlayerIsNotInTheGameException
|
||||
import eventDemo.contexts.game.domain.game.errors.ThePlayerMustPlayACardException
|
||||
import eventDemo.contexts.game.domain.game.gameState.Game.Direction
|
||||
import eventDemo.shared.game.Card
|
||||
import eventDemo.shared.game.Card.Color
|
||||
import eventDemo.shared.game.DiscardPile
|
||||
import eventDemo.shared.game.DrawPile
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.game.PlayerList
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
fun PlayerList.nextPlayerTurn(
|
||||
lastPlayerId: Player.PlayerId,
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-15
@@ -1,5 +1,6 @@
|
||||
package eventDemo.contexts.game.infrastructure.configuration.ktor
|
||||
|
||||
import eventDemo.shared.http.HttpErrorBadRequest
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -10,7 +11,6 @@ import io.ktor.server.plugins.cors.routing.CORS
|
||||
import io.ktor.server.plugins.statuspages.StatusPages
|
||||
import io.ktor.server.resources.Resources
|
||||
import io.ktor.server.response.respondText
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
fun Application.configureHttpRouting() {
|
||||
install(CORS) {
|
||||
@@ -38,17 +38,3 @@ fun Application.configureHttpRouting() {
|
||||
class BadRequestException(
|
||||
val httpError: HttpErrorBadRequest,
|
||||
) : Exception()
|
||||
|
||||
@Serializable
|
||||
class HttpErrorBadRequest(
|
||||
val title: String = HttpStatusCode.BadRequest.description,
|
||||
val invalidParams: List<InvalidParam> = emptyList(),
|
||||
) {
|
||||
val statusCode: Int = HttpStatusCode.BadRequest.value
|
||||
|
||||
@Serializable
|
||||
data class InvalidParam(
|
||||
val name: String,
|
||||
val reason: String,
|
||||
)
|
||||
}
|
||||
+11
-11
@@ -1,21 +1,21 @@
|
||||
package eventDemo.contexts.game.infrastructure.configuration.ktor
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.CommandIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.command.CommandId
|
||||
import eventDemo.libs.eventSource.EventId
|
||||
import eventDemo.libs.serializer.UUIDSerializer
|
||||
import eventDemo.shared.game.Player
|
||||
import eventDemo.shared.ids.CommandId
|
||||
import eventDemo.shared.ids.EventId
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.CommandIdSerializer
|
||||
import eventDemo.shared.serializers.EventIdSerializer
|
||||
import eventDemo.shared.serializers.GameIdSerializer
|
||||
import eventDemo.shared.serializers.PlayerIdSerializer
|
||||
import eventDemo.shared.serializers.UUIDSerializer
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
fun Application.configureSerialization() {
|
||||
install(ContentNegotiation) {
|
||||
@@ -29,7 +29,7 @@ fun defaultJsonSerializer(): Json =
|
||||
Json {
|
||||
serializersModule =
|
||||
SerializersModule {
|
||||
contextual(UUID::class) { UUIDSerializer }
|
||||
contextual(Uuid::class) { UUIDSerializer }
|
||||
contextual(GameId::class) { GameIdSerializer }
|
||||
contextual(EventId::class) { EventIdSerializer }
|
||||
contextual(CommandId::class) { CommandIdSerializer }
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -2,9 +2,9 @@ package eventDemo.contexts.game.infrastructure.persistence.eventStore
|
||||
|
||||
import eventDemo.contexts.game.application.ports.GameEventStore
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.libs.eventSource.eventStore.EventStore
|
||||
import eventDemo.libs.eventSource.eventStore.EventStoreInMemory
|
||||
import eventDemo.shared.ids.GameId
|
||||
|
||||
/**
|
||||
* A stream to publish and read the played card event.
|
||||
+1
-1
@@ -2,9 +2,9 @@ package eventDemo.contexts.game.infrastructure.persistence.eventStore
|
||||
|
||||
import eventDemo.contexts.game.application.ports.GameEventStore
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.libs.eventSource.eventStore.EventStore
|
||||
import eventDemo.libs.eventSource.eventStore.EventStoreInPostgresql
|
||||
import eventDemo.shared.ids.GameId
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.sql.DataSource
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.application.ports.GameEventStore
|
||||
import eventDemo.contexts.game.application.ports.GameProjectionBus
|
||||
import eventDemo.contexts.game.application.projections.applyEvent
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.infrastructure.persistence.projections.models.GameList
|
||||
import eventDemo.domain.event.projection.GameListRepository
|
||||
import eventDemo.shared.game.projection.GameList
|
||||
import eventDemo.shared.ids.GameId
|
||||
import io.github.oshai.kotlinlogging.withLoggingContext
|
||||
|
||||
/**
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
package eventDemo.contexts.game.infrastructure.persistence.projections.bus
|
||||
|
||||
import eventDemo.contexts.game.application.ports.GameProjectionBus
|
||||
import eventDemo.contexts.game.infrastructure.persistence.projections.models.GameProjection
|
||||
import eventDemo.libs.bus.Bus
|
||||
import eventDemo.libs.bus.BusInMemory
|
||||
import eventDemo.shared.game.projection.GameProjection
|
||||
import java.util.UUID
|
||||
|
||||
class GameProjectionBusInMemory :
|
||||
+1
-1
@@ -2,9 +2,9 @@ package eventDemo.contexts.game.infrastructure.persistence.projections.bus
|
||||
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import eventDemo.contexts.game.application.ports.GameProjectionBus
|
||||
import eventDemo.contexts.game.infrastructure.persistence.projections.models.GameProjection
|
||||
import eventDemo.libs.bus.Bus
|
||||
import eventDemo.libs.bus.BusInRabbitMQ
|
||||
import eventDemo.shared.game.projection.GameProjection
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.UUID
|
||||
|
||||
File renamed without changes.
+2
-2
@@ -3,8 +3,8 @@ package eventDemo.contexts.game.infrastructure.rest
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.notification.toNotification
|
||||
import eventDemo.contexts.game.application.ports.GameEventStore
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.shared.serializers.GameIdSerializer
|
||||
import eventDemo.sharedKernel.currentUserId
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.resources.Resource
|
||||
+3
-3
@@ -1,22 +1,22 @@
|
||||
package eventDemo.contexts.game.infrastructure.websocket
|
||||
|
||||
import eventDemo.contexts.game.application.channels.GameChannelsSubscriber
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.libs.helpers.fromFrameChannel
|
||||
import eventDemo.libs.helpers.toObjectChannel
|
||||
import eventDemo.shared.ids.GameId
|
||||
import eventDemo.sharedKernel.currentUserId
|
||||
import io.ktor.server.auth.authenticate
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.websocket.webSocket
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import java.util.UUID
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
fun Route.gameWebSocket(channelSubscriber: GameChannelsSubscriber) {
|
||||
authenticate {
|
||||
webSocket("/games/{id}") {
|
||||
channelSubscriber.subscribePlayerToGameChannels(
|
||||
gameId = GameId(UUID.fromString(call.parameters["id"]!!)),
|
||||
gameId = GameId(Uuid.parse(call.parameters["id"]!!)),
|
||||
userId = call.currentUserId,
|
||||
incomingCommandChannel = toObjectChannel(incoming),
|
||||
sendNotificationChannel = fromFrameChannel(outgoing),
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
@@ -1,5 +1,7 @@
|
||||
package eventDemo.libs.command
|
||||
|
||||
import eventDemo.shared.command.Command
|
||||
import eventDemo.shared.ids.CommandId
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -0,0 +1,16 @@
|
||||
package eventDemo.libs.eventSource
|
||||
|
||||
import eventDemo.shared.ids.AggregateId
|
||||
import eventDemo.shared.ids.EventId
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* The basic interface for an Event
|
||||
* @see eventDemo.libs.eventSource.eventStore.EventStream
|
||||
*/
|
||||
interface Event<ID : AggregateId> {
|
||||
val eventId: EventId
|
||||
val aggregateId: ID
|
||||
val createdAt: Instant
|
||||
val version: Int
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.libs.eventSource.eventStore
|
||||
|
||||
import eventDemo.libs.eventSource.AggregateId
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.shared.ids.AggregateId
|
||||
import io.github.oshai.kotlinlogging.withLoggingContext
|
||||
|
||||
interface EventStore<E : Event<ID>, ID : AggregateId> {
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.libs.eventSource.eventStore
|
||||
|
||||
import eventDemo.libs.eventSource.AggregateId
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.shared.ids.AggregateId
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.libs.eventSource.eventStore
|
||||
|
||||
import eventDemo.libs.eventSource.AggregateId
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.shared.ids.AggregateId
|
||||
import javax.sql.DataSource
|
||||
|
||||
class EventStoreInPostgresql<E : Event<ID>, ID : AggregateId>(
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.libs.eventSource.eventStore
|
||||
|
||||
import eventDemo.libs.eventSource.AggregateId
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.shared.ids.AggregateId
|
||||
import io.github.oshai.kotlinlogging.withLoggingContext
|
||||
|
||||
/**
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eventDemo.libs.eventSource.eventStore
|
||||
|
||||
import eventDemo.libs.eventSource.AggregateId
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.shared.ids.AggregateId
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import java.util.Queue
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
+7
-6
@@ -1,12 +1,13 @@
|
||||
package eventDemo.libs.eventSource.eventStore
|
||||
|
||||
import eventDemo.libs.eventSource.AggregateId
|
||||
import eventDemo.libs.eventSource.Event
|
||||
import eventDemo.shared.ids.AggregateId
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.github.oshai.kotlinlogging.withLoggingContext
|
||||
import org.postgresql.util.PGobject
|
||||
import org.postgresql.util.PSQLException
|
||||
import javax.sql.DataSource
|
||||
import kotlin.uuid.toJavaUuid
|
||||
|
||||
/**
|
||||
* An In-Memory implementation of an event stream.
|
||||
@@ -39,8 +40,8 @@ class EventStreamInPostgresql<E : Event<ID>, ID : AggregateId>(
|
||||
on conflict (id) do nothing
|
||||
""".trimIndent(),
|
||||
).use {
|
||||
it.setObject(1, event.eventId.id)
|
||||
it.setObject(2, event.aggregateId.id)
|
||||
it.setObject(1, event.eventId.id.toJavaUuid())
|
||||
it.setObject(2, event.aggregateId.id.toJavaUuid())
|
||||
it.setInt(3, event.version)
|
||||
it.setObject(4, PGJsonb(objectToString(event)))
|
||||
it.executeUpdate()
|
||||
@@ -69,7 +70,7 @@ class EventStreamInPostgresql<E : Event<ID>, ID : AggregateId>(
|
||||
order by version asc
|
||||
""".trimIndent(),
|
||||
).use {
|
||||
it.setObject(1, aggregateId.id)
|
||||
it.setObject(1, aggregateId.id.toJavaUuid())
|
||||
it.executeQuery().use { resultSet ->
|
||||
buildSet {
|
||||
while (resultSet.next()) {
|
||||
@@ -95,7 +96,7 @@ class EventStreamInPostgresql<E : Event<ID>, ID : AggregateId>(
|
||||
order by version asc
|
||||
""".trimIndent(),
|
||||
).use {
|
||||
it.setObject(1, aggregateId.id)
|
||||
it.setObject(1, aggregateId.id.toJavaUuid())
|
||||
it.executeQuery().use { resultSet ->
|
||||
resultSet.next()
|
||||
}
|
||||
@@ -116,7 +117,7 @@ class EventStreamInPostgresql<E : Event<ID>, ID : AggregateId>(
|
||||
).use { stmt ->
|
||||
stmt.setInt(1, version.first)
|
||||
stmt.setInt(2, version.last)
|
||||
stmt.setObject(3, aggregateId.id)
|
||||
stmt.setObject(3, aggregateId.id.toJavaUuid())
|
||||
stmt.executeQuery().use { resultSet ->
|
||||
buildSet {
|
||||
while (resultSet.next()) {
|
||||
File renamed without changes.
Loaded 100 of 224 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user