Skip to main content

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โ€‹

ConceptKey Point
ImmutabilityNo assignment โ†’ no race conditions, no concurrent update bugs
SegregationIsolate mutation at boundaries; keep core logic pure
Event SourcingStore events (immutable facts), derive state by replay
Java toolsRecords, stream pipelines, Axon Framework, CQRS
Architecture lessonImmutable data crossing boundaries prevents hidden coupling and state corruption