├── .github ├── CODEONWERS ├── create-release-note-body.py ├── create-release-note-header.py ├── next-semver.py ├── semver-level.py └── workflows │ ├── bump-version.yml │ ├── ci.yml │ ├── release.yml │ └── snapshot.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.ja.md ├── README.md ├── build.gradle.kts ├── docs ├── DATABASE_SCHEMA.ja.md ├── DATABASE_SCHEMA.md └── javadoc │ ├── allclasses-index.html │ ├── allpackages-index.html │ ├── com │ └── github │ │ └── j5ik2o │ │ └── event │ │ └── store │ │ └── adapter │ │ └── java │ │ ├── Aggregate.html │ │ ├── AggregateAndEvent.html │ │ ├── AggregateId.html │ │ ├── DefaultEventSerializer.html │ │ ├── DefaultKeyResolver.html │ │ ├── DefaultSnapshotSerializer.html │ │ ├── DeserializationException.html │ │ ├── DeserializationRuntimeException.html │ │ ├── Event.html │ │ ├── EventSerializer.html │ │ ├── EventStore.html │ │ ├── EventStoreAsync.html │ │ ├── EventStoreBaseException.html │ │ ├── EventStoreBaseRuntimeException.html │ │ ├── EventStoreOptions.html │ │ ├── EventStoreReadException.html │ │ ├── EventStoreReadRuntimeException.html │ │ ├── EventStoreWriteException.html │ │ ├── EventStoreWriteRuntimeException.html │ │ ├── KeyResolver.html │ │ ├── OptimisticLockException.html │ │ ├── OptimisticLockRuntimeException.html │ │ ├── SerializationException.html │ │ ├── SerializationRuntimeException.html │ │ ├── SnapshotSerializer.html │ │ ├── internal │ │ ├── EventStoreAsyncForDynamoDB.html │ │ ├── EventStoreForDynamoDB.html │ │ ├── package-summary.html │ │ └── package-tree.html │ │ ├── package-summary.html │ │ └── package-tree.html │ ├── copy.svg │ ├── element-list │ ├── help-doc.html │ ├── index-all.html │ ├── index.html │ ├── legal │ ├── ADDITIONAL_LICENSE_INFO │ ├── ASSEMBLY_EXCEPTION │ ├── LICENSE │ ├── jquery.md │ └── jqueryUI.md │ ├── link.svg │ ├── member-search-index.js │ ├── module-search-index.js │ ├── overview-summary.html │ ├── overview-tree.html │ ├── package-search-index.js │ ├── resources │ ├── glass.png │ └── x.png │ ├── script-dir │ ├── jquery-3.7.1.min.js │ ├── jquery-ui.min.css │ └── jquery-ui.min.js │ ├── script.js │ ├── search-page.js │ ├── search.html │ ├── search.js │ ├── serialized-form.html │ ├── stylesheet.css │ ├── tag-search-index.js │ └── type-search-index.js ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── renovate.json ├── settings.gradle.kts ├── src ├── main │ └── java │ │ └── com │ │ └── github │ │ └── j5ik2o │ │ └── event │ │ └── store │ │ └── adapter │ │ └── java │ │ ├── Aggregate.java │ │ ├── AggregateAndEvent.java │ │ ├── AggregateId.java │ │ ├── DefaultEventSerializer.java │ │ ├── DefaultKeyResolver.java │ │ ├── DefaultSnapshotSerializer.java │ │ ├── DeserializationException.java │ │ ├── DeserializationRuntimeException.java │ │ ├── Event.java │ │ ├── EventSerializer.java │ │ ├── EventStore.java │ │ ├── EventStoreAsync.java │ │ ├── EventStoreBaseException.java │ │ ├── EventStoreBaseRuntimeException.java │ │ ├── EventStoreOptions.java │ │ ├── EventStoreReadException.java │ │ ├── EventStoreReadRuntimeException.java │ │ ├── EventStoreWriteException.java │ │ ├── EventStoreWriteRuntimeException.java │ │ ├── KeyResolver.java │ │ ├── OptimisticLockException.java │ │ ├── OptimisticLockRuntimeException.java │ │ ├── SerializationException.java │ │ ├── SerializationRuntimeException.java │ │ ├── SnapshotSerializer.java │ │ └── internal │ │ ├── EventStoreAsyncForDynamoDB.java │ │ ├── EventStoreForDynamoDB.java │ │ └── EventStoreSupport.java └── test │ ├── java │ └── com │ │ └── github │ │ └── j5ik2o │ │ └── event │ │ └── store │ │ └── adapter │ │ └── java │ │ └── internal │ │ ├── DynamoDBAsyncUtils.java │ │ ├── DynamoDBUtils.java │ │ ├── EventStoreAsyncForDynamoDBTest.java │ │ ├── EventStoreForDynamoDBTest.java │ │ ├── IdGenerator.java │ │ ├── OptimisticLockException.java │ │ ├── RepositoryException.java │ │ ├── UserAccount.java │ │ ├── UserAccountEvent.java │ │ ├── UserAccountId.java │ │ ├── UserAccountRepository.java │ │ └── UserAccountRepositoryAsync.java │ └── resources │ └── logback-test.xml └── version /.github/CODEONWERS: -------------------------------------------------------------------------------- 1 | * @j5ik2o 2 | -------------------------------------------------------------------------------- /.github/create-release-note-body.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import csv 6 | import re 7 | 8 | commit_messages = {'BREAKING CHANGE': [], 'build': [], 'ci': [], 'feat': [], 'fix': [], 'docs': [], 'style': [], 'refactor': [], 'perf': [], 'test': [], 'revert': [], 'chore': []} 9 | 10 | titles = {'BREAKING CHANGE': 'Breaking Changes', 11 | 'build': 'Build Systems', 12 | 'ci': 'Continuous Integration', 13 | 'feat': 'Features', 14 | 'fix': 'Bug Fixes', 15 | 'docs': 'Documentation', 16 | 'style': 'Styles', 17 | 'refactor': 'Code Refactoring', 18 | 'perf': 'Performance Improvements', 19 | 'test': 'Tests', 20 | 'revert': 'Reverts', 21 | 'chore': 'Chores'} 22 | 23 | cin = csv.reader(sys.stdin, delimiter="\t") # 補足: 開く対象がファイルのときは newline='' をパラメータに追加 24 | 25 | def match_append(key, row): 26 | r = re.match(f"^{key}(.*)?\: (.*)", row[2]) 27 | if r: 28 | commit_messages[key].append((r.group(2), row)) 29 | 30 | for row in cin: 31 | for key in commit_messages.keys(): 32 | match_append(key, row) 33 | 34 | for k,v in titles.items(): 35 | if commit_messages[k]: 36 | print(f"### {v}\n") 37 | for messages in commit_messages[k]: 38 | print(f"* {messages[0]} ({messages[1][0]})") 39 | if commit_messages[k]: 40 | print() 41 | -------------------------------------------------------------------------------- /.github/create-release-note-header.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | from datetime import datetime, timezone 6 | 7 | args = sys.argv 8 | 9 | server_url = args[1] 10 | repo_name = args[2] 11 | previous_tag = args[3] 12 | next_tag = args[4] 13 | 14 | repo_url = f"{server_url}/{repo_name}" 15 | compare_url = f"{repo_url}/compare/{previous_tag}...{next_tag}" 16 | 17 | next_version = next_tag.replace("v", "") 18 | 19 | utc_now = datetime.now(timezone.utc) 20 | today = utc_now.strftime("%Y-%m-%d") 21 | 22 | header=f""" 23 | ### [{next_version}]({compare_url}) ({today}) 24 | 25 | """ 26 | 27 | print(header) 28 | 29 | -------------------------------------------------------------------------------- /.github/next-semver.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import semver 5 | import re 6 | 7 | args = sys.argv 8 | 9 | for s in sys.stdin: 10 | r = re.match('.*v?(\d+\.\d+\.\d+)', s) 11 | if r: 12 | cur_ver = semver.VersionInfo.parse(r.group(1)) 13 | next_ver = "" 14 | if args[1] == "snapshot": 15 | next_ver = f"{cur_ver.bump_patch()}-SNAPSHOT" 16 | elif args[1] == "major": 17 | next_ver = str(cur_ver.bump_major()) 18 | elif args[1] == "minor": 19 | next_ver = str(cur_ver.bump_minor()) 20 | else: 21 | next_ver = str(cur_ver.bump_patch()) 22 | print(next_ver) 23 | -------------------------------------------------------------------------------- /.github/semver-level.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import csv 5 | import re 6 | 7 | commit_messages = {'BREAKING CHANGE': 0, 'build': 0, 'ci': 0, 'feat': 0, 'fix': 0, 'docs': 0, 'style': 0, 'refactor': 0, 'perf': 0, 'test': 0, 'revert': 0, 'chore': 0} 8 | 9 | rules = {'major': ['perf', 'BREAKING CHANGE'], 'minor': ['feat', 'revert'], 'patch': ['build', 'ci', 'fix', 'docs', 'style', 'refactor', 'chore', 'test']} 10 | 11 | cin = csv.reader(sys.stdin, delimiter="\t") 12 | 13 | def match_append(key, row): 14 | r = re.match(f"^{key}(.*)?\: (.*)", row[2]) 15 | if r: 16 | commit_messages[key]+=1 17 | 18 | for row in cin: 19 | for key in commit_messages.keys(): 20 | match_append(key, row) 21 | 22 | if sum(commit_messages.values()) > 0: 23 | for k,v in rules.items(): 24 | sum = 0 25 | for t in v: 26 | sum += commit_messages[t] 27 | if sum > 0: 28 | print(k) 29 | break 30 | else: 31 | sys.exit(-1) 32 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | name: Bump-Version 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | force_bump: 6 | description: 'Force version bump' 7 | required: true 8 | type: boolean 9 | default: false 10 | schedule: 11 | - cron: "0 0 * * *" 12 | jobs: 13 | bump-version: 14 | runs-on: ubuntu-latest 15 | steps: 16 | # ───────────────────────────────────────── 17 | # 1. ソース取得 18 | # ───────────────────────────────────────── 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | persist-credentials: true # PAT を使うステップがあるので true 23 | token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 24 | 25 | # ───────────────────────────────────────── 26 | # 2. Python と依存ライブラリ 27 | # ───────────────────────────────────────── 28 | - uses: actions/setup-python@v5 29 | with: 30 | python-version: "3.12" # 3.13 は pre-release のため安定版を利用 31 | - run: pip install semver 32 | 33 | # ───────────────────────────────────────── 34 | # 3. “比較対象” を決定 35 | # ───────────────────────────────────────── 36 | - id: defines 37 | run: | 38 | if git tag -l | grep -q .; then 39 | LATEST_TAG=$(git describe --abbrev=0 --tags) 40 | echo "prev_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT" 41 | else 42 | FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) 43 | echo "prev_tag=$FIRST_COMMIT" >> "$GITHUB_OUTPUT" 44 | fi 45 | 46 | # ───────────────────────────────────────── 47 | # 4. 変更点をカウント(force_bump 対応) 48 | # ───────────────────────────────────────── 49 | - name: Calculate changes from the latest tag to HEAD 50 | id: changes 51 | run: | 52 | BASE=${{ steps.defines.outputs.prev_tag }} 53 | 54 | COUNT=$(git log "$BASE"..HEAD --pretty=format:"%s" \ 55 | --no-merges -P \ 56 | --grep='^(BREAKING CHANGE|build|ci|feat|fix|docs|style|refactor|perf|test|revert|chore)(\(.*\))?:' \ 57 | | awk 'END{print NR}') 58 | 59 | echo "COUNT(before force)=$COUNT" 60 | 61 | # ----- force_bump ----- 62 | if [[ "${{ inputs.force_bump }}" == "true" ]]; then 63 | echo "force_bump requested — overriding COUNT to 1" 64 | COUNT=1 65 | fi 66 | # ---------------------- 67 | 68 | echo "count=$COUNT" >> "$GITHUB_OUTPUT" 69 | 70 | # ───────────────────────────────────────── 71 | # 5. SemVer レベル算定 72 | # ───────────────────────────────────────── 73 | - name: Calculate semver level 74 | id: semver_level 75 | if: steps.changes.outputs.count > 0 76 | run: | 77 | SEMVER_LEVEL=$(git log ${{ steps.defines.outputs.prev_tag }}..HEAD \ 78 | --pretty=format:"%h%x09%H%x09%s" \ 79 | --no-merges -P \ 80 | --grep='^(BREAKING CHANGE|build|ci|feat|fix|docs|style|refactor|perf|test|revert|chore)(\(.*\))?:' \ 81 | | python3 "${GITHUB_WORKSPACE}"/.github/semver-level.py) 82 | 83 | echo "semver_level=$SEMVER_LEVEL" >> "$GITHUB_OUTPUT" 84 | 85 | # ───────────────────────────────────────── 86 | # 6. 次のバージョン番号を算出 87 | # ───────────────────────────────────────── 88 | - name: Get the next version 89 | id: versions 90 | if: steps.changes.outputs.count > 0 91 | run: | 92 | NEXT_VERSION=$(echo ${{ steps.defines.outputs.prev_tag }} \ 93 | | python3 "${GITHUB_WORKSPACE}"/.github/next-semver.py \ 94 | ${{ steps.semver_level.outputs.semver_level }}) 95 | 96 | echo "next_version=$NEXT_VERSION" >> "$GITHUB_OUTPUT" 97 | echo "next_tag=v$NEXT_VERSION" >> "$GITHUB_OUTPUT" 98 | 99 | # ───────────────────────────────────────── 100 | # 7. `version` ファイルを更新 101 | # ───────────────────────────────────────── 102 | - name: Set the next version 103 | run: echo ${{ steps.versions.outputs.next_version }} > version 104 | 105 | # ───────────────────────────────────────── 106 | # 8. コミット & Push 107 | # ───────────────────────────────────────── 108 | - name: git commit & push 109 | id: git_commit_push 110 | if: steps.changes.outputs.count > 0 111 | run: | 112 | git config --global user.email "j5ik2o@gmail.com" 113 | git config --global user.name "Junichi Kato" 114 | git diff 115 | git add . 116 | git commit -m "version up to ${{ steps.versions.outputs.next_tag }}" 117 | git push origin main 118 | echo "commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" 119 | 120 | # ───────────────────────────────────────── 121 | # 9. タグ付け & リリースノート生成 122 | # ───────────────────────────────────────── 123 | - name: tagging and push tag 124 | id: tag_version 125 | if: steps.changes.outputs.count > 0 126 | run: | 127 | git tag -a "${{ steps.versions.outputs.next_tag }}" \ 128 | ${{ steps.git_commit_push.outputs.commit_sha }} \ 129 | -m "${{ steps.versions.outputs.next_tag }}" 130 | git push origin "${{ steps.versions.outputs.next_tag }}" 131 | 132 | python3 "${GITHUB_WORKSPACE}"/.github/create-release-note-header.py \ 133 | ${{ github.server_url }} \ 134 | ${{ github.repository }} \ 135 | ${{ steps.defines.outputs.prev_tag }} \ 136 | ${{ steps.versions.outputs.next_tag }} > changelog.txt 137 | 138 | git log ${{ steps.defines.outputs.prev_tag }}..${{ steps.versions.outputs.next_tag }} \ 139 | --pretty=format:"%h%x09%H%x09%s" --no-merges --full-history -P \ 140 | --grep='^(BREAKING CHANGE|build|ci|feat|fix|docs|style|refactor|perf|test|revert|chore)(\(.*\))?:.*$' \ 141 | | python3 "${GITHUB_WORKSPACE}"/.github/create-release-note-body.py \ 142 | >> changelog.txt 143 | 144 | # ───────────────────────────────────────── 145 | # 10. GitHub Release 作成 146 | # ───────────────────────────────────────── 147 | - name: Create a GitHub release 148 | if: steps.changes.outputs.count > 0 149 | uses: actions/create-release@v1 150 | env: 151 | GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 152 | with: 153 | tag_name: ${{ steps.versions.outputs.next_tag }} 154 | release_name: Release ${{ steps.versions.outputs.next_tag }} 155 | body_path: changelog.txt 156 | 157 | # ───────────────────────────────────────── 158 | # 11. Snapshot 版へ移行 159 | # ───────────────────────────────────────── 160 | - name: Switch to Snapshot version 161 | if: steps.changes.outputs.count > 0 162 | run: | 163 | rm changelog.txt 164 | SNAPSHOT_VERSION=$(echo "${{ steps.versions.outputs.next_version }}" \ 165 | | python3 "${GITHUB_WORKSPACE}"/.github/next-semver.py snapshot) 166 | echo "$SNAPSHOT_VERSION" > version 167 | git add . 168 | git commit -m "version up to $SNAPSHOT_VERSION" 169 | git push origin main 170 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - 'version' 8 | pull_request: 9 | branches: 10 | - main 11 | paths-ignore: 12 | - 'version' 13 | schedule: 14 | - cron: '0 * * * *' 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | name: "Run lint" 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-java@v4 22 | with: 23 | distribution: temurin 24 | java-version: 11 25 | - uses: gradle/gradle-build-action@v3 26 | with: 27 | gradle-version: 8.3 28 | arguments: spotlessCheck --info 29 | test: 30 | runs-on: ubuntu-latest 31 | needs: lint 32 | env: 33 | JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 34 | JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 35 | strategy: 36 | matrix: 37 | java: [ 11, 17, 19 ] # LTS versions + latest version 38 | name: "Run tests with JDK ${{ matrix.java }}" 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: actions/setup-java@v4 42 | with: 43 | distribution: temurin 44 | java-version: ${{ matrix.java }} 45 | - uses: gradle/gradle-build-action@v3 46 | with: 47 | gradle-version: 8.3 48 | arguments: test --info 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - 'v[0-9]+.[0-9]+.[0-9]+' 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | env: 10 | JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 11 | JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 12 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 13 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 14 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 15 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | persist-credentials: false 21 | token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 22 | - uses: actions/setup-java@v4 23 | with: 24 | distribution: temurin 25 | java-version: 11 26 | - uses: gradle/gradle-build-action@v3 27 | with: 28 | gradle-version: 8.3 29 | arguments: publishToSonatype closeAndReleaseSonatypeStagingRepository --info --stacktrace -------------------------------------------------------------------------------- /.github/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Snapshot 2 | on: 3 | workflow_run: 4 | workflows: 5 | - CI 6 | types: 7 | - completed 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: false 11 | jobs: 12 | snapshot: 13 | runs-on: ubuntu-latest 14 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 15 | env: 16 | JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 17 | JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 18 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 19 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 20 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 21 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | ref: ${{ github.event.workflow_run.head_branch }} 26 | fetch-depth: 0 27 | - uses: actions/setup-java@v4 28 | with: 29 | distribution: temurin 30 | java-version: 11 31 | - uses: gradle/gradle-build-action@v3 32 | with: 33 | gradle-version: 8.3 34 | arguments: publishToSonatype --info --stacktrace 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file *.log 5 | 6 | # BlueJ files 7 | *.ctxt 8 | 9 | # Mobile Tools for Java (J2ME) 10 | .mtj.tmp/ 11 | 12 | # Package Files # 13 | *.jar 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | bin/ 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | replay_pid* 26 | 27 | .gradle/ 28 | /build/ 29 | 30 | .idea/ 31 | *.iml 32 | 33 | gradle-app.setting 34 | !gradle-wrapper.jar 35 | .gradletasknamecache 36 | 37 | .vscode/* 38 | !.vscode/settings.json 39 | 40 | .DS_Store 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[java]": { 3 | "spotlessGradle.format.enable": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.spotlessGradle": true 6 | } 7 | }, 8 | "java.configuration.updateBuildConfiguration": "automatic" 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Junichi Kato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # event-store-adapter-java 2 | 3 | [![CI](https://github.com/j5ik2o/event-store-adapter-java/actions/workflows/ci.yml/badge.svg)](https://github.com/j5ik2o/event-store-adapter-java/actions/workflows/ci.yml) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.j5ik2o/event-store-adapter-java/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.j5ik2o/event-store-adapter-java) 5 | [![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com) 6 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 7 | [![tokei](https://tokei.rs/b1/github/j5ik2o/event-store-adapter-java)](https://github.com/XAMPPRocky/tokei) 8 | 9 | このライブラリは、DynamoDBをCQRS/Event Sourcing用のEvent Storeにするためのものです。 10 | 11 | [English](./README.md) 12 | 13 | # 使い方 14 | 15 | EventStoreを使えば、Event Sourcing対応リポジトリを簡単に実装できます。 16 | 17 | ```java 18 | public final class UserAccountRepositoryAsync { 19 | 20 | @Nonnull private final EventStoreAsync eventStore; 21 | 22 | public UserAccountRepositoryAsync( 23 | @Nonnull EventStoreAsync eventStore) { 24 | this.eventStore = eventStore; 25 | } 26 | 27 | @Nonnull 28 | public CompletableFuture store(@Nonnull UserAccountEvent event, long version) { 29 | return eventStore.persistEvent(event, version); 30 | } 31 | 32 | @Nonnull 33 | public CompletableFuture store( 34 | @Nonnull UserAccountEvent event, @Nonnull UserAccount aggregate) { 35 | return eventStore.persistEventAndSnapshot(event, aggregate); 36 | } 37 | 38 | @Nonnull 39 | public CompletableFuture> findById(@Nonnull UserAccountId id) { 40 | return eventStore 41 | .getLatestSnapshotById(UserAccount.class, id) 42 | .thenCompose(result -> { 43 | if (result.isEmpty()) { 44 | return CompletableFuture.completedFuture(Optional.empty()); 45 | } else { 46 | return eventStore.getEventsByIdSinceSequenceNumber(UserAccountEvent.class, 47 | id, result.get().getAggregate().getSequenceNumber() + 1) 48 | .thenApply(events -> Optional.of(UserAccount.replay(events, result.get().getAggregate()))); 49 | } 50 | }); 51 | } 52 | } 53 | ``` 54 | 55 | 以下はリポジトリの使用例です。 56 | 57 | ```java 58 | var eventStore = EventStoreAsyncForDynamoDB.create( 59 | client, 60 | JOURNAL_TABLE_NAME, 61 | SNAPSHOT_TABLE_NAME, 62 | JOURNAL_AID_INDEX_NAME, 63 | SNAPSHOT_AID_INDEX_NAME, 64 | 32); 65 | var userAccountRepository = new UserAccountRepositoryAsync(eventStore); 66 | 67 | var id = new UserAccountId(IdGenerator.generate().toString()); 68 | var aggregateAndEvent1 = UserAccount.create(id, "test-1"); 69 | var aggregate1 = aggregateAndEvent1.getAggregate(); 70 | 71 | var result = userAccountRepository.store(aggregateAndEvent1.getEvent(), aggregate1) 72 | .thenCompose(r -> { 73 | var aggregateAndEvent2 = aggregate1.changeName("test-2"); 74 | return userAccountRepository.store( 75 | aggregateAndEvent2.getEvent(), aggregateAndEvent2.getAggregate().getVersion()); 76 | }).thenCompose(r -> userAccountRepository.findById(id)).join(); 77 | 78 | if (result.isPresent()) { 79 | assertEquals(result.get().getId(), aggregateAndEvent2.getAggregate().getId()); 80 | assertEquals(result.get().getName(), "test-2"); 81 | } else { 82 | fail("result is empty"); 83 | } 84 | ``` 85 | 86 | ## テーブル仕様 87 | 88 | [docs/DATABASE_SCHEMA.ja.md](docs/DATABASE_SCHEMA.ja.md)を参照してください。 89 | 90 | ## ライセンス 91 | 92 | MITライセンスです。詳細は[LICENSE](LICENSE)を参照してください。 93 | 94 | ## 他の言語のための実装 95 | 96 | - [for Java](https://github.com/j5ik2o/event-store-adapter-java) 97 | - [for Scala](https://github.com/j5ik2o/event-store-adapter-scala) 98 | - [for Kotlin](https://github.com/j5ik2o/event-store-adapter-kotlin) 99 | - [for Rust](https://github.com/j5ik2o/event-store-adapter-rs) 100 | - [for Go](https://github.com/j5ik2o/event-store-adapter-go) 101 | - [for JavaScript/TypeScript](https://github.com/j5ik2o/event-store-adapter-js) 102 | - [for .NET](https://github.com/j5ik2o/event-store-adapter-dotnet) 103 | - [for PHP](https://github.com/j5ik2o/event-store-adapter-php) 104 | 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # event-store-adapter-java 2 | 3 | [![CI](https://github.com/j5ik2o/event-store-adapter-java/actions/workflows/ci.yml/badge.svg)](https://github.com/j5ik2o/event-store-adapter-java/actions/workflows/ci.yml) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.j5ik2o/event-store-adapter-java/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.j5ik2o/event-store-adapter-java) 5 | [![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com) 6 | [![License](https://img.shields.io/badge/License-APACHE2.0-blue.svg)](https://opensource.org/licenses/apache-2-0) 7 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 8 | [![tokei](https://tokei.rs/b1/github/j5ik2o/event-store-adapter-java)](https://github.com/XAMPPRocky/tokei) 9 | 10 | This library is designed to turn DynamoDB into an Event Store for CQRS/Event Sourcing. 11 | 12 | [日本語](./README.ja.md) 13 | 14 | # Usage 15 | 16 | You can easily implement an Event Sourcing-enabled repository using EventStore. 17 | 18 | ```java 19 | public final class UserAccountRepositoryAsync { 20 | 21 | @Nonnull private final EventStoreAsync eventStore; 22 | 23 | public UserAccountRepositoryAsync( 24 | @Nonnull EventStoreAsync eventStore) { 25 | this.eventStore = eventStore; 26 | } 27 | 28 | @Nonnull 29 | public CompletableFuture store(@Nonnull UserAccountEvent event, long version) { 30 | return eventStore.persistEvent(event, version); 31 | } 32 | 33 | @Nonnull 34 | public CompletableFuture store( 35 | @Nonnull UserAccountEvent event, @Nonnull UserAccount aggregate) { 36 | return eventStore.persistEventAndSnapshot(event, aggregate); 37 | } 38 | 39 | @Nonnull 40 | public CompletableFuture> findById(@Nonnull UserAccountId id) { 41 | return eventStore 42 | .getLatestSnapshotById(UserAccount.class, id) 43 | .thenCompose(result -> { 44 | if (result.isEmpty()) { 45 | return CompletableFuture.completedFuture(Optional.empty()); 46 | } else { 47 | return eventStore.getEventsByIdSinceSequenceNumber(UserAccountEvent.class, 48 | id, result.get().getSequenceNumber() + 1) 49 | .thenApply(events -> Optional.of(UserAccount.replay(events, result.get()))); 50 | } 51 | }); 52 | } 53 | } 54 | ``` 55 | 56 | The following is an example of the repository usage. 57 | 58 | ```java 59 | var eventStore = EventStoreAsync.ofDynamoDB( 60 | client, 61 | JOURNAL_TABLE_NAME, 62 | SNAPSHOT_TABLE_NAME, 63 | JOURNAL_AID_INDEX_NAME, 64 | SNAPSHOT_AID_INDEX_NAME, 65 | 32); 66 | var userAccountRepository = new UserAccountRepositoryAsync(eventStore); 67 | 68 | var id = new UserAccountId(IdGenerator.generate().toString()); 69 | var aggregateAndEvent1 = UserAccount.create(id, "test-1"); 70 | var aggregate1 = aggregateAndEvent1.getAggregate(); 71 | 72 | var result = userAccountRepository.store(aggregateAndEvent1.getEvent(), aggregate1) 73 | .thenCompose(r -> { 74 | var aggregateAndEvent2 = aggregate1.changeName("test-2"); 75 | return userAccountRepository.store( 76 | aggregateAndEvent2.getEvent(), aggregateAndEvent2.getAggregate().getVersion()); 77 | }).thenCompose(r -> userAccountRepository.findById(id)).join(); 78 | 79 | if (result.isPresent()) { 80 | assertEquals(result.get().getId(), aggregateAndEvent2.getAggregate().getId()); 81 | assertEquals(result.get().getName(), "test-2"); 82 | } else { 83 | fail("result is empty"); 84 | } 85 | ``` 86 | 87 | ## Table Specifications 88 | 89 | See [docs/DATABASE_SCHEMA.md](docs/DATABASE_SCHEMA.md). 90 | 91 | ## License. 92 | 93 | MIT License. See [LICENSE](LICENSE) for details. 94 | 95 | ## Links 96 | 97 | - [Common Documents](https://github.com/j5ik2o/event-store-adapter) 98 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.api.tasks.testing.logging.TestExceptionFormat 2 | import org.gradle.api.tasks.testing.logging.TestLogEvent 3 | import org.gradle.kotlin.dsl.register 4 | 5 | plugins { 6 | `java-library` 7 | `maven-publish` 8 | signing 9 | id("com.diffplug.spotless") version "7.0.4" 10 | id("io.github.gradle-nexus.publish-plugin") version "2.0.0" 11 | } 12 | 13 | group = "io.github.j5ik2o" 14 | version = File("./version").readText().trim() 15 | extra["isReleaseVersion"] = !version.toString().endsWith("SNAPSHOT") 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.1") 23 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.1") 24 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 25 | 26 | testImplementation("ch.qos.logback:logback-classic:1.5.18") 27 | testImplementation("org.testcontainers:testcontainers:1.21.1") 28 | testImplementation("org.testcontainers:junit-jupiter:1.21.1") 29 | testImplementation("org.testcontainers:localstack:1.21.1") 30 | 31 | testImplementation("de.huxhorn.sulky:de.huxhorn.sulky.ulid:8.3.0") 32 | 33 | implementation("io.vavr:vavr:0.10.6") 34 | implementation("software.amazon.awssdk:dynamodb:2.31.62") 35 | implementation("com.fasterxml.jackson.core:jackson-databind:2.19.0") 36 | implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.19.0") 37 | implementation("org.slf4j:slf4j-api:1.7.36") 38 | implementation("com.google.code.findbugs:jsr305:3.0.2") 39 | } 40 | 41 | spotless { 42 | java { 43 | googleJavaFormat() 44 | } 45 | } 46 | 47 | repositories { 48 | mavenCentral() 49 | 50 | // 認証付き snapshot リポジトリ 51 | maven { 52 | name = "sonatypeSnapshots" 53 | url = uri("https://central.sonatype.com/repository/maven-snapshots/") 54 | credentials { 55 | username = System.getenv("SONATYPE_USERNAME") 56 | password = System.getenv("SONATYPE_PASSWORD") 57 | } 58 | mavenContent { snapshotsOnly() } 59 | } 60 | } 61 | 62 | java { 63 | sourceCompatibility = JavaVersion.VERSION_11 64 | targetCompatibility = JavaVersion.VERSION_11 65 | } 66 | 67 | tasks { 68 | 69 | withType { 70 | useJUnitPlatform() 71 | outputs.upToDateWhen { false } 72 | testLogging { 73 | showStandardStreams = true 74 | events = setOf(TestLogEvent.STARTED, TestLogEvent.SKIPPED, TestLogEvent.PASSED, TestLogEvent.FAILED) 75 | exceptionFormat = TestExceptionFormat.FULL 76 | } 77 | } 78 | 79 | this.register("javadocToDocsFolder") { 80 | from(javadoc) 81 | into("docs/javadoc") 82 | } 83 | 84 | assemble { 85 | dependsOn("javadocToDocsFolder") 86 | } 87 | 88 | this.register("sourcesJar") { 89 | from(sourceSets.main.get().allJava) 90 | archiveClassifier.set("sources") 91 | } 92 | 93 | this.register("javadocJar") { 94 | from(javadoc) 95 | archiveClassifier.set("javadoc") 96 | } 97 | 98 | withType { 99 | onlyIf { project.extra["isReleaseVersion"] as Boolean } 100 | } 101 | 102 | withType { 103 | gradleVersion = "8.14.2" 104 | } 105 | 106 | withType { 107 | options.encoding = "UTF-8" 108 | options.compilerArgs.add("-Xlint:deprecation") 109 | } 110 | } 111 | 112 | publishing { 113 | publications { 114 | create("mavenJava") { 115 | from(components["java"]) 116 | afterEvaluate { 117 | artifactId = tasks.jar.get().archiveBaseName.get() 118 | } 119 | artifact(tasks["sourcesJar"]) 120 | artifact(tasks["javadocJar"]) 121 | 122 | setVersion(project.version) 123 | pom { 124 | name.set(project.name) 125 | packaging = "jar" 126 | description.set("Event Store Adapter for Java") 127 | url.set("https://github.com/j5ik2o/event-store-adapter-java") 128 | licenses { 129 | license { 130 | name.set("The MIT License") 131 | url.set("https://opensource.org/license/mit/") 132 | } 133 | } 134 | developers { 135 | developer { 136 | id.set("j5ik2o") 137 | name.set("Junichi Kato") 138 | email.set("j5ik2o@gmail.com") 139 | } 140 | } 141 | scm { 142 | connection.set("scm:git:git@github.com:j5ik2o/event-store-adapter-java.git") 143 | developerConnection.set("scm:git:git@github.com:j5ik2o/event-store-adapter-java.git") 144 | url.set("https://github.com/j5ik2o/event-store-adapter-java") 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | nexusPublishing { 152 | this.repositories { 153 | this.sonatype { 154 | packageGroup = "io.github.j5ik2o" 155 | nexusUrl = uri("https://ossrh-staging-api.central.sonatype.com/service/local/") 156 | snapshotRepositoryUrl = uri("https://central.sonatype.com/repository/maven-snapshots/") 157 | username = System.getenv("SONATYPE_USERNAME") 158 | password = System.getenv("SONATYPE_PASSWORD") 159 | } 160 | } 161 | } 162 | 163 | signing { 164 | val signingKey = System.getenv("SIGNING_KEY") 165 | val signingPassword = System.getenv("SIGNING_PASSWORD") 166 | useInMemoryPgpKeys(signingKey, signingPassword) 167 | sign(publishing.publications["mavenJava"]) 168 | } 169 | 170 | -------------------------------------------------------------------------------- /docs/DATABASE_SCHEMA.ja.md: -------------------------------------------------------------------------------- 1 | ## EventStoreが利用するDynamoDBのテーブル構成 2 | 3 | - Journal 4 | - Snapshot 5 | 6 | いずれのテーブルも、キー設計の前提としては、論理シャード内で最大限に書き込みが分散させることを想定しています。 7 | 8 | ### Journalテーブル 9 | 10 | 集約で起きたイベントを保存するためのテーブル。原則的に、このイベントを使って集約状態を再生(リプレイ)します。 11 | 12 | | キー名 | 説明 | 具体的な値 | 備考 | 13 | |:------------|:--------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---| 14 | | pkey | パーションキー(集約種別名-hash(集約ID) % 論理シャードサイズ) | user-account-1 | | 15 | | skey | ソートキー(集約種別名-集約IDの値部分-シーケンス番号) | user-account-01H42K4ABWQ5V2XQEP3A48VE0Z-12345 | | 16 | | aid | 集約ID | user-account-01H42K4ABWQ5V2XQEP3A48VE0Z | | 17 | | ser_nr | シーケンス番号(開始番号は1) | 12345 | | 18 | | payload | イベント内容 | {"type":"Created","id":"01H42KBHCW1BZG504J4ZXKA2F2","aggregate_id":{"value":"01890535-c59c-72d5-08a8-dcea316374c8"},"seq_nr":1,"name":"test","members":{"members_ids_by_user_account_id":{"01H42KBHCWBDTZYQ7P78T8BTWX":"01H42KBHCWA8NE32M49YH544H1"},"members":{"01H42KBHCWA8NE32M49YH544H1":{"id":"01H42KBHCWA8NE32M49YH544H1","user_account_id":{"value":"01890535-c59c-5b75-ff5c-f63a3485eb9d"},"role":"Admin"}}},"occurred_at":"2023-06-29T03:32:37.404481Z"} | | 19 | | occurred_at | 発生日時 | 2023-06-29T03:32:37.404481Z | | 20 | 21 | aidとseq_nrはGSIが適用されており、リプレイ時はこのインデックスを利用されます。 22 | 23 | ### Snapshotテーブル 24 | 25 | 集約の状態を保存するためのテーブルであり、集約のリプレイを高速化するためのテーブルです。スナップショット保存後にもイベントは保存されるため、最新の集約状態を表さない場合あります。 26 | 27 | | キー名 | 説明 | 具体的な値 | 備考 | 28 | |:--------|:-----------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---| 29 | | pkey | パーションキー(集約種別名-hash(集約ID) % 論理シャードサイズ) | user-account-1 | | 30 | | skey | ソートキー(集約種別名-集約IDの値部分-シーケンス番号), 最新のスナップショットはシーケンス番号=0として読み書きされます。 | user-account-01H42K4ABWQ5V2XQEP3A48VE0Z-12345 | | 31 | | payload | 集約の状態 | {"id":{"value":"0189053a-d0b4-8f9b-4fb6-db91f72ccf16"},"name":"test","members":{"members_ids_by_user_account_id":{"01H42KNM5MBVRBZTADAZ9ETSPZ":"01H42KNM5M2QEW700VCW4J2KYE"},"members":{"01H42KNM5M2QEW700VCW4J2KYE":{"id":"01H42KNM5M2QEW700VCW4J2KYE","user_account_id":{"value":"0189053a-d0b4-5ef0-bfe9-4d57d2ed66df"},"role":"Admin"}}},"messages":[],"seq_nr_counter":1,"version":1} | | 32 | | aid | 集約ID | user-account-01H42K4ABWQ5V2XQEP3A48VE0Z | | 33 | | ser_nr | シーケンス番号(開始番号は1) | 12345 | | 34 | | ttl | 削除のためのTTL(秒) | 1624980000 | | 35 | | version | バージョン(楽観的ロック用)(開始番号は1) | 1 | | 36 | 37 | - スナップショット冗長化機能が無効な場合はskey=0にスナップショットが保存されるだけですが、有効にした場合はスナップショットはskey=0以外にskey=aggregate.seq_nr()の2件保存されます。スナップショットを保存するたびにskey=aggregate.seq_nr()のスナップショットが増えますが、あなたはスナップショットは上限を指定できます(デフォルトは1)。上限を超えた場合は古いスナップショットから削除されます。デフォルトでは、クライアント主導で削除されます。TTLを使ってDynamoDB自身に削除させることもできます。 38 | - aidとseq_nrはGSIが適用されており、リプレイ時はこのインデックスを利用されます。 39 | 40 | ### イベント及びスナップショットの書き込み 41 | 42 | 1. 集約にてコマンドが受理されると、最新のseq_nrが付与されたイベントが生成されます。 43 | 2. 生成されたイベントはjournalテーブルに書き込まれます。ただし、この書き込みは必ずsnapshotテーブルと同じトランザクションで行われ、versionが一致する条件下で実施されます。初回のイベント以外は、snapshotのpayloadを更新することはオプションです。 44 | 45 | ### イベント及びスナップショットを使って集約をリプレイする 46 | 47 | 1. 集約のIDを指定して、スナップショットを取得します。 48 | 2. 取得した集約のIDとスナップショットのシーケンス番号以降のイベントをjournalテーブルから読み込みます。 49 | 3. 読み込んだイベントをスナップショットに適用することで、最新の集約状態を取得します。 50 | -------------------------------------------------------------------------------- /docs/javadoc/allpackages-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | すべてのパッケージ (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 47 |
48 |
49 |
50 |

すべてのパッケージ

51 |
52 |
パッケージの概要
53 |
54 |
パッケージ
55 |
説明
56 | 57 |
 
58 | 59 |
 
60 |
61 |
62 |
63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/javadoc/com/github/j5ik2o/event/store/adapter/java/Aggregate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Aggregate (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 80 |
81 |
82 | 83 |
84 | 85 |

インタフェース Aggregate<This extends Aggregate<This,AID>,AID extends AggregateId>

86 |
87 |
88 |
89 |
型パラメータ:
90 |
This - Aggregate type / 集約の型
91 |
AID - Aggregate ID type / 集約IDの型
92 |
93 |
94 |
public interface Aggregate<This extends Aggregate<This,AID>,AID extends AggregateId>
95 |
This is an interface for representing aggregates. / 集約を表すためのインターフェース。
96 |
97 |
98 |
    99 | 100 |
  • 101 |
    102 |

    メソッドの概要

    103 |
    104 |
    105 |
    106 |
    107 |
    修飾子とタイプ
    108 |
    メソッド
    109 |
    説明
    110 | 111 | 112 |
    113 |
    Returns the aggregate ID. / 集約IDを返します。
    114 |
    115 |
    long
    116 | 117 |
    118 |
    Returns the sequence number. / シーケンス番号を返します。
    119 |
    120 |
    long
    121 | 122 |
    123 |
    Returns the version. / バージョンを返します。
    124 |
    125 | 126 |
    withVersion(long version)
    127 |
    128 |
    Sets the version. / バージョンを設定します。
    129 |
    130 |
    131 |
    132 |
    133 |
    134 |
  • 135 |
136 |
137 |
138 |
    139 | 140 |
  • 141 |
    142 |

    メソッドの詳細

    143 |
      144 |
    • 145 |
      146 |

      getId

      147 |
      @Nonnull 148 | AID getId()
      149 |
      Returns the aggregate ID. / 集約IDを返します。
      150 |
      151 |
      戻り値:
      152 |
      aggregate ID / 集約ID
      153 |
      154 |
      155 |
    • 156 |
    • 157 |
      158 |

      getSequenceNumber

      159 |
      long getSequenceNumber()
      160 |
      Returns the sequence number. / シーケンス番号を返します。
      161 |
      162 |
      戻り値:
      163 |
      sequence number / シーケンス番号
      164 |
      165 |
      166 |
    • 167 |
    • 168 |
      169 |

      getVersion

      170 |
      long getVersion()
      171 |
      Returns the version. / バージョンを返します。
      172 |
      173 |
      戻り値:
      174 |
      version / バージョン
      175 |
      176 |
      177 |
    • 178 |
    • 179 |
      180 |

      withVersion

      181 |
      This withVersion(long version)
      182 |
      Sets the version. / バージョンを設定します。
      183 |
      184 |
      パラメータ:
      185 |
      version - version / バージョン
      186 |
      戻り値:
      187 |
      this
      188 |
      189 |
      190 |
    • 191 |
    192 |
    193 |
  • 194 |
195 |
196 | 197 |
198 |
199 |
200 | 201 | 202 | -------------------------------------------------------------------------------- /docs/javadoc/com/github/j5ik2o/event/store/adapter/java/AggregateId.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AggregateId (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 80 |
81 |
82 | 83 |
84 | 85 |

インタフェース AggregateId

86 |
87 |
88 |
89 |
public interface AggregateId
90 |
This is an interface for representing aggregate IDs. / 集約IDを表すためのインターフェース。
91 |
92 |
93 |
    94 | 95 |
  • 96 |
    97 |

    メソッドの概要

    98 |
    99 |
    100 |
    101 |
    102 |
    修飾子とタイプ
    103 |
    メソッド
    104 |
    説明
    105 | 106 | 107 |
    108 |
    Returns the string representation. / 文字列表現を返します。
    109 |
    110 | 111 | 112 |
    113 |
    Returns the type name. / 型名を返します。
    114 |
    115 | 116 | 117 |
    118 |
    Returns the value. / 値を返します。
    119 |
    120 |
    121 |
    122 |
    123 |
    124 |
  • 125 |
126 |
127 |
128 |
    129 | 130 |
  • 131 |
    132 |

    メソッドの詳細

    133 |
      134 |
    • 135 |
      136 |

      getTypeName

      137 |
      @Nonnull 138 | String getTypeName()
      139 |
      Returns the type name. / 型名を返します。
      140 |
      141 |
      戻り値:
      142 |
      type name / 型名
      143 |
      144 |
      145 |
    • 146 |
    • 147 |
      148 |

      getValue

      149 |
      @Nonnull 150 | String getValue()
      151 |
      Returns the value. / 値を返します。
      152 |
      153 |
      戻り値:
      154 |
      value / 値
      155 |
      156 |
      157 |
    • 158 |
    • 159 |
      160 |

      asString

      161 |
      @Nonnull 162 | String asString()
      163 |
      Returns the string representation. / 文字列表現を返します。
      164 |
      165 |
      戻り値:
      166 |
      string representation / 文字列表現
      167 |
      168 |
      169 |
    • 170 |
    171 |
    172 |
  • 173 |
174 |
175 | 176 |
177 |
178 |
179 | 180 | 181 | -------------------------------------------------------------------------------- /docs/javadoc/com/github/j5ik2o/event/store/adapter/java/KeyResolver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | KeyResolver (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 80 |
81 |
82 | 83 |
84 | 85 |

インタフェース KeyResolver<AID extends AggregateId>

86 |
87 |
88 |
89 |
型パラメータ:
90 |
AID - Aggregate ID type / 集約IDの型
91 |
92 |
93 |
既知の実装クラスのリスト:
94 |
DefaultKeyResolver
95 |
96 |
97 |
public interface KeyResolver<AID extends AggregateId>
98 |
This is an interface for resolving partition keys and sort keys from aggregate IDs. / 99 | 集約IDからパーティションキーとソートキーを解決するためのインターフェース。
100 |
101 |
102 |
    103 | 104 |
  • 105 |
    106 |

    メソッドの概要

    107 |
    108 |
    109 |
    110 |
    111 |
    修飾子とタイプ
    112 |
    メソッド
    113 |
    説明
    114 | 115 |
    resolvePartitionKey(AID aggregateId, 116 | long shardCount)
    117 |
    118 |
    Resolves the partition key from the aggregate id. / 集約IDからパーティションキーを解決します。
    119 |
    120 | 121 |
    resolveSortKey(AID aggregateId, 122 | long sequenceNumber)
    123 |
    124 |
    Resolves the sort key from the aggregate id and sequence number. / 集約IDとシーケンス番号からソートキーを解決します。
    125 |
    126 |
    127 |
    128 |
    129 |
    130 |
  • 131 |
132 |
133 |
134 |
    135 | 136 |
  • 137 |
    138 |

    メソッドの詳細

    139 |
      140 |
    • 141 |
      142 |

      resolvePartitionKey

      143 |
      @Nonnull 144 | String resolvePartitionKey(@Nonnull 145 | AID aggregateId, 146 | long shardCount)
      147 |
      Resolves the partition key from the aggregate id. / 集約IDからパーティションキーを解決します。
      148 |
      149 |
      パラメータ:
      150 |
      aggregateId - aggregate id / 集約ID
      151 |
      shardCount - shard count / シャード数
      152 |
      戻り値:
      153 |
      partition key / パーティションキー
      154 |
      155 |
      156 |
    • 157 |
    • 158 |
      159 |

      resolveSortKey

      160 |
      @Nonnull 161 | String resolveSortKey(@Nonnull 162 | AID aggregateId, 163 | long sequenceNumber)
      164 |
      Resolves the sort key from the aggregate id and sequence number. / 集約IDとシーケンス番号からソートキーを解決します。
      165 |
      166 |
      パラメータ:
      167 |
      aggregateId - aggregate id / 集約ID
      168 |
      sequenceNumber - sequence number / シーケンス番号
      169 |
      戻り値:
      170 |
      sort key / ソートキー
      171 |
      172 |
      173 |
    • 174 |
    175 |
    176 |
  • 177 |
178 |
179 | 180 |
181 |
182 |
183 | 184 | 185 | -------------------------------------------------------------------------------- /docs/javadoc/com/github/j5ik2o/event/store/adapter/java/internal/package-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.github.j5ik2o.event.store.adapter.java.internal (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 64 |
65 |
66 |
67 |

パッケージ com.github.j5ik2o.event.store.adapter.java.internal

68 |
69 |
70 |
package com.github.j5ik2o.event.store.adapter.java.internal
71 |
72 | 98 |
99 |
100 |
101 |
102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/javadoc/com/github/j5ik2o/event/store/adapter/java/internal/package-tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.github.j5ik2o.event.store.adapter.java.internal クラス階層 (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 47 |
48 |
49 |
50 |

パッケージcom.github.j5ik2o.event.store.adapter.java.internalの階層

51 |
52 | パッケージ階層: 53 | 56 |
57 |

クラス階層

58 | 66 |
67 |
68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /docs/javadoc/com/github/j5ik2o/event/store/adapter/java/package-tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.github.j5ik2o.event.store.adapter.java クラス階層 (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 47 |
48 |
49 |
50 |

パッケージcom.github.j5ik2o.event.store.adapter.javaの階層

51 |
52 | パッケージ階層: 53 | 56 |
57 |

クラス階層

58 | 98 |
99 |
100 |

インタフェース階層

101 |
    102 |
  • com.github.j5ik2o.event.store.adapter.java.Aggregate<This,AID>
  • 103 |
  • com.github.j5ik2o.event.store.adapter.java.AggregateId
  • 104 |
  • com.github.j5ik2o.event.store.adapter.java.Event<AID>
  • 105 |
  • com.github.j5ik2o.event.store.adapter.java.EventSerializer<AID,E>
  • 106 |
  • com.github.j5ik2o.event.store.adapter.java.EventStoreOptions<This,AID,A,E> 107 |
      108 |
    • com.github.j5ik2o.event.store.adapter.java.EventStore<AID,A,E>
    • 109 |
    • com.github.j5ik2o.event.store.adapter.java.EventStoreAsync<AID,A,E>
    • 110 |
    111 |
  • 112 |
  • com.github.j5ik2o.event.store.adapter.java.KeyResolver<AID>
  • 113 |
  • com.github.j5ik2o.event.store.adapter.java.SnapshotSerializer<AID,A>
  • 114 |
115 |
116 |
117 |
118 |
119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/javadoc/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | 28 | 29 | 31 | 33 | 34 | -------------------------------------------------------------------------------- /docs/javadoc/element-list: -------------------------------------------------------------------------------- 1 | com.github.j5ik2o.event.store.adapter.java 2 | com.github.j5ik2o.event.store.adapter.java.internal 3 | -------------------------------------------------------------------------------- /docs/javadoc/help-doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | APIヘルプ (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 62 |
63 |
64 |

JavaDocヘルプ

65 | 85 |
86 |
87 |

ナビゲーション

88 | 概要ページから、各ページおよび各ページの上部にあるナビゲーション・バーでリンクを使用しているドキュメントを参照できます。 索引および検索ボックスを使用すると、次を含め、特定の宣言および概要ページにナビゲートできます: すべてのパッケージ, すべてのクラスおよびインタフェース 89 | 99 |
100 |
101 |
102 |

ページの種類

103 | 次の各セクションでは、このコレクションの様々な種類のページについて説明します。 104 |
105 |

概要

106 |

概要ページはAPIドキュメントのフロント・ページで、各パッケージの概要を含む全パッケージのリストが表示されます。一連のパッケージの概要説明も表示されます。

107 |
108 |
109 |

パッケージ

110 |

各パッケージには、そのパッケージのクラスおよびインタフェースのリストと、それぞれの概要を含むページがあります。これらのページは次のカテゴリで構成されます:

111 |
    112 |
  • インタフェース
  • 113 |
  • クラス
  • 114 |
  • 列挙クラス
  • 115 |
  • 例外クラス
  • 116 |
  • 注釈インタフェース
  • 117 |
118 |
119 |
120 |

クラスまたはインタフェース

121 |

各クラス、インタフェース、ネストされたクラスおよびネストされたインタフェースには個別のページがあります。各ページには次のように、宣言および説明と、メンバーの概要表およびメンバーの詳細説明から構成される3つのセクションが含まれます。これらの各セクションのエントリが空か適用できない場合は省略されます。

122 |
    123 |
  • クラス階層表示
  • 124 |
  • 直系のサブクラス
  • 125 |
  • 既知のサブインタフェースのリスト
  • 126 |
  • 既知の実装クラスのリスト
  • 127 |
  • クラスまたはインタフェースの宣言
  • 128 |
  • クラスまたはインタフェースの説明
  • 129 |
130 |
131 |
    132 |
  • ネストされたクラスの概要
  • 133 |
  • 列挙型定数の概要
  • 134 |
  • フィールドの概要
  • 135 |
  • プロパティの概要
  • 136 |
  • コンストラクタの概要
  • 137 |
  • メソッドの概要
  • 138 |
  • 必須要素の概要
  • 139 |
  • 任意要素の概要
  • 140 |
141 |
142 |
    143 |
  • 列挙型定数の詳細
  • 144 |
  • フィールド詳細
  • 145 |
  • プロパティの詳細
  • 146 |
  • コンストラクタの詳細
  • 147 |
  • メソッドの詳細
  • 148 |
  • 要素の詳細
  • 149 |
150 |

ノート: 注釈インタフェースには、必須またはオプションの要素が含まれており、メソッドは含まれていません。 列挙クラスのみに列挙定数があります。 レコード・クラスのコンポーネントがレコード・クラスの宣言の一部として表示されます。 プロパティはJavaFXの機能です。

151 |

詳細な説明はソース・コードに現れる順に並べられますが、概要エントリはアルファベット順に並べられます。これによって、プログラマが設定した論理的なグループ分けが保持されます。

152 |
153 |
154 |

その他のファイル

155 |

パッケージおよびモジュールには、周辺の宣言に関連する追加情報が記載されているページが含まれている場合があります。

156 |
157 |
158 |

階層ツリー(クラス階層)

159 |

すべてのパッケージにはクラス階層ページがあり、さらに各パッケージの階層があります。各階層ページは、クラスのリストとインタフェースのリストを含みます。クラスはjava.lang.Objectを開始点とする継承構造で編成されます。インタフェースは、java.lang.Objectからは継承しません。

160 |
    161 |
  • 概要ページを表示しているときに「階層ツリー」をクリックすると、全パッケージの階層が表示されます。
  • 162 |
  • 特定のパッケージ、クラスまたはインタフェースのページを表示しているときに「階層ツリー」をクリックすると、該当するパッケージのみの階層が表示されます。
  • 163 |
164 |
165 |
166 |

直列化された形式

167 |

直列化可能または外部化可能な各クラスは、直列化フィールドとメソッドの説明を含みます。この情報は、APIを使用するのではなく、実装するユーザーに役立ちます。ナビゲーション・バーにリンクがない場合、直列化されたクラスに移動して、クラス記述の「関連項目」セクションにある「直列化された形式」をクリックすることにより、この情報を表示できます。

168 |
169 |
170 |

すべてのパッケージ

171 |

すべてのパッケージページには、ドキュメントに含まれるすべてのパッケージのアルファベット順の索引が含まれます。

172 |
173 |
174 |

すべてのクラスおよびインタフェース

175 |

すべてのクラスおよびインタフェースには、注釈インタフェース、列挙クラスおよびレコード・クラスなど、ドキュメントに含まれるすべてのクラスおよびインタフェースのアルファベット順の索引が含まれます。

176 |
177 |
178 |

索引

179 |

索引には、ドキュメント内のすべてのクラス、インタフェース、コンストラクタ、メソッドおよびフィールドのアルファベット順の索引と、すべてのパッケージ, すべてのクラスおよびインタフェースなどの概要ページが含まれます。

180 |
181 |
182 |
183 | このヘルプ・ファイルは、標準docletによって生成されたAPIドキュメントに適用されます。
184 |
185 |
186 | 187 | 188 | -------------------------------------------------------------------------------- /docs/javadoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 概要 (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 47 |
48 |
49 |
50 |

event-store-adapter-java 1.1.268-SNAPSHOT API

51 |
52 |
53 |
パッケージ
54 |
55 |
パッケージ
56 |
説明
57 | 58 |
 
59 | 60 |
 
61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/javadoc/legal/ADDITIONAL_LICENSE_INFO: -------------------------------------------------------------------------------- 1 | ADDITIONAL INFORMATION ABOUT LICENSING 2 | 3 | Certain files distributed by Oracle America, Inc. and/or its affiliates are 4 | subject to the following clarification and special exception to the GPLv2, 5 | based on the GNU Project exception for its Classpath libraries, known as the 6 | GNU Classpath Exception. 7 | 8 | Note that Oracle includes multiple, independent programs in this software 9 | package. Some of those programs are provided under licenses deemed 10 | incompatible with the GPLv2 by the Free Software Foundation and others. 11 | For example, the package includes programs licensed under the Apache 12 | License, Version 2.0 and may include FreeType. Such programs are licensed 13 | to you under their original licenses. 14 | 15 | Oracle facilitates your further distribution of this package by adding the 16 | Classpath Exception to the necessary parts of its GPLv2 code, which permits 17 | you to use that code in combination with other independent modules not 18 | licensed under the GPLv2. However, note that this would not permit you to 19 | commingle code under an incompatible license with Oracle's GPLv2 licensed 20 | code by, for example, cutting and pasting such code into a file also 21 | containing Oracle's GPLv2 licensed code and then distributing the result. 22 | 23 | Additionally, if you were to remove the Classpath Exception from any of the 24 | files to which it applies and distribute the result, you would likely be 25 | required to license some or all of the other code in that distribution under 26 | the GPLv2 as well, and since the GPLv2 is incompatible with the license terms 27 | of some items included in the distribution by Oracle, removing the Classpath 28 | Exception could therefore effectively compromise your ability to further 29 | distribute the package. 30 | 31 | Failing to distribute notices associated with some files may also create 32 | unexpected legal consequences. 33 | 34 | Proceed with caution and we recommend that you obtain the advice of a lawyer 35 | skilled in open source matters before removing the Classpath Exception or 36 | making modifications to this package which may subsequently be redistributed 37 | and/or involve the use of third party software. 38 | -------------------------------------------------------------------------------- /docs/javadoc/legal/ASSEMBLY_EXCEPTION: -------------------------------------------------------------------------------- 1 | 2 | OPENJDK ASSEMBLY EXCEPTION 3 | 4 | The OpenJDK source code made available by Oracle America, Inc. (Oracle) at 5 | openjdk.org ("OpenJDK Code") is distributed under the terms of the GNU 6 | General Public License version 2 7 | only ("GPL2"), with the following clarification and special exception. 8 | 9 | Linking this OpenJDK Code statically or dynamically with other code 10 | is making a combined work based on this library. Thus, the terms 11 | and conditions of GPL2 cover the whole combination. 12 | 13 | As a special exception, Oracle gives you permission to link this 14 | OpenJDK Code with certain code licensed by Oracle as indicated at 15 | https://openjdk.org/legal/exception-modules-2007-05-08.html 16 | ("Designated Exception Modules") to produce an executable, 17 | regardless of the license terms of the Designated Exception Modules, 18 | and to copy and distribute the resulting executable under GPL2, 19 | provided that the Designated Exception Modules continue to be 20 | governed by the licenses under which they were offered by Oracle. 21 | 22 | As such, it allows licensees and sublicensees of Oracle's GPL2 OpenJDK Code 23 | to build an executable that includes those portions of necessary code that 24 | Oracle could not provide under GPL2 (or that Oracle has provided under GPL2 25 | with the Classpath exception). If you modify or add to the OpenJDK code, 26 | that new GPL2 code may still be combined with Designated Exception Modules 27 | if the new code is made subject to this exception by its copyright holder. 28 | -------------------------------------------------------------------------------- /docs/javadoc/legal/jquery.md: -------------------------------------------------------------------------------- 1 | ## jQuery v3.7.1 2 | 3 | ### jQuery License 4 | ``` 5 | jQuery v 3.7.1 6 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/javadoc/legal/jqueryUI.md: -------------------------------------------------------------------------------- 1 | ## jQuery UI v1.13.2 2 | 3 | ### jQuery UI License 4 | ``` 5 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 6 | 7 | This software consists of voluntary contributions made by many 8 | individuals. For exact contribution history, see the revision history 9 | available at https://github.com/jquery/jquery-ui 10 | 11 | The following license applies to all parts of this software except as 12 | documented below: 13 | 14 | ==== 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining 17 | a copy of this software and associated documentation files (the 18 | "Software"), to deal in the Software without restriction, including 19 | without limitation the rights to use, copy, modify, merge, publish, 20 | distribute, sublicense, and/or sell copies of the Software, and to 21 | permit persons to whom the Software is furnished to do so, subject to 22 | the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 29 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 31 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 32 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 33 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 34 | 35 | ==== 36 | 37 | Copyright and related rights for sample code are waived via CC0. Sample 38 | code is defined as all source code contained within the demos directory. 39 | 40 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 41 | 42 | ==== 43 | 44 | All files located in the node_modules and external directories are 45 | externally maintained libraries used by this software which have their 46 | own licenses; we recommend you read them, as their terms may differ from 47 | the terms above. 48 | 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/javadoc/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/javadoc/module-search-index.js: -------------------------------------------------------------------------------- 1 | moduleSearchIndex = [];updateSearchResults(); -------------------------------------------------------------------------------- /docs/javadoc/overview-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | event-store-adapter-java 1.1.268-SNAPSHOT API 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 |
19 | 22 |

index.html

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/javadoc/package-search-index.js: -------------------------------------------------------------------------------- 1 | packageSearchIndex = [{"l":"com.github.j5ik2o.event.store.adapter.java"},{"l":"com.github.j5ik2o.event.store.adapter.java.internal"},{"l":"すべてのパッケージ","u":"allpackages-index.html"}];updateSearchResults(); -------------------------------------------------------------------------------- /docs/javadoc/resources/glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/event-store-adapter-java/32d8ba12ebedcd19a1e2d6e997967601be243c19/docs/javadoc/resources/glass.png -------------------------------------------------------------------------------- /docs/javadoc/resources/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/event-store-adapter-java/32d8ba12ebedcd19a1e2d6e997967601be243c19/docs/javadoc/resources/x.png -------------------------------------------------------------------------------- /docs/javadoc/script-dir/jquery-ui.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.13.2 - 2023-02-27 2 | * http://jqueryui.com 3 | * Includes: core.css, autocomplete.css, menu.css 4 | * Copyright jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;-ms-filter:"alpha(opacity=0)"}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0} -------------------------------------------------------------------------------- /docs/javadoc/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 | * 5 | * This code is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License version 2 only, as 7 | * published by the Free Software Foundation. Oracle designates this 8 | * particular file as subject to the "Classpath" exception as provided 9 | * by Oracle in the LICENSE file that accompanied this code. 10 | * 11 | * This code is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 | * version 2 for more details (a copy is included in the LICENSE file that 15 | * accompanied this code). 16 | * 17 | * You should have received a copy of the GNU General Public License version 18 | * 2 along with this work; if not, write to the Free Software Foundation, 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 | * 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 | * or visit www.oracle.com if you need additional information or have any 23 | * questions. 24 | */ 25 | 26 | var moduleSearchIndex; 27 | var packageSearchIndex; 28 | var typeSearchIndex; 29 | var memberSearchIndex; 30 | var tagSearchIndex; 31 | 32 | var oddRowColor = "odd-row-color"; 33 | var evenRowColor = "even-row-color"; 34 | var sortAsc = "sort-asc"; 35 | var sortDesc = "sort-desc"; 36 | var tableTab = "table-tab"; 37 | var activeTableTab = "active-table-tab"; 38 | 39 | function loadScripts(doc, tag) { 40 | createElem(doc, tag, 'search.js'); 41 | 42 | createElem(doc, tag, 'module-search-index.js'); 43 | createElem(doc, tag, 'package-search-index.js'); 44 | createElem(doc, tag, 'type-search-index.js'); 45 | createElem(doc, tag, 'member-search-index.js'); 46 | createElem(doc, tag, 'tag-search-index.js'); 47 | } 48 | 49 | function createElem(doc, tag, path) { 50 | var script = doc.createElement(tag); 51 | var scriptElement = doc.getElementsByTagName(tag)[0]; 52 | script.src = pathtoroot + path; 53 | scriptElement.parentNode.insertBefore(script, scriptElement); 54 | } 55 | 56 | // Helper for making content containing release names comparable lexicographically 57 | function makeComparable(s) { 58 | return s.toLowerCase().replace(/(\d+)/g, 59 | function(n, m) { 60 | return ("000" + m).slice(-4); 61 | }); 62 | } 63 | 64 | // Switches between two styles depending on a condition 65 | function toggleStyle(classList, condition, trueStyle, falseStyle) { 66 | if (condition) { 67 | classList.remove(falseStyle); 68 | classList.add(trueStyle); 69 | } else { 70 | classList.remove(trueStyle); 71 | classList.add(falseStyle); 72 | } 73 | } 74 | 75 | // Sorts the rows in a table lexicographically by the content of a specific column 76 | function sortTable(header, columnIndex, columns) { 77 | var container = header.parentElement; 78 | var descending = header.classList.contains(sortAsc); 79 | container.querySelectorAll("div.table-header").forEach( 80 | function(header) { 81 | header.classList.remove(sortAsc); 82 | header.classList.remove(sortDesc); 83 | } 84 | ) 85 | var cells = container.children; 86 | var rows = []; 87 | for (var i = columns; i < cells.length; i += columns) { 88 | rows.push(Array.prototype.slice.call(cells, i, i + columns)); 89 | } 90 | var comparator = function(a, b) { 91 | var ka = makeComparable(a[columnIndex].textContent); 92 | var kb = makeComparable(b[columnIndex].textContent); 93 | if (ka < kb) 94 | return descending ? 1 : -1; 95 | if (ka > kb) 96 | return descending ? -1 : 1; 97 | return 0; 98 | }; 99 | var sorted = rows.sort(comparator); 100 | var visible = 0; 101 | sorted.forEach(function(row) { 102 | if (row[0].style.display !== 'none') { 103 | var isEvenRow = visible++ % 2 === 0; 104 | } 105 | row.forEach(function(cell) { 106 | toggleStyle(cell.classList, isEvenRow, evenRowColor, oddRowColor); 107 | container.appendChild(cell); 108 | }) 109 | }); 110 | toggleStyle(header.classList, descending, sortDesc, sortAsc); 111 | } 112 | 113 | // Toggles the visibility of a table category in all tables in a page 114 | function toggleGlobal(checkbox, selected, columns) { 115 | var display = checkbox.checked ? '' : 'none'; 116 | document.querySelectorAll("div.table-tabs").forEach(function(t) { 117 | var id = t.parentElement.getAttribute("id"); 118 | var selectedClass = id + "-tab" + selected; 119 | // if selected is empty string it selects all uncategorized entries 120 | var selectUncategorized = !Boolean(selected); 121 | var visible = 0; 122 | document.querySelectorAll('div.' + id) 123 | .forEach(function(elem) { 124 | if (selectUncategorized) { 125 | if (elem.className.indexOf(selectedClass) === -1) { 126 | elem.style.display = display; 127 | } 128 | } else if (elem.classList.contains(selectedClass)) { 129 | elem.style.display = display; 130 | } 131 | if (elem.style.display === '') { 132 | var isEvenRow = visible++ % (columns * 2) < columns; 133 | toggleStyle(elem.classList, isEvenRow, evenRowColor, oddRowColor); 134 | } 135 | }); 136 | var displaySection = visible === 0 ? 'none' : ''; 137 | t.parentElement.style.display = displaySection; 138 | document.querySelector("li#contents-" + id).style.display = displaySection; 139 | }) 140 | } 141 | 142 | // Shows the elements of a table belonging to a specific category 143 | function show(tableId, selected, columns) { 144 | if (tableId !== selected) { 145 | document.querySelectorAll('div.' + tableId + ':not(.' + selected + ')') 146 | .forEach(function(elem) { 147 | elem.style.display = 'none'; 148 | }); 149 | } 150 | document.querySelectorAll('div.' + selected) 151 | .forEach(function(elem, index) { 152 | elem.style.display = ''; 153 | var isEvenRow = index % (columns * 2) < columns; 154 | toggleStyle(elem.classList, isEvenRow, evenRowColor, oddRowColor); 155 | }); 156 | updateTabs(tableId, selected); 157 | } 158 | 159 | function updateTabs(tableId, selected) { 160 | document.getElementById(tableId + '.tabpanel') 161 | .setAttribute('aria-labelledby', selected); 162 | document.querySelectorAll('button[id^="' + tableId + '"]') 163 | .forEach(function(tab, index) { 164 | if (selected === tab.id || (tableId === selected && index === 0)) { 165 | tab.className = activeTableTab; 166 | tab.setAttribute('aria-selected', true); 167 | tab.setAttribute('tabindex',0); 168 | } else { 169 | tab.className = tableTab; 170 | tab.setAttribute('aria-selected', false); 171 | tab.setAttribute('tabindex',-1); 172 | } 173 | }); 174 | } 175 | 176 | function switchTab(e) { 177 | var selected = document.querySelector('[aria-selected=true]'); 178 | if (selected) { 179 | if ((e.keyCode === 37 || e.keyCode === 38) && selected.previousSibling) { 180 | // left or up arrow key pressed: move focus to previous tab 181 | selected.previousSibling.click(); 182 | selected.previousSibling.focus(); 183 | e.preventDefault(); 184 | } else if ((e.keyCode === 39 || e.keyCode === 40) && selected.nextSibling) { 185 | // right or down arrow key pressed: move focus to next tab 186 | selected.nextSibling.click(); 187 | selected.nextSibling.focus(); 188 | e.preventDefault(); 189 | } 190 | } 191 | } 192 | 193 | var updateSearchResults = function() {}; 194 | 195 | function indexFilesLoaded() { 196 | return moduleSearchIndex 197 | && packageSearchIndex 198 | && typeSearchIndex 199 | && memberSearchIndex 200 | && tagSearchIndex; 201 | } 202 | // Copy the contents of the local snippet to the clipboard 203 | function copySnippet(button) { 204 | copyToClipboard(button.nextElementSibling.innerText); 205 | switchCopyLabel(button, button.firstElementChild); 206 | } 207 | function copyToClipboard(content) { 208 | var textarea = document.createElement("textarea"); 209 | textarea.style.height = 0; 210 | document.body.appendChild(textarea); 211 | textarea.value = content; 212 | textarea.select(); 213 | document.execCommand("copy"); 214 | document.body.removeChild(textarea); 215 | } 216 | function switchCopyLabel(button, span) { 217 | var copied = span.getAttribute("data-copied"); 218 | button.classList.add("visible"); 219 | var initialLabel = span.innerHTML; 220 | span.innerHTML = copied; 221 | setTimeout(function() { 222 | button.classList.remove("visible"); 223 | setTimeout(function() { 224 | if (initialLabel !== copied) { 225 | span.innerHTML = initialLabel; 226 | } 227 | }, 100); 228 | }, 1900); 229 | } 230 | // Workaround for scroll position not being included in browser history (8249133) 231 | document.addEventListener("DOMContentLoaded", function(e) { 232 | var contentDiv = document.querySelector("div.flex-content"); 233 | window.addEventListener("popstate", function(e) { 234 | if (e.state !== null) { 235 | contentDiv.scrollTop = e.state; 236 | } 237 | }); 238 | window.addEventListener("hashchange", function(e) { 239 | history.replaceState(contentDiv.scrollTop, document.title); 240 | }); 241 | var timeoutId; 242 | contentDiv.addEventListener("scroll", function(e) { 243 | if (timeoutId) { 244 | clearTimeout(timeoutId); 245 | } 246 | timeoutId = setTimeout(function() { 247 | history.replaceState(contentDiv.scrollTop, document.title); 248 | }, 100); 249 | }); 250 | if (!location.hash) { 251 | history.replaceState(contentDiv.scrollTop, document.title); 252 | } 253 | }); 254 | -------------------------------------------------------------------------------- /docs/javadoc/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 検索 (event-store-adapter-java 1.1.268-SNAPSHOT API) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 22 |
23 | 43 |
44 |
45 |

検索

46 |
47 | 48 | 49 |
50 | その他のリソース 51 |
52 |
53 |
54 |

ヘルプ・ページでは、JavaDoc検索の範囲および構文の概要について説明します。

55 |

<ctrl>または<cmd>キーを左右の矢印キーと組み合せて使用すると、このページの結果タブを切り替えることができます。

56 |

次のURLテンプレートは、この機能をサポートするブラウザでこのページを検索エンジンとして構成するために使用できます。Google ChromeおよびMozilla Firefoxで動作することがテストされています。他のブラウザでは、この機能がサポートされていないか、別のURL形式が必要になる場合があります。

57 | link 58 |

59 | 60 |

61 |
62 |

検索索引をロード中...

63 | 67 |
68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /docs/javadoc/tag-search-index.js: -------------------------------------------------------------------------------- 1 | tagSearchIndex = [{"l":"直列化された形式","h":"","u":"serialized-form.html"}];updateSearchResults(); -------------------------------------------------------------------------------- /docs/javadoc/type-search-index.js: -------------------------------------------------------------------------------- 1 | typeSearchIndex = [{"p":"com.github.j5ik2o.event.store.adapter.java","l":"Aggregate"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"AggregateAndEvent"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"AggregateId"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"DefaultEventSerializer"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"DefaultKeyResolver"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"DefaultSnapshotSerializer"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"DeserializationException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"DeserializationRuntimeException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"Event"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventSerializer"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStore"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreAsync"},{"p":"com.github.j5ik2o.event.store.adapter.java.internal","l":"EventStoreAsyncForDynamoDB"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreBaseException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreBaseRuntimeException"},{"p":"com.github.j5ik2o.event.store.adapter.java.internal","l":"EventStoreForDynamoDB"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreOptions"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreReadException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreReadRuntimeException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreWriteException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"EventStoreWriteRuntimeException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"KeyResolver"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"OptimisticLockException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"OptimisticLockRuntimeException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"SerializationException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"SerializationRuntimeException"},{"p":"com.github.j5ik2o.event.store.adapter.java","l":"SnapshotSerializer"},{"l":"すべてのクラスおよびインタフェース","u":"allclasses-index.html"}];updateSearchResults(); -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/event-store-adapter-java/32d8ba12ebedcd19a1e2d6e997967601be243c19/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended"], 4 | "labels": ["renovate"], 5 | "commitMessagePrefix": "chore(deps):", 6 | "platformAutomerge": true, 7 | "packageRules": [ 8 | { 9 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 10 | "automerge": true 11 | }, 12 | { 13 | "matchDepTypes": ["devDependencies"], 14 | "automerge": true 15 | } 16 | ], 17 | "prHourlyLimit": 0, 18 | "prConcurrentLimit": 5 19 | } 20 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "event-store-adapter-java" 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/Aggregate.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This is an interface for representing aggregates. / 集約を表すためのインターフェース。 7 | * 8 | * @param Aggregate type / 集約の型 9 | * @param Aggregate ID type / 集約IDの型 10 | */ 11 | public interface Aggregate, AID extends AggregateId> { 12 | /** 13 | * Returns the aggregate ID. / 集約IDを返します。 14 | * 15 | * @return aggregate ID / 集約ID 16 | */ 17 | @Nonnull 18 | AID getId(); 19 | 20 | /** 21 | * Returns the sequence number. / シーケンス番号を返します。 22 | * 23 | * @return sequence number / シーケンス番号 24 | */ 25 | long getSequenceNumber(); 26 | 27 | /** 28 | * Returns the version. / バージョンを返します。 29 | * 30 | * @return version / バージョン 31 | */ 32 | long getVersion(); 33 | 34 | /** 35 | * Sets the version. / バージョンを設定します。 36 | * 37 | * @param version version / バージョン 38 | * @return this 39 | */ 40 | This withVersion(long version); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/AggregateAndEvent.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This is a class for representing aggregates and events. / 集約とイベントを表すためのクラスです。 7 | * 8 | * @param Aggregate ID type / 集約IDの型 9 | * @param Aggregate type / 集約の型 10 | * @param Event type / イベントの型 11 | */ 12 | public final class AggregateAndEvent< 13 | AID extends AggregateId, A extends Aggregate, E extends Event> { 14 | @Nonnull private final A aggregate; 15 | @Nonnull private final E event; 16 | 17 | /** 18 | * Creates a new instance. / 新しいインスタンスを生成します。 19 | * 20 | * @param aggregate aggregate / 集約 21 | * @param event event / イベント 22 | */ 23 | public AggregateAndEvent(@Nonnull A aggregate, @Nonnull E event) { 24 | this.aggregate = aggregate; 25 | this.event = event; 26 | } 27 | 28 | /** 29 | * Returns the aggregate. / 集約を返します。 30 | * 31 | * @return aggregate / 集約 32 | */ 33 | @Nonnull 34 | public A getAggregate() { 35 | return aggregate; 36 | } 37 | 38 | /** 39 | * Returns the event. / イベントを返します。 40 | * 41 | * @return event / イベント 42 | */ 43 | @Nonnull 44 | public E getEvent() { 45 | return event; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/AggregateId.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** This is an interface for representing aggregate IDs. / 集約IDを表すためのインターフェース。 */ 6 | public interface AggregateId { 7 | /** 8 | * Returns the type name. / 型名を返します。 9 | * 10 | * @return type name / 型名 11 | */ 12 | @Nonnull 13 | String getTypeName(); 14 | 15 | /** 16 | * Returns the value. / 値を返します。 17 | * 18 | * @return value / 値 19 | */ 20 | @Nonnull 21 | String getValue(); 22 | 23 | /** 24 | * Returns the string representation. / 文字列表現を返します。 25 | * 26 | * @return string representation / 文字列表現 27 | */ 28 | @Nonnull 29 | String asString(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/DefaultEventSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import java.io.IOException; 6 | import javax.annotation.Nonnull; 7 | 8 | public final class DefaultEventSerializer> 9 | implements EventSerializer { 10 | private static final ObjectMapper objectMapper = new ObjectMapper(); 11 | 12 | static { 13 | objectMapper.findAndRegisterModules(); 14 | } 15 | 16 | public DefaultEventSerializer() {} 17 | 18 | @Nonnull 19 | @Override 20 | public byte[] serialize(@Nonnull E event) throws SerializationException { 21 | try { 22 | return objectMapper.writeValueAsBytes(event); 23 | } catch (JsonProcessingException e) { 24 | throw new SerializationException(e); 25 | } 26 | } 27 | 28 | @Nonnull 29 | @Override 30 | public E deserialize(@Nonnull byte[] bytes, @Nonnull Class clazz) 31 | throws DeserializationException { 32 | try { 33 | return objectMapper.readValue(bytes, clazz); 34 | } catch (IOException e) { 35 | throw new DeserializationException(e); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/DefaultKeyResolver.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | public class DefaultKeyResolver implements KeyResolver { 6 | @Nonnull 7 | @Override 8 | public String resolvePartitionKey(@Nonnull AID aggregateId, long shardCount) { 9 | var remainder = Math.abs(aggregateId.getValue().hashCode()) % shardCount; 10 | return String.format("%s-%d", aggregateId.getTypeName(), remainder); 11 | } 12 | 13 | @Nonnull 14 | @Override 15 | public String resolveSortKey(@Nonnull AID aggregateId, long sequenceNumber) { 16 | return String.format( 17 | "%s-%s-%d", aggregateId.getTypeName(), aggregateId.getValue(), sequenceNumber); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/DefaultSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import java.io.IOException; 6 | import javax.annotation.Nonnull; 7 | 8 | public final class DefaultSnapshotSerializer> 9 | implements SnapshotSerializer { 10 | private static final ObjectMapper objectMapper = new ObjectMapper(); 11 | 12 | static { 13 | objectMapper.findAndRegisterModules(); 14 | } 15 | 16 | public DefaultSnapshotSerializer() {} 17 | 18 | @Nonnull 19 | @Override 20 | public byte[] serialize(@Nonnull A snapshot) throws SerializationException { 21 | try { 22 | return objectMapper.writeValueAsBytes(snapshot); 23 | } catch (JsonProcessingException e) { 24 | throw new SerializationException(e); 25 | } 26 | } 27 | 28 | @Nonnull 29 | @Override 30 | public A deserialize(@Nonnull byte[] bytes, @Nonnull Class clazz) 31 | throws DeserializationException { 32 | try { 33 | return objectMapper.readValue(bytes, clazz); 34 | } catch (IOException e) { 35 | throw new DeserializationException(e); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/DeserializationException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class DeserializationException extends EventStoreBaseException { 4 | public DeserializationException() { 5 | super(); 6 | } 7 | 8 | public DeserializationException(String message) { 9 | super(message); 10 | } 11 | 12 | public DeserializationException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public DeserializationException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/DeserializationRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class DeserializationRuntimeException extends EventStoreBaseRuntimeException { 4 | public DeserializationRuntimeException() { 5 | super(); 6 | } 7 | 8 | public DeserializationRuntimeException(String message) { 9 | super(message); 10 | } 11 | 12 | public DeserializationRuntimeException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public DeserializationRuntimeException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/Event.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import java.time.Instant; 4 | import javax.annotation.Nonnull; 5 | 6 | /** 7 | * This is an interface for representing events. / イベントを表すためのインターフェース。 8 | * 9 | * @param Aggregate ID type / 集約IDの型 10 | */ 11 | public interface Event { 12 | /** 13 | * Returns the ID. / IDを返します。 14 | * 15 | * @return ID / ID 16 | */ 17 | @Nonnull 18 | String getId(); 19 | 20 | /** 21 | * Returns the aggregate ID. / 集約IDを返します。 22 | * 23 | * @return aggregate ID / 集約ID 24 | */ 25 | @Nonnull 26 | AID getAggregateId(); 27 | 28 | /** 29 | * Returns the sequence number. / シーケンス番号を返します。 30 | * 31 | * @return sequence number / シーケンス番号 32 | */ 33 | long getSequenceNumber(); 34 | 35 | /** 36 | * Returns the occurred at. / 発生日時を返します。 37 | * 38 | * @return occurred at / 発生日時 39 | */ 40 | @Nonnull 41 | Instant getOccurredAt(); 42 | 43 | /** 44 | * Determines whether it is a generated event. / 生成型のイベントであるかどうかを判定します。 45 | * 46 | * @return true if it is a generated event / 生成型のイベントである場合はtrue 47 | */ 48 | boolean isCreated(); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This is an interface for serializing and deserializing events. / 7 | * イベントのシリアライズとデシリアライズを行うためのインターフェース。 8 | * 9 | * @param Aggregate ID type / 集約IDの型 10 | * @param Event type / イベントの型 11 | */ 12 | public interface EventSerializer> { 13 | /** 14 | * Serializes the event. / イベントをシリアライズします。 15 | * 16 | * @param event event / イベント 17 | * @return serialized event / シリアライズされたイベント 18 | * @throws SerializationException if an error occurred during serialization / シリアライズ中にエラーが発生した場合 19 | */ 20 | @Nonnull 21 | byte[] serialize(@Nonnull E event) throws SerializationException; 22 | 23 | /** 24 | * Deserializes the event. / イベントをデシリアライズします。 25 | * 26 | * @param bytes bytes / バイト列 27 | * @param clazz class of {@link Event} E to be deserialized / デシリアライズ対象のイベントEのクラス 28 | * @return deserialized event / デシリアライズされたイベント 29 | * @throws DeserializationException if an error occurred during deserialization / 30 | * デシリアライズ中にエラーが発生した場合 31 | */ 32 | @Nonnull 33 | E deserialize(@Nonnull byte[] bytes, @Nonnull Class clazz) throws DeserializationException; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStore.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import com.github.j5ik2o.event.store.adapter.java.internal.EventStoreForDynamoDB; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import javax.annotation.Nonnull; 7 | import software.amazon.awssdk.services.dynamodb.DynamoDbClient; 8 | 9 | /** 10 | * Represents an event store. / イベントストアを表します。 11 | * 12 | * @param AggregateId 13 | * @param Aggregate 14 | * @param Event 15 | */ 16 | public interface EventStore< 17 | AID extends AggregateId, A extends Aggregate, E extends Event> 18 | extends EventStoreOptions, AID, A, E> { 19 | /** 20 | * Creates an instance of {@link EventStore} for DynamoDB. / DynamoDB用の{@link 21 | * EventStore}のインスタンスを作成します。 22 | * 23 | * @param dynamoDbClient {@link DynamoDbClient} 24 | * @param journalTableName journal table name / ジャーナルテーブル名 25 | * @param snapshotTableName snapshot table name / スナップショットテーブル名 26 | * @param journalAidIndexName journal aggregate id index name / ジャーナル集約IDインデックス名 27 | * @param snapshotAidIndexName snapshot aggregate id index name / スナップショット集約IDインデックス名 28 | * @param shardCount shard count / シャード数 29 | * @return {@link EventStore} 30 | * @param aggregate id type / 集約IDの型 31 | * @param aggregate type / 集約の型 32 | * @param event type / イベントの型 33 | */ 34 | @Nonnull 35 | static , E extends Event> 36 | EventStore ofDynamoDB( 37 | @Nonnull DynamoDbClient dynamoDbClient, 38 | @Nonnull String journalTableName, 39 | @Nonnull String snapshotTableName, 40 | @Nonnull String journalAidIndexName, 41 | @Nonnull String snapshotAidIndexName, 42 | long shardCount) { 43 | return EventStoreForDynamoDB.create( 44 | dynamoDbClient, 45 | journalTableName, 46 | snapshotTableName, 47 | journalAidIndexName, 48 | snapshotAidIndexName, 49 | shardCount); 50 | } 51 | 52 | /** 53 | * Gets the latest snapshot by the aggregate id. / 集約IDによる最新のスナップショットを取得します。 54 | * 55 | * @param clazz class of {@link Aggregate} A to be serialized / シリアライズ対象の集約Aのクラス 56 | * @param aggregateId id of {@link Aggregate} A / 集約AのID 57 | * @return latest snapshot / 最新のスナップショット 58 | * @throws EventStoreReadException if an error occurred during reading from the event store / 59 | * イベントストアからの読み込み中にエラーが発生した場合 60 | * @throws DeserializationException if an error occurred during serialization / シリアライズ中にエラーが発生した場合 61 | */ 62 | @Nonnull 63 | Optional getLatestSnapshotById(@Nonnull Class clazz, @Nonnull AID aggregateId) 64 | throws EventStoreReadException, DeserializationException; 65 | 66 | /** 67 | * Gets the events by the aggregate id and since the sequence number. / IDとシーケンス番号以降のイベントを取得します。 68 | * 69 | * @param clazz class of {@link Event} E to be serialized / シリアライズ対象のイベントEのクラス 70 | * @param aggregateId id of {@link Aggregate} A / 集約AのID 71 | * @param sequenceNumber sequence number / シーケンス番号 72 | * @return events / イベント 73 | * @throws EventStoreReadException if an error occurred during reading from the event store / 74 | * イベントストアからの読み込み中にエラーが発生した場合 75 | * @throws DeserializationException if an error occurred during serialization / シリアライズ中にエラーが発生した場合 76 | */ 77 | @Nonnull 78 | List getEventsByIdSinceSequenceNumber( 79 | @Nonnull Class clazz, @Nonnull AID aggregateId, long sequenceNumber) 80 | throws EventStoreReadException, DeserializationException; 81 | 82 | /** 83 | * Persists an event only. / イベントのみを永続化します。 84 | * 85 | * @param event {@link Event} 86 | * @param version バージョン 87 | * @throws EventStoreWriteException if an error occurred during writing to the event store / 88 | * イベントストアへの書き込み中にエラーが発生した場合 89 | * @throws SerializationException if an error occurred during serialization / シリアライズ中にエラーが発生した場合 90 | * @throws OptimisticLockException if an error occurred during optimistic lock / 91 | * 楽観的ロック中にエラーが発生した場合 92 | */ 93 | void persistEvent(@Nonnull E event, long version) 94 | throws EventStoreWriteException, SerializationException, OptimisticLockException; 95 | 96 | /** 97 | * Persists an event and a snapshot. / イベントとスナップショットを永続化します。 98 | * 99 | * @param event {@link Event} 100 | * @param aggregate {@link Aggregate} 101 | * @throws EventStoreWriteException if an error occurred during writing to the event store / 102 | * イベントストアへの書き込み中にエラーが発生した場合 103 | * @throws SerializationException if an error occurred during serialization / シリアライズ中にエラーが発生した場合 104 | * @throws OptimisticLockException if an error occurred during optimistic lock / 105 | * 楽観的ロック中にエラーが発生した場合 106 | */ 107 | void persistEventAndSnapshot(@Nonnull E event, @Nonnull A aggregate) 108 | throws EventStoreWriteException, SerializationException, OptimisticLockException; 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreAsync.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import com.github.j5ik2o.event.store.adapter.java.internal.EventStoreAsyncForDynamoDB; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.concurrent.CompletableFuture; 7 | import javax.annotation.Nonnull; 8 | import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; 9 | 10 | /** 11 | * Asynchronous version of {@link EventStore}. / {@link EventStore}の非同期版。 12 | * 13 | * @param Aggregate ID type / 集約IDの型 14 | * @param Aggregate type / 集約の型 15 | * @param Event type / イベントの型 16 | */ 17 | public interface EventStoreAsync< 18 | AID extends AggregateId, A extends Aggregate, E extends Event> 19 | extends EventStoreOptions, AID, A, E> { 20 | static , E extends Event> 21 | EventStoreAsync ofDynamoDB( 22 | @Nonnull DynamoDbAsyncClient dynamoDbAsyncClient, 23 | @Nonnull String journalTableName, 24 | @Nonnull String snapshotTableName, 25 | @Nonnull String journalAidIndexName, 26 | @Nonnull String snapshotAidIndexName, 27 | long shardCount) { 28 | return EventStoreAsyncForDynamoDB.create( 29 | dynamoDbAsyncClient, 30 | journalTableName, 31 | snapshotTableName, 32 | journalAidIndexName, 33 | snapshotAidIndexName, 34 | shardCount); 35 | } 36 | 37 | /** 38 | * Gets the latest snapshot by the aggregate id. / 集約IDによる最新のスナップショットを取得します。 39 | * 40 | * @param clazz class of {@link Aggregate} A to be deserialized / デシリアライズ対象の集約Aのクラス 41 | * @param aggregateId id of {@link Aggregate} A / 集約AのID 42 | * @return latest snapshot wrapped by {@link CompletableFuture} / {@link 43 | * CompletableFuture}でラップされた最新のスナップショット 44 | */ 45 | @Nonnull 46 | CompletableFuture> getLatestSnapshotById( 47 | @Nonnull Class clazz, @Nonnull AID aggregateId); 48 | 49 | /** 50 | * Gets the events by the aggregate id and since the sequence number. / 集約IDとシーケンス番号以降のイベントを取得します。 51 | * 52 | * @param clazz class of {@link Event} E to be deserialized / デシリアライズ対象のイベントEのクラス 53 | * @param aggregateId {@link Aggregate} id / 集約のID 54 | * @param sequenceNumber sequence number / シーケンス番号 55 | * @return list of {@link Event} wrapped by {@link CompletableFuture} / {@link 56 | * CompletableFuture}でラップされた{@link Event}のリスト 57 | */ 58 | @Nonnull 59 | CompletableFuture> getEventsByIdSinceSequenceNumber( 60 | @Nonnull Class clazz, @Nonnull AID aggregateId, long sequenceNumber); 61 | 62 | /** 63 | * Persists an event only. / イベントのみを永続化します。 64 | * 65 | * @param event {@link Event} / イベント 66 | * @param version version / バージョン 67 | * @return {@link CompletableFuture} without result / 結果を持たない{@link CompletableFuture} 68 | */ 69 | @Nonnull 70 | CompletableFuture persistEvent(@Nonnull E event, long version); 71 | 72 | /** 73 | * Persists an event and a snapshot. / イベントとスナップショットを永続化します。 74 | * 75 | * @param event {@link Event} / イベント 76 | * @param aggregate {@link Aggregate} / 集約 77 | * @return {@link CompletableFuture} without result / 結果を持たない{@link CompletableFuture} 78 | */ 79 | @Nonnull 80 | CompletableFuture persistEventAndSnapshot(@Nonnull E event, @Nonnull A aggregate); 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreBaseException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public abstract class EventStoreBaseException extends Exception { 4 | protected EventStoreBaseException() { 5 | super(); 6 | } 7 | 8 | protected EventStoreBaseException(String message) { 9 | super(message); 10 | } 11 | 12 | protected EventStoreBaseException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | protected EventStoreBaseException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreBaseRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public abstract class EventStoreBaseRuntimeException extends RuntimeException { 4 | protected EventStoreBaseRuntimeException() { 5 | super(); 6 | } 7 | 8 | protected EventStoreBaseRuntimeException(String message) { 9 | super(message); 10 | } 11 | 12 | protected EventStoreBaseRuntimeException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | protected EventStoreBaseRuntimeException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreOptions.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import java.time.Duration; 4 | import javax.annotation.Nonnull; 5 | 6 | public interface EventStoreOptions< 7 | This extends EventStoreOptions, 8 | AID extends AggregateId, 9 | A extends Aggregate, 10 | E extends Event> { 11 | @Nonnull 12 | This withKeepSnapshotCount(long keepSnapshotCount); 13 | 14 | @Nonnull 15 | This withDeleteTtl(@Nonnull Duration deleteTtl); 16 | 17 | @Nonnull 18 | This withKeyResolver(@Nonnull KeyResolver keyResolver); 19 | 20 | @Nonnull 21 | This withEventSerializer(@Nonnull EventSerializer eventSerializer); 22 | 23 | @Nonnull 24 | This withSnapshotSerializer(@Nonnull SnapshotSerializer snapshotSerializer); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreReadException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class EventStoreReadException extends Exception { 4 | public EventStoreReadException() { 5 | super(); 6 | } 7 | 8 | public EventStoreReadException(String message) { 9 | super(message); 10 | } 11 | 12 | public EventStoreReadException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public EventStoreReadException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreReadRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class EventStoreReadRuntimeException extends EventStoreBaseRuntimeException { 4 | public EventStoreReadRuntimeException() { 5 | super(); 6 | } 7 | 8 | public EventStoreReadRuntimeException(String message) { 9 | super(message); 10 | } 11 | 12 | public EventStoreReadRuntimeException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public EventStoreReadRuntimeException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreWriteException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class EventStoreWriteException extends EventStoreBaseException { 4 | public EventStoreWriteException() { 5 | super(); 6 | } 7 | 8 | public EventStoreWriteException(String message) { 9 | super(message); 10 | } 11 | 12 | public EventStoreWriteException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public EventStoreWriteException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/EventStoreWriteRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class EventStoreWriteRuntimeException extends EventStoreBaseRuntimeException { 4 | public EventStoreWriteRuntimeException() { 5 | super(); 6 | } 7 | 8 | public EventStoreWriteRuntimeException(String message) { 9 | super(message); 10 | } 11 | 12 | public EventStoreWriteRuntimeException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public EventStoreWriteRuntimeException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/KeyResolver.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This is an interface for resolving partition keys and sort keys from aggregate IDs. / 7 | * 集約IDからパーティションキーとソートキーを解決するためのインターフェース。 8 | * 9 | * @param Aggregate ID type / 集約IDの型 10 | */ 11 | public interface KeyResolver { 12 | /** 13 | * Resolves the partition key from the aggregate id. / 集約IDからパーティションキーを解決します。 14 | * 15 | * @param aggregateId aggregate id / 集約ID 16 | * @param shardCount shard count / シャード数 17 | * @return partition key / パーティションキー 18 | */ 19 | @Nonnull 20 | String resolvePartitionKey(@Nonnull AID aggregateId, long shardCount); 21 | 22 | /** 23 | * Resolves the sort key from the aggregate id and sequence number. / 集約IDとシーケンス番号からソートキーを解決します。 24 | * 25 | * @param aggregateId aggregate id / 集約ID 26 | * @param sequenceNumber sequence number / シーケンス番号 27 | * @return sort key / ソートキー 28 | */ 29 | @Nonnull 30 | String resolveSortKey(@Nonnull AID aggregateId, long sequenceNumber); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/OptimisticLockException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class OptimisticLockException extends EventStoreBaseException { 4 | public OptimisticLockException() { 5 | super(); 6 | } 7 | 8 | public OptimisticLockException(String message) { 9 | super(message); 10 | } 11 | 12 | public OptimisticLockException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public OptimisticLockException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/OptimisticLockRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class OptimisticLockRuntimeException extends EventStoreBaseRuntimeException { 4 | public OptimisticLockRuntimeException() { 5 | super(); 6 | } 7 | 8 | public OptimisticLockRuntimeException(String message) { 9 | super(message); 10 | } 11 | 12 | public OptimisticLockRuntimeException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public OptimisticLockRuntimeException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/SerializationException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class SerializationException extends EventStoreBaseException { 4 | public SerializationException() { 5 | super(); 6 | } 7 | 8 | public SerializationException(String message) { 9 | super(message); 10 | } 11 | 12 | public SerializationException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public SerializationException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/SerializationRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | public final class SerializationRuntimeException extends EventStoreBaseRuntimeException { 4 | public SerializationRuntimeException() { 5 | super(); 6 | } 7 | 8 | public SerializationRuntimeException(String message) { 9 | super(message); 10 | } 11 | 12 | public SerializationRuntimeException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public SerializationRuntimeException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/j5ik2o/event/store/adapter/java/SnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * This is an interface for serializing and deserializing snapshots. / 7 | * スナップショットのシリアライズとデシリアライズを行うためのインターフェース。 8 | * 9 | * @param Aggregate ID type / 集約IDの型 10 | * @param Aggregate type / 集約の型 11 | */ 12 | public interface SnapshotSerializer> { 13 | /** 14 | * Serializes the snapshot. / スナップショットをシリアライズします。 15 | * 16 | * @param snapshot snapshot / スナップショット 17 | * @return serialized snapshot / シリアライズされたスナップショット 18 | * @throws SerializationException if an error occurred during serialization / シリアライズ中にエラーが発生した場合 19 | */ 20 | @Nonnull 21 | byte[] serialize(@Nonnull A snapshot) throws SerializationException; 22 | 23 | /** 24 | * Deserializes the snapshot. / スナップショットをデシリアライズします。 25 | * 26 | * @param bytes bytes / バイト列 27 | * @param clazz class of {@link Aggregate} A to be deserialized / デシリアライズ対象の集約Aのクラス 28 | * @return deserialized snapshot / デシリアライズされたスナップショット 29 | * @throws DeserializationException if an error occurred during deserialization / 30 | * デシリアライズ中にエラーが発生した場合 31 | */ 32 | @Nonnull 33 | A deserialize(@Nonnull byte[] bytes, @Nonnull Class clazz) throws DeserializationException; 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/DynamoDBAsyncUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import org.testcontainers.containers.localstack.LocalStackContainer; 5 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; 6 | import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; 7 | import software.amazon.awssdk.regions.Region; 8 | import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; 9 | import software.amazon.awssdk.services.dynamodb.model.*; 10 | 11 | public class DynamoDBAsyncUtils { 12 | public static DynamoDbAsyncClient createDynamoDbAsyncClient(LocalStackContainer localstack) { 13 | return DynamoDbAsyncClient.builder() 14 | .endpointOverride(localstack.getEndpoint()) 15 | .credentialsProvider( 16 | StaticCredentialsProvider.create( 17 | AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey()))) 18 | .region(Region.of(localstack.getRegion())) 19 | .build(); 20 | } 21 | 22 | public static CompletableFuture createSnapshotTable( 23 | DynamoDbAsyncClient client, String tableName, String indexName) { 24 | var pt = ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(5L).build(); 25 | var response = 26 | client.createTable( 27 | CreateTableRequest.builder() 28 | .tableName(tableName) 29 | .attributeDefinitions( 30 | AttributeDefinition.builder() 31 | .attributeName("pkey") 32 | .attributeType(ScalarAttributeType.S) 33 | .build(), 34 | AttributeDefinition.builder() 35 | .attributeName("skey") 36 | .attributeType(ScalarAttributeType.S) 37 | .build(), 38 | AttributeDefinition.builder() 39 | .attributeName("aid") 40 | .attributeType(ScalarAttributeType.S) 41 | .build(), 42 | AttributeDefinition.builder() 43 | .attributeName("seq_nr") 44 | .attributeType(ScalarAttributeType.N) 45 | .build()) 46 | .keySchema( 47 | KeySchemaElement.builder().attributeName("pkey").keyType(KeyType.HASH).build(), 48 | KeySchemaElement.builder().attributeName("skey").keyType(KeyType.RANGE).build()) 49 | .globalSecondaryIndexes( 50 | GlobalSecondaryIndex.builder() 51 | .indexName(indexName) 52 | .keySchema( 53 | KeySchemaElement.builder() 54 | .attributeName("aid") 55 | .keyType(KeyType.HASH) 56 | .build(), 57 | KeySchemaElement.builder() 58 | .attributeName("seq_nr") 59 | .keyType(KeyType.RANGE) 60 | .build()) 61 | .projection(Projection.builder().projectionType(ProjectionType.ALL).build()) 62 | .provisionedThroughput(pt) 63 | .build()) 64 | .provisionedThroughput(pt) 65 | .build()); 66 | return response 67 | .thenCompose( 68 | r -> { 69 | return client.updateTimeToLive( 70 | UpdateTimeToLiveRequest.builder() 71 | .tableName(tableName) 72 | .timeToLiveSpecification( 73 | TimeToLiveSpecification.builder() 74 | .enabled(true) 75 | .attributeName("ttl") 76 | .build()) 77 | .build()); 78 | }) 79 | .thenRun(() -> {}); 80 | } 81 | 82 | public static CompletableFuture createJournalTable( 83 | DynamoDbAsyncClient client, String tableName, String indexName) { 84 | var pt = ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(5L).build(); 85 | return client 86 | .createTable( 87 | CreateTableRequest.builder() 88 | .tableName(tableName) 89 | .attributeDefinitions( 90 | AttributeDefinition.builder() 91 | .attributeName("pkey") 92 | .attributeType(ScalarAttributeType.S) 93 | .build(), 94 | AttributeDefinition.builder() 95 | .attributeName("skey") 96 | .attributeType(ScalarAttributeType.S) 97 | .build(), 98 | AttributeDefinition.builder() 99 | .attributeName("aid") 100 | .attributeType(ScalarAttributeType.S) 101 | .build(), 102 | AttributeDefinition.builder() 103 | .attributeName("seq_nr") 104 | .attributeType(ScalarAttributeType.N) 105 | .build()) 106 | .keySchema( 107 | KeySchemaElement.builder().attributeName("pkey").keyType(KeyType.HASH).build(), 108 | KeySchemaElement.builder().attributeName("skey").keyType(KeyType.RANGE).build()) 109 | .globalSecondaryIndexes( 110 | GlobalSecondaryIndex.builder() 111 | .indexName(indexName) 112 | .keySchema( 113 | KeySchemaElement.builder() 114 | .attributeName("aid") 115 | .keyType(KeyType.HASH) 116 | .build(), 117 | KeySchemaElement.builder() 118 | .attributeName("seq_nr") 119 | .keyType(KeyType.RANGE) 120 | .build()) 121 | .projection(Projection.builder().projectionType(ProjectionType.ALL).build()) 122 | .provisionedThroughput(pt) 123 | .build()) 124 | .provisionedThroughput(pt) 125 | .build()) 126 | .thenRun(() -> {}); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/DynamoDBUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import org.testcontainers.containers.localstack.LocalStackContainer; 4 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; 5 | import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; 6 | import software.amazon.awssdk.regions.Region; 7 | import software.amazon.awssdk.services.dynamodb.DynamoDbClient; 8 | import software.amazon.awssdk.services.dynamodb.model.*; 9 | 10 | public class DynamoDBUtils { 11 | public static DynamoDbClient createDynamoDbClient(LocalStackContainer localstack) { 12 | return DynamoDbClient.builder() 13 | .endpointOverride(localstack.getEndpoint()) 14 | .credentialsProvider( 15 | StaticCredentialsProvider.create( 16 | AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey()))) 17 | .region(Region.of(localstack.getRegion())) 18 | .build(); 19 | } 20 | 21 | public static void createSnapshotTable( 22 | DynamoDbClient client, String tableName, String indexName) { 23 | var pt = ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(5L).build(); 24 | client.createTable( 25 | CreateTableRequest.builder() 26 | .tableName(tableName) 27 | .attributeDefinitions( 28 | AttributeDefinition.builder() 29 | .attributeName("pkey") 30 | .attributeType(ScalarAttributeType.S) 31 | .build(), 32 | AttributeDefinition.builder() 33 | .attributeName("skey") 34 | .attributeType(ScalarAttributeType.S) 35 | .build(), 36 | AttributeDefinition.builder() 37 | .attributeName("aid") 38 | .attributeType(ScalarAttributeType.S) 39 | .build(), 40 | AttributeDefinition.builder() 41 | .attributeName("seq_nr") 42 | .attributeType(ScalarAttributeType.N) 43 | .build()) 44 | .keySchema( 45 | KeySchemaElement.builder().attributeName("pkey").keyType(KeyType.HASH).build(), 46 | KeySchemaElement.builder().attributeName("skey").keyType(KeyType.RANGE).build()) 47 | .globalSecondaryIndexes( 48 | GlobalSecondaryIndex.builder() 49 | .indexName(indexName) 50 | .keySchema( 51 | KeySchemaElement.builder() 52 | .attributeName("aid") 53 | .keyType(KeyType.HASH) 54 | .build(), 55 | KeySchemaElement.builder() 56 | .attributeName("seq_nr") 57 | .keyType(KeyType.RANGE) 58 | .build()) 59 | .projection(Projection.builder().projectionType(ProjectionType.ALL).build()) 60 | .provisionedThroughput(pt) 61 | .build()) 62 | .provisionedThroughput(pt) 63 | .build()); 64 | client.updateTimeToLive( 65 | UpdateTimeToLiveRequest.builder() 66 | .tableName(tableName) 67 | .timeToLiveSpecification( 68 | TimeToLiveSpecification.builder().enabled(true).attributeName("ttl").build()) 69 | .build()); 70 | } 71 | 72 | public static void createJournalTable(DynamoDbClient client, String tableName, String indexName) { 73 | var pt = ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(5L).build(); 74 | client.createTable( 75 | CreateTableRequest.builder() 76 | .tableName(tableName) 77 | .attributeDefinitions( 78 | AttributeDefinition.builder() 79 | .attributeName("pkey") 80 | .attributeType(ScalarAttributeType.S) 81 | .build(), 82 | AttributeDefinition.builder() 83 | .attributeName("skey") 84 | .attributeType(ScalarAttributeType.S) 85 | .build(), 86 | AttributeDefinition.builder() 87 | .attributeName("aid") 88 | .attributeType(ScalarAttributeType.S) 89 | .build(), 90 | AttributeDefinition.builder() 91 | .attributeName("seq_nr") 92 | .attributeType(ScalarAttributeType.N) 93 | .build()) 94 | .keySchema( 95 | KeySchemaElement.builder().attributeName("pkey").keyType(KeyType.HASH).build(), 96 | KeySchemaElement.builder().attributeName("skey").keyType(KeyType.RANGE).build()) 97 | .globalSecondaryIndexes( 98 | GlobalSecondaryIndex.builder() 99 | .indexName(indexName) 100 | .keySchema( 101 | KeySchemaElement.builder() 102 | .attributeName("aid") 103 | .keyType(KeyType.HASH) 104 | .build(), 105 | KeySchemaElement.builder() 106 | .attributeName("seq_nr") 107 | .keyType(KeyType.RANGE) 108 | .build()) 109 | .projection(Projection.builder().projectionType(ProjectionType.ALL).build()) 110 | .provisionedThroughput(pt) 111 | .build()) 112 | .provisionedThroughput(pt) 113 | .build()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/EventStoreAsyncForDynamoDBTest.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.fail; 5 | import static org.testcontainers.containers.localstack.LocalStackContainer.Service.DYNAMODB; 6 | 7 | import com.github.j5ik2o.event.store.adapter.java.EventStoreAsync; 8 | import org.junit.jupiter.api.Test; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.testcontainers.containers.localstack.LocalStackContainer; 12 | import org.testcontainers.junit.jupiter.Container; 13 | import org.testcontainers.junit.jupiter.Testcontainers; 14 | import org.testcontainers.utility.DockerImageName; 15 | 16 | @Testcontainers 17 | public class EventStoreAsyncForDynamoDBTest { 18 | 19 | private static final Logger LOGGER = 20 | LoggerFactory.getLogger(EventStoreAsyncForDynamoDBTest.class); 21 | 22 | private static final String JOURNAL_TABLE_NAME = "journal"; 23 | private static final String SNAPSHOT_TABLE_NAME = "snapshot"; 24 | 25 | private static final String JOURNAL_AID_INDEX_NAME = "journal-aid-index"; 26 | private static final String SNAPSHOT_AID_INDEX_NAME = "snapshot-aid-index"; 27 | 28 | DockerImageName localstackImage = DockerImageName.parse("localstack/localstack:2.1.0"); 29 | 30 | @Container 31 | public LocalStackContainer localstack = 32 | new LocalStackContainer(localstackImage).withServices(DYNAMODB); 33 | 34 | @Test 35 | public void persistAndGet() { 36 | try (var client = DynamoDBAsyncUtils.createDynamoDbAsyncClient(localstack)) { 37 | DynamoDBAsyncUtils.createJournalTable(client, JOURNAL_TABLE_NAME, JOURNAL_AID_INDEX_NAME) 38 | .join(); 39 | DynamoDBAsyncUtils.createSnapshotTable(client, SNAPSHOT_TABLE_NAME, SNAPSHOT_AID_INDEX_NAME) 40 | .join(); 41 | client.listTables().join().tableNames().forEach(System.out::println); 42 | 43 | EventStoreAsync eventStore = 44 | EventStoreAsync.ofDynamoDB( 45 | client, 46 | JOURNAL_TABLE_NAME, 47 | SNAPSHOT_TABLE_NAME, 48 | JOURNAL_AID_INDEX_NAME, 49 | SNAPSHOT_AID_INDEX_NAME, 50 | 32); 51 | 52 | var id = new UserAccountId(IdGenerator.generate().toString()); 53 | var aggregateAndEvent = UserAccount.create(id, "test-1"); 54 | eventStore 55 | .persistEventAndSnapshot(aggregateAndEvent.getEvent(), aggregateAndEvent.getAggregate()) 56 | .join(); 57 | 58 | var result = eventStore.getLatestSnapshotById(UserAccount.class, id).join(); 59 | if (result.isPresent()) { 60 | assertEquals(result.get().getId(), aggregateAndEvent.getAggregate().getId()); 61 | LOGGER.info("result = {}", result.get()); 62 | } else { 63 | fail("result is empty"); 64 | } 65 | } 66 | } 67 | 68 | @Test 69 | public void repositoryStoreAndFindById() { 70 | try (var client = DynamoDBAsyncUtils.createDynamoDbAsyncClient(localstack)) { 71 | DynamoDBAsyncUtils.createJournalTable(client, JOURNAL_TABLE_NAME, JOURNAL_AID_INDEX_NAME) 72 | .join(); 73 | DynamoDBAsyncUtils.createSnapshotTable(client, SNAPSHOT_TABLE_NAME, SNAPSHOT_AID_INDEX_NAME) 74 | .join(); 75 | client.listTables().join().tableNames().forEach(System.out::println); 76 | 77 | EventStoreAsyncForDynamoDB eventStore = 78 | EventStoreAsyncForDynamoDB.create( 79 | client, 80 | JOURNAL_TABLE_NAME, 81 | SNAPSHOT_TABLE_NAME, 82 | JOURNAL_AID_INDEX_NAME, 83 | SNAPSHOT_AID_INDEX_NAME, 84 | 32); 85 | var userAccountRepository = new UserAccountRepositoryAsync(eventStore); 86 | 87 | var id = new UserAccountId(IdGenerator.generate().toString()); 88 | var aggregateAndEvent1 = UserAccount.create(id, "test-1"); 89 | var aggregate1 = aggregateAndEvent1.getAggregate(); 90 | 91 | var result = 92 | userAccountRepository 93 | .store(aggregateAndEvent1.getEvent(), aggregate1) 94 | .thenCompose( 95 | ignored -> { 96 | var aggregateAndEvent2 = aggregate1.changeName("test-2"); 97 | return userAccountRepository.store( 98 | aggregateAndEvent2.getEvent(), 99 | aggregateAndEvent2.getAggregate().getVersion()); 100 | }) 101 | .thenCompose(r -> userAccountRepository.findById(id)) 102 | .join(); 103 | 104 | if (result.isPresent()) { 105 | assertEquals(result.get().getId(), aggregateAndEvent1.getAggregate().getId()); 106 | assertEquals(result.get().getName(), "test-2"); 107 | } else { 108 | fail("result is empty"); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/EventStoreForDynamoDBTest.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.fail; 5 | import static org.testcontainers.containers.localstack.LocalStackContainer.Service.DYNAMODB; 6 | 7 | import com.github.j5ik2o.event.store.adapter.java.*; 8 | import org.junit.jupiter.api.Test; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.testcontainers.containers.localstack.LocalStackContainer; 12 | import org.testcontainers.junit.jupiter.Container; 13 | import org.testcontainers.junit.jupiter.Testcontainers; 14 | import org.testcontainers.utility.DockerImageName; 15 | 16 | @Testcontainers 17 | public class EventStoreForDynamoDBTest { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(EventStoreForDynamoDBTest.class); 20 | 21 | private static final String JOURNAL_TABLE_NAME = "journal"; 22 | private static final String SNAPSHOT_TABLE_NAME = "snapshot"; 23 | 24 | private static final String JOURNAL_AID_INDEX_NAME = "journal-aid-index"; 25 | private static final String SNAPSHOT_AID_INDEX_NAME = "snapshot-aid-index"; 26 | 27 | DockerImageName localstackImage = DockerImageName.parse("localstack/localstack:2.1.0"); 28 | 29 | @Container 30 | public LocalStackContainer localstack = 31 | new LocalStackContainer(localstackImage).withServices(DYNAMODB); 32 | 33 | @Test 34 | public void persistAndGet() 35 | throws SerializationException, 36 | EventStoreWriteException, 37 | EventStoreReadException, 38 | OptimisticLockException, 39 | DeserializationException { 40 | try (var client = DynamoDBUtils.createDynamoDbClient(localstack)) { 41 | DynamoDBUtils.createJournalTable(client, JOURNAL_TABLE_NAME, JOURNAL_AID_INDEX_NAME); 42 | DynamoDBUtils.createSnapshotTable(client, SNAPSHOT_TABLE_NAME, SNAPSHOT_AID_INDEX_NAME); 43 | client.listTables().tableNames().forEach(System.out::println); 44 | 45 | EventStore eventStore = 46 | EventStore.ofDynamoDB( 47 | client, 48 | JOURNAL_TABLE_NAME, 49 | SNAPSHOT_TABLE_NAME, 50 | JOURNAL_AID_INDEX_NAME, 51 | SNAPSHOT_AID_INDEX_NAME, 52 | 32); 53 | 54 | var id = new UserAccountId(IdGenerator.generate().toString()); 55 | var aggregateAndEvent = UserAccount.create(id, "test-1"); 56 | try { 57 | eventStore.persistEventAndSnapshot( 58 | aggregateAndEvent.getEvent(), aggregateAndEvent.getAggregate()); 59 | } catch (com.github.j5ik2o.event.store.adapter.java.OptimisticLockException e) { 60 | throw new OptimisticLockException(e); 61 | } 62 | 63 | var result = eventStore.getLatestSnapshotById(UserAccount.class, id); 64 | if (result.isPresent()) { 65 | assertEquals(result.get().getId(), aggregateAndEvent.getAggregate().getId()); 66 | LOGGER.info("result = {}", result.get()); 67 | } else { 68 | fail("result is empty"); 69 | } 70 | } 71 | } 72 | 73 | @Test 74 | public void repositoryStoreAndFindById() { 75 | try (var client = DynamoDBUtils.createDynamoDbClient(localstack)) { 76 | DynamoDBUtils.createJournalTable(client, JOURNAL_TABLE_NAME, JOURNAL_AID_INDEX_NAME); 77 | DynamoDBUtils.createSnapshotTable(client, SNAPSHOT_TABLE_NAME, SNAPSHOT_AID_INDEX_NAME); 78 | client.listTables().tableNames().forEach(System.out::println); 79 | 80 | EventStore eventStore = 81 | EventStore.ofDynamoDB( 82 | client, 83 | JOURNAL_TABLE_NAME, 84 | SNAPSHOT_TABLE_NAME, 85 | JOURNAL_AID_INDEX_NAME, 86 | SNAPSHOT_AID_INDEX_NAME, 87 | 32); 88 | var userAccountRepository = new UserAccountRepository(eventStore); 89 | 90 | var id = new UserAccountId(IdGenerator.generate().toString()); 91 | var aggregateAndEvent1 = UserAccount.create(id, "test-1"); 92 | var aggregate1 = aggregateAndEvent1.getAggregate(); 93 | 94 | userAccountRepository.store(aggregateAndEvent1.getEvent(), aggregate1); 95 | 96 | var aggregateAndEvent2 = aggregate1.changeName("test-2"); 97 | 98 | userAccountRepository.store( 99 | aggregateAndEvent2.getEvent(), aggregateAndEvent2.getAggregate().getVersion()); 100 | 101 | var result = userAccountRepository.findById(id); 102 | if (result.isPresent()) { 103 | assertEquals(result.get().getId(), aggregateAndEvent2.getAggregate().getId()); 104 | assertEquals(result.get().getName(), "test-2"); 105 | assertEquals(result.get().getSequenceNumber(), 2L); 106 | assertEquals(result.get().getVersion(), 2L); 107 | } else { 108 | fail("result is empty"); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/IdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import de.huxhorn.sulky.ulid.ULID; 4 | import javax.annotation.Nonnull; 5 | 6 | public final class IdGenerator { 7 | private static final ULID ulid = new ULID(); 8 | private static ULID.Value prevValue = ulid.nextValue(); 9 | 10 | @Nonnull 11 | public static synchronized ULID.Value generate() { 12 | var result = ulid.nextMonotonicValue(prevValue); 13 | if (result == null) { 14 | throw new IllegalStateException("ULID overflow"); 15 | } 16 | prevValue = result; 17 | return result; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/OptimisticLockException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | public class OptimisticLockException extends RuntimeException { 4 | public OptimisticLockException() { 5 | super(); 6 | } 7 | 8 | public OptimisticLockException(String message) { 9 | super(message); 10 | } 11 | 12 | public OptimisticLockException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public OptimisticLockException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/RepositoryException.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | public final class RepositoryException extends RuntimeException { 4 | public RepositoryException() { 5 | super(); 6 | } 7 | 8 | public RepositoryException(String message) { 9 | super(message); 10 | } 11 | 12 | public RepositoryException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public RepositoryException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/UserAccount.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.github.j5ik2o.event.store.adapter.java.Aggregate; 5 | import com.github.j5ik2o.event.store.adapter.java.AggregateAndEvent; 6 | import java.time.Instant; 7 | import java.util.List; 8 | import javax.annotation.Nonnull; 9 | 10 | public final class UserAccount implements Aggregate { 11 | @Nonnull private final UserAccountId id; 12 | private long sequenceNumber; 13 | @Nonnull private final String name; 14 | 15 | private long version; 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | 22 | UserAccount that = (UserAccount) o; 23 | 24 | if (sequenceNumber != that.sequenceNumber) return false; 25 | if (version != that.version) return false; 26 | if (!id.equals(that.id)) return false; 27 | return name.equals(that.name); 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | int result = id.hashCode(); 33 | result = 31 * result + (int) (sequenceNumber ^ (sequenceNumber >>> 32)); 34 | result = 31 * result + name.hashCode(); 35 | result = 31 * result + (int) (version ^ (version >>> 32)); 36 | return result; 37 | } 38 | 39 | private UserAccount( 40 | @Nonnull @JsonProperty("id") UserAccountId id, 41 | @JsonProperty("sequenceNumber") long sequenceNumber, 42 | @Nonnull @JsonProperty("name") String name, 43 | @JsonProperty("version") long version) { 44 | this.id = id; 45 | this.sequenceNumber = sequenceNumber; 46 | this.name = name; 47 | this.version = version; 48 | } 49 | 50 | @Nonnull 51 | public static UserAccount replay( 52 | @Nonnull List events, @Nonnull UserAccount snapshot) { 53 | return events.stream().reduce(snapshot, UserAccount::applyEvent, (u1, u2) -> u2); 54 | } 55 | 56 | @Nonnull 57 | public UserAccount applyEvent(@Nonnull UserAccountEvent event) { 58 | if (event instanceof UserAccountEvent.Renamed) { 59 | var result = changeName(((UserAccountEvent.Renamed) event).getName()); 60 | return result.getAggregate(); 61 | } else { 62 | throw new IllegalArgumentException(); 63 | } 64 | } 65 | 66 | @Nonnull 67 | public static AggregateAndEvent create( 68 | @Nonnull UserAccountId id, @Nonnull String name) { 69 | var userAccount = new UserAccount(id, 0L, name, 1L); 70 | userAccount.sequenceNumber++; 71 | return new AggregateAndEvent<>( 72 | userAccount, 73 | new UserAccountEvent.Created( 74 | IdGenerator.generate().toString(), 75 | userAccount.id, 76 | userAccount.sequenceNumber, 77 | name, 78 | Instant.now())); 79 | } 80 | 81 | @Nonnull 82 | public AggregateAndEvent changeName( 83 | @Nonnull String name) { 84 | var userAccount = new UserAccount(id, sequenceNumber, name, version); 85 | userAccount.sequenceNumber++; 86 | return new AggregateAndEvent<>( 87 | userAccount, 88 | new UserAccountEvent.Renamed( 89 | IdGenerator.generate().toString(), 90 | userAccount.id, 91 | userAccount.sequenceNumber, 92 | name, 93 | Instant.now())); 94 | } 95 | 96 | @Nonnull 97 | @Override 98 | public UserAccountId getId() { 99 | return id; 100 | } 101 | 102 | @Override 103 | public long getSequenceNumber() { 104 | return sequenceNumber; 105 | } 106 | 107 | @Override 108 | public long getVersion() { 109 | return version; 110 | } 111 | 112 | @Nonnull 113 | public String getName() { 114 | return name; 115 | } 116 | 117 | @Override 118 | public UserAccount withVersion(long version) { 119 | return new UserAccount(id, sequenceNumber, name, version); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/UserAccountEvent.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import com.github.j5ik2o.event.store.adapter.java.Event; 5 | import java.time.Instant; 6 | import javax.annotation.Nonnull; 7 | 8 | @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") 9 | @JsonSubTypes({ 10 | @JsonSubTypes.Type(name = "created", value = UserAccountEvent.Created.class), 11 | @JsonSubTypes.Type(name = "renamed", value = UserAccountEvent.Renamed.class) 12 | }) 13 | public interface UserAccountEvent extends Event { 14 | 15 | @Nonnull 16 | UserAccountId getAggregateId(); 17 | 18 | long getSequenceNumber(); 19 | 20 | @JsonTypeName("created") 21 | @JsonIgnoreProperties( 22 | value = {"created"}, 23 | allowGetters = true) 24 | final class Created implements UserAccountEvent { 25 | 26 | @Nonnull private final String id; 27 | @Nonnull private final UserAccountId aggregateId; 28 | 29 | private final long sequenceNumber; 30 | 31 | @Nonnull private final String name; 32 | 33 | @Nonnull private final Instant occurredAt; 34 | 35 | public Created( 36 | @Nonnull @JsonProperty("id") String id, 37 | @Nonnull @JsonProperty("aggregateId") UserAccountId aggregateId, 38 | @JsonProperty("sequenceNumber") long sequenceNumber, 39 | @Nonnull @JsonProperty("name") String name, 40 | @Nonnull @JsonProperty("occurredAt") Instant occurredAt) { 41 | this.id = id; 42 | this.aggregateId = aggregateId; 43 | this.sequenceNumber = sequenceNumber; 44 | this.name = name; 45 | this.occurredAt = occurredAt; 46 | } 47 | 48 | @Override 49 | public boolean isCreated() { 50 | return true; 51 | } 52 | 53 | @Nonnull 54 | @Override 55 | public String getId() { 56 | return id; 57 | } 58 | 59 | @Nonnull 60 | @Override 61 | public UserAccountId getAggregateId() { 62 | return aggregateId; 63 | } 64 | 65 | @Override 66 | public long getSequenceNumber() { 67 | return sequenceNumber; 68 | } 69 | 70 | @Nonnull 71 | public String getName() { 72 | return name; 73 | } 74 | 75 | @Nonnull 76 | @Override 77 | public Instant getOccurredAt() { 78 | return occurredAt; 79 | } 80 | } 81 | 82 | @JsonTypeName("renamed") 83 | @JsonIgnoreProperties( 84 | value = {"created"}, 85 | allowGetters = true) 86 | final class Renamed implements UserAccountEvent { 87 | 88 | @Nonnull private final String id; 89 | @Nonnull private final UserAccountId aggregateId; 90 | private final long sequenceNumber; 91 | @Nonnull private final String name; 92 | @Nonnull private final Instant occurredAt; 93 | 94 | public Renamed( 95 | @Nonnull @JsonProperty("id") String id, 96 | @Nonnull @JsonProperty("aggregateId") UserAccountId aggregateId, 97 | @JsonProperty("sequenceNumber") long sequenceNumber, 98 | @Nonnull @JsonProperty("name") String name, 99 | @Nonnull @JsonProperty("occurredAt") Instant occurredAt) { 100 | this.id = id; 101 | this.aggregateId = aggregateId; 102 | this.sequenceNumber = sequenceNumber; 103 | this.name = name; 104 | this.occurredAt = occurredAt; 105 | } 106 | 107 | @Override 108 | public boolean isCreated() { 109 | return false; 110 | } 111 | 112 | @Nonnull 113 | @Override 114 | public String getId() { 115 | return id; 116 | } 117 | 118 | @Nonnull 119 | @Override 120 | public UserAccountId getAggregateId() { 121 | return aggregateId; 122 | } 123 | 124 | @Nonnull 125 | public String getName() { 126 | return name; 127 | } 128 | 129 | @Override 130 | public long getSequenceNumber() { 131 | return sequenceNumber; 132 | } 133 | 134 | @Nonnull 135 | @Override 136 | public Instant getOccurredAt() { 137 | return occurredAt; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/UserAccountId.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.github.j5ik2o.event.store.adapter.java.AggregateId; 5 | import javax.annotation.Nonnull; 6 | 7 | public final class UserAccountId implements AggregateId { 8 | @Nonnull private final String typeName; 9 | @Nonnull private final String value; 10 | 11 | public UserAccountId(@Nonnull @JsonProperty("value") String value) { 12 | typeName = "user-account"; 13 | this.value = value; 14 | } 15 | 16 | @Override 17 | public boolean equals(Object o) { 18 | if (this == o) return true; 19 | if (o == null || getClass() != o.getClass()) return false; 20 | 21 | UserAccountId that = (UserAccountId) o; 22 | 23 | if (!typeName.equals(that.typeName)) return false; 24 | return value.equals(that.value); 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | int result = typeName.hashCode(); 30 | result = 31 * result + value.hashCode(); 31 | return result; 32 | } 33 | 34 | @Nonnull 35 | @JsonProperty(access = JsonProperty.Access.READ_ONLY) 36 | @Override 37 | public String getTypeName() { 38 | return typeName; 39 | } 40 | 41 | @Nonnull 42 | @Override 43 | public String getValue() { 44 | return value; 45 | } 46 | 47 | @Nonnull 48 | @Override 49 | public String asString() { 50 | return String.format("%s-%s", getTypeName(), getValue()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/UserAccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import com.github.j5ik2o.event.store.adapter.java.*; 4 | import java.util.Optional; 5 | import javax.annotation.Nonnull; 6 | 7 | public final class UserAccountRepository { 8 | @Nonnull private final EventStore eventStore; 9 | 10 | public UserAccountRepository( 11 | @Nonnull EventStore eventStore) { 12 | this.eventStore = eventStore; 13 | } 14 | 15 | public void store(@Nonnull UserAccountEvent event, long version) { 16 | try { 17 | eventStore.persistEvent(event, version); 18 | } catch (EventStoreWriteException | SerializationException e) { 19 | throw new RepositoryException(e); 20 | } catch (com.github.j5ik2o.event.store.adapter.java.OptimisticLockException e) { 21 | throw new OptimisticLockException(e); 22 | } 23 | } 24 | 25 | public void store(@Nonnull UserAccountEvent event, @Nonnull UserAccount aggregate) { 26 | try { 27 | eventStore.persistEventAndSnapshot(event, aggregate); 28 | } catch (EventStoreWriteException | SerializationException e) { 29 | throw new RepositoryException(e); 30 | } catch (com.github.j5ik2o.event.store.adapter.java.OptimisticLockException e) { 31 | throw new OptimisticLockException(e); 32 | } 33 | } 34 | 35 | @Nonnull 36 | public Optional findById(@Nonnull UserAccountId id) { 37 | try { 38 | var snapshot = eventStore.getLatestSnapshotById(UserAccount.class, id); 39 | if (snapshot.isEmpty()) { 40 | return Optional.empty(); 41 | } else { 42 | var events = 43 | eventStore.getEventsByIdSinceSequenceNumber( 44 | UserAccountEvent.class, id, snapshot.get().getSequenceNumber() + 1); 45 | return Optional.of(UserAccount.replay(events, snapshot.get())); 46 | } 47 | } catch (EventStoreReadException | DeserializationException e) { 48 | throw new RepositoryException(e); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/github/j5ik2o/event/store/adapter/java/internal/UserAccountRepositoryAsync.java: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.event.store.adapter.java.internal; 2 | 3 | import com.github.j5ik2o.event.store.adapter.java.EventStoreAsync; 4 | import java.util.Optional; 5 | import java.util.concurrent.CompletableFuture; 6 | import javax.annotation.Nonnull; 7 | 8 | public final class UserAccountRepositoryAsync { 9 | 10 | @Nonnull private final EventStoreAsync eventStore; 11 | 12 | public UserAccountRepositoryAsync( 13 | @Nonnull EventStoreAsync eventStore) { 14 | this.eventStore = eventStore; 15 | } 16 | 17 | @Nonnull 18 | public CompletableFuture store(@Nonnull UserAccountEvent event, long version) { 19 | return eventStore.persistEvent(event, version); 20 | } 21 | 22 | @Nonnull 23 | public CompletableFuture store( 24 | @Nonnull UserAccountEvent event, @Nonnull UserAccount aggregate) { 25 | return eventStore.persistEventAndSnapshot(event, aggregate); 26 | } 27 | 28 | @Nonnull 29 | public CompletableFuture> findById(@Nonnull UserAccountId id) { 30 | return eventStore 31 | .getLatestSnapshotById(UserAccount.class, id) 32 | .thenCompose( 33 | result -> { 34 | if (result.isEmpty()) { 35 | return CompletableFuture.completedFuture(Optional.empty()); 36 | } else { 37 | return eventStore 38 | .getEventsByIdSinceSequenceNumber( 39 | UserAccountEvent.class, id, result.get().getSequenceNumber() + 1) 40 | .thenApply(events -> Optional.of(UserAccount.replay(events, result.get()))); 41 | } 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | debug 7 | 8 | 9 | [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 1.2.34-SNAPSHOT 2 | --------------------------------------------------------------------------------