Chapter 6: Functional Programming
"Functional programming imposes discipline upon assignment."
๐ For New Learnersโ
The Oldest Paradigm, Adopted Lastโ
Functional programming is the oldest of the three paradigms โ rooted in Alonzo Church's ฮป-calculus (1936) โ yet the last to gain mainstream adoption. Its core idea: variables don't vary. Once a value is bound, it never changes. No assignment. No mutation.
This sounds restrictive. It is. But it eliminates an entire universe of bugs.
Why Immutability Matters for Architectureโ
Consider every race condition, concurrency bug, or unexpected state corruption you've debugged. What do they share?
Multiple threads competing to update the same mutable state.
If state never changes, there is nothing to compete over. Race conditions become impossible by design.
"All race conditions, deadlock conditions, and concurrent update problems are due to mutable variables."
Segregation of Mutabilityโ
Pure immutability is impractical โ you eventually write to databases, accept input, publish events. The solution is segregation:
- Keep the core of the system pure (no side effects)
- Isolate mutation at the boundaries
- Data crossing boundaries travels as immutable structures
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Pure Functional Core โ โ No mutable state
โ (transforms inputs โ outputs) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Mutable State Boundary โ โ DB writes, I/O
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Event Sourcingโ
Instead of storing current state (mutable), store the history of events (immutable). Current state is derived by replaying events.
- Only appends โ no updates or deletes
- Complete audit trail preserved
- State at any past moment is reproducible
- CRUD becomes CR
๐ฌ Senior Deep Diveโ
Immutable Value Objects in Javaโ
// Mutable โ dangerous to share
public class Money {
private BigDecimal amount;
public void setAmount(BigDecimal a) { this.amount = a; }
}
// Immutable record โ safe to share across threads and boundaries
public record Money(BigDecimal amount, Currency currency) {
public Money add(Money other) {
return new Money(this.amount.add(other.amount), this.currency);
}
}
Functional Pipelines vs Mutable Loopsโ
// Mutable โ shared mutable list, harder to parallelize safely
List<OrderSummary> results = new ArrayList<>();
for (Order o : orders) {
if (o.isActive()) results.add(new OrderSummary(o.getId(), o.getTotal()));
}
// Immutable pipeline โ pure transformations, parallelizable
List<OrderSummary> results = orders.stream()
.filter(Order::isActive)
.map(o -> new OrderSummary(o.getId(), o.getTotal()))
.toList();
Event Sourcing with Spring + Axonโ
// Immutable event โ a fact that happened
public record OrderPlacedEvent(String orderId, List<LineItem> items, Instant at) {}
@Aggregate
public class Order {
@AggregateIdentifier private String orderId;
private OrderStatus status;
@CommandHandler
public Order(PlaceOrderCommand cmd) {
apply(new OrderPlacedEvent(cmd.orderId(), cmd.items(), Instant.now()));
}
@EventSourcingHandler
public void on(OrderPlacedEvent event) {
this.orderId = event.orderId();
this.status = OrderStatus.PLACED;
}
}
The aggregate only publishes immutable events; state is always reconstructed from the event log โ never stored directly.
The Architectural Segregation Patternโ
Spring's @Transactional boundary is exactly the "mutable state fence":
Domain / Use Cases โ Pure: no side effects, fully unit-testable
โ (immutable DTOs / commands / events)
Adapters / Repositories โ @Transactional: where mutation is committed
โ
Database / Message Bus โ Where state actually persists
Everything inside the use case should be a pure function of its inputs. Spring infrastructure handles the commit.
Summaryโ
| Concept | Key Point |
|---|---|
| Immutability | No assignment โ no race conditions, no concurrent update bugs |
| Segregation | Isolate mutation at boundaries; keep core logic pure |
| Event Sourcing | Store events (immutable facts), derive state by replay |
| Java tools | Records, stream pipelines, Axon Framework, CQRS |
| Architecture lesson | Immutable data crossing boundaries prevents hidden coupling and state corruption |