Skip to content

feat(ledger): add rest api for accounts and transactions - #106

Merged
tiana-code merged 2 commits into
mainfrom
feat/E-01-rest-api
Jun 14, 2026
Merged

feat(ledger): add rest api for accounts and transactions#106
tiana-code merged 2 commits into
mainfrom
feat/E-01-rest-api

Conversation

@tiana-code

Copy link
Copy Markdown
Owner

What

Thin REST edge over the existing ledger application services (closes E-01 backend).

  • POST /v1/accounts, GET /v1/accounts/{id}, GET /v1/accounts/{id}/balance
  • POST /v1/transactions
  • Request/response DTOs (data class + Jakarta validation), prefixed-ULID ids on the wire
  • IdempotencyFilter (Idempotency-Key guard + body buffering) reusing IdempotencyService
  • RFC 7807 application/problem+json mapping: 404 / 409 (duplicate, idempotency) / 422 (double-entry, currency, domain) / 503 (concurrency, Retry-After) / 400
  • JWT resource-server security, actor = jwt.subject; swagger + api-docs + health permitted
  • Springdoc OpenAPI document (BSL-1.1 license)

Design notes

  • Idempotency boundary: IdempotencyStore.runOrReplay is @Transactional and runs the action inside the tx, which the write services join (atomic exactly-once). A filter wrapping the whole dispatch would dead-end on rollback-only marking after @RestControllerAdvice converts an inner exception, so the filter only guards the header and buffers the body; execute() is called in the controller (inside the tx-joining call stack), where exceptions propagate cleanly to the advice and leave no idempotency row.
  • No value classes as controller params / MapStruct: Kotlin unboxes value-class method params in the JVM signature (breaks MVC binding) and mangles value-class getters/constructors (breaks MapStruct, same as Add MapStruct mappers (entity to domain, entity to DTO) #33). The key crosses the filter as a String; DTO mapping is a hand @Component mapper consistent with the persistence adapters.

Tests

  • @WebMvcTest slices for both controllers (status/mapping/idempotency/validation/auth), IdempotencyFilter unit test, mapper test. 118 unit tests green locally.
  • LedgerApiSmokeIT (CI-only, Testcontainers): full context loads, /v3/api-docs served, idempotent replay returns identical bytes, unauthenticated -> 401.

Transaction get/reverse deferred to #105.

Closes #35
Closes #36
Closes #37
Closes #40
Closes #43

Thin controllers over the existing application services: create, get and
balance for accounts and post for transactions. Adds request/response DTOs
with bean validation, an Idempotency-Key servlet filter that reuses
IdempotencyService, RFC 7807 problem responses mapping domain conflicts to
409 and invariant violations to 422, JWT resource-server security with the
subject as actor, and a served OpenAPI document.

Transaction get and reverse are deferred to #105 (need new service methods).

Closes #35
Closes #36
Closes #37
Closes #40
Closes #43
Comment thread services/ledger/src/main/kotlin/com/fincore/ledger/config/SecurityConfig.kt Dismissed
response_body is a jsonb column normalized by postgres, so replay is logically
identical rather than byte-identical; compare parsed json trees.
@tiana-code
tiana-code merged commit d034825 into main Jun 14, 2026
6 checks passed
@tiana-code
tiana-code deleted the feat/E-01-rest-api branch June 14, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants