Skip to content

lGiaroli/fraud-decision-engine-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fraud Decision Engine

Fraud Decision Engine is a portfolio-ready, local-first Java 21 + Spring Boot application that simulates how a fintech backend could evaluate transactions in real time. It accepts payment-like requests, applies configurable antifraud rules, routes through a versioned decision flow, calculates a risk score, returns a final decision, and persists the full audit trail needed to explain why the outcome happened.

The project stays intentionally practical:

  • one Spring Boot application
  • zero mandatory cloud services
  • H2 by default for frictionless local startup
  • PostgreSQL profile available for a more production-like run
  • a built-in browser dashboard for realistic demos without a separate frontend stack

Why this project is useful in a portfolio

This repo is designed to show backend engineering depth, not just API scaffolding.

It demonstrates:

  • clean package separation across HTTP, orchestration, rules, persistence, audit, and events
  • configurable rule weights, flags, thresholds, and versioned decision flow nodes
  • idempotent request handling with replay for duplicate transactionId submissions
  • transactional outbox persistence with a stable TransactionEvaluatedV1 schema
  • structured logs, correlation/trace propagation, Micrometer metrics, and latency percentiles
  • a polished local UX so the system can be explored like an internal risk tooling product

Feature Highlights

  • POST /api/v1/fraud/evaluate for real-time fraud evaluation
  • GET / modern local dashboard for demos, testing, and exploration
  • versioned rule configuration stored in the database
  • versioned decision flow stored in the database
  • idempotent duplicate handling with deterministic replay
  • conflict detection for reused transactionId with different payloads
  • H2 + Flyway local runtime, PostgreSQL profile ready
  • structured JSON errors
  • structured ECS-style logs
  • Actuator + Micrometer metrics
  • evaluation timing, per-rule timing, and latency percentile visibility
  • local outbox table and versioned event envelope
  • Postman collection and curl examples included

Repository Assets

Stack

  • Java 21
  • Spring Boot 3.5
  • Maven + Maven Wrapper
  • Spring Web
  • Spring Validation
  • Spring Data JPA
  • Flyway
  • H2 Database
  • PostgreSQL driver
  • Spring Boot Actuator
  • Micrometer
  • JUnit 5 + Mockito

Local Experience

The repo is meant to feel complete when cloned locally:

  • start the service with one command
  • open http://localhost:8080/
  • run scenarios through a modern SaaS-style dashboard
  • inspect decisions, events, configuration, health, and metrics
  • tweak runtime config in H2 without recompiling Java

There is no separate frontend build step. The dashboard is a static UI served directly by Spring Boot.

Architecture

The codebase is organized by responsibility:

  • controller: REST endpoints and UI entrypoint
  • dto: request/response contracts
  • application/service: orchestration, evaluation, queries, metrics, and caching
  • domain: core decision models and enums
  • flow: versioned decision flow models and execution trace
  • rules: isolated antifraud rule implementations
  • entity: JPA persistence models
  • repository: Spring Data + query repositories
  • mapper: mapping between entities, domain, and DTOs
  • audit: audit record creation and structured reasoning
  • event: event envelope, outbox payload, and publication metadata
  • exception: global error handling and custom exceptions
  • config: application configuration, seed data, and runtime wiring

High-level diagram

flowchart LR
    User[Browser / Postman / curl]

    subgraph App[Fraud Decision Engine]
        Dashboard[Static Dashboard]
        InfoApi[API Info]
        EvalApi[FraudEvaluationController]
        ConfigApi[FraudConfigurationController]
        EvalService[FraudEvaluationService]
        FlowEngine[DecisionFlowEngine]
        Rules[Fraud Rules]
        QueryServices[Decision / Event / Config Query Services]
        Audit[FraudAuditService]
        Metrics[Micrometer + Actuator]
        Outbox[FraudEventService]
        Cache[Local Cache]
    end

    subgraph DB[(H2 Local / PostgreSQL Profile)]
        Transactions[(transactions)]
        Decisions[(fraud_decisions)]
        Events[(fraud_events)]
        KnownDevices[(known_devices)]
        BlacklistedIps[(blacklisted_ips)]
        RuleConfigs[(rule_configurations)]
        Thresholds[(decision_threshold_configurations)]
        FlowConfigs[(decision_flow_configurations)]
        Flyway[(flyway_schema_history)]
    end

    User --> Dashboard
    User --> InfoApi
    User --> EvalApi
    User --> ConfigApi

    EvalApi --> EvalService
    ConfigApi --> QueryServices
    InfoApi --> QueryServices

    EvalService --> Cache
    EvalService --> FlowEngine
    FlowEngine --> Rules
    EvalService --> Audit
    EvalService --> Metrics
    EvalService --> Outbox
    EvalService --> Transactions
    EvalService --> Decisions
    Outbox --> Events
    QueryServices --> Events
    QueryServices --> Decisions
    QueryServices --> RuleConfigs
    QueryServices --> Thresholds
    QueryServices --> FlowConfigs
    Cache --> KnownDevices
    Cache --> BlacklistedIps
Loading

More implementation notes are available in docs/architecture.md.

Core Decision Logic

Implemented rules

The current seeded rule set includes:

  1. HIGH_AMOUNT
    • adds the configured weight when amount > amountThreshold
  2. NEW_DEVICE
    • adds the configured weight when the deviceId was not seen before for the same userId
  3. FOREIGN_COUNTRY
    • adds the configured weight when country != homeCountry
  4. RAPID_REPEAT_TRANSACTIONS
    • adds the configured weight when the user already crossed the configured velocity threshold in the configured time window
  5. BLACKLISTED_IP
    • adds the configured weight when the IP is found in the suspicious IP table
  6. MISSING_ATA_DOCUMENTATION
    • adds the configured weight when the decision flow routes an ATA transaction through documentary checks and the required flag is absent

Decision thresholds

  • score >= rejectThreshold -> REJECT
  • score >= reviewThreshold and < rejectThreshold -> REVIEW
  • score < reviewThreshold -> APPROVE

Dynamic decision flow

The system does not hardcode a single flat execution path anymore.

The active flow is versioned and stored in the database. The default seeded flow:

  1. routes by customerType
  2. sends ATA traffic through documentary checks first
  3. runs shared base fraud rules
  4. computes the final decision through score thresholds

That means customer-segment branching can evolve by changing the active flow configuration, without recompiling Java, as long as the engine is still using supported node types and existing rule identifiers.

Runtime Configuration

You can change behavior locally without touching code:

  • rule_configurations
    • enable or disable rules
    • change weights
    • edit rule parameters
  • decision_threshold_configurations
    • change review and reject thresholds
  • decision_flow_configurations
    • activate another flow version
    • replace the JSON definition for the active flow

Local H2 console:

  • http://localhost:8080/h2-console

Data Model

The project persists:

  • transactions
  • fraud_decisions
  • fraud_events
  • known_devices
  • blacklisted_ips
  • rule_configurations
  • decision_threshold_configurations
  • decision_flow_configurations

Flyway manages schema creation under src/main/resources/db/migration, and Hibernate runs in validation mode rather than mutating the schema on startup.

Running Locally

Start the application

Windows:

.\mvnw.cmd spring-boot:run

macOS / Linux:

./mvnw spring-boot:run

Then open:

  • Dashboard: http://localhost:8080/
  • API info: http://localhost:8080/api/v1/info
  • Health: http://localhost:8080/actuator/health
  • Metrics: http://localhost:8080/actuator/metrics
  • H2 Console: http://localhost:8080/h2-console

Run with PostgreSQL

.\mvnw.cmd spring-boot:run -Dspring-boot.run.profiles=postgres

Environment variables:

  • FRAUD_DB_URL
  • FRAUD_DB_USERNAME
  • FRAUD_DB_PASSWORD

Run tests

Windows:

.\mvnw.cmd test

macOS / Linux:

./mvnw test

Dashboard

The built-in dashboard is intentionally part of the product story, not an afterthought.

It includes:

  • scenario presets for approve, review, reject, ATA, and velocity cases
  • replay and conflict actions to demonstrate idempotency behavior
  • modern decision summary with risk score, reasons, latency, correlation id, and trace id
  • rule trace and decision flow trace pulled from the persisted TransactionEvaluatedV1 event
  • recent decisions and outbox events
  • operational snapshot cards for health, latency percentiles, and slowest rules
  • live view of active thresholds, rules, and decision flow nodes

This makes the repo far easier to demo than raw JSON alone while still keeping the architecture as a single backend app.

API Quick Start

Main endpoint

POST /api/v1/fraud/evaluate

Example request:

{
  "transactionId": "tx-1001",
  "userId": "user-55",
  "amount": 250000,
  "currency": "ARS",
  "merchantId": "MERC-01",
  "channel": "WEB",
  "country": "AR",
  "deviceId": "dev-888",
  "ipAddress": "192.168.0.10"
}

Example response:

{
  "transactionId": "tx-1001",
  "configurationVersion": 1,
  "flowId": "customer-segment-fraud-flow",
  "flowVersion": 1,
  "decision": "REVIEW",
  "riskScore": 50,
  "reasons": [
    "HIGH_AMOUNT",
    "NEW_DEVICE"
  ],
  "evaluatedAt": "2026-04-15T10:22:00",
  "evaluationTimeMs": 14
}

Optional routing fields:

  • customerType: defaults to STANDARD
  • documentFlags: optional object, for example { "ATA_DOCUMENTATION": false }

Useful endpoints

  • GET / -> dashboard
  • GET /api/v1/info -> project/about metadata and endpoint discovery
  • POST /api/v1/fraud/evaluate -> evaluate one transaction
  • GET /api/v1/fraud/decisions/{transactionId} -> fetch one persisted decision
  • GET /api/v1/fraud/decisions?page=0&size=20 -> list recent decisions
  • GET /api/v1/fraud/events/{eventId} -> fetch one stored outbox event
  • GET /api/v1/fraud/events?status=PENDING&page=0&size=20 -> list outbox events
  • GET /api/v1/fraud/transactions/{transactionId}/latest-event -> fetch latest event for one transaction
  • GET /api/v1/fraud/configuration/active -> inspect active thresholds, rules, and flow definition
  • GET /actuator/health -> health endpoint
  • GET /actuator/metrics -> discover metrics
  • GET /actuator/caches -> inspect local caches

curl Examples

Evaluate a transaction:

curl -X POST http://localhost:8080/api/v1/fraud/evaluate \
  -H "Content-Type: application/json" \
  -H "X-Correlation-Id: demo-curl-001" \
  -d '{
    "transactionId":"tx-1001",
    "userId":"user-55",
    "amount":250000,
    "currency":"ARS",
    "merchantId":"MERC-01",
    "channel":"WEB",
    "country":"AR",
    "deviceId":"dev-888",
    "ipAddress":"192.168.0.10"
  }'

Get a stored decision:

curl http://localhost:8080/api/v1/fraud/decisions/tx-1001

Get the latest event for a transaction:

curl http://localhost:8080/api/v1/fraud/transactions/tx-1001/latest-event

Inspect active configuration:

curl http://localhost:8080/api/v1/fraud/configuration/active

Check health:

curl http://localhost:8080/actuator/health

Inspect latency metrics:

curl http://localhost:8080/actuator/metrics/fraud.evaluation.duration

Observability and Audit

Each evaluation records:

  • the received request
  • the configuration version and flow version used
  • rule-by-rule evaluation details
  • final score and decision
  • evaluation duration
  • correlation id and trace id
  • a versioned outbox event

Structured JSON logs are enabled through Spring Boot structured logging support.

Micrometer / Actuator expose, among others:

  • fraud.evaluation.duration
  • fraud.evaluation.phase.duration
  • fraud.evaluation.total
  • fraud.evaluation.replay.total
  • fraud.rule.evaluations.total
  • fraud.rule.triggered.total
  • fraud.rule.duration
  • fraud.flow.execution.total
  • fraud.flow.node.executions.total

Latency percentiles are available for the evaluation timer:

  • p50
  • p95
  • p99

Performance

Performance is part of the project, not a footnote.

Built-in instrumentation covers:

  • total evaluation latency via evaluationTimeMs
  • per-rule execution time in microseconds
  • per-phase latency metrics
  • slow-rule and slow-evaluation structured warning logs
  • local caches for high-frequency lookups

Manual benchmark

.\mvnw.cmd "-Dfraud.benchmark.enabled=true" "-Dfraud.benchmark.iterations=500" "-Dfraud.benchmark.warmup=100" "-Dtest=FraudEvaluationBenchmarkTest" test

This benchmark is intentionally lightweight. It is useful as a local code-path baseline, not as an end-to-end SLA.

Performance choices already applied

  • Flyway-managed schema instead of ddl-auto=update
  • local cache for active configuration and frequent lookups
  • per-rule latency measurement
  • per-phase latency measurement
  • optimized raw SQL path for the rapid repeat lookup
  • simple transactional outbox kept local but future-ready

Event Schema and Outbox

Events are stored in fraud_events using a local transactional outbox pattern.

The persisted event envelope is stable and versioned:

{
  "metadata": {
    "eventId": "7d3d0c27-3167-4c35-8f2e-1a13cb0b7d5d",
    "eventType": "TransactionEvaluatedV1",
    "eventVersion": 1,
    "aggregateType": "TRANSACTION",
    "aggregateId": "tx-1001",
    "occurredAt": "2026-04-15T10:22:00",
    "correlationId": "corr-123",
    "traceId": "trace-456"
  },
  "payload": {
    "transactionId": "tx-1001",
    "userId": "user-55",
    "configurationVersion": 1,
    "flowId": "customer-segment-fraud-flow",
    "flowVersion": 1,
    "reviewThreshold": 40,
    "rejectThreshold": 70,
    "riskScore": 72,
    "decision": "REJECT",
    "reasons": ["HIGH_AMOUNT", "NEW_DEVICE"],
    "ruleEvaluations": [],
    "decisionFlowTrace": {
      "flowId": "customer-segment-fraud-flow",
      "flowVersion": 1,
      "steps": []
    },
    "evaluatedAt": "2026-04-15T10:22:00",
    "evaluationTimeMs": 14
  }
}

Current publication states:

  • PENDING
  • PUBLISHED
  • FAILED

Idempotency and Concurrency

  • the engine computes a deterministic fingerprint from the normalized request
  • same transactionId + same payload -> replay the persisted decision
  • same transactionId + different payload -> 409 Conflict
  • concurrent requests for the same transactionId are serialized inside this single-node app through an in-memory execution lock

This is a good local and monolith-friendly design, while still making the next production step obvious.

Tradeoffs

  • Monolith over microservices: chosen to keep local setup simple and keep the focus on backend design quality.
  • H2 over PostgreSQL by default: chosen for zero-friction startup, while PostgreSQL remains available as a profile.
  • Flyway over ddl-auto mutation: chosen so schema evolution is explicit and reviewable.
  • Database outbox over Kafka/RabbitMQ: chosen to keep async-readiness without introducing infrastructure noise.
  • Local cache over external cache infrastructure: chosen to improve hot-path lookups without adding Redis in the first version.
  • In-memory idempotency lock over distributed coordination: intentionally sufficient for a single-node demo, but not the final production answer.

What I'd Do Next In Production

  • move PostgreSQL to the default runtime and validate the rapid repeat query plan with production-like data
  • introduce a background outbox publisher and connect it to Kafka, SNS/SQS, or another transport
  • replace in-memory idempotency locking with a multi-instance-safe strategy
  • add authentication, authorization, and rate limiting
  • expose OpenAPI documentation
  • wire OpenTelemetry for distributed tracing export
  • add alerting and dashboards around latency, errors, replay rate, and slow rules
  • expand the fraud model with merchant risk, behavioral features, and manual review workflow
  • add load testing against a production-like database profile

About

Portfolio-ready fraud decision engine with a modern SaaS dashboard, versioned decision flows, idempotent replay, and observability-first backend design.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors