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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 34 additions & 24 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,29 +131,40 @@ Invoke-WebRequest http://localhost:8545 -Method Post -ContentType "application/j

## Project Structure

```text
backend/
|- common/
| |- event-model/
| `- shared-contracts/
|- command-service/
|- event-store-service/
|- audit-writer-service/
`- query-service/

blockchain/
|- contracts/
|- scripts/
`- test/

deploy/
|- docker-compose.yml
`- init-db.sql

docs/
|- ARCHITECTURE.md
|- CQRS_FLOW.md
`- DEPLOYMENT.md
```plantuml
@startuml
top to bottom direction

folder "distributed-audit-ledger/" as Repo {
folder "backend/" as Backend {
folder "common/" as Common {
folder "event-model/" as EventModel
folder "shared-contracts/" as SharedContracts
}
folder "command-service/" as Command
folder "event-store-service/" as EventStore
folder "audit-writer-service/" as AuditWriter
folder "query-service/" as Query
}

folder "blockchain/" as Blockchain {
folder "contracts/" as Contracts
folder "scripts/" as Scripts
folder "test/" as Tests
}

folder "deploy/" as Deploy {
file "docker-compose.yml" as Compose
file "init-db.sql" as InitDb
}

folder "docs/" as Docs {
file "ARCHITECTURE.md" as Arch
file "CQRS_FLOW.md" as Cqrs
file "DEPLOYMENT.md" as Deployment
}
}
@enduml
```

---
Expand Down Expand Up @@ -320,4 +331,3 @@ docker compose -f <repo-root>\deploy\docker-compose.yml logs ganache
- [ ] Tests added or updated for behavior changes.
- [ ] `mvn verify` (backend) and `npm test` (blockchain) pass locally.
- [ ] Docs updated when architecture/workflow changed.

54 changes: 23 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,29 @@ A distributed, event‑sourced audit platform built on CQRS, Event Sourcing, Rea

## 🏛 High‑Level Architecture

```mermaid
graph LR
Client["🌐 Client (Angular UI)"]

Client -->|"POST /commands/user/login"| CmdService["📤 Command Service (8081)"]

CmdService -->|"Kafka: user.login.events"| Kafka["🔄 Kafka"]

Kafka -->|Consumer: event-store-consumer| EventStore["📊 Event Store Service (8082)<br/>- Canonical hash<br/>- PostgreSQL audit.events<br/>- Flyway migrations"]
Kafka -->|Consumer: audit-writer-consumer| AuditWriter["⛓️ Audit Writer Service (8083)<br/>- Canonical hash<br/>- Web3j AuditLedger<br/>- DLT: user.login.events.dlt"]

EventStore -->|Persisted events| Postgres["🐘 PostgreSQL<br/>audit.events"]
Blockchain["🔐 Smart Contract<br/>AuditLedger (Ganache)"]
AuditWriter -->|appendAuditRecord| Blockchain

QueryService["📖 Query Service (8084)<br/>- GET /api/audit-logs<br/>- GET /api/audit-logs/{id}<br/>- GET /api/audit-logs/{id}/integrity-check"]

AuditWriter -.->|"Read for integrity"| Blockchain
Postgres -->|Read Models| QueryService
Blockchain -->|Hash Verification| QueryService

Client -->|"GET /api/audit-logs"| QueryService

style Client fill:#e1f5ff
style CmdService fill:#fff3e0
style EventStore fill:#f3e5f5
style AuditWriter fill:#fce4ec
style QueryService fill:#e8f5e9
style Blockchain fill:#ffe0b2
style Postgres fill:#e3f2fd
style Kafka fill:#f0f4c3
```plantuml
@startuml
left to right direction

rectangle "Client" as Client
rectangle "command-service\n8081" as CommandService
queue "Kafka\ntopic user.login.events" as Kafka
rectangle "event-store-service\n8082" as EventStore
rectangle "audit-writer-service\n8083" as AuditWriter
database "PostgreSQL\naudit.events" as Postgres
rectangle "Ganache\nAuditLedger" as Blockchain
rectangle "query-service\n8084" as QueryService

Client --> CommandService : POST /commands/user/login
CommandService --> Kafka : publish event
Kafka --> EventStore : event-store-consumer
Kafka --> AuditWriter : audit-writer-consumer
EventStore --> Postgres : persist payload and event_hash
AuditWriter --> Blockchain : appendAuditRecord
Client --> QueryService : GET /api/audit-logs
Postgres --> QueryService : read models
Blockchain --> QueryService : integrity verification
@enduml
```

Full architecture details are available in [**docs/ARCHITECTURE.md**](docs/ARCHITECTURE.md) and [**docs/CQRS_FLOW.md**](docs/CQRS_FLOW.md).
Expand Down
45 changes: 23 additions & 22 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,28 +80,29 @@ The diagram below shows the **target integration flow** for upcoming backend iss
(`#5` and beyond). In this PR (`#4`) only service skeletons and shared modules are
bootstrapped.

```
Client
▼ command API (planned)
command-service (8081)
│ publishes UserLoggedInEvent
Kafka topic: user.login.events
│ │
▼ ▼
event-store audit-writer
-service -service
(8082) (8083)
│ persists │ appendAuditRecord()
▼ to Postgres ▼
audit.events Ganache
│ blockchain
query-service (8084)
│ query API (planned)
Angular UI
```plantuml
@startuml
top to bottom direction

rectangle "Client / Angular UI" as Client
rectangle "command-service (8081)" as Cmd
queue "Kafka: user.login.events" as Kafka
rectangle "event-store-service (8082)" as ES
rectangle "audit-writer-service (8083)" as AW
database "PostgreSQL audit.events" as DB
rectangle "Ganache / AuditLedger.sol" as BC
rectangle "query-service (8084)" as Q

Client --> Cmd : command API
Cmd --> Kafka : UserLoggedInEvent
Kafka --> ES
Kafka --> AW
ES --> DB : persist
AW --> BC : appendAuditRecord
DB --> Q
BC --> Q
Q --> Client : query API
@enduml
```

## Issue tracker
Expand Down
23 changes: 14 additions & 9 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,20 @@ curl -s -X POST http://localhost:8545 \

The schema is applied automatically from `init-db.sql` on first start.

```
audit.events
├── id BIGSERIAL PK
├── aggregate_id VARCHAR(128)
├── event_type VARCHAR(128)
├── user_id VARCHAR(255)
├── payload JSONB
├── event_hash VARCHAR(64)
└── created_at TIMESTAMP
```plantuml
@startuml
entity "audit.events" as AUDIT_EVENTS {
* id : BIGSERIAL
--
event_id : VARCHAR(36) UNIQUE
aggregate_id : VARCHAR(128)
event_type : VARCHAR(128)
user_id : VARCHAR(255)
payload : JSONB
event_hash : VARCHAR(64)
created_at : TIMESTAMP
}
Comment thread
igorsatsyuk marked this conversation as resolved.
@enduml
```

## Environment Variables
Expand Down
132 changes: 61 additions & 71 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,31 @@ This document describes the **Distributed Audit Ledger** system architecture usi

## System Overview Diagram

```
┌─────────────────────────────────────────────────────────────────────┐
│ Client Applications │
│ (Angular UI / REST API) │
└──────────────┬──────────────────────────────────────────────┬────────┘
│ │
WRITE SIDE READ SIDE
│ │
┌────────▼────────┐ ┌────────▼───────┐
│ command-service │ │ query-service │
│ (PORT 8081) │ │ (PORT 8084) │
│ WebFlux API │ │ WebFlux API │
└────────┬────────┘ │ │
│ │ ┌─────────────┐│
│ Kafka Message Flow │ │ Read Views ││
│ │ │(PostgreSQL) ││
│ ┌──────────────────┐ │ └─────────────┘│
│ ▼ ▼ │ │
┌─────────────┐ ┌──────────────┐ │ /api/audit-logs│
│ Event │ │ Audit │ │ /api/audit-logs│
│ Store │ │ Writer │ │ /{id}/integrity-check│
│ Service │ │ Service │ └────────────────┘
│ (PORT 8082) │ │ (PORT 8083) │
│ WebFlux + │ │ Web3j Client │
│ R2DBC │ │ + Ganache │
└──────┬──────┘ └──────┬───────┘
│ user.login.events topic │
│ │
│ Persists │ Anchors
│ Event Hash │ Hash
│ │
┌──────▼──────────┐ ┌──────▼──────────┐
│ PostgreSQL │ │ Ganache RPC │
│ audit.events │ │ AuditLedger.sol │
│ ├── id │ │ (Blockchain) │
│ ├── event_id │ │ │
│ ├── payload │ │ ┌─────────────┐ │
│ ├── event_hash │ │ │Record Store │ │
│ └── created_at │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘
```plantuml
@startuml
left to right direction

rectangle "Client Applications" as Client
rectangle "command-service\n8081" as CommandService
queue "Kafka\ntopic user.login.events" as Kafka
rectangle "event-store-service\n8082" as EventStore
rectangle "audit-writer-service\n8083" as AuditWriter
database "PostgreSQL\naudit.events" as Postgres
rectangle "AuditLedger on Ganache" as Blockchain
rectangle "query-service\n8084" as QueryService
rectangle "GET audit logs\nand integrity-check" as ReadApi

Client --> CommandService : POST /commands/user/login
CommandService --> Kafka : publish event
Kafka --> EventStore : event-store-consumer
Kafka --> AuditWriter : audit-writer-consumer
EventStore --> Postgres : persist canonical event_hash
AuditWriter --> Blockchain : anchor hash
Client --> QueryService : GET audit APIs
Postgres --> QueryService : read models
Blockchain --> QueryService : verify hash
QueryService --> ReadApi
@enduml
```

## Core Pattern
Expand Down Expand Up @@ -99,24 +84,25 @@ The platform implements a **CQRS + Event Sourcing** architecture with blockchain

### Event Topic

```
Topic: user.login.events
├── Partition Key: event.eventId (stable partition mapping per event record)
│ └─ Note: this does not provide per-user ordering across multiple events
├── Schema: UserLoggedInEvent (shared from event-model)
├── Consumers:
│ ├── event-store-service (group: event-store-consumer)
│ │ └─ Writes to audit.events with computed event_hash
│ │
│ └── audit-writer-service (group: audit-writer-consumer)
│ ├─ Computes SHA-256 hash (using CanonicalObjectMapperFactory)
│ ├─ Writes to blockchain via AuditLedger.appendAuditRecord()
│ ├─ Retries via DefaultErrorHandler backoff
│ └─ DLT only for recoverable/terminal failures
└── DLT: user.login.events.dlt
- Not all failures go to DLT:
- BlockchainNotConfiguredException -> rethrown, offset remains uncommitted
- ReceiptTimeoutException -> rethrown, offset remains uncommitted
```plantuml
@startuml
top to bottom direction

queue "user.login.events" as Topic
rectangle "event-store-service\nconsumer" as EventStoreConsumer
rectangle "audit-writer-service\nconsumer" as AuditWriterConsumer
database "PostgreSQL audit.events" as Db
rectangle "AuditLedger" as Contract
queue "user.login.events.dlt" as Dlt
rectangle "Rethrow\n(offset uncommitted)" as Rethrow

Topic --> EventStoreConsumer
Topic --> AuditWriterConsumer
EventStoreConsumer --> Db : write payload and event_hash
AuditWriterConsumer --> Contract : appendAuditRecord
AuditWriterConsumer --> Dlt : recoverable or terminal failures
AuditWriterConsumer --> Rethrow : BlockchainNotConfiguredException\nor ReceiptTimeoutException
@enduml
```

### Database Schema
Expand Down Expand Up @@ -194,18 +180,23 @@ function isHashExists(bytes32 _hash) public view returns (bool) {

All backend services are **reactive-first** with Spring WebFlux + Project Reactor, with controlled blocking in Kafka listeners where offset semantics require completion guarantees:

```
Client Request
WebFlux Controller (non-blocking)
Service Layer (Mono<T> / Flux<T>)
R2DBC Repository (async DB queries)
Connection Pool (reactive driver)
PostgreSQL
```plantuml
@startuml
top to bottom direction

rectangle "Client Request" as A
rectangle "WebFlux Controller\nnon-blocking" as B
rectangle "Service Layer\nMono/Flux" as C
rectangle "R2DBC Repository\nasync queries" as D
rectangle "Reactive Connection Pool" as E
database "PostgreSQL" as F

A --> B
B --> C
C --> D
D --> E
E --> F
@enduml
```

- **R2DBC** (Reactive Relational Database Connectivity) replaces JPA
Expand Down Expand Up @@ -235,4 +226,3 @@ PostgreSQL
5. **Reactive Stack**: WebFlux + R2DBC minimize thread context switches and connection pool pressure
6. **Canonical JSON**: Deterministic serialization (sorted fields) ensures DB and blockchain hashes match
7. **Dead-Letter Topic**: DLT is used for recoverable/terminal errors; configuration/receipt-timeout failures are rethrown to keep source offsets uncommitted

Loading