From cddde3b097822062d12c76b141d1be8ea4cf5d65 Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sat, 7 Feb 2026 22:13:28 +0100 Subject: [PATCH 1/4] chore: prepare release 2.1.5 --- CHANGELOG.md | 5 +++++ CODING_GUIDE.md | 2 +- README.md | 6 +++--- ai-tools/agents/jbct-coder.md | 4 ++-- ai-tools/skills/jbct/README.md | 2 +- articles/jbct-aether-series/00-aether-let-java-be-java.md | 0 book/TABLE_OF_CONTENTS.md | 2 +- book/ch01-introduction.md | 2 +- book/manuscript/ch01-introduction.md | 2 +- series/INDEX.md | 2 +- 10 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 articles/jbct-aether-series/00-aether-let-java-be-java.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be5d65..17cf401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.5] - 2026-02-07 + +### Changed +- Version bump for next development cycle + ## [2.1.4] - 2026-02-01 ### Changed diff --git a/CODING_GUIDE.md b/CODING_GUIDE.md index 2f53134..a928b2d 100644 --- a/CODING_GUIDE.md +++ b/CODING_GUIDE.md @@ -6,7 +6,7 @@ description: "Revolutionary technology for writing deterministic, AI-friendly, h # Java Backend Coding Technology: Writing Code in the Era of AI -**Version:** 2.1.4 | **Repository:** [github.com/siy/coding-technology](https://github.com/siy/coding-technology) | **Changelog:** [CHANGELOG.md](https://github.com/siy/coding-technology/blob/main/CHANGELOG.md) +**Version:** 2.1.5 | **Repository:** [github.com/siy/coding-technology](https://github.com/siy/coding-technology) | **Changelog:** [CHANGELOG.md](https://github.com/siy/coding-technology/blob/main/CHANGELOG.md) ## Introduction: Code in a New Era diff --git a/README.md b/README.md index f71a411..dec77b8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Java Backend Coding Technology -> **Version 2.1.4** | [Full Changelog](CHANGELOG.md) +> **Version 2.1.5** | [Full Changelog](CHANGELOG.md) A framework-agnostic methodology for writing predictable, testable Java backend code optimized for human-AI collaboration. @@ -231,7 +231,7 @@ void email_acceptsValidFormat() { ### Changelog & Versioning - **[CHANGELOG.md](CHANGELOG.md)** - Version history following [Keep a Changelog](https://keepachangelog.com/) - - Current version: 2.1.4 (2026-01-19) + - Current version: 2.1.5 (2026-02-07) - Golden formatting patterns, Pragmatica Lite 0.11.2, 36 lint rules - Semantic versioning for documentation releases @@ -392,6 +392,6 @@ If you find this useful, consider [sponsoring](https://github.com/sponsors/siy). --- -**Version:** 2.1.4 | **Last Updated:** 2026-01-19 | **[Full Changelog](CHANGELOG.md)** +**Version:** 2.1.5 | **Last Updated:** 2026-02-07 | **[Full Changelog](CHANGELOG.md)** **Copyright © 2025 Sergiy Yevtushenko. Released under the [MIT License](LICENSE).** diff --git a/ai-tools/agents/jbct-coder.md b/ai-tools/agents/jbct-coder.md index dc60cb7..36e7047 100644 --- a/ai-tools/agents/jbct-coder.md +++ b/ai-tools/agents/jbct-coder.md @@ -1,7 +1,7 @@ --- name: jbct-coder title: Java Backend Coding Technology Agent -description: Specialized agent for generating business logic code using Java Backend Coding Technology v2.1.4 with Pragmatica Lite Core 0.11.2. Produces deterministic, AI-friendly code that matches human-written code structurally and stylistically. Includes evolutionary testing strategy guidance. +description: Specialized agent for generating business logic code using Java Backend Coding Technology v2.1.5 with Pragmatica Lite Core 0.11.2. Produces deterministic, AI-friendly code that matches human-written code structurally and stylistically. Includes evolutionary testing strategy guidance. tools: Read, Write, Edit, MultiEdit, Grep, Glob, LS, Bash, TodoWrite, Task, WebSearch, WebFetch --- @@ -2018,7 +2018,7 @@ public class JooqUserRepository implements SaveUser { ## References -- **Full Guide**: `CODING_GUIDE.md` - Comprehensive explanation of all patterns and principles (v2.1.4) +- **Full Guide**: `CODING_GUIDE.md` - Comprehensive explanation of all patterns and principles (v2.1.5) - **Testing Strategy**: `series/part-05-testing-strategy.md` - Evolutionary testing approach, integration-first philosophy, test organization - **Systematic Application**: `series/part-10-systematic-application.md` - Checkpoints for coding and review - **API Reference**: `CLAUDE.md` - Complete Pragmatica Lite API documentation diff --git a/ai-tools/skills/jbct/README.md b/ai-tools/skills/jbct/README.md index a476e1a..a4244b7 100644 --- a/ai-tools/skills/jbct/README.md +++ b/ai-tools/skills/jbct/README.md @@ -89,7 +89,7 @@ Claude Code navigates to specific files as needed, optimizing context usage. ## Version -Based on Java Backend Coding Technology v2.1.4 +Based on Java Backend Coding Technology v2.1.5 ## License diff --git a/articles/jbct-aether-series/00-aether-let-java-be-java.md b/articles/jbct-aether-series/00-aether-let-java-be-java.md new file mode 100644 index 0000000..e69de29 diff --git a/book/TABLE_OF_CONTENTS.md b/book/TABLE_OF_CONTENTS.md index b7e99b0..86239c8 100644 --- a/book/TABLE_OF_CONTENTS.md +++ b/book/TABLE_OF_CONTENTS.md @@ -1,7 +1,7 @@ # Java Backend Coding Technology ## Unified Code Through Functional Composition -**Based on:** JBCT v2.1.4 | **Pragmatica Lite Core:** 0.11.2 +**Based on:** JBCT v2.1.5 | **Pragmatica Lite Core:** 0.11.2 --- diff --git a/book/ch01-introduction.md b/book/ch01-introduction.md index e3d583e..8a70607 100644 --- a/book/ch01-introduction.md +++ b/book/ch01-introduction.md @@ -1,6 +1,6 @@ # Chapter 1: Introduction - Code Unification -**Based on:** JBCT v2.1.4 | **Pragmatica Lite Core:** 0.11.2 +**Based on:** JBCT v2.1.5 | **Pragmatica Lite Core:** 0.11.2 ## What You'll Learn diff --git a/book/manuscript/ch01-introduction.md b/book/manuscript/ch01-introduction.md index e3d583e..8a70607 100644 --- a/book/manuscript/ch01-introduction.md +++ b/book/manuscript/ch01-introduction.md @@ -1,6 +1,6 @@ # Chapter 1: Introduction - Code Unification -**Based on:** JBCT v2.1.4 | **Pragmatica Lite Core:** 0.11.2 +**Based on:** JBCT v2.1.5 | **Pragmatica Lite Core:** 0.11.2 ## What You'll Learn diff --git a/series/INDEX.md b/series/INDEX.md index 10c5d0b..439f16e 100644 --- a/series/INDEX.md +++ b/series/INDEX.md @@ -1,6 +1,6 @@ # Java Backend Coding Technology: Complete Learning Series -**Based on:** [CODING_GUIDE.md](../CODING_GUIDE.md) v2.1.4 +**Based on:** [CODING_GUIDE.md](../CODING_GUIDE.md) v2.1.5 ## About This Series From d0bcea4653c83967080bacfc1747963fde1ec64d Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sat, 7 Feb 2026 22:41:56 +0100 Subject: [PATCH 2/4] docs: add interface + factory pattern justification to guide and series --- CODING_GUIDE.md | 6 +++ .../00-aether-let-java-be-java.md | 45 +++++++++++++++++++ series/part-06-advanced-patterns.md | 6 +++ series/part-09-production-systems.md | 6 +++ 4 files changed, 63 insertions(+) diff --git a/CODING_GUIDE.md b/CODING_GUIDE.md index a928b2d..2bbcf2a 100644 --- a/CODING_GUIDE.md +++ b/CODING_GUIDE.md @@ -2351,6 +2351,12 @@ public interface ProcessOrder { Four steps, each a single-method interface. The `execute()` body reads top-to-bottom: validate → reserve → process payment → confirm. Each step returns `Result`, so we chain with `flatMap`. If any step fails, the chain short-circuits and returns the failure. +**Why interface + factory?** Every component — use case, step, adapter — is defined as an interface with a static factory method. This is not arbitrary convention: + +- **Substitutability**: Anyone can implement the interface. Testing, stubbing incomplete implementations, swapping adapters — all work without framework magic or inheritance hierarchies. +- **Implementation isolation**: Each implementation is self-contained. No shared base classes, no abstract methods to override, no coupling between implementations. Each intersection between implementations is unnecessary coupling with corresponding maintenance overhead — up to needing deep understanding of two projects instead of one, with zero benefit. +- **Disposable implementation**: A local record or lambda returned by the factory can't be referenced externally. The implementation is replaceable by definition. The interface is the design artifact; the implementation is incidental. + Async example (same structure, different types): ```java public Promise execute(Request request) { diff --git a/articles/jbct-aether-series/00-aether-let-java-be-java.md b/articles/jbct-aether-series/00-aether-let-java-be-java.md index e69de29..49be066 100644 --- a/articles/jbct-aether-series/00-aether-let-java-be-java.md +++ b/articles/jbct-aether-series/00-aether-let-java-be-java.md @@ -0,0 +1,45 @@ +# Pragmatica Aether: Let Java Be Java + +We're building java applications like any other in any other language. Assembling fat jars, building docker images, deploying in kubernetes. This looks normal, +everyone does it. We're ignoring the fact, that this approach contradicts the basic principles of software engineering: proper abstraction layering and +separation of concerns. We're wearing each application into a heavy winter coat, arming it to the teeth to make it survive in the hostile environment. Why not just make +environment more friendly instead? Let the application handle business logic. Let the environment handle infrastructure. That's what Aether is all about. + +## Pragmatica Aether: Let Java Be Java +The core idea of Aether is to let application be focused on that matters: business logic. Scaling, unreliable connectivity, service discovery, retries, circuit breakers, configuration, +observability, logging, tracing, monitoring, security, etc., etc. are not application concerns and should not be handled at business logic level. + +As a side effect, Aether makes unnecessary fat application frameworks. You still "deep" application, but not into heavy winter coat of Spring Boot, but into friendly +environment of Aether. This immediately gives a number of benefits: +- Clean separation between business logic and infrastructure -> independent maintenance, simple testing, (TODO: more advantages) +- Instant deployment readiness: mvn package -> application is ready to run, no need to wait for docker image build +- Transparent scaling +- Consistent configuration, logging, tracing, observability +- Simple mental model: method call is just a method call +- No frameworks. No magic. No hidden complexity or processing/startup overhead. + +## Aether: Closer Look +Aether is a fault-tolerant distributed runtime environment, capable of dynamic loading and execution of Java applications. The application consists of one or more components called "slices". +In traditional designs like modular monolith, each slice could be considered a service. It has a defined contract in the form of an interface. But, unlike traditional service, slice +is also a minimal unit of deployment and scaling. For convenience, entire application is defined as a blueprint - declarative configuration of slices that can be deployed as a whole. +There are no operational or complexity tradeoffs, slice could be as lean as single method. Actually this is the best recommended approach for the Aether applications. It also +means that your application can be scaled with per use case granularity - feature unfeasible for microservices due to enormous operational overhead. + +From the infrastructure perspective, Aether is cluster of identical nodes. Since runtime and business logic are decoupled, application and node upgrades are independent of each other. +Fault-tolerant nature of Aether enables seamless upgrades and rollbacks without downtime. It also enables various advanced deployment scenarios and strategies like +transparent multi-cloud deployment, transition from on-premise to cloud and back, transition between cloud providers. + +## Aether: Scaling Done Right +Aether design inherently supports two levels of horizontal scaling: +- Slices scaling: new slice instance is started at one of the already running nodes. +- Node scaling: new node is added to the cluster. + +Aether has built-in configurable support for launching new nodes and replacing failed nodes. Fault tolerant nature makes it completely feasible to use cheaper +spot instances for additional scaling needs during peak loads. Using these features does not require PhD in distributed systems or team of DevOps engineers. + +But that's not all. Aether uses layered scaling decision design. At the lowest level, Aether uses decision tree to make almost instant scaling decision based on metrics. The next level is a simple +built-in predictive scaler, which observes patterns and tries to predict future load before it happens. And at the highest level, there is a configurable LLM-based scaling and cluster health monitoring. +It tries to predict long-term load patterns and monitors cluster health to make scaling and maintenance decisions. + + + diff --git a/series/part-06-advanced-patterns.md b/series/part-06-advanced-patterns.md index db51d29..2a2dffd 100644 --- a/series/part-06-advanced-patterns.md +++ b/series/part-06-advanced-patterns.md @@ -90,6 +90,12 @@ public interface ProcessOrder { Four steps, each a single-method interface. The `execute()` body reads top-to-bottom: validate → reserve → process payment → confirm. Each step returns `Result`, so we chain with `flatMap`. If any step fails, the chain short-circuits and returns the failure. +**Why interface + factory?** Every component — use case, step, adapter — is defined as an interface with a static factory method. This is not arbitrary convention: + +- **Substitutability**: Anyone can implement the interface. Testing, stubbing incomplete implementations, swapping adapters — all work without framework magic or inheritance hierarchies. +- **Implementation isolation**: Each implementation is self-contained. No shared base classes, no abstract methods to override, no coupling between implementations. Each intersection between implementations is unnecessary coupling with corresponding maintenance overhead — up to needing deep understanding of two projects instead of one, with zero benefit. +- **Disposable implementation**: A local record or lambda returned by the factory can't be referenced externally. The implementation is replaceable by definition. The interface is the design artifact; the implementation is incidental. + ### Async Example Same structure, different types: diff --git a/series/part-09-production-systems.md b/series/part-09-production-systems.md index 8ff56d8..32e7f8c 100644 --- a/series/part-09-production-systems.md +++ b/series/part-09-production-systems.md @@ -101,6 +101,12 @@ public interface RegisterUser { This is a **Sequencer pattern**: validate → check uniqueness → hash password → save → generate token. Five steps, clearly defined. +**Why interface + factory?** Every component — use case, step, adapter — is defined as an interface with a static factory method. This is not arbitrary convention: + +- **Substitutability**: Anyone can implement the interface. Testing, stubbing incomplete implementations, swapping adapters — all work without framework magic or inheritance hierarchies. +- **Implementation isolation**: Each implementation is self-contained. No shared base classes, no abstract methods to override, no coupling between implementations. Each intersection between implementations is unnecessary coupling with corresponding maintenance overhead — up to needing deep understanding of two projects instead of one, with zero benefit. +- **Disposable implementation**: A local record or lambda returned by the factory can't be referenced externally. The implementation is replaceable by definition. The interface is the design artifact; the implementation is incidental. + ### Step 2: Validated Request Nested record with the factory method: From b9cacc557ee0260164399e063e66e9d3f86068a0 Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sat, 7 Feb 2026 22:42:01 +0100 Subject: [PATCH 3/4] docs: add interface + factory pattern article for Medium/dev.to --- articles/interface-factory-pattern.md | 188 ++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 articles/interface-factory-pattern.md diff --git a/articles/interface-factory-pattern.md b/articles/interface-factory-pattern.md new file mode 100644 index 0000000..d4d2cc9 --- /dev/null +++ b/articles/interface-factory-pattern.md @@ -0,0 +1,188 @@ +--- +title: "Why Interface + Factory? The Java Pattern That Makes Everything Replaceable" +tags: [java, architecture, design-patterns, functional-programming] +canonical_url: https://pragmatica.dev/articles/interface-factory-pattern +published: true +description: "Interfaces with static factory methods aren't just convention. They make implementations disposable, testing trivial, and coupling impossible." +--- + +# Why Interface + Factory? The Java Pattern That Makes Everything Replaceable + +## The Pattern + +Every component — use case, processing step, adapter — is defined as an interface with a static factory method: + +```java +public interface ProcessOrder { + record Request(String orderId, String paymentToken) {} + record Response(OrderConfirmation confirmation) {} + + Result execute(Request request); + + interface ValidateInput { + Result apply(Request raw); + } + interface ReserveInventory { + Result apply(ValidRequest req); + } + interface ProcessPayment { + Result apply(Reservation reservation); + } + interface ConfirmOrder { + Result apply(Payment payment); + } + + static ProcessOrder processOrder(ValidateInput validate, + ReserveInventory reserve, + ProcessPayment processPayment, + ConfirmOrder confirm) { + return request -> validate.apply(request) + .flatMap(reserve::apply) + .flatMap(processPayment::apply) + .flatMap(confirm::apply); + } +} +``` + +Four steps. Each is a single-method interface. The factory method accepts all dependencies as parameters and returns a lambda implementing the use case. The body reads exactly like the business process: validate, reserve, process payment, confirm. + +This isn't arbitrary convention. There are three specific reasons this structure exists. + +## Reason 1: Substitutability Without Magic + +Anyone can implement the interface. No framework. No inheritance hierarchy. No annotations. + +Testing becomes trivial: + +```java +@Test +void order_fails_when_inventory_insufficient() { + var useCase = ProcessOrder.processOrder( + request -> Result.success(new ValidRequest(request)), // always valid + req -> INSUFFICIENT_INVENTORY.result(), // always fails + reservation -> { throw new AssertionError("unreachable"); }, + payment -> { throw new AssertionError("unreachable"); } + ); + + useCase.execute(new Request("order-1", "tok_123")) + .onSuccess(Assertions::fail); +} +``` + +No mocking framework. No `@Mock` annotations. No `when().thenReturn()` chains. The test constructs the exact scenario it needs with plain lambdas. + +Stubbing incomplete implementations during development is equally straightforward: + +```java +// Payment gateway isn't ready yet? Stub it. +var useCase = ProcessOrder.processOrder( + realValidator, + realInventoryService, + reservation -> Result.success(new Payment("stub-" + reservation.id(), Money.ZERO)), + realConfirmation +); +``` + +The team working on inventory doesn't need to wait for the payment team. Each step is independently implementable. + +## Reason 2: Implementation Isolation + +Each implementation is self-contained. No shared base classes. No abstract methods to override. No coupling between implementations whatsoever. + +Compare with the typical abstract class approach: + +```java +// The abstract class trap +public abstract class AbstractOrderProcessor { + protected final Logger log = LoggerFactory.getLogger(getClass()); + + public final Result execute(Request request) { + log.info("Processing order: {}", request.orderId()); + var result = doExecute(request); + log.info("Order result: {}", result); + return result; + } + + protected abstract Result doExecute(Request request); + protected abstract Result validate(Request request); + + // "Shared utility" that every subclass now depends on + protected Result calculateTotal(List items) { + // 47 lines of logic that one subclass needed once + } +} +``` + +Now every implementation is coupled to the base class. Change `calculateTotal` and you need to understand every subclass. Add logging to `execute` and every implementation gets it whether appropriate or not. The base class becomes a gravity well — accumulating shared code that creates invisible dependencies between implementations that should have nothing in common. + +With interface + factory, there is no shared implementation code. Period. Each intersection between implementations is unnecessary coupling with corresponding maintenance overhead — up to needing deep understanding of two projects instead of one, with zero benefit. + +```java +// Implementation A: uses database +static ProcessPayment databasePayment(PaymentRepository repo) { + return reservation -> repo.charge(reservation.paymentToken(), reservation.total()) + .map(Payment::fromRecord); +} + +// Implementation B: uses external API +static ProcessPayment stripePayment(StripeClient client) { + return reservation -> client.createCharge(reservation.total(), reservation.paymentToken()) + .map(Payment::fromStripe); +} +``` + +These implementations don't know about each other. They don't share code. They don't share a base class. They share a contract — the interface — and nothing else. + +## Reason 3: Disposable Implementation + +Here's the subtle one. The factory method returns a lambda or local record. It can't be referenced externally by class name. + +```java +static ProcessOrder processOrder(ValidateInput validate, + ReserveInventory reserve, + ProcessPayment processPayment, + ConfirmOrder confirm) { + return request -> validate.apply(request) // this lambda IS the implementation + .flatMap(reserve::apply) + .flatMap(processPayment::apply) + .flatMap(confirm::apply); +} +``` + +No code anywhere says `new ProcessOrderImpl()`. No code depends on the implementation class. The implementation is replaceable by definition — because nothing can reference it. + +The interface is the design artifact. The implementation is incidental. + +This might sound academic until you need to: +- Replace a synchronous implementation with an async one +- Swap a database adapter for an API adapter +- Add a caching layer around an existing step +- Completely rewrite a step's internals + +In each case, the interface stays. The factory method signature stays. The implementation — which nothing references — gets replaced. No downstream changes. No adapter layers. No "backwards compatibility." + +## The Compound Effect + +Each reason is valuable on its own. Together, they create a system where: + +**Testing is configuration.** You assemble the exact combination of real and stubbed components you need. No mocking framework overhead. No "mock everything" test fragility. + +**Refactoring is safe.** Replacing an implementation can't break other implementations because they don't share code. The compiler enforces the contract through the interface. + +**Complexity is bounded.** Understanding one implementation requires understanding only that implementation and the interfaces it consumes. Not a base class hierarchy. Not shared utilities. Not other implementations. + +**Incremental development is natural.** Stub what's not ready. Replace stubs with real implementations one at a time. Each step can be developed, tested, and deployed independently. + +## When Does This Not Apply? + +When there genuinely is one implementation and always will be. Pure utility functions, mathematical computations, simple data transformations — these don't need the interface + factory treatment. A static method is fine. + +The pattern pays for itself when there's any possibility of multiple implementations — including the test implementation, which almost always exists. + +## The Shift + +Most Java codebases default to classes. Interface extraction happens later, reluctantly, when testing forces it or when the second implementation appears. + +Flip this. Start with the interface. Define the contract. The implementation follows naturally — and when it needs to change, nothing else does. + +The interface is what you design. The implementation is what you happen to write today. From 06098e633216826f8f01d2203fcb99d409b969e3 Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Sat, 7 Feb 2026 22:43:51 +0100 Subject: [PATCH 4/4] docs: add interface + factory justification to book chapters --- book/ch08-advanced-patterns.md | 6 ++++++ book/ch12-registeruser-example.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/book/ch08-advanced-patterns.md b/book/ch08-advanced-patterns.md index afbf9b2..ed04d92 100644 --- a/book/ch08-advanced-patterns.md +++ b/book/ch08-advanced-patterns.md @@ -78,6 +78,12 @@ public interface ProcessOrder { Four steps, each a single-method interface. The `execute()` body reads top-to-bottom: validate -> reserve -> process payment -> confirm. Each step returns `Result`, so we chain with `flatMap`. If any step fails, the chain short-circuits and returns the failure. +**Why interface + factory?** Every component — use case, step, adapter — is defined as an interface with a static factory method. This is not arbitrary convention: + +- **Substitutability**: Anyone can implement the interface. Testing, stubbing incomplete implementations, swapping adapters — all work without framework magic or inheritance hierarchies. +- **Implementation isolation**: Each implementation is self-contained. No shared base classes, no abstract methods to override, no coupling between implementations. Each intersection between implementations is unnecessary coupling with corresponding maintenance overhead — up to needing deep understanding of two projects instead of one, with zero benefit. +- **Disposable implementation**: A local record or lambda returned by the factory can't be referenced externally. The implementation is replaceable by definition. The interface is the design artifact; the implementation is incidental. + ### Async Example Same structure, different types: diff --git a/book/ch12-registeruser-example.md b/book/ch12-registeruser-example.md index 4f852d5..d15fe73 100644 --- a/book/ch12-registeruser-example.md +++ b/book/ch12-registeruser-example.md @@ -85,6 +85,12 @@ public interface RegisterUser { This is a **Sequencer pattern**: validate -> check uniqueness -> create user -> save -> generate token. +**Why interface + factory?** Every component — use case, step, adapter — is defined as an interface with a static factory method. This is not arbitrary convention: + +- **Substitutability**: Anyone can implement the interface. Testing, stubbing incomplete implementations, swapping adapters — all work without framework magic or inheritance hierarchies. +- **Implementation isolation**: Each implementation is self-contained. No shared base classes, no abstract methods to override, no coupling between implementations. Each intersection between implementations is unnecessary coupling with corresponding maintenance overhead — up to needing deep understanding of two projects instead of one, with zero benefit. +- **Disposable implementation**: A local record or lambda returned by the factory can't be referenced externally. The implementation is replaceable by definition. The interface is the design artifact; the implementation is incidental. + --- ## Step 2: Validated Request