Skip to content

edvardFH/KataRefund

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KataRefund

Java Spring Boot Maven H2

A Spring Boot REST API built as a time-constrained coding challenge (< half a day). Demonstrates Clean Architecture (Onion pattern) across two bounded contexts: order history and refund requests. Domain highlights include Money arithmetic, evidence validation, and domain factory methods enforcing ownership rules.


The Challenge

Two user stories, delivered as a working, tested API:

Order history: As a customer, I can view the history of my orders. I can submit a refund request for a specific product.

Refund evidence: For refund requests, I must provide evidence: a photo and an issue description.


Architecture Overview

flowchart TD
    HTTP([HTTP Request])

    subgraph ADAPTER["ADAPTER (fr.kata.refund.adapters)"]
        OC[OrderController]
        RC[RefundController]
        P[JPA Repositories]
        EH[GlobalExceptionHandler]
    end

    subgraph APPLICATION["APPLICATION (fr.kata.refund.application)"]
        OS[CustomerOrderService]
        RS[CustomerRefundService]
        OR[OrderRepository interface]
        RR[RefundRepository interface]
    end

subgraph DOMAIN["DOMAIN (fr.kata.refund.domain)"]
O[Order aggregate]
RF[RefundRequest aggregate]
M[Money · RefundEvidence]
VO[OrderId · ProductId · CustomerId]
end

HTTP --> OC
HTTP --> RC
OC --> OS
RC --> RS
OS --> OR
RS --> RR
OS --> O
RS --> RF
OR -.->|implemented by|P
RR -.->|implemented by|P

style ADAPTER fill: #dbeafe,stroke: #3b82f6
style APPLICATION fill: #dcfce7,stroke: #22c55e
style DOMAIN fill: #fef9c3,stroke: #eab308
Loading

Each layer depends only inward. The domain has no knowledge of Spring, JPA, or HTTP. Both bounded contexts (Order, Refund) share the same layered structure and dependency rule.


Clean Architecture: Layer Independence

Layer Package Responsibility
ADAPTER adapters/controllers/order Order endpoints, DTOs
ADAPTER adapters/controllers/refund Refund endpoints, evidence DTOs
ADAPTER adapters/persistence JPA entities, mappers, JpaOrderRepository, JpaRefundRepository
APPLICATION application/order CustomerOrderService, OrderRepository port
APPLICATION application/refund CustomerRefundService, RefundRepository port
DOMAIN domain/order Order aggregate, OrderItem, OrderStatus, Money
DOMAIN domain/refund RefundRequest aggregate, RefundState, RefundEvidence, PhotoEvidence
CONFIG config ApplicationConfig: Spring bean wiring only

Domain proof: Money enforces monetary rules, zero framework dependency:

// DOMAIN layer: no Spring, no JPA
public record Money(BigDecimal amount, Currency currency) {
    public Money {
        if (amount == null || currency == null) throw new DomainException("Money amount/currency must be provided");
        if (amount.scale() > 2) amount = amount.setScale(2, RoundingMode.HALF_UP);
        if (amount.compareTo(BigDecimal.ZERO) < 0) throw new DomainException("Money amount must be >= 0");
    }

    public Money add(Money other) {
        if (!currency.equals(other.currency)) throw new DomainException("Currency mismatch");
        return new Money(amount.add(other.amount), currency);
    }
}

Currency mismatch, negative amounts, and rounding are domain rules, not DTO annotations.


Architecture Decisions

ADR-01: Clean Architecture (Onion pattern)

Context: Two bounded contexts (Order + Refund) risked coupling business logic to Spring/JPA if no boundary was enforced.

Decision: Three concentric layers (ADAPTER → APPLICATION → DOMAIN), dependencies inward only. Domain layer imports zero framework classes.

Consequence: Both aggregates are testable without a Spring context. Infrastructure is swappable without touching business rules.


ADR-02: Manual bean wiring in config package

Context: Using @Service / @Component in the application layer would introduce Spring coupling where there should be none.

Decision: ApplicationConfig instantiates CustomerOrderService and CustomerRefundService explicitly and injects JPA adapters.

Consequence: Application and domain layers remain framework-agnostic. The config package is the only place that knows both sides.


ADR-03: Money as a Value Object with arithmetic guard

Context: Order total computation and line-item pricing require monetary arithmetic. Using double or raw BigDecimal primitives scattered across the codebase leads to silent rounding errors and cross-currency bugs.

Decision: Money(BigDecimal amount, Currency currency) encapsulates rounding (2 decimal places), non-negativity, and currency coherence. add() and multiply() enforce same-currency constraint at call time.

Consequence: No cross-currency arithmetic can silently succeed. Rounding policy is declared once in the domain.


ADR-04: RefundRequest.submit() as a domain factory

Context: Submitting a refund requires two business invariants: the requester must own the order, and the product must exist in that order. Enforcing these in the service layer leaks domain rules into the application layer.

Decision: RefundRequest.submit(id, order, productId, requester, evidence, now) is a static factory on the aggregate. It verifies ownership and product membership before constructing the record.

Consequence: Invariants are co-located with the aggregate they protect. The service layer delegates rule enforcement to the domain and catches DomainException to re-throw as BusinessRuleViolationException (409).


Getting Started

Prerequisites: JDK 21, Maven 3.9+

git clone <repo-url>
cd KataRefund
mvn spring-boot:run

Verify with the Swagger UI: http://localhost:8080/swagger-ui/index.html

All endpoints require an X-Customer-Id header (missing header → 401).


Tech Stack

Concern Technology
Language Java 21
Framework Spring Boot 3
Build Maven 3.9
Database H2 (in-memory)
ORM Spring Data JPA
API docs OpenAPI / Swagger UI

Testing

mvn test
  • domain/: pure unit tests, no Spring context
  • application/: InMemoryOrderRepository and InMemoryRefundRepository stubs, no Spring context
  • KataRefundApplicationTests: Spring Boot context smoke test

API

Interactive documentation: Swagger UI (requires a local running instance)

  • GET /api/v1/orders — list orders for the authenticated customer
  • POST /api/v1/refunds — submit a refund request with photo evidence

All endpoints require X-Customer-Id header. Missing header → 401 Unauthorized.

About

Spring Boot REST API · Clean Architecture (Onion) · two bounded contexts: order history and refund requests · Java 21

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages