Skip to content

Commit 21bc52f

Browse files
committed
Keep track of current version.
Also retry once in case of possible key conflict (and other errors).
1 parent c9a87dc commit 21bc52f

File tree

8 files changed

+155
-58
lines changed

8 files changed

+155
-58
lines changed

src/main/kotlin/no/nav/klage/document/api/DocumentController.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,35 @@ class DocumentController(
5353
@RequestBody(required = false) input: DocumentUpdateInput,
5454
): DocumentView {
5555
log("updateDocument called with id $documentId")
56-
secureLogger.debug("updateDocument with id {}: current version: {} received json: {}", documentId, input.currentVersion, input.json)
57-
return mapToDocumentView(documentService.updateDocument(
58-
documentId = documentId,
59-
json = input.json,
60-
currentVersion = input.currentVersion,
61-
))
56+
secureLogger.debug(
57+
"updateDocument with id {}: current FE version: {} received json: {}",
58+
documentId,
59+
input.currentVersion,
60+
input.json
61+
)
62+
63+
var mapToDocumentView: DocumentView
64+
try {
65+
mapToDocumentView = mapToDocumentView(
66+
documentService.updateDocument(
67+
documentId = documentId,
68+
json = input.json,
69+
currentVersion = input.currentVersion,
70+
)
71+
)
72+
} catch (e: Exception) {
73+
logger.warn("Failed to update document $documentId. Trying one more time.", e)
74+
75+
mapToDocumentView = mapToDocumentView(
76+
documentService.updateDocument(
77+
documentId = documentId,
78+
json = input.json,
79+
currentVersion = input.currentVersion,
80+
)
81+
)
82+
}
83+
84+
return mapToDocumentView
6285
}
6386

6487
@Operation(

src/main/kotlin/no/nav/klage/document/domain/DocumentVersion.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package no.nav.klage.document.domain
22

3-
import jakarta.persistence.Column
4-
import jakarta.persistence.Entity
5-
import jakarta.persistence.Id
6-
import jakarta.persistence.IdClass
7-
import jakarta.persistence.Table
3+
import jakarta.persistence.*
84
import java.time.LocalDateTime
95
import java.util.*
106

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package no.nav.klage.document.domain
2+
3+
import jakarta.persistence.Entity
4+
import jakarta.persistence.Id
5+
import jakarta.persistence.Table
6+
import java.util.*
7+
8+
@Entity
9+
@Table(name = "latest_document_version", schema = "klage")
10+
class LatestDocumentVersion(
11+
@Id
12+
val documentId: UUID,
13+
val currentVersion: Int,
14+
) {
15+
override fun equals(other: Any?): Boolean {
16+
if (this === other) return true
17+
if (javaClass != other?.javaClass) return false
18+
19+
other as LatestDocumentVersion
20+
21+
return documentId == other.documentId
22+
}
23+
24+
override fun hashCode(): Int {
25+
return documentId.hashCode()
26+
}
27+
28+
override fun toString(): String {
29+
return "Document(id=$documentId, currentVersion=$currentVersion)"
30+
}
31+
32+
}

src/main/kotlin/no/nav/klage/document/repositories/DocumentVersionRepository.kt

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,11 @@ package no.nav.klage.document.repositories
33
import no.nav.klage.document.domain.DocumentVersion
44
import no.nav.klage.document.domain.DocumentVersionId
55
import org.springframework.data.jpa.repository.JpaRepository
6-
import org.springframework.data.jpa.repository.Query
76
import java.util.*
87

98
interface DocumentVersionRepository : JpaRepository<DocumentVersion, DocumentVersionId> {
109

1110
fun findByDocumentId(documentId: UUID): List<DocumentVersion>
1211

13-
fun findByDocumentIdAndVersion(documentId: UUID, version: Int): DocumentVersion
14-
15-
@Query(
16-
"""
17-
select max(dv.version) from DocumentVersion dv
18-
where dv.documentId = :documentId
19-
"""
20-
)
21-
fun findLatestVersionNumber(documentId: UUID): Int
22-
2312
fun deleteByDocumentId(documentId: UUID)
2413
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package no.nav.klage.document.repositories
2+
3+
import no.nav.klage.document.domain.LatestDocumentVersion
4+
import org.springframework.data.jpa.repository.JpaRepository
5+
import java.util.*
6+
7+
interface LatestDocumentVersionRepository : JpaRepository<LatestDocumentVersion, UUID>

src/main/kotlin/no/nav/klage/document/service/DocumentService.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import no.nav.klage.document.domain.DocumentVersionId
66
import no.nav.klage.document.repositories.CommentRepository
77
import no.nav.klage.document.repositories.DocumentRepository
88
import no.nav.klage.document.repositories.DocumentVersionRepository
9+
import no.nav.klage.document.repositories.LatestDocumentVersionRepository
910
import no.nav.klage.document.util.TokenUtil
1011
import no.nav.klage.document.util.getLogger
1112
import no.nav.klage.document.util.getSecureLogger
@@ -20,6 +21,7 @@ class DocumentService(
2021
private val documentVersionRepository: DocumentVersionRepository,
2122
private val commentRepository: CommentRepository,
2223
private val documentRepository: DocumentRepository,
24+
private val latestDocumentRepository: LatestDocumentVersionRepository,
2325
private val tokenUtil: TokenUtil,
2426
) {
2527

@@ -53,7 +55,7 @@ class DocumentService(
5355

5456
fun updateDocument(documentId: UUID, json: String, currentVersion: Int?): DocumentVersion {
5557
val now = LocalDateTime.now()
56-
val latestVersionNumber = documentVersionRepository.findLatestVersionNumber(documentId = documentId)
58+
val latestVersionNumber = latestDocumentRepository.findById(documentId).get().currentVersion
5759

5860
if (currentVersion != null && latestVersionNumber != currentVersion) {
5961
logger.warn(
@@ -74,11 +76,16 @@ class DocumentService(
7476
}
7577

7678
val documentVersion =
77-
documentVersionRepository.findByDocumentIdAndVersion(documentId = documentId, version = latestVersionNumber)
79+
documentVersionRepository.findById(
80+
DocumentVersionId(
81+
documentId = documentId,
82+
version = latestVersionNumber
83+
)
84+
).get()
7885
return documentVersionRepository.save(
7986
DocumentVersion(
8087
documentId = documentVersion.documentId,
81-
version = documentVersion.version + 1,
88+
version = latestVersionNumber + 1,
8289
json = json,
8390
created = now,
8491
modified = now,
@@ -88,9 +95,13 @@ class DocumentService(
8895
}
8996

9097
fun getDocument(documentId: UUID, version: Int?): DocumentVersion {
91-
val versionToUse = version ?: documentVersionRepository.findLatestVersionNumber(documentId = documentId)
92-
return documentVersionRepository.findById(DocumentVersionId(documentId = documentId, version = versionToUse))
93-
.get()
98+
val versionToUse = version ?: latestDocumentRepository.findById(documentId).get().currentVersion
99+
return documentVersionRepository.findById(
100+
DocumentVersionId(
101+
documentId = documentId,
102+
version = versionToUse
103+
)
104+
).get()
94105
}
95106

96107
fun deleteDocument(documentId: UUID) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
CREATE TABLE klage.latest_document_version
2+
(
3+
document_id UUID PRIMARY KEY,
4+
current_version INT NOT NULL DEFAULT 1,
5+
CONSTRAINT latest_document_version_document_id_fkey
6+
FOREIGN KEY (document_id) REFERENCES klage.document (id)
7+
);
8+
9+
INSERT INTO klage.latest_document_version(document_id, current_version)
10+
SELECT document_id, MAX(version)
11+
FROM klage.document_version
12+
GROUP BY document_id;
13+
14+
CREATE FUNCTION update_current_version() RETURNS TRIGGER
15+
LANGUAGE plpgsql AS
16+
$f$
17+
BEGIN
18+
INSERT INTO klage.latest_document_version AS lnv
19+
VALUES (NEW.document_id)
20+
ON CONFLICT(document_id) DO UPDATE SET current_version=NEW.version;
21+
RETURN NEW;
22+
END;
23+
$f$;
24+
25+
CREATE TRIGGER tg_document_version
26+
BEFORE INSERT
27+
ON klage.document_version
28+
FOR EACH ROW
29+
EXECUTE FUNCTION update_current_version();

src/test/kotlin/no/nav/klage/document/repositories/RepositoryTest.kt

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class RepositoryTest {
3232
@Autowired
3333
lateinit var documentVersionRepository: DocumentVersionRepository
3434

35+
@Autowired
36+
lateinit var latestDocumentVersionRepository: LatestDocumentVersionRepository
37+
3538
@Autowired
3639
lateinit var documentRepository: DocumentRepository
3740

@@ -192,7 +195,7 @@ class RepositoryTest {
192195
)
193196
)
194197

195-
val documentVersion = testEntityManager.persistAndFlush(
198+
testEntityManager.persistAndFlush(
196199
DocumentVersion(
197200
documentId = document.id,
198201
version = 1,
@@ -203,7 +206,7 @@ class RepositoryTest {
203206
)
204207
)
205208

206-
val documentVersion2 = testEntityManager.persistAndFlush(
209+
testEntityManager.persistAndFlush(
207210
DocumentVersion(
208211
documentId = document.id,
209212
version = 2,
@@ -228,52 +231,59 @@ class RepositoryTest {
228231
}
229232

230233
@Test
231-
fun `find next document version number`() {
234+
fun `latest version number is recorded`() {
232235
val now = LocalDateTime.now()
233236

234-
val document = testEntityManager.persistAndFlush(
237+
val document1 = testEntityManager.persistAndFlush(
235238
Document(
236239
created = now,
237240
modified = now,
238241
)
239242
)
240243

241-
testEntityManager.persistAndFlush(
242-
DocumentVersion(
243-
documentId = document.id,
244-
version = 1,
245-
authorNavIdent = "abc",
246-
json = "{}",
244+
val document2 = testEntityManager.persistAndFlush(
245+
Document(
247246
created = now,
248247
modified = now,
249248
)
250249
)
251-
testEntityManager.persistAndFlush(
252-
DocumentVersion(
253-
documentId = document.id,
254-
version = 3,
255-
authorNavIdent = "abc",
256-
json = "{}",
257-
created = now,
258-
modified = now,
250+
251+
val numberOfDocumenVersionsToCreateDocument1 = 10
252+
val numberOfDocumenVersionsToCreateDocument2 = 15
253+
254+
repeat(numberOfDocumenVersionsToCreateDocument1) {
255+
testEntityManager.persistAndFlush(
256+
DocumentVersion(
257+
documentId = document1.id,
258+
version = it + 1,
259+
authorNavIdent = "abc",
260+
json = "{}",
261+
created = now,
262+
modified = now,
263+
)
259264
)
260-
)
261-
testEntityManager.persistAndFlush(
262-
DocumentVersion(
263-
documentId = document.id,
264-
version = 2,
265-
authorNavIdent = "abc",
266-
json = "{}",
267-
created = now,
268-
modified = now,
265+
}
266+
267+
repeat(numberOfDocumenVersionsToCreateDocument2) {
268+
testEntityManager.persistAndFlush(
269+
DocumentVersion(
270+
documentId = document2.id,
271+
version = it + 1,
272+
authorNavIdent = "abc",
273+
json = "{}",
274+
created = now,
275+
modified = now,
276+
)
269277
)
270-
)
278+
}
271279

272280
testEntityManager.clear()
273281

274-
val latestVersionNumber = documentVersionRepository.findLatestVersionNumber(documentId = document.id)
282+
val latestVersionNumberDocument1 = latestDocumentVersionRepository.findById(document1.id).get().currentVersion
283+
val latestVersionNumberDocument2 = latestDocumentVersionRepository.findById(document2.id).get().currentVersion
275284

276-
assertThat(latestVersionNumber).isEqualTo(3)
285+
assertThat(latestVersionNumberDocument1).isEqualTo(numberOfDocumenVersionsToCreateDocument1)
286+
assertThat(latestVersionNumberDocument2).isEqualTo(numberOfDocumenVersionsToCreateDocument2)
277287
}
278288

279289
}

0 commit comments

Comments
 (0)