<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sergiy Yevtushenko</title>
    <description>The latest articles on Forem by Sergiy Yevtushenko (@siy).</description>
    <link>https://forem.com/siy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F145374%2F6e93547b-45cc-430e-9659-3adea03266b5.jpeg</url>
      <title>Forem: Sergiy Yevtushenko</title>
      <link>https://forem.com/siy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/siy"/>
    <language>en</language>
    <item>
      <title>Java Backend Design Technology: A Process-First Methodology</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Mon, 13 Apr 2026 21:53:25 +0000</pubDate>
      <link>https://forem.com/siy/java-backend-design-technology-a-process-first-methodology-2p4m</link>
      <guid>https://forem.com/siy/java-backend-design-technology-a-process-first-methodology-2p4m</guid>
      <description>&lt;h1&gt;
  
  
  Java Backend Design Technology: A Process-First Methodology
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;From Requirements to Code in Eight Questions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every backend system is a collection of processes, not a collection of entities.&lt;/p&gt;

&lt;p&gt;This sounds obvious when stated plainly. Yet the dominant design methodology for twenty years has been entity-first: identify User, Order, Product. Define their attributes. Attach behavior. Build services that operate on shared objects. The result is a system where every feature is coupled to every other feature through shared data models — and where every new requirement triggers an architecture discussion.&lt;/p&gt;

&lt;p&gt;A different approach is gaining traction. Independent practitioners across five languages — F#, Rust, Java, Scala, .NET — have independently converged on the same structural insight: start with behavior, derive data. Design around what the system &lt;em&gt;does&lt;/em&gt;, not what it &lt;em&gt;stores&lt;/em&gt;. Six of them are documented in &lt;a href="https://dev.to/siy/the-quiet-consensus-5hhk"&gt;The Quiet Consensus&lt;/a&gt;, but the pattern extends far beyond those six.&lt;/p&gt;

&lt;p&gt;Structured programming eliminated goto debates by making control flow mechanical. This methodology does the same for design: it makes most architectural decisions mechanical, determined by the problem rather than by the developer's preferences.&lt;/p&gt;

&lt;p&gt;This article presents &lt;strong&gt;Java Backend Design Technology&lt;/strong&gt; (JBDT) — the design phase of &lt;a href="https://pragmatica.dev/" rel="noopener noreferrer"&gt;Java Backend Coding Technology&lt;/a&gt; (JBCT). It's a concrete, repeatable process for going from requirements to code structure. No entity diagrams. No aggregate boundaries. No architecture review boards. Just eight questions and the types that emerge from the answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Questions Framework
&lt;/h2&gt;

&lt;p&gt;For any feature, ask these eight questions. The answers produce the code structure mechanically.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;What triggers this process?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What data does it need?&lt;/strong&gt; — this becomes the Request record&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does success look like?&lt;/strong&gt; — this becomes the Response record&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What can go wrong?&lt;/strong&gt; — these become error types (sealed interface with enum for fixed messages, records for contextual errors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What are the steps?&lt;/strong&gt; — these become step interfaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which steps depend on each other?&lt;/strong&gt; — dependencies become sequential chains; independent steps become parallel operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are there conditional paths?&lt;/strong&gt; — these become branching logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is there collection processing?&lt;/strong&gt; — this becomes iteration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the entire design process. No other questions need answering. Pattern selection, type placement, error strategy — all determined by these eight answers.&lt;/p&gt;

&lt;p&gt;Let's see it work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Applying the Framework: Place an Order
&lt;/h2&gt;

&lt;p&gt;Requirements: a customer places an order with items, a shipping address, and a payment method. The system checks inventory, processes payment, creates the order, and sends confirmation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 1 — What triggers this?&lt;/strong&gt; A customer submits an order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 2 — What data does it need?&lt;/strong&gt; Customer ID, list of items with quantities, shipping address, payment method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
               &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;shippingAddress&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentMethod&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Question 3 — What does success look like?&lt;/strong&gt; An order confirmation with an ID and estimated delivery.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;estimatedDelivery&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Question 4 — What can go wrong?&lt;/strong&gt; Invalid inputs, insufficient inventory, payment declined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrderError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Cause&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;General&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrderError&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;EMPTY_CART&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cart is empty"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="no"&gt;INVALID_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Shipping address is invalid"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;InsufficientInventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrderError&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;PaymentDeclined&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrderError&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Question 5 — What are the steps?&lt;/strong&gt; Validate the request. Check inventory. Process payment. Create order. Send confirmation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 6 — Which steps depend on each other?&lt;/strong&gt; Validation must happen first. Inventory check and payment processing are independent of each other. Order creation depends on both succeeding. Confirmation depends on the order being created.&lt;/p&gt;

&lt;p&gt;This directly produces the composition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrder&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CheckInventory&lt;/span&gt; &lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;CreateOrder&lt;/span&gt; &lt;span class="n"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;SendConfirmation&lt;/span&gt; &lt;span class="n"&gt;sendConfirmation&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ValidRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;reserveOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;createOrder:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;sendConfirmation:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReservedOrder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;reserveOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CheckInventory&lt;/span&gt; &lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="nc"&gt;ValidRequest&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                       &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ReservedOrder:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Questions 7 and 8&lt;/strong&gt; — no conditional paths or collection processing in the main flow.&lt;/p&gt;

&lt;p&gt;Notice what happened. We didn't draw a class diagram. We didn't debate which aggregate owns what. We didn't create a shared &lt;code&gt;Order&lt;/code&gt; entity used by every feature. We asked eight questions, wrote down the answers, and the code structure emerged.&lt;/p&gt;

&lt;p&gt;Notice also: &lt;code&gt;ValidateCart&lt;/code&gt;, &lt;code&gt;ProcessPayment&lt;/code&gt;, &lt;code&gt;InsufficientInventory&lt;/code&gt;, &lt;code&gt;PaymentDeclined&lt;/code&gt; — these are exactly the words a domain expert would use when describing this process. The shared vocabulary between developers and business emerged directly from the design process, without dedicated modeling sessions. This is DDD's ubiquitous language in practice — emerging naturally rather than being constructed.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Second Example: Publish Article
&lt;/h2&gt;

&lt;p&gt;To show this isn't specific to e-commerce, let's design a content publishing feature.&lt;/p&gt;

&lt;p&gt;Requirements: an author submits an article. The system validates the content, checks for duplicates, generates a slug, and publishes to multiple platforms.&lt;/p&gt;

&lt;p&gt;Walking through the questions quickly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trigger:&lt;/strong&gt; Author submits article&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data needed:&lt;/strong&gt; Title, body, tags, author ID, target platforms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success:&lt;/strong&gt; Published URLs for each platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures:&lt;/strong&gt; Invalid content, duplicate title, platform rejection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steps:&lt;/strong&gt; Validate → check duplicates → generate slug → publish to platforms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; Validation and duplicate check are independent. Slug generation depends on both. Publishing to each platform is independent (parallel).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional paths:&lt;/strong&gt; If a platform rejects, continue with others (best-effort)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collection processing:&lt;/strong&gt; Publishing to multiple platforms
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;PublishArticle&lt;/span&gt; &lt;span class="nf"&gt;publishArticle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidateContent&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;CheckDuplicates&lt;/span&gt; &lt;span class="n"&gt;checkDups&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;GenerateSlug&lt;/span&gt; &lt;span class="n"&gt;generateSlug&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;PlatformPublisher&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validateArticle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;checkDups&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;generateSlug:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidArticle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;validateArticle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidateContent&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                     &lt;span class="nc"&gt;CheckDuplicates&lt;/span&gt; &lt;span class="n"&gt;checkDups&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                     &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                       &lt;span class="n"&gt;checkDups&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ValidArticle:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different domain, same eight questions, same mechanical process. The structure is determined by the answers, not by preferences.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why It Works: Processes as Knowledge Gathering
&lt;/h2&gt;

&lt;p&gt;There's a deeper structure underneath the eight questions.&lt;/p&gt;

&lt;p&gt;Every backend process is fundamentally an act of &lt;strong&gt;knowledge gathering&lt;/strong&gt;. Each step acquires a piece of knowledge. The process ends — successfully or not — when enough knowledge has accumulated to formulate an answer.&lt;/p&gt;

&lt;p&gt;In PlaceOrder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; gathers knowledge: "the inputs are well-formed"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory check&lt;/strong&gt; gathers knowledge: "the items are available"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment processing&lt;/strong&gt; gathers knowledge: "the funds are secured"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order creation&lt;/strong&gt; gathers knowledge: "the order is persisted"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A failure at any step is also knowledge. A declined payment tells the process "funds are not available" — and that's enough to formulate the answer "order cannot be placed." The process doesn't need to continue once it has enough knowledge to respond.&lt;/p&gt;

&lt;p&gt;This reframes data modeling entirely. Instead of asking "what data exists in the system?" (which produces entity diagrams), you ask "what does this process need to know?" The first question leads to shared entities. The second leads to per-process types — exactly what the methodology produces.&lt;/p&gt;




&lt;h2&gt;
  
  
  Data Dependency Graphs
&lt;/h2&gt;

&lt;p&gt;The knowledge-gathering view has a formal structure. Three operators describe how pieces of knowledge relate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequential&lt;/strong&gt; — need A before gathering B. "Validate first, then check inventory."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ALL(A, B)&lt;/strong&gt; — need both, they're independent. "Check inventory AND process payment."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ANY(A, B)&lt;/strong&gt; — either source suffices. "Get credit score from internal system OR external bureau."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Between operators, transformation functions convert one piece of knowledge into another — pure business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Detailed Example: Resolve Customer Credit
&lt;/h3&gt;

&lt;p&gt;A lending system needs to make a credit decision. The process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Attempt to get the customer's credit score from the internal scoring system&lt;/li&gt;
&lt;li&gt;If internal scoring is unavailable, fall back to an external credit bureau&lt;/li&gt;
&lt;li&gt;Independently, retrieve the customer's payment history&lt;/li&gt;
&lt;li&gt;Combine credit score and payment history to calculate a risk assessment&lt;/li&gt;
&lt;li&gt;Apply lending policy to produce the final decision&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a data dependency graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CreditDecision = ApplyPolicy(
                     Assess(
                         ALL(
                             ANY(InternalScore, ExternalBureau),
                             PaymentHistory
                         )
                     )
                 )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading from the inside out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ANY(InternalScore, ExternalBureau)&lt;/code&gt; — gather credit score from whichever source responds successfully first, any response is equally correct.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ALL(..., PaymentHistory)&lt;/code&gt; — gather the credit score (from either source) AND the payment history independently, in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Assess(...)&lt;/code&gt; — transform both pieces of knowledge into a risk assessment. Pure function, no I/O.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ApplyPolicy(...)&lt;/code&gt; — transform the risk assessment into a lending decision. Pure function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This maps directly to code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ResolveCredit&lt;/span&gt; &lt;span class="nf"&gt;resolveCredit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InternalScoring&lt;/span&gt; &lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                   &lt;span class="nc"&gt;ExternalBureau&lt;/span&gt; &lt;span class="n"&gt;external&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                   &lt;span class="nc"&gt;PaymentHistoryService&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                   &lt;span class="nc"&gt;RiskAssessor&lt;/span&gt; &lt;span class="n"&gt;assessor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                   &lt;span class="nc"&gt;LendingPolicy&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gatherCreditData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;external&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;assessor:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;assess&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;policy:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CreditData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;gatherCreditData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InternalScoring&lt;/span&gt; &lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                    &lt;span class="nc"&gt;ExternalBureau&lt;/span&gt; &lt;span class="n"&gt;external&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                    &lt;span class="nc"&gt;PaymentHistoryService&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                    &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obtainCreditScore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;external&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                       &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retrieve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;CreditData:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CreditScore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;obtainCreditScore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InternalScoring&lt;/span&gt; &lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                      &lt;span class="nc"&gt;ExternalBureau&lt;/span&gt; &lt;span class="n"&gt;external&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                      &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;external&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three methods, one pattern each. &lt;code&gt;resolveCredit&lt;/code&gt; is the Sequencer (gather → assess → decide). &lt;code&gt;gatherCreditData&lt;/code&gt; is the Fork-Join — the &lt;code&gt;ALL&lt;/code&gt; operator gathering independent pieces of knowledge in parallel. &lt;code&gt;obtainCreditScore&lt;/code&gt; is the fallback — the &lt;code&gt;ANY&lt;/code&gt; operator trying the internal source first, falling back to external. The sequential &lt;code&gt;.map&lt;/code&gt; calls are transformations that convert gathered knowledge into the final answer.&lt;/p&gt;

&lt;p&gt;The DDG notation captures more useful information than an entity-relationship diagram. An ER diagram tells you what data exists. A DDG tells you what a process needs to know, where it gets that knowledge, and what depends on what. The code writes itself from the graph.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Requirements Change
&lt;/h2&gt;

&lt;p&gt;The product owner says: "We need to check for fraud before processing payment."&lt;/p&gt;

&lt;p&gt;In an entity-first design, this triggers questions: Does the Order entity need a fraud status? Where does the fraud check live in the service layer? Do we need a new aggregate?&lt;/p&gt;

&lt;p&gt;In process-first design, the response is mechanical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a new step interface: &lt;code&gt;CheckFraud&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask question 6: does fraud check depend on other steps? It needs the validated request — so it comes after validation.&lt;/li&gt;
&lt;li&gt;Is it independent of other steps? It's independent of inventory check but payment should wait for it.&lt;/li&gt;
&lt;li&gt;Insert it into the composition:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrder&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CheckInventory&lt;/span&gt; &lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;CreateOrder&lt;/span&gt; &lt;span class="n"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;SendConfirmation&lt;/span&gt; &lt;span class="n"&gt;sendConfirmation&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                             &lt;span class="nc"&gt;CheckFraud&lt;/span&gt; &lt;span class="n"&gt;checkFraud&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ValidRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;reserveOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;checkFraud&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;createOrder:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;sendConfirmation:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReservedOrder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;reserveOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CheckInventory&lt;/span&gt; &lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="nc"&gt;CheckFraud&lt;/span&gt; &lt;span class="n"&gt;checkFraud&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="nc"&gt;ValidRequest&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                       &lt;span class="n"&gt;verifyAndPay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkFraud&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ReservedOrder:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PaymentResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;verifyAndPay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CheckFraud&lt;/span&gt; &lt;span class="n"&gt;checkFraud&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="nc"&gt;ValidRequest&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;checkFraud&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One new step interface. One change to the composition. No restructuring. No entity changes. No architecture discussion.&lt;/p&gt;

&lt;p&gt;This is what mechanical design evolution looks like: patterns are used from the start, so evolution is adding and recomposing — never restructuring.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Processes Meet Persistence
&lt;/h2&gt;

&lt;p&gt;"But what about the database? Don't you end up with entities anyway?"&lt;/p&gt;

&lt;p&gt;Yes — and that's fine. The difference is how they get there.&lt;/p&gt;

&lt;p&gt;Multiple use cases converge on the same database tables. &lt;code&gt;PlaceOrder&lt;/code&gt; writes order rows. &lt;code&gt;CancelOrder&lt;/code&gt; updates the status column. &lt;code&gt;TrackOrder&lt;/code&gt; reads shipping info. &lt;code&gt;GenerateInvoice&lt;/code&gt; reads billing fields. The &lt;code&gt;orders&lt;/code&gt; table is the union of what these processes need.&lt;/p&gt;

&lt;p&gt;But this entity &lt;strong&gt;emerged&lt;/strong&gt; from process convergence. It wasn't designed upfront. Every column has a known consumer — the process that needed it. No speculative fields "just in case." When a new process needs something new, you add it, and you know exactly why.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;entities are discovered, not invented.&lt;/strong&gt; They're the natural intersection of processes that share persistence — a composition of views, not a universal model.&lt;/p&gt;

&lt;p&gt;This isn't always the case. In event-sourced systems, entities may never materialize into a single flat record. Each process folds the event stream into exactly the shape it needs — different state reconstructions for different contexts (Rico Fritzsche explores this in depth in &lt;a href="https://levelup.gitconnected.com/beyond-aggregates-lean-functional-event-sourcing-1f008cf236fc" rel="noopener noreferrer"&gt;Beyond Aggregates&lt;/a&gt;). Whether your entities live as database rows, event folds, or CQRS projections, the principle holds: they're composed from process needs, not designed ahead of time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Consequences
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Design meetings get shorter.&lt;/strong&gt; When the methodology is mechanical, there's less to debate. "What are the use cases? What are the answers to the eight questions?" produces a design in minutes, not hours. Architects are freed to focus on genuinely hard problems — infrastructure, scaling, cross-system integration — instead of mediating aggregate boundary debates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The six JBCT structural patterns map directly to BPMN constructs&lt;/strong&gt; — code written using these patterns is structurally equivalent to a business process diagram. For the full pattern-BPMN mapping, see the &lt;a href="https://pragmatica.dev/" rel="noopener noreferrer"&gt;JBCT series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing becomes a side effect of design.&lt;/strong&gt; Every step interface is a test seam. Stub it, test the composition. The design produces testable code by construction — no special effort required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-assisted development benefits directly.&lt;/strong&gt; Given the eight questions answered, an AI assistant produces structurally correct code because the design space is fully constrained. There's essentially one valid structure for a given set of answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Pick a feature from your current project. Ask the eight questions. Write down the Request, Response, error types, and step interfaces before writing any implementation code.&lt;/p&gt;

&lt;p&gt;Notice how the structure emerges from the answers — not from architectural decisions or entity modeling. Notice how the types you name are the words your domain expert would use. Notice how you didn't need to debate anything.&lt;/p&gt;

&lt;p&gt;That's JBDT in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going Deeper
&lt;/h2&gt;

&lt;p&gt;Java Backend Design Technology is the design phase of &lt;a href="https://pragmatica.dev/" rel="noopener noreferrer"&gt;Java Backend Coding Technology&lt;/a&gt; — a complete methodology covering design, implementation patterns, testing strategy, and tooling. JBDT has been validated against a 326,000-line distributed runtime (&lt;a href="https://github.com/pragmaticalabs/pragmatica" rel="noopener noreferrer"&gt;Aether&lt;/a&gt;) and formalized into a &lt;a href="https://pragmatica.dev/" rel="noopener noreferrer"&gt;comprehensive learning series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you've independently arrived at similar conclusions from your own domain, language, or tradition — I'd like to hear about it. The convergence is the interesting part.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Further reading:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/siy/the-quiet-consensus-5hhk"&gt;The Quiet Consensus&lt;/a&gt; — the convergent evolution toward process-first design&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/swlh/hidden-anatomy-of-backend-applications-data-dependencies-5e4ce735b0e1" rel="noopener noreferrer"&gt;Hidden Anatomy of Backend Applications: Data Dependencies&lt;/a&gt; — the DDG formalism&lt;/li&gt;
&lt;li&gt;Scott Wlaschin, &lt;em&gt;&lt;a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/" rel="noopener noreferrer"&gt;Domain Modeling Made Functional&lt;/a&gt;&lt;/em&gt; — workflows with typed boundaries in F#&lt;/li&gt;
&lt;li&gt;Rico Fritzsche, &lt;em&gt;&lt;a href="https://levelup.gitconnected.com/beyond-aggregates-lean-functional-event-sourcing-1f008cf236fc" rel="noopener noreferrer"&gt;Beyond Aggregates: Lean, Functional Event Sourcing&lt;/a&gt;&lt;/em&gt; — aggregateless design with event sourcing&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>design</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Quiet Consensus</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Sun, 12 Apr 2026 10:27:18 +0000</pubDate>
      <link>https://forem.com/siy/the-quiet-consensus-5hhk</link>
      <guid>https://forem.com/siy/the-quiet-consensus-5hhk</guid>
      <description>&lt;p&gt;Something is happening in software design that nobody organized.&lt;/p&gt;

&lt;p&gt;Practitioners from different languages, different domains, different traditions are arriving at the same conclusion — independently, without coordination, often without knowing about each other's work.&lt;/p&gt;

&lt;p&gt;The conclusion: &lt;strong&gt;business processes, not data entities, are the natural unit of software decomposition.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't a manifesto. There was no conference keynote. No working group. Just a growing body of work from people solving real problems who kept ending up in the same place.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Standard Starting Point
&lt;/h3&gt;

&lt;p&gt;For two decades, the dominant approach to backend design has been data-first. Identify the entities — User, Order, Product. Define their attributes and relationships. Attach behavior. Build services that operate on shared objects.&lt;/p&gt;

&lt;p&gt;This approach, formalized in Domain-Driven Design's tactical patterns, produces shared entity models that serve multiple contexts. The coupling is structural: change a shared entity, and every consumer is affected. Add a field to Order, and every service that touches orders must accommodate it — even services that don't care about the new field.&lt;/p&gt;

&lt;p&gt;The pattern works. It has built enormous systems. But practitioners working at scale report the same friction points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entities grow into God objects because every feature needs something different from the same concept&lt;/li&gt;
&lt;li&gt;Mapping layers accumulate (DTO to entity to DTO) because the entity doesn't fit any single feature perfectly&lt;/li&gt;
&lt;li&gt;Architecture discussions become debates about aggregate boundaries — who owns what, how big should the aggregate be, where does this behavior belong&lt;/li&gt;
&lt;li&gt;The "where does domain logic live?" question never settles — rich domain model, anemic domain model, transaction scripts, domain services. Each team picks differently, each project mixes approaches, and the answer changes depending on who you ask. The debate persists because entity-first design doesn't have a natural home for behavior that spans multiple entities&lt;/li&gt;
&lt;li&gt;Coupling increases with every new feature because features share entities instead of owning their own types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't implementation failures. They're structural consequences of starting with data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Convergent Evolution
&lt;/h3&gt;

&lt;p&gt;In biology, convergent evolution describes species that develop the same trait independently — wings in birds, bats, and insects. Different lineages, different mechanisms, same solution to the same problem.&lt;/p&gt;

&lt;p&gt;Something similar is happening in software design. Practitioners from different ecosystems are converging on the same structural adaptation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scott Wlaschin&lt;/strong&gt; (F#) models domains as workflows with typed inputs and outputs, composing small functions into complete use cases. His phrase "make illegal states unrepresentable" captures the type-driven approach. His book &lt;a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/" rel="noopener noreferrer"&gt;&lt;em&gt;Domain Modeling Made Functional&lt;/em&gt;&lt;/a&gt; demonstrates how types replace defensive coding and how workflows replace entity models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rico Fritzsche&lt;/strong&gt; (Rust/TypeScript) models domains as contextual decisions, not shared entities. Each feature slice owns its own state reconstruction. In his framing, entities are not fixed structures — they are &lt;a href="https://levelup.gitconnected.com/how-to-model-domain-logic-without-shared-entities-05c938eee73f" rel="noopener noreferrer"&gt;"flexible, context-dependent manifestations"&lt;/a&gt;. A "Seat" is a row and number in booking, a reservation status in availability, a price category in pricing. Three different types, three different processes, no shared entity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roman Weis&lt;/strong&gt; (Java) proposes focusing &lt;a href="https://medium.com/codex/lets-build-business-software-an-alternative-approach-to-the-standard-ddd-implementation-47e586b5f81f" rel="noopener noreferrer"&gt;"100% on behavior — the commands"&lt;/a&gt; instead of finding the perfect aggregate root. Business logic belongs in scoped, task-based commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sandro Mancuso&lt;/strong&gt; (Java/Craftsmanship) starts from external usage. His &lt;a href="https://www.codurance.com/publications/2017/12/08/introducing-idd" rel="noopener noreferrer"&gt;Interaction-Driven Design&lt;/a&gt; lets the domain model emerge from actual needs — use cases first, internal structure second. The domain isn't modeled in advance; it's discovered through implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jimmy Bogard&lt;/strong&gt; (.NET) organizes code by features, not layers. His &lt;a href="https://www.jimmybogard.com/vertical-slice-architecture/" rel="noopener noreferrer"&gt;Vertical Slice Architecture&lt;/a&gt; minimizes coupling between slices and maximizes coupling within a slice. Each feature is self-contained. Shared abstractions are extracted only when proven necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debasish Ghosh&lt;/strong&gt; (Scala) expresses behavior as pure function compositions rather than object methods, with immutable types and explicit side effects. His book &lt;a href="https://www.manning.com/books/functional-and-reactive-domain-modeling" rel="noopener noreferrer"&gt;&lt;em&gt;Functional and Reactive Domain Modeling&lt;/em&gt;&lt;/a&gt; shows how algebraic types and composition replace the entity-service-repository pattern.&lt;/p&gt;

&lt;p&gt;Six practitioners. Five languages. Different continents, different communities, different audiences. None of them cites the others as primary inspiration. They arrived at the same place because they were solving the same problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  What They Share
&lt;/h3&gt;

&lt;p&gt;Strip away the language-specific details and the individual vocabulary, and the shared structure becomes clear:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processes over entities.&lt;/strong&gt; The primary decomposition unit is a business operation with a trigger, inputs, outputs, and failure modes — not a data entity with attributes and relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-context types.&lt;/strong&gt; Data structures are shaped by the process that uses them. A "User" in registration has different fields than a "User" in authentication or billing. Each process owns its own types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functional composition.&lt;/strong&gt; Small, pure, testable operations composed into larger workflows. The composition itself is the design — not a layer on top of a design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No shared domain model.&lt;/strong&gt; Domain knowledge is distributed across processes, not centralized in entity classes. Shared types exist only for validated domain concepts (email addresses, monetary amounts) that genuinely mean the same thing across contexts.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Driving the Convergence
&lt;/h3&gt;

&lt;p&gt;Independent convergence implies shared environmental pressure. Several forces are pushing practitioners toward process-first design simultaneously:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed systems demand it.&lt;/strong&gt; Microservices and serverless architectures naturally align with process-based decomposition. Each service IS a process. Trying to maintain shared entity models across service boundaries creates the exact coupling that microservices were supposed to eliminate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale reveals entity-model friction.&lt;/strong&gt; Small systems don't feel the pain of shared entities. At 5 services, a shared User entity is manageable. At 50, it's a coordination bottleneck. At 500, it's impossible. Teams that grow past a certain size independently discover that process boundaries work better than entity boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functional programming went mainstream.&lt;/strong&gt; Rust brought algebraic types, pattern matching, &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt;, and immutability by default to systems programming — proving these aren't academic preferences but engineering necessities. Java added records, sealed interfaces, and pattern matching. C# added records and discriminated unions. TypeScript refined literal types and discriminated unions. The languages now support typed composition natively, without framework overhead. What was theoretical in 2010 is practical in 2025.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-assisted development rewards deterministic patterns.&lt;/strong&gt; When the design process is mechanical — ask these questions, the structure follows — AI can participate reliably. Process-first design constrains the design space in ways that make AI-generated code structurally correct more often. This wasn't a design goal for any of the practitioners cited. It's an emergent benefit.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Observation to Practice
&lt;/h3&gt;

&lt;p&gt;Observing convergence is interesting. Formalizing it is useful.&lt;/p&gt;

&lt;p&gt;If processes are the natural decomposition unit, the design activity becomes identifying processes and their boundaries. A process has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed input&lt;/strong&gt; — what triggers it and what data it needs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed output&lt;/strong&gt; — what success looks like&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed failures&lt;/strong&gt; — what can go wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steps&lt;/strong&gt; — sub-processes with their own typed boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't abstract categories. They're concrete questions you can ask about any feature requirement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What triggers this process?&lt;/li&gt;
&lt;li&gt;What data does it need?&lt;/li&gt;
&lt;li&gt;What does success look like?&lt;/li&gt;
&lt;li&gt;What can go wrong?&lt;/li&gt;
&lt;li&gt;What are the steps?&lt;/li&gt;
&lt;li&gt;Which steps depend on each other?&lt;/li&gt;
&lt;li&gt;Are there conditional paths?&lt;/li&gt;
&lt;li&gt;Is there collection processing?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The answers determine the code structure. Not guidelines — deterministic mapping. Independent steps become parallel operations. Sequential dependencies become chains. Conditional paths become branches. The developer doesn't invent the structure; they discover it from the answers.&lt;/p&gt;

&lt;p&gt;Consider an order placement process. The questions yield:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input:&lt;/strong&gt; customer, items, address, payment method&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; order confirmation with estimated delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures:&lt;/strong&gt; invalid items, insufficient inventory, payment declined&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steps:&lt;/strong&gt; validate, check inventory, process payment, create order, send confirmation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; inventory check and payment are independent; order creation depends on both&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dependency analysis tells you the composition pattern: validate first (sequential), then inventory and payment in parallel (fork-join), then create order (sequential), then confirm (sequential). The code writes itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;validate → (check inventory ∥ process payment) → create order → confirm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No architecture meeting required. No debate about aggregate boundaries. No class diagram. The process structure IS the architecture, derived mechanically from the requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context-Specific Types
&lt;/h3&gt;

&lt;p&gt;One consequence of process-first design deserves emphasis: types belong to processes, not to the domain.&lt;/p&gt;

&lt;p&gt;In entity-first design, you model "Seat" once and every feature uses that model. In process-first design, each process models exactly what it needs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Booking&lt;/strong&gt; — a location to select (row, number)&lt;br&gt;
&lt;strong&gt;Reservation&lt;/strong&gt; — a time-limited hold (id, reserved until)&lt;br&gt;
&lt;strong&gt;Pricing&lt;/strong&gt; — a cost input (id, category, base price)&lt;/p&gt;

&lt;p&gt;Three different types, three different processes. No shared entity, no conflict, no coupling. Change the pricing model — only the pricing process changes. Add reservation expiry logic — only the reservation process is affected.&lt;/p&gt;

&lt;p&gt;Shared types emerge only when genuinely needed: an email address means the same thing in registration and login, so it becomes a shared value object. But the sharing is discovered from evidence, not designed from speculation.&lt;/p&gt;
&lt;h3&gt;
  
  
  What This Changes
&lt;/h3&gt;

&lt;p&gt;When processes own their types and composition follows from dependency analysis:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The language footprint shrinks.&lt;/strong&gt; Most language features serve entity-model infrastructure — inheritance hierarchies, mutable state, reflection. Process-first design uses a small subset: records for data, sealed interfaces for alternatives, lambdas for composition. The rest becomes unnecessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code reads like business documentation.&lt;/strong&gt; A process method reads as a sequence of named business operations. New team members learn the domain by reading the code, not the framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design evolves mechanically.&lt;/strong&gt; New step? Add a step interface and insert it in the chain. Steps become independent? Change sequential to parallel. Process grows too large? Extract a sub-process. No "refactoring to patterns" — the patterns are used from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business stakeholders can validate structure.&lt;/strong&gt; When code maps directly to process descriptions, a business analyst can look at the composition and verify it matches the business process. The gap between specification and implementation narrows to zero.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Replaces Entity Modeling
&lt;/h3&gt;

&lt;p&gt;A natural objection: if we don't start with entities, what happens to data modeling?&lt;/p&gt;

&lt;p&gt;It doesn't disappear. It transforms.&lt;/p&gt;

&lt;p&gt;Every backend process is fundamentally an act of knowledge gathering. Check inventory — now you know availability. Process payment — now you know if funds cleared. Each step acquires a piece of knowledge. The process ends — successfully or not — when enough knowledge has accumulated to formulate an answer.&lt;/p&gt;

&lt;p&gt;This reframes data modeling entirely. Instead of asking "what data exists in the system?" (entity diagram), you ask "what does this process need to know?" (dependency graph). The data model becomes a &lt;a href="https://medium.com/swlh/hidden-anatomy-of-backend-applications-data-dependencies-5e4ce735b0e1" rel="noopener noreferrer"&gt;data dependency graph&lt;/a&gt; scoped to each process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PlaceOrder = Transform(ALL(InventoryStatus, PaymentResult))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ALL&lt;/code&gt; means "I need both pieces of knowledge — they're independent." Sequential chaining means "I need this knowledge before I can gather the next." &lt;code&gt;ANY&lt;/code&gt; means "I can get this knowledge from multiple sources — the first success is enough."&lt;/p&gt;

&lt;p&gt;These operators map directly to composition patterns in code. &lt;code&gt;ALL&lt;/code&gt; is a fork-join. Sequential chaining is a flatMap. &lt;code&gt;ANY&lt;/code&gt; is a fallback. The code structure mirrors the knowledge dependency structure — not because of a design framework, but because gathering knowledge to produce answers is what the code actually does.&lt;/p&gt;

&lt;p&gt;The consequence: data types are scoped to the knowledge a process needs, not to what exists in the database. A "Seat" in the booking process carries row and number. A "Seat" in the pricing process carries category and base price. They're different knowledge, gathered for different answers. No shared entity needed.&lt;/p&gt;

&lt;p&gt;Entity modeling asks: "What is a Seat?" — and produces one answer that fits no process perfectly.&lt;br&gt;
Process modeling asks: "What does this process need to know about seats?" — and produces exactly the right answer for each process.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Relationship to DDD
&lt;/h3&gt;

&lt;p&gt;This isn't a rejection of Domain-Driven Design. DDD's most enduring contributions — bounded contexts, ubiquitous language, the insistence that software should model the domain — remain essential.&lt;/p&gt;

&lt;p&gt;What's being reconsidered is the starting point. DDD's tactical patterns start with entities and aggregates, then attach behavior. Process-first design starts with behavior, then derives the types. The strategic patterns (bounded contexts, context mapping) are fully compatible — in fact, process boundaries often align with context boundaries more naturally than entity boundaries do.&lt;/p&gt;

&lt;p&gt;The ubiquitous language still matters. But in process-first design, it emerges from use case identification and type definition rather than from separate modeling sessions. When a domain expert says "we need to check inventory before processing payment," that sentence maps directly to step interfaces and their ordering. The code reads like the conversation that produced it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Quiet Part
&lt;/h3&gt;

&lt;p&gt;Nobody organized this convergence. There's no foundation, no standard, no certification program. Just practitioners solving problems and publishing what they found.&lt;/p&gt;

&lt;p&gt;That's what makes it credible. When one person proposes a new methodology, it's an opinion. When six people from different ecosystems independently arrive at the same methodology, it's a signal. The environmental pressures — distributed systems, team scaling, AI-assisted development, functional language features — are producing the same structural adaptation across the industry.&lt;/p&gt;

&lt;p&gt;The consensus is quiet because it doesn't need to be loud. It's not replacing anything overnight. It's just that every year, more teams try process-first design, find that it works, and don't go back.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The convergence described here is formalized in &lt;a href="https://pragmaticalabs.io/jbct.html" rel="noopener noreferrer"&gt;JBCT&lt;/a&gt; (Java Backend Coding Technology) — a methodology with patterns, tooling, and implementation guidance. The approach has been validated against a &lt;a href="https://pragmaticalabs.io/aether.html" rel="noopener noreferrer"&gt;326,000-line distributed runtime&lt;/a&gt; built entirely with process-first design.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Further reading: Scott Wlaschin, &lt;a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/" rel="noopener noreferrer"&gt;Domain Modeling Made Functional&lt;/a&gt;. Debasish Ghosh, &lt;a href="https://www.manning.com/books/functional-and-reactive-domain-modeling/" rel="noopener noreferrer"&gt;Functional and Reactive Domain Modeling&lt;/a&gt;. Rico Fritzsche, &lt;a href="https://levelup.gitconnected.com/how-to-model-domain-logic-without-shared-entities-05c938eee73f" rel="noopener noreferrer"&gt;How to Model Domain Logic Without Shared Entities&lt;/a&gt;. Roman Weis, &lt;a href="https://medium.com/codex/lets-build-business-software-an-alternative-approach-to-the-standard-ddd-implementation-47e586b5f81f" rel="noopener noreferrer"&gt;Alternative Approach to DDD&lt;/a&gt;. Sandro Mancuso, &lt;a href="https://www.codurance.com/publications/2017/12/08/introducing-idd" rel="noopener noreferrer"&gt;Interaction-Driven Design&lt;/a&gt;. Jimmy Bogard, &lt;a href="https://www.jimmybogard.com/vertical-slice-architecture/" rel="noopener noreferrer"&gt;Vertical Slice Architecture&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwaredesign</category>
      <category>architecture</category>
      <category>domaindrivendesign</category>
      <category>functional</category>
    </item>
    <item>
      <title>We Should Write Java Code Differently: Less Language, More Business</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Mon, 06 Apr 2026 12:21:55 +0000</pubDate>
      <link>https://forem.com/siy/we-should-write-java-code-differently-less-language-more-business-5b4h</link>
      <guid>https://forem.com/siy/we-should-write-java-code-differently-less-language-more-business-5b4h</guid>
      <description>&lt;p&gt;How much of your code is actually about your business?&lt;/p&gt;

&lt;p&gt;Open any Java service method. Count the lines. How many describe what the business does? And how many are null checks, exception handling, try-catch blocks, type conversions, logging boilerplate, and framework annotations?&lt;/p&gt;

&lt;p&gt;In most codebases, the answer is uncomfortable. Technical ceremony dominates. Business logic hides between the scaffolding. A new developer reads the code and understands &lt;em&gt;how&lt;/em&gt; it works — but not &lt;em&gt;what&lt;/em&gt; it does or &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This isn't a skill problem. It's a language problem. Java gives us powerful tools, but doesn't guide us toward using them in ways that preserve business meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Ratio
&lt;/h3&gt;

&lt;p&gt;Consider a typical service method that processes an order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the input (null checks, field validation, exception wrapping)&lt;/li&gt;
&lt;li&gt;Check inventory (try-catch around HTTP call, retry logic, timeout handling, response parsing)&lt;/li&gt;
&lt;li&gt;Calculate pricing (more HTTP, more try-catch, more parsing)&lt;/li&gt;
&lt;li&gt;Create the order (database call, transaction management, exception handling)&lt;/li&gt;
&lt;li&gt;Return the result (response mapping, error conversion)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Five business steps. But the code for each step is 80% technical handling and 20% business intent. The ratio is inverted — the scaffolding is louder than the signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shrinking the Technical Part
&lt;/h3&gt;

&lt;p&gt;What if the technical surface was standardized to the point where it almost disappeared?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Return types encode behavior.&lt;/strong&gt; A method returning &lt;code&gt;Result&amp;lt;Order&amp;gt;&lt;/code&gt; tells you it can fail — without looking at the implementation. &lt;code&gt;Option&amp;lt;User&amp;gt;&lt;/code&gt; tells you the value might be absent. &lt;code&gt;Promise&amp;lt;Response&amp;gt;&lt;/code&gt; tells you it's asynchronous. The type signature &lt;em&gt;is&lt;/em&gt; the high-level documentation. No Javadoc needed to explain "this method might throw" — the return type already said it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composition operators have fixed semantics.&lt;/strong&gt; &lt;code&gt;flatMap&lt;/code&gt; means "if the previous step succeeded, do this next." &lt;code&gt;all()&lt;/code&gt; means "these operations are independent — they have no ordering dependency and can execute in parallel." These aren't just API methods. They're business-level statements about relationships between operations. When you read &lt;code&gt;all(checkInventory, calculatePricing)&lt;/code&gt;, you know these two things don't depend on each other. That's domain knowledge encoded in structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error types are exhaustive.&lt;/strong&gt; A sealed interface listing every failure mode means a business analyst can read the error hierarchy and understand what can go wrong — without reading implementation code. The errors aren't strings or exception classes buried in catch blocks. They're first-class types that enumerate the business failure domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patterns are structural, not creative.&lt;/strong&gt; A Sequencer means "do these things in this order." Fork-Join means "do these things in parallel, combine results." A Leaf is a single operation with no sub-steps. The developer doesn't invent control flow — they select from a small set of patterns that map directly to how business processes work.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Remains
&lt;/h3&gt;

&lt;p&gt;When the technical part shrinks, what's left is business logic — and it becomes the dominant signal in the code.&lt;/p&gt;

&lt;p&gt;The order processing method becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                 &lt;span class="nc"&gt;PricingService&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;pricing:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;OrderResult:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;placed&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines. Each line is a business step. The technical ceremony — HTTP calls, serialization, retry, error handling — exists, but it's handled by the runtime and the type system, not by the developer in this method.&lt;/p&gt;

&lt;p&gt;Read it aloud: "Check inventory. Then calculate pricing. Then create the order." That's not a description of the code. That &lt;em&gt;is&lt;/em&gt; the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preserving Knowledge
&lt;/h3&gt;

&lt;p&gt;This matters beyond aesthetics.&lt;/p&gt;

&lt;p&gt;When technical ceremony dominates, a new team member reads the code and learns the framework. They understand &lt;em&gt;how&lt;/em&gt; things are wired — which annotations trigger what, which configuration goes where, which exception handler catches what. This knowledge is framework-specific and doesn't transfer.&lt;/p&gt;

&lt;p&gt;When business logic dominates, the same team member reads the code and learns the domain. They understand &lt;em&gt;what&lt;/em&gt; the system does — which operations depend on each other, what can fail, what the valid states are. This knowledge survives framework migrations, team changes, and technology shifts.&lt;/p&gt;

&lt;p&gt;The original developer's intent — the business reasoning behind the code — is preserved in the structure itself. Not in comments that drift from reality. Not in documentation that nobody updates. In the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Language Shrinks
&lt;/h3&gt;

&lt;p&gt;Something unexpected happens when you measure language features against business value: most of them become unnecessary.&lt;/p&gt;

&lt;p&gt;Java is a large language. Inheritance hierarchies, checked exceptions, mutable state, reflection, annotation processing, type erasure workarounds, synchronized blocks, volatile fields — these are powerful tools. But when the goal is expressing business logic clearly, how many of them do you actually use?&lt;/p&gt;

&lt;p&gt;The answer is surprisingly few. Records for data. Sealed interfaces for type-safe alternatives. Lambdas and method references for composition. Pattern matching for dispatch. That's most of it.&lt;/p&gt;

&lt;p&gt;The rest — the features that generate conference talks and blog posts about clever techniques — serves the technical ceremony, not the business logic. Class inheritance exists to share implementation, not to model business concepts. Checked exceptions exist to force handling, but &lt;code&gt;Result&lt;/code&gt; types handle errors more expressively. Mutable state exists for performance optimization, but immutable records are sufficient for business data.&lt;/p&gt;

&lt;p&gt;This isn't a limitation. It's a feature. When the useful subset of the language is small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The learning curve compresses. A new developer doesn't need to master all of Java — just the subset that carries business meaning.&lt;/li&gt;
&lt;li&gt;Code becomes predictable. When there are three ways to express something, developers argue about style. When there's one way, they focus on the domain.&lt;/li&gt;
&lt;li&gt;The "which feature should I use here?" decision disappears. The answer is always the same small set of constructs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implication is broader than one language. If Java's business-relevant subset is this small, adding more language features doesn't increase business expressiveness — it increases the technical surface that competes for attention with the business logic. Expressiveness comes from domain modeling, not from language syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Boundary Is Clear
&lt;/h3&gt;

&lt;p&gt;The technical part of the code should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small&lt;/strong&gt; — a handful of types and patterns, not a framework vocabulary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardized&lt;/strong&gt; — the same patterns everywhere, no per-developer style&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantically meaningful&lt;/strong&gt; — each construct maps to a business concept&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The business part should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dominant&lt;/strong&gt; — more visible than the technical scaffolding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readable&lt;/strong&gt; — a sequence of named operations, not a tangle of callbacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exhaustive&lt;/strong&gt; — every failure mode visible, every dependency declared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When these two conditions are met, code becomes what it should have been from the start: an executable specification of what the business does.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the sixth article in the "We Should Write Java Code Differently" series. Previous: &lt;a href="https://dev.to/siy/we-should-write-java-code-differently-the-di-confusion-192h"&gt;The DI Confusion&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>cleancode</category>
      <category>backend</category>
    </item>
    <item>
      <title>We Should Write Java Code Differently: The DI Confusion</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Sun, 05 Apr 2026 08:44:15 +0000</pubDate>
      <link>https://forem.com/siy/we-should-write-java-code-differently-the-di-confusion-192h</link>
      <guid>https://forem.com/siy/we-should-write-java-code-differently-the-di-confusion-192h</guid>
      <description>&lt;p&gt;Inversion of Control is one of the most impactful ideas in software engineering. It fundamentally changed how we structure applications — making code testable, modular, and composable. Dependency Injection, its most common implementation, became the backbone of virtually every modern Java framework.&lt;/p&gt;

&lt;p&gt;But somewhere along the way, DI acquired a second job. And that second job is quietly causing problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assembly vs. Provisioning
&lt;/h3&gt;

&lt;p&gt;DI was designed to solve one problem: &lt;strong&gt;assembling an application from its components.&lt;/strong&gt; Controller depends on service, service depends on repository — wire them together, done. The components are internal. The wiring is deterministic. Configuration is minimal or zero.&lt;/p&gt;

&lt;p&gt;But most frameworks also use DI for something fundamentally different: &lt;strong&gt;resource provisioning.&lt;/strong&gt; Database connections, HTTP clients, message brokers, caches, stream consumers — external resources that the application needs access to.&lt;/p&gt;

&lt;p&gt;These two concerns look similar on the surface. Both involve "giving a component something it needs." But they behave completely differently:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assembly&lt;/strong&gt; — internal components, part of the application. Minimal or no configuration. Created once at startup. Dependencies are your code. No security concerns. Failures are deterministic, caught at compile time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provisioning&lt;/strong&gt; — external resources, part of the infrastructure. Environment-dependent, complex configuration. Managed lifecycle: pools, reconnects, health checks. Dependencies are drivers, SDKs, adapters. Requires credentials, certificates, rotation. Failures are environmental, discovered at runtime.&lt;/p&gt;

&lt;p&gt;By fusing these into one mechanism, frameworks created a set of consequences that the industry accepted as normal.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Consequences
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Your application bundles infrastructure.&lt;/strong&gt;&lt;br&gt;
Database drivers, connection pools, HTTP client libraries, messaging SDKs — they all live in your application's dependency tree. A typical backend service is 60% infrastructure dependencies, 40% business logic. Your &lt;code&gt;pom.xml&lt;/code&gt; is a manifest of things that aren't your problem but became your problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment leaks into the artifact.&lt;/strong&gt;&lt;br&gt;
Dev, staging, production — each needs different resource configurations. Connection strings, pool sizes, timeouts, retry policies. The application artifact is identical, but the resource configuration isn't. Managing these variations across services and environments becomes a combinatorial challenge that grows with every new service and every new resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credentials live where they shouldn't.&lt;/strong&gt;&lt;br&gt;
Because the application provisions its own resources, it needs secrets. Database passwords, API keys, certificates. Every service, every environment. Now multiply by the number of services. Secret management becomes an infrastructure project of its own — not because the problem is inherently hard, but because every application was given responsibility for its own credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure changes become application changes.&lt;/strong&gt;&lt;br&gt;
Upgrading a connection pool library? Touch every service. Switching to a different database driver? Rebuild and redeploy every service that uses a database. A security CVE in an adapter library requires coordinated redeployment across the fleet. Infrastructure concerns create application-level change propagation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing fights the wrong battle.&lt;/strong&gt;&lt;br&gt;
A significant portion of test setup is dedicated to mocking or configuring infrastructure that isn't the application's concern. Integration tests need containers for databases, message brokers, caches. The test is verifying business logic, but the setup is provisioning resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separating the Concerns
&lt;/h3&gt;

&lt;p&gt;What if assembly and provisioning were handled by different mechanisms, at different layers?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assembly&lt;/strong&gt; — connecting internal components — can be fully automated. If a use case depends on a repository, and both are part of the application, the wiring is deterministic. No configuration file needed. Annotation processing can resolve it at compile time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provisioning&lt;/strong&gt; — providing access to external resources — belongs to the runtime environment, not the application. The application declares &lt;em&gt;what&lt;/em&gt; it needs: "I need a SQL connection," "I need a notification channel," "I need a stream." The runtime decides &lt;em&gt;how&lt;/em&gt; to provide it based on the deployment environment.&lt;/p&gt;

&lt;p&gt;This separation has immediate consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pom.xml&lt;/code&gt; contains only business dependencies. Infrastructure dependencies belong to the runtime.&lt;/li&gt;
&lt;li&gt;Same artifact deploys to any environment without configuration changes.&lt;/li&gt;
&lt;li&gt;Credentials never touch the application. The runtime handles authentication with the infrastructure.&lt;/li&gt;
&lt;li&gt;A security patch in a database driver is a runtime update — zero application rebuilds, zero redeployments.&lt;/li&gt;
&lt;li&gt;Tests focus on business logic. No mock infrastructure, no test containers for resources the application doesn't own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application becomes what it should have been from the start: pure business logic with declared resource requirements. Everything between the business logic and production is &lt;a href="https://pragmaticalabs.io/docs/developer-guide.html#resource-provisioning" rel="noopener noreferrer"&gt;the runtime's responsibility&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Boundary
&lt;/h3&gt;

&lt;p&gt;This isn't an argument against DI. Inversion of Control remains one of the best ideas our field has produced. The argument is against overloading DI with a concern it wasn't designed for — and paying the price in dependency sprawl, configuration complexity, and operational coupling.&lt;/p&gt;

&lt;p&gt;The boundary is clear: if it's your code, assemble it. If it's infrastructure, declare it and let the environment provide it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the fifth article in the "We Should Write Java Code Differently" series. Previous: &lt;a href="https://dev.to/siy/we-should-write-java-code-differently-frictionless-prod-3mg8"&gt;Frictionless Prod&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>We Should Write Java Code Differently: Frictionless Prod</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Thu, 02 Apr 2026 21:51:07 +0000</pubDate>
      <link>https://forem.com/siy/we-should-write-java-code-differently-frictionless-prod-3mg8</link>
      <guid>https://forem.com/siy/we-should-write-java-code-differently-frictionless-prod-3mg8</guid>
      <description>&lt;h1&gt;
  
  
  We Should Write Java Code Differently: Frictionless Prod
&lt;/h1&gt;

&lt;p&gt;It's not a secret that modern production deployment is extremely complex. Following "best practices" and deploying in Kubernetes "for scalability" makes things even more complex. But how complex exactly? Let's look at the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;A mid-sized e-commerce platform. Nothing exotic -- catalog, cart, checkout, orders, payments, shipping, inventory, pricing, promotions, notifications. Standard bounded contexts, standard domain decomposition.&lt;/p&gt;

&lt;p&gt;In a microservices architecture, this translates to roughly 30 services. Not because someone wanted 30 -- because the domain naturally decomposes into ~10 core services, ~10 supporting services (integrations, async processing, admin), and ~10 platform services (gateway, auth, search, analytics, event processing).&lt;/p&gt;

&lt;p&gt;30 is not a large number. It is a realistic baseline for a system that does what mid-sized e-commerce systems do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 30 Services Actually Cost
&lt;/h2&gt;

&lt;p&gt;Each service needs to be built, deployed, configured, monitored, and kept alive -- independently. On managed Kubernetes, the standard deployment substrate for this scale, here is what the numbers look like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production environment:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12-18 shared platform components (ingress controller, cert manager, external DNS, metrics stack, logging pipeline, tracing collector, secrets integration, policy controller, autoscaler, GitOps controller, backup controller, image registry integration)&lt;/li&gt;
&lt;li&gt;220-280 Kubernetes workload objects (deployments, services, config maps, secrets, service accounts, network policies, autoscalers, pod disruption budgets, ingress rules, worker/consumer deployments)&lt;/li&gt;
&lt;li&gt;260-340 configuration sets (image tags, rollout strategies, replica counts, CPU/memory limits, probes, environment variables, feature flags, secret references, IAM permissions, network exposure rules, alerting configs -- per service)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Staging environment:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Topologically similar to production. You save on capacity, not on configuration complexity. 190-250 workload objects. 220-300 configuration sets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Testing environment:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;80-160 workload objects in a shared cluster with ephemeral namespaces. 100-180 configuration sets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Across all three environments: 500-700 managed runtime objects and 580-820 configuration surfaces.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is before counting databases, message brokers, CDN, object storage, and external SaaS integrations. The application itself -- the business logic that actually generates revenue -- is a small fraction of this surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Team That Manages This
&lt;/h2&gt;

&lt;p&gt;This infrastructure does not manage itself. For a 30-service system on managed Kubernetes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimum viable:&lt;/strong&gt; 3-5 platform/DevOps engineers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typical realistic:&lt;/strong&gt; 5-8 engineers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comfortable/mature:&lt;/strong&gt; 8-12 engineers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is platform and SRE combined -- not including the feature development teams that write the actual business logic. These engineers manage pipelines, rollout policies, cluster security, network configuration, secrets, observability, capacity planning, incident response, and the endless stream of version upgrades across 30 independent deployment units.&lt;/p&gt;

&lt;p&gt;The dominant complexity is not code. It is coordination: version coexistence (new service talking to old service), schema evolution (new code on old database), deployment ordering (which service goes first), and failure propagation (one bad deploy cascading through dependent services).&lt;/p&gt;

&lt;p&gt;A mid-sized organization pays for 5-8 engineers whose entire job is keeping the deployment machinery running. Not building features. Not serving customers. Managing the gap between code and production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Complexity Comes From
&lt;/h2&gt;

&lt;p&gt;This is not accidental. The complexity has three structural sources, each one a consequence of architectural decisions made so long ago that they feel like laws of nature. They are not.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Monolith Turned Inside Out
&lt;/h3&gt;

&lt;p&gt;A microservices system is a monolith turned inside out. Every internal interaction -- a method call, a shared data structure, a module boundary -- transforms into infrastructure configuration. What was a function call within a single process becomes a network call that needs discovery, routing, serialization, timeout handling, retry logic, and circuit breaking. What was an internal module boundary becomes a deployment boundary with its own pipeline, versioning, and rollout policy.&lt;/p&gt;

&lt;p&gt;The problem is not microservices as a concept. The problem is that there are no predefined patterns or limits on how services interact. The infrastructure must be infinitely flexible to accommodate every possible communication topology. Infinite flexibility means every single interaction path must be configured -- sometimes multiple times in different places. A call from the order service to the inventory service touches ingress rules, network policies, service discovery, load balancing, timeout configuration, and retry policy. Each one configured separately. Each one a potential source of misconfiguration.&lt;/p&gt;

&lt;p&gt;A 30-service system with 50-100 service-to-service interactions does not have 30 configuration problems. It has a combinatorial configuration problem that grows with the interaction graph, not with the service count.&lt;/p&gt;

&lt;p&gt;This creates an inherent contradiction. The ideal service boundary is determined by the business domain -- by cohesion, coupling, and team ownership. But the configuration and operational cost of each additional service is so high that it becomes a technical factor in the decomposition decision. Teams merge services that should be separate to avoid operational overhead, or keep services together that should be split because nobody wants to set up another pipeline. The result is almost inevitably a suboptimal split -- service boundaries driven by infrastructure cost rather than domain structure.&lt;/p&gt;

&lt;p&gt;And there is a human cost too. Every service boundary is a communication boundary. Two services owned by two teams means coordination meetings, API contracts, versioning negotiations, shared testing environments, deployment ordering discussions. Conway's Law works in reverse here -- the architecture forces organizational communication patterns that would not exist if the split were different. The more services, the more cross-team coordination. The more coordination, the slower the delivery. The very thing microservices promised to fix -- team independence -- is undermined by the infrastructure overhead of maintaining the boundaries between them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Substrate-Application Disconnect
&lt;/h3&gt;

&lt;p&gt;Kubernetes runs containers. It starts a binary, monitors a health endpoint, restarts it if it fails. That is the extent of the relationship. Once the binary is running, it is on its own.&lt;/p&gt;

&lt;p&gt;This creates a two-way blindness. The application cannot trust the environment -- it must assume that any network call can fail, any service can be unavailable, any response can be delayed. So the application implements its own retries, its own circuit breakers, its own service discovery, its own health reporting. All of this requires configuration.&lt;/p&gt;

&lt;p&gt;The blindness goes the other direction too. The substrate knows nothing about the application. It does not know which services talk to each other, what constitutes a meaningful health check for this specific business logic, which services must be deployed before others, or whether a particular service's "healthy" status actually means it can process requests. The cluster is fault-tolerant at the container level, but the application gets no benefit from that -- it must build its own fault tolerance on top.&lt;/p&gt;

&lt;p&gt;The result: the application carries infrastructure concerns that the runtime should handle, and the runtime cannot provide services that would require understanding the application. Both sides are doing extra work because neither side can see the other.&lt;/p&gt;

&lt;p&gt;There is a particularly painful consequence of this disconnect: the application and its infrastructure share a lifecycle. Every microservice bundles its own web server, serialization library, HTTP client, connection pool, metrics agent, and retry framework. When any of these components needs a security patch -- a CVE in Netty, a vulnerability in Jackson, an update in the connection pool -- the business logic must be rebuilt, retested, repackaged, and redeployed. All 30 services. Zero changes to business logic. Pure infrastructure maintenance.&lt;/p&gt;

&lt;p&gt;This is not a deployment -- it is a tax. A Netty CVE means 30 rebuilds, 30 pipeline runs, 30 test suites, 30 rollouts. Each one risks introducing regressions in business logic that nobody touched. The operational burden scales with the service count, and the trigger has nothing to do with the business. The application and the runtime are monolithically coupled inside every single service, even as the services themselves are distributed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tool Multiplication Effect
&lt;/h3&gt;

&lt;p&gt;Because the substrate and application are disconnected, the gap between them must be filled. Each gap spawns a tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routing and mTLS between services? Service mesh.&lt;/li&gt;
&lt;li&gt;TLS certificate lifecycle? Certificate manager.&lt;/li&gt;
&lt;li&gt;Configuration across environments? Config service.&lt;/li&gt;
&lt;li&gt;Database schema evolution? Migration tool.&lt;/li&gt;
&lt;li&gt;Connection management? Connection pooler.&lt;/li&gt;
&lt;li&gt;Metrics and tracing? Observability agents.&lt;/li&gt;
&lt;li&gt;Deployment orchestration? GitOps controller.&lt;/li&gt;
&lt;li&gt;Secret management? Vault or cloud secrets integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each tool has its own configuration language, its own upgrade cycle, its own failure modes, and its own operational surface. Each tool solves a real problem -- but the problem only exists because the substrate and application cannot communicate.&lt;/p&gt;

&lt;p&gt;A 30-service system on managed Kubernetes typically depends on 9-12 distinct operational tools beyond Kubernetes itself. Each one was added to solve a legitimate gap. Together, they are the gap. The complexity is not in any single tool -- it is in the interaction between all of them. A certificate renewal that breaks a service mesh sidecar that causes a health check failure that triggers a cascading restart -- this class of incident exists only because the tools operate independently, each with partial knowledge, none with the full picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What If the Gap Didn't Exist?
&lt;/h2&gt;

&lt;p&gt;The three sources of complexity share a root cause: the application and the runtime are strangers. The runtime starts a binary and watches a health endpoint. The binary assumes a hostile environment and brings its own infrastructure. The gap between them fills with tools. The tools fill with configuration. The configuration fills with inconsistencies. The inconsistencies fill incident postmortems.&lt;/p&gt;

&lt;p&gt;What changes if the runtime understands the application?&lt;/p&gt;

&lt;h3&gt;
  
  
  Unification: From Multiplication to Addition
&lt;/h3&gt;

&lt;p&gt;In a Kubernetes-based system, configuration complexity is multiplicative. Each service multiplied by each environment multiplied by each configuration surface produces the 580-820 number we saw earlier. Every new service adds a full column of config. Every new environment multiplies the entire matrix.&lt;/p&gt;

&lt;p&gt;This multiplication exists because nothing is shared. Each service configures its own database connection, its own TLS, its own retries, its own health checks, its own metrics, its own log format. Even when two services use the same database, they each carry their own connection configuration -- independently managed, independently misconfigured.&lt;/p&gt;

&lt;p&gt;A unified runtime changes the math. When the runtime handles TLS, there is one TLS configuration -- not 30. When the runtime handles database connections, there is one database configuration per database -- not one per service. When the runtime handles metrics, there is one observability configuration -- not 30 agents with 30 scrape configs.&lt;/p&gt;

&lt;p&gt;The dependency structure changes from a product of factors to a sum of components. 30 slices sharing 3 databases, 1 TLS configuration, and 1 observability setup produce roughly 35 configuration surfaces -- not 340. The complexity scales with the number of distinct resources, not with the number of services that use them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration: The Cluster Becomes Service-Aware
&lt;/h3&gt;

&lt;p&gt;When the application is embedded into the runtime -- not just started by it -- the two-way blindness disappears.&lt;/p&gt;

&lt;p&gt;The runtime knows what slices exist, what resources they need, which slices communicate with each other, and what "healthy" means for each one. It knows because the application declared it: this slice needs a database, that slice publishes to a stream, this slice depends on that one. The declarations are not configuration files scattered across repositories. They are part of the application itself -- compiled in, type-checked, deployed as a single artifact.&lt;/p&gt;

&lt;p&gt;From the application's perspective, the environment becomes trustworthy. Retries, circuit breaking, load balancing, service discovery -- these are not application concerns anymore. The runtime handles them because the runtime knows the topology. When slice A calls slice B, the runtime knows where B lives, which instances are healthy, how to route the request, and what to do if it fails. The application code is a method call. Everything between the call and the response is the runtime's responsibility.&lt;/p&gt;

&lt;p&gt;This is not a framework providing libraries. It is a managed runtime providing services -- the way an operating system provides networking and storage to applications. The application does not implement TCP. It calls an API. The same principle, applied one level up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Communication Fabric
&lt;/h3&gt;

&lt;p&gt;The deepest integration point is inter-slice communication. In a microservices system, service-to-service calls cross process boundaries, network boundaries, and trust boundaries. Each crossing adds latency, failure modes, and configuration.&lt;/p&gt;

&lt;p&gt;When slices run inside the runtime, the communication fabric is built in. A call from one slice to another is a typed method invocation. The runtime resolves the target, handles serialization, routes the request -- potentially to the same node (microsecond latency, zero network overhead) or to a remote node (transparent, with automatic retry and failover). The slice developer writes &lt;code&gt;inventory.check(items)&lt;/code&gt; and gets back a &lt;code&gt;Promise&amp;lt;Availability&amp;gt;&lt;/code&gt;. The infrastructure between the call and the response is invisible.&lt;/p&gt;

&lt;p&gt;This is not RPC dressed up as a method call. The type system guarantees that the caller and callee agree on the contract at compile time. There are no surprise serialization failures, no version mismatches discovered in production, no "the other service changed its API and nobody told us." The contract is a Java interface. The compiler enforces it.&lt;/p&gt;

&lt;p&gt;The same fabric carries pub/sub, streaming, and scheduled task execution. All inter-slice communication -- synchronous, asynchronous, event-driven -- flows through the same runtime-managed infrastructure. One communication model, one set of guarantees, one thing to understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Provisioning: Declare, Don't Configure
&lt;/h3&gt;

&lt;p&gt;In the traditional model, the application includes its dependencies and configures them. A service that needs a database brings a connection pool library, configures the URL, credentials, pool size, timeouts, and retry behavior. A service that needs messaging brings a client library, configures the broker address, topics, serialization, and consumer groups. Each dependency is another library, another config surface, another thing to upgrade.&lt;/p&gt;

&lt;p&gt;In a unified runtime, the application declares what it needs. A slice annotated with &lt;code&gt;@Sql&lt;/code&gt; gets a database connection -- provisioned, pooled, health-checked, and monitored by the runtime. A slice that declares a stream gets a stream -- partitioned, replicated, persistent. The slice does not know or care how the database connection is managed. It declares intent. The runtime delivers.&lt;/p&gt;

&lt;p&gt;This is why 30 slices do not produce 30× configuration. The resources are shared by design. Three slices using the same database share one connection pool. Ten slices publishing to the same stream share one stream configuration. The configuration count tracks resources, not consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Different Tradeoffs: Splitup Without Penalty
&lt;/h3&gt;

&lt;p&gt;When the operational cost of each additional slice is zero -- no pipeline, no deployment descriptor, no config set, no monitoring surface -- the decomposition decision changes fundamentally.&lt;/p&gt;

&lt;p&gt;In a microservices system, splitting a service in two is an infrastructure project: new repository, new pipeline, new deployment config, new monitoring, new network policies, new on-call surface. This cost discourages splitting, leading to oversized services that bundle unrelated logic because nobody wants to pay the operational tax of separation.&lt;/p&gt;

&lt;p&gt;When slices are the unit, splitting is a code decision. Extract an interface, annotate it with &lt;code&gt;@Slice&lt;/code&gt;, implement the business logic. The runtime handles deployment, routing, scaling, and monitoring automatically. There is no operational penalty for having 50 slices instead of 10. The only consideration is the domain: does this logic belong together, or should it be independent?&lt;/p&gt;

&lt;p&gt;This aligns perfectly with vertical slicing. Each slice is a complete vertical: an interface (the contract), an implementation (the logic), and resource declarations (what it needs). No horizontal layers spread across packages. No shared mutable state between slices. Each slice is independently deployable, independently scalable, and independently understandable.&lt;/p&gt;

&lt;p&gt;The scaling is linear -- in development, not just in production. Adding a new slice to the system does not require understanding the deployment topology, the network configuration, or the CI/CD pipeline. It requires understanding the business domain. Write the interface. Implement the logic. Deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  JBCT: Structure That Carries Across Boundaries
&lt;/h3&gt;

&lt;p&gt;This is where the code methodology meets the runtime. JBCT's six structural patterns -- Sequencer, Fork-Join, Condition, Iteration, Aspects, Leaf -- are not just coding rules. They are the structural language of business processes. A Sequencer is a sequence of dependent steps. A Fork-Join is parallel independent operations. A Condition is a routing decision. These map directly to BPMN constructs -- the same notation business analysts use to describe processes.&lt;/p&gt;

&lt;p&gt;When every slice follows the same six patterns, several things happen at once:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code becomes the specification.&lt;/strong&gt; A business analyst who understands the process can review the code structure and verify that it matches the intended workflow. Not because the code is simple -- because the structure is familiar. The patterns are the same shapes they draw on whiteboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-assisted development becomes predictable.&lt;/strong&gt; The six patterns constrain the generation space. An AI that produces JBCT code generates variations of known structures, not arbitrary architectures. The mechanical rules mean less manual correction, less review overhead, less accumulated drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer onboarding compresses.&lt;/strong&gt; A developer who learns the six patterns can read and contribute to any slice in the system. There is no per-service learning curve, no "this team does it differently." The patterns are the same everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The entire stack speaks the same language.&lt;/strong&gt; The business process is described in BPMN. The code implements it in JBCT patterns that mirror the BPMN structure. The runtime deploys and operates it without configuration. From business requirement to production, the structure is consistent. No translation layers. No impedance mismatches. No "we need a meeting to explain what the code does."&lt;/p&gt;

&lt;h3&gt;
  
  
  Independent Lifecycles
&lt;/h3&gt;

&lt;p&gt;Remember the Netty CVE problem -- 30 rebuilds, 30 pipelines, 30 rollouts, zero business logic changes?&lt;/p&gt;

&lt;p&gt;When the application and the runtime are separate layers, their lifecycles decouple completely. The runtime handles web serving, TLS, connection pooling, serialization, metrics, retry logic. The application handles business logic. When Netty needs a patch, the runtime updates -- once, across all nodes, without touching a single slice. When business logic changes, the slice deploys -- without rebuilding the runtime.&lt;/p&gt;

&lt;p&gt;Update the runtime: roll out new nodes, slices continue serving. Update a slice: deploy a new version, the runtime handles routing. Update the database driver: runtime concern, not application concern. Update Java itself: runtime update, slices are unaffected.&lt;/p&gt;

&lt;p&gt;Two independent update cadences. Two independent test surfaces. Two independent rollout schedules. The security patch that used to trigger 30 rebuilds is now a single runtime update that applications never see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aether: What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Everything described above is not a thought experiment. It is a working system called &lt;a href="https://pragmaticalabs.io/aether.html" rel="noopener noreferrer"&gt;Aether&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is what the 30-slice e-commerce application looks like in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Application
&lt;/h3&gt;

&lt;p&gt;A slice is a Java interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PlaceOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;PricingService&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;pricing:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;OrderResult:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;placed&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire service. The interface is the contract. The factory method parameters are the dependencies. The implementation is the business logic. There is no framework, no annotations for routing or serialization or retry, no configuration file. The runtime handles all of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deployment
&lt;/h3&gt;

&lt;p&gt;The blueprint is a TOML file that describes the desired state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.example:commerce:1.0.0"&lt;/span&gt;

&lt;span class="nn"&gt;[[slices]]&lt;/span&gt;
&lt;span class="py"&gt;artifact&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.example:order-service:1.0.0"&lt;/span&gt;
&lt;span class="py"&gt;instances&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="nn"&gt;[[slices]]&lt;/span&gt;
&lt;span class="py"&gt;artifact&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.example:inventory-service:1.0.0"&lt;/span&gt;
&lt;span class="py"&gt;instances&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

&lt;span class="c"&gt;# ... remaining slices&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Maven plugin packages the blueprint automatically -- along with database schema migration scripts and application configuration -- into a single deployable JAR. The entire build-to-production workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build and install to local Maven repository&lt;/span&gt;
mvn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Push artifacts from local Maven repo to production cluster&lt;/span&gt;
aether artifact push org.example:commerce:1.0.0

&lt;span class="c"&gt;# Deploy&lt;/span&gt;
aether deploy org.example:commerce:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three commands. The runtime resolves artifacts, runs schema migrations, distributes slice instances across nodes, registers routes, and starts serving traffic. No Docker images, no Helm charts, no deployment descriptors, no pipeline configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cluster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[deployment]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"aws"&lt;/span&gt;
&lt;span class="py"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"eu-west-1"&lt;/span&gt;

&lt;span class="nn"&gt;[deployment.instances]&lt;/span&gt;
&lt;span class="py"&gt;core&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"m5.large"&lt;/span&gt;

&lt;span class="nn"&gt;[cluster]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"production"&lt;/span&gt;
&lt;span class="py"&gt;core.count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

&lt;span class="nn"&gt;[cluster.auto_heal]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aether cluster bootstrap &lt;span class="nt"&gt;--config&lt;/span&gt; cluster.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five nodes. Auto-healing. TLS derived from a shared secret -- no external certificate authority, no cert-manager. Service discovery via consensus -- no Consul, no etcd. Metrics and tracing built in -- no Prometheus scrape configs, no observability agents. QUIC transport with mandatory TLS 1.3 between all nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aether cluster scale &lt;span class="nt"&gt;--core&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two additional nodes join the cluster automatically -- same binary, same config. Worker nodes can scale independently via gossip protocol, adding capacity without touching the consensus layer. A 5-node cluster grows to 50 nodes without architecture changes. The runtime includes a reactive scaling controller that adjusts capacity based on CPU, latency, queue depth, and error rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Development
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build your slices&lt;/span&gt;
mvn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Start a 5-node cluster on your laptop with your application deployed&lt;/span&gt;
aether-forge &lt;span class="nt"&gt;--blueprint&lt;/span&gt; org.example:commerce:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A full cluster on your laptop. Real consensus, real routing, real failure scenarios. Web dashboard with live metrics. Kill a node, watch recovery. Deploy a new slice version, watch traffic shift. The same runtime that runs in production runs on your machine -- not a simulation, not a mock, the actual system. When it works in Forge, it works in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Numbers
&lt;/h3&gt;

&lt;p&gt;For the same 30-slice e-commerce application:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration sets (production):&lt;/strong&gt; 260-340 on Kubernetes. ~15 on Aether.&lt;br&gt;
&lt;strong&gt;Configuration sets (all environments):&lt;/strong&gt; 580-820 on Kubernetes. ~18 on Aether.&lt;br&gt;
&lt;strong&gt;Platform components:&lt;/strong&gt; 12-18 per environment on Kubernetes. 1 binary on Aether.&lt;br&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; 30 independent pipelines on Kubernetes. 3 commands on Aether.&lt;br&gt;
&lt;strong&gt;Runtime security patch:&lt;/strong&gt; 30 rebuilds on Kubernetes. 1 rolling update on Aether.&lt;br&gt;
&lt;strong&gt;Operational team:&lt;/strong&gt; 5-8 dedicated engineers on Kubernetes. 1 person on Aether.&lt;/p&gt;

&lt;p&gt;One person does not mean zero operational work. Monitoring the cluster, watching for bottlenecks, checking alerts, reviewing scaling behavior, planning capacity -- that is enough work for one person. But it is one person, not eight. And that person spends time on operational judgment, not on managing the configuration machinery that connects 12 tools to 30 services across 3 environments.&lt;/p&gt;




&lt;p&gt;Running Java should be as easy as writing it.&lt;/p&gt;

&lt;p&gt;With Aether, it is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pragmaticalabs.io" rel="noopener noreferrer"&gt;pragmaticalabs.io&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previously: &lt;a href="https://dev.to/siy/introduction-to-pragmatic-functional-java-142m"&gt;Introduction to Pragmatic Functional Java&lt;/a&gt; (2019) | &lt;a href="https://dev.to/siy/we-should-write-java-code-differently-210b"&gt;We Should Write Java Code Differently&lt;/a&gt; (2021) | &lt;a href="https://dev.to/siy/we-should-write-java-code-differently-lets-get-practical-1ib2"&gt;We Should Write Java Code Differently: Let's Get Practical&lt;/a&gt; (2026)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>devops</category>
      <category>architecture</category>
      <category>distributed</category>
    </item>
    <item>
      <title>We Should Write Java Code Differently: Let's Get Practical</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Sun, 22 Mar 2026 21:54:48 +0000</pubDate>
      <link>https://forem.com/siy/we-should-write-java-code-differently-lets-get-practical-1ib2</link>
      <guid>https://forem.com/siy/we-should-write-java-code-differently-lets-get-practical-1ib2</guid>
      <description>&lt;h1&gt;
  
  
  We Should Write Java Code Differently: Let's Get Practical
&lt;/h1&gt;

&lt;p&gt;A few years ago I wrote about &lt;a href="https://dev.to/siy/we-should-write-java-code-differently-210b"&gt;why we should write Java code differently&lt;/a&gt;. The core argument: most of what slows us down is not the amount of code — it's the amount of context we lose while writing it. Nullable variables, business exceptions, framework magic — each one eats information that should have been explicit.&lt;/p&gt;

&lt;p&gt;That article diagnosed the problem. This one delivers the tools.&lt;/p&gt;

&lt;p&gt;Three types — &lt;code&gt;Option&lt;/code&gt;, &lt;code&gt;Result&lt;/code&gt;, and &lt;code&gt;Promise&lt;/code&gt; — cover virtually every return value in a Java backend. They compose with the same &lt;code&gt;map&lt;/code&gt;/&lt;code&gt;flatMap&lt;/code&gt; vocabulary you already know from Streams. Once you internalize them, the code you write becomes shorter, safer, and — this is the part that surprised me — significantly easier to read months later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Option: A Value That Might Not Be There
&lt;/h2&gt;

&lt;p&gt;If you've used Streams, you already understand the core mechanic — transform the value inside, chain transformations, extract at the end. &lt;code&gt;Option&lt;/code&gt; applies the same thinking to absent values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;findUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;findAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addressId&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any step returns &lt;code&gt;Option.none()&lt;/code&gt;, the whole chain short-circuits. No null checks, no early returns, no branching. The absent-value case propagates automatically.&lt;/p&gt;

&lt;p&gt;Where does &lt;code&gt;Option&lt;/code&gt; come from? At adapter boundaries — wrapping nullable external APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside business logic, &lt;code&gt;Option&lt;/code&gt; is the container for everything genuinely optional. The type makes the absence visible in the signature, not hidden in a javadoc comment.&lt;/p&gt;

&lt;p&gt;A few things that make &lt;code&gt;Option&lt;/code&gt; practical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pattern matching with sealed types&lt;/span&gt;
&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Some&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;None&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handleAbsence&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Convert to Result when absence is an error&lt;/span&gt;
&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Combine multiple Options — all must be present&lt;/span&gt;
&lt;span class="nc"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Contact:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conversion methods matter. &lt;code&gt;Option&lt;/code&gt; is not an island — it flows into &lt;code&gt;Result&lt;/code&gt; and &lt;code&gt;Promise&lt;/code&gt; when the context demands it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Result: Errors Without Exceptions
&lt;/h2&gt;

&lt;p&gt;This is where things change fundamentally.&lt;/p&gt;

&lt;p&gt;Consider typical Java error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;registerUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RegistrationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isBlank&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ValidationException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Email is required"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;existsByEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DuplicateEmailException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passwordHasher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HashingException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RegistrationFailedException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Password hashing failed"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method signature says it returns &lt;code&gt;User&lt;/code&gt;. It lies. It can throw three different exceptions, and the compiler won't tell you about any of them. The caller has no idea what to catch. The next developer reading this code has to trace every path to understand what can go wrong.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;Result&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;registerUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RegistrationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ensureUnique&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hashAndCreateUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;userRepository:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ensureUnique&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;existsByEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
           &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;EMAIL_ALREADY_EXISTS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
           &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;hashAndCreateUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;passwordHasher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The return type tells the truth — this operation can fail. Every failure path is visible in the chain. No exceptions thrown, no exceptions caught. The compiler enforces that the caller handles the &lt;code&gt;Result&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Errors Work
&lt;/h3&gt;

&lt;p&gt;Errors are values, not exceptions. They implement the &lt;code&gt;Cause&lt;/code&gt; interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;RegistrationError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Cause&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;General&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RegistrationError&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;EMAIL_ALREADY_EXISTS&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Email already registered"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="no"&gt;TOKEN_GENERATION_FAILED&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token generation failed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;General&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="nd"&gt;@Override&lt;/span&gt; 
        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed messages become enum constants. Dynamic messages become records. All are sealed — the compiler knows every possible failure. Pattern matching works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sendWelcome&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Failure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cause&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logAndRespond&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cause&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Composition
&lt;/h3&gt;

&lt;p&gt;The real power shows in composition. &lt;code&gt;Result.all()&lt;/code&gt; collects independent validations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;ValidRegistration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Password&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PhoneNumber&lt;/span&gt; &lt;span class="n"&gt;phoneNumber&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidRegistration&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;validRegistration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Registration&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt;
                          &lt;span class="nc"&gt;Password&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt; 
                          &lt;span class="nc"&gt;PhoneNumber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;phoneNumber&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ValidRegistration:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;    
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three validations run. All failures are collected — not just the first one. The &lt;code&gt;map&lt;/code&gt; only executes if all three succeed. One line replaces the usual cascade of if-checks-and-early-returns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interfacing with Legacy Code
&lt;/h3&gt;

&lt;p&gt;The world throws exceptions. &lt;code&gt;lift()&lt;/code&gt; catches them at the boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;DatabaseError:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exception goes in, &lt;code&gt;Result&lt;/code&gt; comes out. The boundary is explicit. Business logic stays clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  Promise: Result, But Async
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Promise&lt;/code&gt; is &lt;code&gt;Result&lt;/code&gt; where the answer hasn't arrived yet. Same &lt;code&gt;map&lt;/code&gt;, same &lt;code&gt;flatMap&lt;/code&gt;, same mental model — just non-blocking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EnrichedUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadEnriched&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;findUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;loadUserWithOrders&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EnrichedUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadUserWithOrders&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;findOrders&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EnrichedUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;findUser&lt;/code&gt; fails, the chain short-circuits — just like &lt;code&gt;Result&lt;/code&gt;. If &lt;code&gt;loadUserWithOrders&lt;/code&gt; fails, same thing. Errors are &lt;code&gt;Cause&lt;/code&gt; values, same as in &lt;code&gt;Result&lt;/code&gt;. The only difference: the chain executes asynchronously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel Operations
&lt;/h3&gt;

&lt;p&gt;Independent operations run in parallel with &lt;code&gt;Promise.all()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Profile&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Notification&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadUserDashboard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetchProfile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; 
                       &lt;span class="n"&gt;fetchOrders&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; 
                       &lt;span class="n"&gt;fetchNotifications&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Dashboard:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three async calls, all independent, all in parallel. Result combined when all complete. If any fails, the whole thing fails — with a meaningful &lt;code&gt;Cause&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For "first success wins" semantics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Key&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;any&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetchFromPrimaryCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; 
                       &lt;span class="n"&gt;fetchFromReplicaCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; 
                       &lt;span class="n"&gt;fetchFromDatabase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Side Effects
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Promise&lt;/code&gt; distinguishes between dependent and independent actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;findUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;validateAccess&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// dependent — runs in sequence&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;loadProfile&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// dependent — runs after validation&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;metrics:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;recordAccess&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// independent — runs async, doesn't block chain&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onFailure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;logger:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// independent — runs async on failure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependent actions (&lt;code&gt;map&lt;/code&gt;, &lt;code&gt;flatMap&lt;/code&gt;) execute in order and can fail the chain. Independent actions (&lt;code&gt;onSuccess&lt;/code&gt;, &lt;code&gt;onFailure&lt;/code&gt;) run asynchronously and never affect the chain's outcome. This distinction eliminates an entire class of bugs where logging or metrics accidentally break the business flow.&lt;br&gt;
There are also dependent side effects, for the cases when ordering matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;findUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;validateAccess&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// dependent — runs in sequence&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;loadProfile&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// dependent — runs after validation&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;metrics:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;recordAccess&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// dependent — runs on success, in order like map/flatMap&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withFailure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;logger:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// dependent — runs on failure, in order like map/flatMap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Timeouts and Recovery
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;fetchFromRemoteService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeSpan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;recover&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cause&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cachedFallback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the remote call doesn't resolve in 5 seconds, it fails. &lt;code&gt;recover&lt;/code&gt; converts the failure back to success using a fallback. Clean, composable, no try-catch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Types Together
&lt;/h2&gt;

&lt;p&gt;The real picture emerges when all three work together. Consider a realistic operation — processing an incoming order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderConfirmation&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RawOrderRequest&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ValidOrder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;// Result&amp;lt;ValidOrder&amp;gt; — sync validation&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;                      &lt;span class="c1"&gt;// → Promise&amp;lt;ValidOrder&amp;gt;&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;enrichOrder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;orderRepository:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;eventBus:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;publishOrderCreated&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EnrichedOrder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;enrichOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidOrder&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventoryService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt;
                       &lt;span class="n"&gt;pricingService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;calculate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt;
                       &lt;span class="n"&gt;customerService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;availability&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
                           &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EnrichedOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;availability&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; — &lt;code&gt;ValidOrder.validOrder()&lt;/code&gt; returns &lt;code&gt;Result&amp;lt;ValidOrder&amp;gt;&lt;/code&gt;. Parse, don't validate. If the input is malformed, a &lt;code&gt;Cause&lt;/code&gt; explains why. No exception.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync to async&lt;/strong&gt; — &lt;code&gt;.async()&lt;/code&gt; lifts the &lt;code&gt;Result&lt;/code&gt; into a &lt;code&gt;Promise&lt;/code&gt;. From here, everything is non-blocking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel fetch&lt;/strong&gt; — &lt;code&gt;enrichOrder&lt;/code&gt; calls three independent services simultaneously. All must succeed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enrichment&lt;/strong&gt; — results combined into &lt;code&gt;EnrichedOrder&lt;/code&gt;. The &lt;code&gt;map&lt;/code&gt; only runs if all three calls succeed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence&lt;/strong&gt; — saved to repository. Returns &lt;code&gt;Promise&amp;lt;OrderConfirmation&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side effect&lt;/strong&gt; — event published asynchronously. Does not affect the response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No try-catch. No null checks. No &lt;code&gt;if (result == null) return error&lt;/code&gt;. Every failure path handled by the type system. Every step clearly visible.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Decision Tree
&lt;/h2&gt;

&lt;p&gt;Choosing the right type is mechanical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can this operation fail?
├── NO: Can the value be absent?
│   ├── NO → return T
│   └── YES → return Option&amp;lt;T&amp;gt;
└── YES: Is it async/IO?
    ├── NO → return Result&amp;lt;T&amp;gt;
    └── YES → return Promise&amp;lt;T&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four return kinds. No judgment calls. The decision tree covers every method in a Java backend.&lt;/p&gt;

&lt;p&gt;One allowed combination: &lt;code&gt;Result&amp;lt;Option&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; — when the value is genuinely optional but validation can still fail. Example: an optional referral code that, if provided, must match a specific format.&lt;/p&gt;

&lt;p&gt;One forbidden combination: &lt;code&gt;Promise&amp;lt;Result&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; — double error channel. &lt;code&gt;Promise&lt;/code&gt; already carries failure semantics. Nesting &lt;code&gt;Result&lt;/code&gt; inside it means two places to check for errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changes In Practice
&lt;/h2&gt;

&lt;p&gt;After a few weeks of writing code this way, something shifts. You stop thinking about error handling as a separate concern. It's not something you add after the happy path — it's embedded in the types. The compiler catches what used to be runtime surprises.&lt;/p&gt;

&lt;p&gt;Code reviews get faster. When every method returns one of four types, the shape of the code becomes predictable. You don't need to trace exception paths through five layers. The return type tells you everything.&lt;/p&gt;

&lt;p&gt;Testing simplifies. Each step in a chain is independently testable. Failures are values you can assert on — not exceptions you have to catch. Mock a dependency to return &lt;code&gt;EMAIL_ALREADY_EXISTS.result()&lt;/code&gt; and verify the chain handles it correctly.&lt;/p&gt;

&lt;p&gt;And the types compose. A &lt;code&gt;Result&lt;/code&gt; from validation flows into a &lt;code&gt;Promise&lt;/code&gt; for async processing, which fans out into parallel &lt;code&gt;Promise.all()&lt;/code&gt;, which combines back into a single response. Each piece connects to the next with &lt;code&gt;flatMap&lt;/code&gt;. The vocabulary is always the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Naming That Scales
&lt;/h2&gt;

&lt;p&gt;Types solve the "what can happen" question. But there's another source of friction — naming. Every code review has that moment: "should this be &lt;code&gt;fetchUser&lt;/code&gt; or &lt;code&gt;loadUser&lt;/code&gt; or &lt;code&gt;getUser&lt;/code&gt;?" The debate is real, and it wastes time because there's no shared vocabulary.&lt;/p&gt;

&lt;p&gt;Zone-based naming eliminates this. The idea, adapted from &lt;a href="https://medium.com/@brandt.a.derrick/how-to-write-clean-code-actually-5205963ec524" rel="noopener noreferrer"&gt;Derrick Brandt's systematic approach to clean code&lt;/a&gt;: verbs belong to abstraction levels. Use the wrong verb at the wrong level, and the name signals something misleading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zone 2 — orchestration steps.&lt;/strong&gt; These coordinate other operations. They don't touch databases or parse bytes. They organize.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verb&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;validate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checking rules and constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;process&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transforming or interpreting data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;load&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieving data for use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;save&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persisting changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resolve&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Determining ambiguous cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Assembling complex objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Informing others of events&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Zone 3 — leaf operations.&lt;/strong&gt; These do the actual work. One responsibility, specific and concrete.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verb&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fetch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull from external source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Break down structured input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build structured output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;calculate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Perform computation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cryptographic transformation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;send&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transmit over network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;extract&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull piece from larger structure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Zone 2 — step interface, orchestration verb&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;LoadUserProfile&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Zone 3 — leaf, implementation verb&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchFromDatabase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CachedUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;extractFromCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see &lt;code&gt;fetch&lt;/code&gt; on a step interface — something's wrong. If you see &lt;code&gt;process&lt;/code&gt; on a leaf — same thing. The verb tells you the abstraction level before you read the parameters.&lt;/p&gt;

&lt;p&gt;This matters for the same reason the four return types matter: it makes code predictable. A developer scanning an unfamiliar codebase can tell from the name alone whether they're looking at orchestration or implementation. No need to open the method body.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If your codebase currently uses exceptions for business errors and null for absent values, you don't need to rewrite everything. Start at the boundaries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick one new feature.&lt;/strong&gt; Write it with &lt;code&gt;Result&lt;/code&gt; returns instead of exceptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrap legacy calls&lt;/strong&gt; with &lt;code&gt;Result.lift()&lt;/code&gt; and &lt;code&gt;Option.option()&lt;/code&gt; at the adapter boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the types propagate.&lt;/strong&gt; Once one method returns &lt;code&gt;Result&lt;/code&gt;, its callers naturally follow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The types are available in &lt;a href="https://github.com/pragmaticalabs/pragmatica" rel="noopener noreferrer"&gt;Pragmatica Core&lt;/a&gt; — a focused library with no transitive dependencies.&lt;/p&gt;

&lt;p&gt;This is the foundation that &lt;a href="https://pragmaticalabs.io/jbct.html" rel="noopener noreferrer"&gt;JBCT&lt;/a&gt; (Java Backend Coding Technology) builds on. Six structural patterns, four return kinds, mechanical rules that make code deterministic and AI-friendly. But the types come first. Everything else follows from getting the types right.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previously: &lt;a href="https://dev.to/siy/introduction-to-pragmatic-functional-java-142m"&gt;Introduction to Pragmatic Functional Java&lt;/a&gt; (2019) | &lt;a href="https://dev.to/siy/we-should-write-java-code-differently-210b"&gt;We Should Write Java Code Differently&lt;/a&gt; (2021)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>functional</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>No Framework, No Pain: Writing Aether Slices</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Thu, 19 Feb 2026 10:08:28 +0000</pubDate>
      <link>https://forem.com/siy/no-framework-no-pain-writing-aether-slices-4mi6</link>
      <guid>https://forem.com/siy/no-framework-no-pain-writing-aether-slices-4mi6</guid>
      <description>&lt;h1&gt;
  
  
  No Framework, No Pain: Writing Aether Slices
&lt;/h1&gt;

&lt;p&gt;The &lt;a href="https://dev.to/siy/pragmatica-aether-let-java-be-java-4k2g"&gt;previous article&lt;/a&gt; introduced Aether's philosophy: return Java to managed runtimes, let the runtime handle infrastructure, and let developers handle business logic. This article shows what that looks like in practice -- what you actually write, how dependencies work, how testing works, and how existing code migrates in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Microservice Is Just an Interface
&lt;/h2&gt;

&lt;p&gt;Here's an entire deployable service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PlaceOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;PricingEngine&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;pricing:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;OrderResult:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;placed&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a simplified example. That's the actual thing. The annotation processor sees &lt;code&gt;@Slice&lt;/code&gt;, reads the factory method signature and generates the wiring code, the proxy for remote calls, and the deployment metadata. You write one interface. You get a service that scales, fails over, and routes transparently across a distributed cluster.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;@Autowired&lt;/code&gt;. No &lt;code&gt;application.yml&lt;/code&gt;. No &lt;code&gt;@Configuration&lt;/code&gt; class. No &lt;code&gt;@Bean&lt;/code&gt; method. No component scan. No service locator. No dependency injection container at all.&lt;/p&gt;

&lt;p&gt;The factory method &lt;em&gt;is&lt;/em&gt; dependency injection. Its parameters &lt;em&gt;are&lt;/em&gt; the declared dependencies. The compiler verifies them. The annotation processor wires them. Nothing to configure, nothing to forget, nothing to debug at 2 AM.&lt;/p&gt;

&lt;p&gt;Notice what the factory returns: a lambda. No implementation class. The interface has one method, so the factory returns a lambda that implements it directly. Business logic as a function. For slices with multiple methods, a private record captures the dependencies and implements the interface -- still no separate &lt;code&gt;Impl&lt;/code&gt; class, no file to maintain, and no indirection to trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Rules, Zero Boilerplate
&lt;/h2&gt;

&lt;p&gt;Every slice follows two rules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The factory method declares dependencies.&lt;/strong&gt; What the slice needs from the outside world appears in one place: the factory method signature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                 &lt;span class="nc"&gt;PricingEngine&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;pricing:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;OrderResult:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;placed&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the factory; know the dependencies. No configuration file can contradict it. No runtime surprise can introduce a dependency the compiler hasn't seen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Promise return types.&lt;/strong&gt; Every method returns &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt;. This isn't a stylistic choice -- it's what makes transparent distribution possible. Whether the call is in-process or cross-network, the caller sees the same type.&lt;/p&gt;

&lt;p&gt;That's it. Two rules. Everything else follows from them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic Is in the Factory Method
&lt;/h2&gt;

&lt;p&gt;The annotation processor looks at each factory parameter and classifies it automatically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the processor sees&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@PrimaryDb SqlConnector db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resource -- provisions from config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InventoryService inventory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;External slice -- generates a network proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OrderValidator validator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local interface with factory -- calls the factory directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You don't configure this. You don't annotate dependencies with &lt;code&gt;@Inject&lt;/code&gt; or &lt;code&gt;@Qualifier&lt;/code&gt; (except for infrastructure resources). You just list what you need, and the processor figures out how to provide it.&lt;/p&gt;

&lt;p&gt;Consider what this means. A parameter annotated with &lt;code&gt;@ResourceQualifier&lt;/code&gt; is infrastructure -- a database connection, an HTTP client, or a message queue. The processor provisions it from configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ResourceQualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SqlConnector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"database.primary"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nd"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;PrimaryDb&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FindOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt; &lt;span class="nf"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PrimaryDb&lt;/span&gt; &lt;span class="nc"&gt;SqlConnector&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;OrderResult:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fromRow&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A parameter that's a &lt;code&gt;@Slice&lt;/code&gt; interface from another package is a remote dependency. The processor generates a proxy record that delegates to the runtime's invocation fabric. Your code calls &lt;code&gt;inventory.check(request)&lt;/code&gt; as a method call. The proxy handles serialization, routing, retry, and failover.&lt;/p&gt;

&lt;p&gt;A parameter that's a plain interface with a static factory method is local. The processor calls the factory directly. No proxy, no network, no overhead.&lt;/p&gt;

&lt;p&gt;All three categories coexist in one factory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;LoanService&lt;/span&gt; &lt;span class="nf"&gt;loanService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PrimaryDb&lt;/span&gt; &lt;span class="nc"&gt;SqlConnector&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                               &lt;span class="nc"&gt;CreditBureau&lt;/span&gt; &lt;span class="n"&gt;creditBureau&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                               &lt;span class="nc"&gt;RiskCalculator&lt;/span&gt; &lt;span class="n"&gt;riskCalculator&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;riskCalculator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;assess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;creditBureau&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;applicant&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
                                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credit&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;persistDecision&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credit&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Database from config. Credit bureau via network proxy. Risk calculator instantiated locally. One line declares it all. The processor handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors Without Exceptions
&lt;/h2&gt;

&lt;p&gt;Frameworks train you to throw exceptions. Spring converts them to HTTP status codes. Jackson serializes error responses. Exception handlers map types to messages. It works until someone throws an unexpected exception, and the generic 500 response tells the caller nothing.&lt;/p&gt;

&lt;p&gt;Slices use sealed Cause hierarchies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderCause&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Cause&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;OrderCause&lt;/span&gt; &lt;span class="no"&gt;EMPTY_ORDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EmptyOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order must have items"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;OrderCause&lt;/span&gt; &lt;span class="no"&gt;INSUFFICIENT_STOCK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InsufficientStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Insufficient stock"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderCause&lt;/span&gt; &lt;span class="nf"&gt;insufficientStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StockStatus&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InsufficientStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Insufficient stock: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;EmptyOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;OrderCause&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;InsufficientStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;OrderCause&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every failure mode is a type. The compiler knows all of them. Pattern matching handles them exhaustively. No surprise &lt;code&gt;NullPointerException&lt;/code&gt; masquerading as a business error. No &lt;code&gt;catch (Exception e)&lt;/code&gt; swallowing context.&lt;/p&gt;

&lt;p&gt;When something fails, you return a failed promise using fluent style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;checkStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stockRequest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;verifyAvailability&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StockStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;verifyAvailability&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StockStatus&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sufficient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;completeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderCause&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insufficientStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;promise&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime propagates the Cause across the network. The caller gets a typed failure, not a string message. The error handling contract is part of the API, not an afterthought in a &lt;code&gt;@ControllerAdvice&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: It's Just Java
&lt;/h2&gt;

&lt;p&gt;No test containers to spin up. No mock server to configure. No &lt;code&gt;@SpringBootTest&lt;/code&gt; annotation that loads half the universe. No mocking frameworks either -- dependencies are interfaces, so you pass lambdas that return exactly what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;placeOrder_succeeds_whenInventoryAvailable&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StockResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RES-123"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="nc"&gt;PricingEngine&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PriceResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ORD-456"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;await&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onFailure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Assertions:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ORD-456"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;placeOrder_fails_whenInventoryUnavailable&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;OrderCause&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INSUFFICIENT_STOCK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;promise&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;PricingEngine&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PriceResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ORD-456"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;await&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
               &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Assertions:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependencies are interfaces. Pass lambdas that return success; pass lambdas that return failure. The factory method wires them in. No reflection, no classpath scanning, no context initialization. No Mockito, no &lt;code&gt;when(...).thenReturn(...)&lt;/code&gt;, no &lt;code&gt;verify(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Test startup is instant because there's nothing to start. No container. No framework. No bean resolution. Just objects calling objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Slices at Any Granularity
&lt;/h2&gt;

&lt;p&gt;A single Maven module can contain as many slices as make sense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commerce/
  src/main/java/org/example/
    order/
      OrderService.java       # @Slice
    payment/
      PaymentService.java     # @Slice
    shipping/
      ShippingService.java    # @Slice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;@Slice&lt;/code&gt; generates its own factory, its own API artifact, its own deployment metadata. The Maven plugin packages them separately. They deploy and scale independently. But they develop together -- shared domain types, shared build, and one repository.&lt;/p&gt;

&lt;p&gt;A slice can be as small as a single method. There's no operational overhead for small slices -- no container to configure, no load balancer to provision, and no monitoring to set up per service. This enables granular scaling that would be operationally insane with traditional microservices: one slice serving 50 instances during peak load while another idles at minimum. With Aether, it's the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Actually Happening
&lt;/h2&gt;

&lt;p&gt;Let's trace what the annotation processor generates for a simple slice with one external dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PlaceOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;OrderResult:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fromAvailability&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processor sees &lt;code&gt;InventoryService&lt;/code&gt; is from a different package and has &lt;code&gt;@Slice&lt;/code&gt; annotation -- external dependency. It generates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A proxy record&lt;/strong&gt; that implements &lt;code&gt;InventoryService&lt;/code&gt; and delegates every method call to the runtime's &lt;code&gt;SliceInvokerFacade&lt;/code&gt;. Your code calls &lt;code&gt;inventory.check(request)&lt;/code&gt;. The proxy serializes the request, routes it to a node hosting InventoryService, deserializes the response, and returns it as a &lt;code&gt;Promise&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A factory class&lt;/strong&gt; that accepts an &lt;code&gt;Aspect&lt;/code&gt; and the &lt;code&gt;SliceInvokerFacade&lt;/code&gt;, creates the proxy, and wires everything together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deployment metadata&lt;/strong&gt; in &lt;code&gt;META-INF/slice/&lt;/code&gt; -- the slice name, its methods, its dependencies. The runtime reads this to build the dependency graph and determine deployment order.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All generated. All verified at compile time. All invisible to your business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Existing Code Is Already Halfway There
&lt;/h2&gt;

&lt;p&gt;You don't need to start from scratch. Any existing Java code becomes a slice with one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Your existing Spring service&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;InventoryRepository&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;PricingService&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OrderResult&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;checkAvailability&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;availability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isAvailable&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;outOfStock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;availability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMissingItems&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;calculateQuote&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCustomerId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="c1"&gt;// ... payment, order creation, notification ...&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrap it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderProcessor&lt;/span&gt; &lt;span class="nf"&gt;orderProcessor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;legacyService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;createLegacyService&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Promise.lift()&lt;/code&gt; wraps the synchronous call, catches any exception, and returns a proper &lt;code&gt;Promise&lt;/code&gt; with a typed failure instead of a stack trace. &lt;br&gt;
Your legacy code runs unchanged inside. The slice deploys to the Aether runtime (initially as a one-process cluster called Ember) alongside your existing application -- same JVM, no new risk. Move to a full Aether cluster when ready. That's a configuration change, not a code change.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Peeling Pattern
&lt;/h3&gt;

&lt;p&gt;The wrapped slice works, but it's a black box. The peeling pattern opens it up incrementally -- one layer at a time, working code at every step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Peel the outer structure.&lt;/strong&gt; Replace the opaque &lt;code&gt;lift()&lt;/code&gt; with a Sequencer where each step is still wrapped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyCheckInventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyCalculatePricing&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyProcessPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyCreateOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the pipeline is visible. You can see the steps, test them individually, and reason about the flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Peel one step deeper.&lt;/strong&gt; Take the hottest &lt;code&gt;lift()&lt;/code&gt; and expand it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Availability&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;checkInventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyCheckWarehouse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
                       &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyCheckSupplier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;combineAvailability&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The outer call is now clean JBCT. The inner calls are still wrapped. Tests pass at every step. Stop anywhere -- mixed JBCT and legacy code works fine. The remaining &lt;code&gt;lift()&lt;/code&gt; calls mark exactly where legacy code lives. When they're all gone, you have a clean slice. But there's no deadline. Each peeling step delivers value on its own.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/siy/fail-safe-your-legacy-java-in-one-sprint-p5l"&gt;full migration walkthrough&lt;/a&gt; covers the complete path -- from initial wrapping through fault tolerance to clean JBCT code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift
&lt;/h2&gt;

&lt;p&gt;Traditional microservice development is a negotiation with frameworks. You learn their abstractions, their lifecycle hooks, their configuration DSLs, their annotation model, their error-handling conventions, and their testing utilities. The framework becomes the center of gravity. Your business logic orbits around it.&lt;/p&gt;

&lt;p&gt;Slices invert this. Business logic is the center. The interface defines the contract. The factory method declares dependencies. The implementation is a lambda. Everything else -- serialization, routing, scaling, failover, and configuration -- is the runtime's problem.&lt;/p&gt;

&lt;p&gt;You don't learn a framework. You write Java interfaces and implement them. Two rules. The rest is just your domain.&lt;/p&gt;

&lt;p&gt;No framework. No pain.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pragmaticalabs.io/aether.html" rel="noopener noreferrer"&gt;Pragmatica Aether&lt;/a&gt; -- distributed Java runtime&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pragmaticalabs/pragmatica" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; -- source code&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pragmaticalabs/pragmatica/blob/main/aether/docs/slice-developers/development-guide.md" rel="noopener noreferrer"&gt;Slice Development Guide&lt;/a&gt; -- full reference&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Pragmatica Aether: Let Java Be Java</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Fri, 13 Feb 2026 14:38:14 +0000</pubDate>
      <link>https://forem.com/siy/pragmatica-aether-let-java-be-java-4k2g</link>
      <guid>https://forem.com/siy/pragmatica-aether-let-java-be-java-4k2g</guid>
      <description>&lt;h2&gt;
  
  
  The Aberration
&lt;/h2&gt;

&lt;p&gt;We build Java applications like Go or Rust programs. Fat JARs. Docker images. Kubernetes deployments. Everyone does it, so it looks normal.&lt;/p&gt;

&lt;p&gt;It contradicts Java's design DNA.&lt;/p&gt;

&lt;p&gt;Java has always been a language for managed environments. Applets ran inside browsers. Servlets ran inside application servers. EJBs ran inside containers like JBoss and WebLogic. OSGi bundles ran inside runtime containers like Eclipse Equinox. In every generation, the pattern was the same: a managed runtime hosts the application. The application handles business logic. The runtime handles infrastructure.&lt;/p&gt;

&lt;p&gt;The fat-jar era threw that away. We stopped letting Java be Java. We started bundling web servers, serialization frameworks, service discovery clients, configuration management, health checks, metrics libraries, and logging frameworks into every application. Then we wrapped the result in a Docker container and deployed it to an orchestration platform that reimplements — poorly — the infrastructure management that Java runtimes used to provide natively.&lt;/p&gt;

&lt;p&gt;This article introduces &lt;a href="https://pragmaticalabs.io/aether.html" rel="noopener noreferrer"&gt;Pragmatica Aether&lt;/a&gt;: a distributed runtime that returns Java to its natural habitat. Application handles business logic. Runtime handles infrastructure. This isn't radical — it's returning to what Java was designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Infrastructure Wearing a Business Logic Mask
&lt;/h2&gt;

&lt;p&gt;Think of what a typical Java microservice carries. A web server (Tomcat, Netty, Undertow). A serialization framework (Jackson, Gson). A dependency injection container (Spring, Guice). A service discovery client (Eureka, Consul). Health check endpoints. Configuration management (Spring Cloud Config, Consul KV). A metrics library (Micrometer, Dropwizard). A logging framework (Logback, Log4j2). Retry logic (Resilience4j). Circuit breakers. HTTP client configuration. The application is wearing a heavy winter coat of infrastructure, armed to the teeth to survive in a hostile environment.&lt;/p&gt;

&lt;p&gt;Now consider the coupling this creates.&lt;/p&gt;

&lt;p&gt;Update Java version — rebuild and test every service. Change your message broker from RabbitMQ to Kafka — modify, rebuild, and redeploy every application that touches messaging. Add a new observability tool — update dependencies in every microservice. Switch cloud providers — rewrite configuration, SDK calls, and deployment manifests across the entire fleet. Each change ripples through dozens or hundreds of services because infrastructure is entangled with business logic at the dependency level.&lt;/p&gt;

&lt;p&gt;This is the coupling trap. Your application's &lt;code&gt;pom.xml&lt;/code&gt; doesn't distinguish between business dependencies and infrastructure dependencies. They compile together, deploy together, and break together. A security patch in Netty requires a new build of every service that embeds a web server — which is all of them.&lt;/p&gt;

&lt;p&gt;Framework lock-in makes this worse. It isn't a vendor problem — it's an architecture problem. Spring's dependency injection fights with Kubernetes service mesh for control over service routing and circuit breaking. The framework's configuration system overlaps with Consul KV and Kubernetes ConfigMaps. Your cloud SDK's retry logic conflicts with Resilience4j. Every layer claims authority over the same cross-cutting concerns, and the conflicts surface as subtle bugs in production — not during development.&lt;/p&gt;

&lt;p&gt;This is an architecture problem. Architecture problems have architectural solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aether: The Core Idea
&lt;/h2&gt;

&lt;p&gt;What you write: an interface annotated with &lt;code&gt;@Slice&lt;/code&gt;, plus business logic implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PlaceOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PricingEngine&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;calculate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                                   &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priced&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;OrderResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;placed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priced&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you don't write: everything else.&lt;/p&gt;

&lt;p&gt;No HTTP clients — inter-slice calls are direct method invocations via generated proxies. No service discovery — the runtime tracks where every slice instance lives. No retry logic — built-in retry with exponential backoff and node failover. No circuit breakers — the reliability fabric handles failure automatically. No serialization code — request/response types are serialized transparently.&lt;/p&gt;

&lt;p&gt;A method call via imported interface is the only visible contract. The only hint that the actual call might be remote is a design requirement: slice methods should be idempotent. This isn't a limitation — it's what enables retry, scaling, and fault tolerance to work transparently. The same request, processed by any available instance, producing the same result. Most read operations are naturally idempotent. For writes, standard patterns like idempotency keys and conditional writes handle it cleanly.&lt;/p&gt;

&lt;p&gt;Everything else is the environment's job: resource provisioning, scaling, transport, discovery, retries, circuit breakers, configuration, observability, logging, tracing, monitoring, security. None of these are application concerns and none should be handled at the business logic level.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/siy/the-six-patterns-that-cover-everything-4d8a"&gt;JBCT Leaf pattern&lt;/a&gt; serves two purposes here: it documents the design ("what we expect from an external implementation") and encourages exactly one interface per dependency. Different implementations may have different technical properties — performance, latency, memory consumption — but as long as they're compatible with the interface, business logic works unchanged.&lt;/p&gt;

&lt;p&gt;You write basically pure business logic that scales from your local computer to a global multi-zone distributed deployment, transparently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under The Hood: What Makes It Work
&lt;/h2&gt;

&lt;p&gt;Five architectural decisions make this possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consensus KV Store.&lt;/strong&gt; A single source of truth for all configuration, deployment state, and service discovery. Based on the &lt;a href="https://dl.acm.org/doi/10.1145/3477132.3483582" rel="noopener noreferrer"&gt;Rabia protocol&lt;/a&gt; — a crash-fault-tolerant, leaderless consensus algorithm published in 2021. Any node can propose; agreement is reached through a two-round voting protocol with a fast path when super-majority agrees in round one. No external config servers. No etcd. No Consul. Configuration changes propagate through consensus and take effect cluster-wide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built-in Artifact Repository.&lt;/strong&gt; DHT-based storage with configurable replication — 3 replicas with quorum reads/writes in production, full replication in development. Artifacts are chunked into 64KB pieces, distributed across nodes via consistent hashing, and integrity-verified with MD5 and SHA-1 on every resolve. No external Nexus or Artifactory needed. During development, slices resolve from your local Maven repository. In production, the cluster is self-contained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ClassLoader Isolation.&lt;/strong&gt; Each slice runs in its own &lt;code&gt;SliceClassLoader&lt;/code&gt; with child-first delegation. Two slices can use different versions of the same library without conflict. Shared dependencies like Pragmatica Lite core are loaded once in a parent classloader. No dependency conflicts. No classpath hell between slices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Declarative Deployment.&lt;/strong&gt; Blueprints — TOML files — describe the desired state: which slices, how many instances.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.example:commerce:1.0.0"&lt;/span&gt;

&lt;span class="nn"&gt;[[slices]]&lt;/span&gt;
&lt;span class="py"&gt;artifact&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.example:inventory-service:1.0.0"&lt;/span&gt;
&lt;span class="py"&gt;instances&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="nn"&gt;[[slices]]&lt;/span&gt;
&lt;span class="py"&gt;artifact&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.example:order-processor:1.0.0"&lt;/span&gt;
&lt;span class="py"&gt;instances&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply with one command: &lt;code&gt;aether blueprint apply commerce.toml&lt;/code&gt;. The cluster resolves artifacts, loads slices, distributes instances across nodes, registers routes, and starts serving traffic. The cluster converges to the desired state automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Independence.&lt;/strong&gt; Aether nodes are identical — there's only one deployment artifact to manage at the infrastructure level. Node updates and application deployments run on completely independent schedules. Update Java — roll it out across nodes without touching applications. Update the Aether runtime — same. Update business logic — deploy new slice versions without touching infrastructure. Each independently, each without downtime. This is the fundamental benefit of proper separation: when layers don't share a deployment unit, they don't share a deployment schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fault Tolerance: The 50% Rule
&lt;/h2&gt;

&lt;p&gt;The system survives failure of less than half the nodes. Performance may degrade until replacements spin up, but functionality remains intact — actual redundancy, not just graceful degradation. A 5-node cluster tolerates 2 simultaneous failures. A 7-node cluster tolerates 3. The same request, processed by any available node, producing the same result. Quorum requires &lt;code&gt;(N/2) + 1&lt;/code&gt; nodes — as long as a majority is alive, the cluster operates normally.&lt;/p&gt;

&lt;p&gt;Leader failover is consensus-based and near-instant. Node replacement happens automatically — the Cluster Deployment Manager detects the deficit and provisions a replacement through the NodeProvider interface. The entire recovery sequence — from failure detection through state restoration to serving traffic — completes without human intervention.&lt;/p&gt;

&lt;p&gt;When a node fails, the recovery is automatic. Requests to slices on the failed node are immediately retried on healthy nodes. A replacement node is provisioned. It connects to peers, restores consensus state from a cluster snapshot, re-resolves artifacts from the DHT, and re-activates assigned slices. Dead nodes are automatically removed from routing tables. The new leader reconciles stale state. No human intervention required.&lt;/p&gt;

&lt;p&gt;Rolling updates leverage this fault tolerance for zero-downtime deployments with weighted traffic routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aether update start org.example:order-processor 2.0.0 &lt;span class="nt"&gt;-n&lt;/span&gt; 3
aether update routing &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; 1:3    &lt;span class="c"&gt;# 25% to v2, 75% to v1&lt;/span&gt;
aether update routing &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; 1:1    &lt;span class="c"&gt;# 50/50&lt;/span&gt;
aether update &lt;span class="nb"&gt;complete&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;          &lt;span class="c"&gt;# 100% to v2, drain v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy during business hours. Shift traffic gradually — 10% canary, then 25%, 50%, 75%, 100%. Monitor health metrics at each step. If health degrades — error rate exceeds thresholds, latency spikes — instant rollback with one command: &lt;code&gt;aether update rollback &amp;lt;id&amp;gt;&lt;/code&gt;. Traffic immediately shifts back to the old version. The 3 AM pager alert becomes an audit log entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  For Every Project: Legacy, Greenfield, And Everything Between
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Legacy Migration
&lt;/h3&gt;

&lt;p&gt;Your legacy Java system doesn't need a complete rewrite. It needs a path forward.&lt;/p&gt;

&lt;p&gt;Pick a relatively independent part of your system — something hitting limits, something with clear boundaries. Extract an interface. Annotate it with &lt;code&gt;@Slice&lt;/code&gt;. Wrap the legacy implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Report&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;generateReport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReportRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyReportService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line to enter the Aether world. &lt;code&gt;Promise.lift()&lt;/code&gt; wraps the legacy call, catches exceptions, and returns a proper &lt;code&gt;Result&lt;/code&gt; inside a &lt;code&gt;Promise&lt;/code&gt;. Your legacy code keeps running. Call sites don't change. You haven't added risk — the initial deployment in Ember runs in the same JVM as your existing application, which means it's no worse than what you have today. You've laid the foundation for removing risk, not adding it. Moving from Ember to a full Aether cluster is a configuration change, not a code change — and that's when the 50% rule starts to apply.&lt;/p&gt;

&lt;p&gt;From there, it's the strangler fig pattern. Extract a hot path, deploy it as a slice, route traffic, repeat. Each extracted slice can be gradually refactored using the &lt;a href="https://dev.to/siy/fail-safe-your-legacy-java-in-one-sprint-p5l"&gt;peeling pattern&lt;/a&gt;: first wrap everything in &lt;code&gt;Promise.lift()&lt;/code&gt;, then decompose into a Sequencer with each step still wrapped, then peel individual steps into clean JBCT patterns. Tests pass at every step. The &lt;code&gt;lift()&lt;/code&gt; calls mark exactly where legacy code remains, making progress visible and remaining work obvious.&lt;/p&gt;

&lt;p&gt;No rewrite required. No big bang migration. One sprint to first slice in production. The &lt;a href="https://dev.to/siy/fail-safe-your-legacy-java-in-one-sprint-p5l"&gt;migration article&lt;/a&gt; covers the full path in detail — from initial wrapping through gradual peeling to clean JBCT code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Greenfield Development
&lt;/h3&gt;

&lt;p&gt;For new projects, slices enable a granularity that's impossible with traditional microservices.&lt;/p&gt;

&lt;p&gt;Each slice can be as lean as a single method — and that's the recommended approach. There are no operational or complexity tradeoffs for small slices because Aether handles all the infrastructure overhead. No container to configure, no load balancer to provision, no monitoring to set up per service. You get per-use-case scaling: one slice serving 50 instances during peak load while another idles at minimum. That kind of granularity would be operationally insane with traditional microservices — each needing its own container, load balancer, monitoring, and deployment pipeline. With Aether, it's the default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/siy/the-six-patterns-that-cover-everything-4d8a"&gt;JBCT patterns&lt;/a&gt; — Leaf, Sequencer, Fork-Join, Condition, Iteration, Aspects — compose naturally within slices. Each slice method is a &lt;a href="https://dev.to/siy/the-underlying-process-of-request-processing-1od4"&gt;data transformation pipeline&lt;/a&gt;: parse input, gather data, process, respond. The patterns provide consistent structure within slices. &lt;a href="https://dev.to/siy/slices-the-right-size-for-microservices-5cco"&gt;Slices provide consistent boundaries&lt;/a&gt; between them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Spectrum
&lt;/h3&gt;

&lt;p&gt;Same slice model, different granularity. A service slice wraps an entire legacy component. A lean slice implements a single method. Both coexist in the same cluster, deployed and scaled independently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/siy/slices-the-right-size-for-microservices-5cco"&gt;Slice is the executable unit&lt;/a&gt;. It can be big or small as necessary and convenient. The architecture accommodates both monolith migration and greenfield development simultaneously. Your legacy system gains fault tolerance while new features get maximum deployment flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling: Two Levels, Three Tiers of Intelligence
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two-Level Horizontal Scaling
&lt;/h3&gt;

&lt;p&gt;Aether scales in two dimensions independently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slice scaling&lt;/strong&gt;: Spin up more instances of a specific slice on existing nodes. Classes are already loaded — scaling takes milliseconds, not seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node scaling&lt;/strong&gt;: Add more machines to the cluster. The node connects, restores state, and begins accepting work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Independent controls, combined effect. Each node hosts at most one instance of a given slice, so scaling a slice beyond the current node count requires adding nodes first. Add 2 more nodes to a 3-node cluster, then scale a hot slice to 5 instances — one per node. No coordination between the two dimensions required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three-Tier Decision System
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tier 1 — Decision Tree (1-second intervals).&lt;/strong&gt; Instant reactive decisions based on CPU utilization, request latency, queue depth, and error rate. CPU above 70%? Add an instance. Below 30% sustained? Remove one (if above minimum). Latency exceeding P95 threshold? Scale up. Error rate above 1% due to timeouts? Scale up. Deterministic, predictable, fast. Handles routine load changes with configurable cooldown periods — 30 seconds for scale-up, 5 minutes for scale-down — to prevent oscillation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2 — TTM Predictor (60-second intervals).&lt;/strong&gt; An ONNX-based machine learning model (Tiny Time Mixers) analyzing a 60-minute sliding window of metrics — CPU usage, request rate, P95 latency, active instances. Forecasts load and adjusts the Decision Tree's thresholds preemptively. If TTM predicts a load increase, it lowers the scale-up CPU threshold by 20% so the reactive tier responds earlier. The cluster scales before the spike arrives, not after. The key design principle: the cluster always survives on Tier 1 alone. TTM enhances; it doesn't replace. If TTM fails — model load error, insufficient data, inference failure — the Decision Tree continues with default thresholds. The error is logged and recorded in metrics. No scaling disruption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3 — LLM-based (planned).&lt;/strong&gt; Long-term capacity planning and cluster health monitoring. Seasonal pattern prediction, maintenance window planning, anomaly investigation. This tier is not yet implemented — the current system operates with Tiers 1 and 2.&lt;/p&gt;

&lt;p&gt;Fault tolerance makes preemptible instances viable for burst scaling. If a spot instance gets reclaimed, the cluster survives — it was designed for nodes to disappear.&lt;/p&gt;

&lt;p&gt;You don't need a PhD in distributed systems or a dedicated platform team. The scaling system manages itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Experience: From Laptop To Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Three Environments, Zero Code Changes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ember.&lt;/strong&gt; Single-process runtime with multiple cluster nodes running in the same JVM. Fast startup, simple debugging. Deploy your slices alongside your existing application — slices call each other directly in-process. No network overhead. Standard debugger breakpoints work as expected. Perfect for local development and unit testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forge.&lt;/strong&gt; A 5-node cluster simulator running on your laptop. Real consensus. Real routing. Real failure scenarios. Kill nodes, crash the leader, trigger rolling restarts — and watch the cluster recover in real time through a web dashboard with D3.js topology visualization, per-node metrics (CPU, heap, leader status), and event timeline. Configurable load generation with TOML-based multi-target configuration lets you stress-test realistic scenarios — set request rates, define body templates, and run duration-limited load tests. Chaos operations include node kill, leader kill, and rolling restart. Forge validates the entire dependency graph before starting anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aether.&lt;/strong&gt; Production cluster. Same slices, same code, different scale. Your code doesn't know which environment it's running in. Whether inter-slice calls are in-process or cross-network is transparent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tooling
&lt;/h3&gt;

&lt;p&gt;37 CLI commands cover deployment, scaling, updates, artifacts, observability, controller configuration, and alerts — in both single-command and interactive REPL modes. A web dashboard streams real-time metrics via WebSocket — no polling. 30+ REST management endpoints enable full programmatic control of everything the CLI can do. Prometheus-compatible metrics export (&lt;code&gt;/metrics/prometheus&lt;/code&gt;) integrates with existing monitoring stacks. Metrics are push-based at 1-second intervals, with zero consensus overhead — they bypass the consensus protocol entirely. Per-method invocation tracking with P50/P95/P99 latency and configurable slow-invocation detection strategies (fixed threshold, adaptive, per-method, composite) surfaces performance issues before users notice. Dynamic aspects let you toggle LOG/METRICS/LOG_AND_METRICS modes per method at runtime via REST API, without redeployment.&lt;/p&gt;

&lt;p&gt;Test realistic failure scenarios on your laptop. Deploy to production with a config change, not a code change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maturity
&lt;/h2&gt;

&lt;p&gt;Aether is a working system, not a concept paper.&lt;/p&gt;

&lt;p&gt;81 end-to-end tests run against real 5-node clusters in Podman containers, validating cluster formation, quorum establishment, slice deployment and scaling, blueprint application with topological ordering, multi-instance distribution, artifact upload and cross-node resolution with integrity verification, leader failure and recovery, node restart with state restoration, and orphaned state cleanup after leader changes.&lt;/p&gt;

&lt;p&gt;The recovery and fault tolerance claims come from automated tests against real clusters, not marketing slides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let Java Be Java
&lt;/h2&gt;

&lt;p&gt;Java's lineage leads here. From applets managed by browsers, through servlets managed by application servers, through EJBs managed by enterprise containers, through OSGi managed by runtime frameworks — to Aether, managed by a distributed runtime.&lt;/p&gt;

&lt;p&gt;The fat-jar era was a detour. An understandable one — when Docker emerged, it offered a universal packaging format, and the industry standardized on it regardless of language. Java adopted the patterns of languages that were designed to produce standalone binaries. We started treating Java applications like Go programs with a heavier runtime. But it was never the destination. Java was designed for managed environments. The JVM makes it possible. The runtime manages the application. That's the lineage. Aether continues it.&lt;/p&gt;

&lt;p&gt;Two entry points exist today. Wrap your legacy monolith behind a &lt;code&gt;@Slice&lt;/code&gt; interface in one sprint and gain fault tolerance without rewriting anything. Or start fresh with maximum clarity — lean slices, explicit contracts, per-use-case scaling. Both paths converge on the same runtime, the same cluster, the same operational model. Both paths can coexist — legacy service slices and new lean slices running side by side.&lt;/p&gt;

&lt;p&gt;Fault tolerance is not an afterthought — it's the foundation. Scaling is not your problem — it's the environment's. Infrastructure is not your code — it's the runtime's. The heavy winter coat comes off. The application breathes.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pragmaticalabs.io" rel="noopener noreferrer"&gt;Pragmatica Aether&lt;/a&gt; — project site&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pragmaticalabs/pragmatica" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; — source code&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Let Java be Java. Let infrastructure be infrastructure. Write business logic that scales from your laptop to global deployment without changing a line of code.&lt;/p&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>distributedsystems</category>
      <category>backend</category>
    </item>
    <item>
      <title>Why Interface + Factory? The Java Pattern That Makes Everything Replaceable</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Sat, 07 Feb 2026 21:39:17 +0000</pubDate>
      <link>https://forem.com/siy/why-interface-factory-the-java-pattern-that-makes-everything-replaceable-3pm4</link>
      <guid>https://forem.com/siy/why-interface-factory-the-java-pattern-that-makes-everything-replaceable-3pm4</guid>
      <description>&lt;h1&gt;
  
  
  Why Interface + Factory? The Java Pattern That Makes Everything Replaceable
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;Every component — use case, processing step, adapter — is defined as an interface with a static factory method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentToken&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderConfirmation&lt;/span&gt; &lt;span class="n"&gt;confirmation&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

    &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ValidateInput&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ReserveInventory&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Reservation&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Reservation&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ConfirmOrder&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidateInput&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;ReserveInventory&lt;/span&gt; &lt;span class="n"&gt;reserve&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                     &lt;span class="nc"&gt;ConfirmOrder&lt;/span&gt; &lt;span class="n"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;reserve:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;processPayment:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;confirm:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This isn't arbitrary convention. There are three specific reasons this structure exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 1: Substitutability Without Magic
&lt;/h2&gt;

&lt;p&gt;Anyone can implement the interface. No framework. No inheritance hierarchy. No annotations.&lt;/p&gt;

&lt;p&gt;Testing becomes trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;order_fails_when_inventory_insufficient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;useCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;  &lt;span class="c1"&gt;// always valid&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;INSUFFICIENT_INVENTORY&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;                &lt;span class="c1"&gt;// always fails&lt;/span&gt;
        &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AssertionError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unreachable"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AssertionError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unreachable"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;useCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tok_123"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
           &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Assertions:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No mocking framework. No &lt;code&gt;@Mock&lt;/code&gt; annotations. No &lt;code&gt;when().thenReturn()&lt;/code&gt; chains. The test constructs the exact scenario it needs with plain lambdas.&lt;/p&gt;

&lt;p&gt;Stubbing incomplete implementations during development is equally straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Payment gateway isn't ready yet? Stub it.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;useCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;realValidator&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;realInventoryService&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stub-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Money&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ZERO&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;realConfirmation&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The team working on inventory doesn't need to wait for the payment team. Each step is independently implementable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 2: Implementation Isolation
&lt;/h2&gt;

&lt;p&gt;Each implementation is self-contained. No shared base classes. No abstract methods to override. No coupling between implementations whatsoever.&lt;/p&gt;

&lt;p&gt;Compare with the typical abstract class approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The abstract class trap&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractOrderProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Processing order: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doExecute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order result: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;doExecute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// "Shared utility" that every subclass now depends on&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Money&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LineItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 47 lines of logic that one subclass needed once&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every implementation is coupled to the base class. Change &lt;code&gt;calculateTotal&lt;/code&gt; and you need to understand every subclass. Add logging to &lt;code&gt;execute&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Implementation A: uses database&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="nf"&gt;databasePayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentRepository&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;paymentToken&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Payment:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fromRecord&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Implementation B: uses external API&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="nf"&gt;stripePayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StripeClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createCharge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;paymentToken&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Payment:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fromStripe&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 3: Disposable Implementation
&lt;/h2&gt;

&lt;p&gt;Here's the subtle one. The factory method returns a lambda or local record. It can't be referenced externally by class name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ProcessOrder&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidateInput&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                 &lt;span class="nc"&gt;ReserveInventory&lt;/span&gt; &lt;span class="n"&gt;reserve&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                 &lt;span class="nc"&gt;ProcessPayment&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                 &lt;span class="nc"&gt;ConfirmOrder&lt;/span&gt; &lt;span class="n"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// this lambda IS the implementation&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;reserve:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;processPayment:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;confirm:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No code anywhere says &lt;code&gt;new ProcessOrderImpl()&lt;/code&gt;. No code depends on the implementation class. The implementation is replaceable by definition — because nothing can reference it.&lt;/p&gt;

&lt;p&gt;The interface is the design artifact. The implementation is incidental.&lt;/p&gt;

&lt;p&gt;This might sound academic until you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace a synchronous implementation with an async one&lt;/li&gt;
&lt;li&gt;Swap a database adapter for an API adapter&lt;/li&gt;
&lt;li&gt;Add a caching layer around an existing step&lt;/li&gt;
&lt;li&gt;Completely rewrite a step's internals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compound Effect
&lt;/h2&gt;

&lt;p&gt;Each reason is valuable on its own. Together, they create a system where:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing is configuration.&lt;/strong&gt; You assemble the exact combination of real and stubbed components you need. No mocking framework overhead. No "mock everything" test fragility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactoring is safe.&lt;/strong&gt; Replacing an implementation can't break other implementations because they don't share code. The compiler enforces the contract through the interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complexity is bounded.&lt;/strong&gt; Understanding one implementation requires understanding only that implementation and the interfaces it consumes. Not a base class hierarchy. Not shared utilities. Not other implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental development is natural.&lt;/strong&gt; Stub what's not ready. Replace stubs with real implementations one at a time. Each step can be developed, tested, and deployed independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Does This Not Apply?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The pattern pays for itself when there's any possibility of multiple implementations — including the test implementation, which almost always exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift
&lt;/h2&gt;

&lt;p&gt;Most Java codebases default to classes. Interface extraction happens later, reluctantly, when testing forces it or when the second implementation appears.&lt;/p&gt;

&lt;p&gt;Flip this. Start with the interface. Define the contract. The implementation follows naturally — and when it needs to change, nothing else does.&lt;/p&gt;

&lt;p&gt;The interface is what you design. The implementation is what you happen to write today.&lt;/p&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>designpatterns</category>
      <category>functional</category>
    </item>
    <item>
      <title>Fail-Safe Your Legacy Java in One Sprint</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Tue, 27 Jan 2026 12:24:03 +0000</pubDate>
      <link>https://forem.com/siy/fail-safe-your-legacy-java-in-one-sprint-d2b</link>
      <guid>https://forem.com/siy/fail-safe-your-legacy-java-in-one-sprint-d2b</guid>
      <description>&lt;h1&gt;
  
  
  Fail-Safe Your Legacy Java in One Sprint
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Scaling Wall
&lt;/h2&gt;

&lt;p&gt;Your Java system works. It's been working for years. But lately, it's showing strain. Response times creep up during peak hours. That batch job that used to finish overnight now runs into morning operations. Users notice.&lt;/p&gt;

&lt;p&gt;The conventional answer is microservices. Decompose the monolith, deploy to Kubernetes, hire a platform team. Eighteen months, significant budget, and a team with specialized skills you don't have. Meanwhile, every deployment feels like Russian roulette. One bad release, one server failure, and the business stops.&lt;/p&gt;

&lt;p&gt;For middle-sized businesses, this isn't a technology problem. It's a survival problem. The system that runs your operations is both essential and fragile. You can't afford to replace it, and you can't afford to lose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 50% Rule
&lt;/h2&gt;

&lt;p&gt;What if half your servers could fail and users wouldn't notice?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pragmaticalabs.io/aether.html" rel="noopener noreferrer"&gt;Aether&lt;/a&gt;, the runtime behind the slice architecture, provides exactly this guarantee. When your code runs across a cluster, failure of less than half the nodes affects only performance, not functionality. Requests automatically route to surviving nodes. No manual intervention, no pager alerts at 3 AM.&lt;/p&gt;

&lt;p&gt;This isn't eventual consistency or graceful degradation. It's actual redundancy. The same request, processed by any available node, producing the same result. Your business keeps running while you fix the failed hardware.&lt;/p&gt;

&lt;p&gt;For a C-level executive, this translates simply: business continuity without enterprise budget. The system that runs your operations becomes the system that survives failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simplest Migration Path
&lt;/h2&gt;

&lt;p&gt;Here's how to start. Pick a relatively independent part of your system. Something that's already hitting limits. Something with clear boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Extract an interface.&lt;/strong&gt; This is mechanical. Any Java developer can do it. The interface defines what the component does, not how.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ReportGenerator&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Report&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;generateReport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReportRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Make it idempotent.&lt;/strong&gt; The same request should produce the same result, even if processed multiple times. Pragmatica Lite provides built-in support for this pattern. For most code, it's a small change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Deploy Ember.&lt;/strong&gt; Ember runs multiple cluster nodes in the same JVM as your existing application. Your legacy code calls the interface exactly as before. No changes to call sites.&lt;/p&gt;

&lt;p&gt;That's it. Your first slice is running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important caveat:&lt;/strong&gt; This initial step is not fault-tolerant. You're still running in a single JVM, which means a single point of failure. But here's the key insight: it's no worse than what you have today. You haven't added risk. You've laid the foundation for removing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Foundation to Fault Tolerance
&lt;/h2&gt;

&lt;p&gt;The foundation is in place. Now you can build on it.&lt;/p&gt;

&lt;p&gt;Moving from Ember to full Aether deployment is a configuration change, not a code change. Your slices, your interfaces, your business logic—all unchanged. You're just telling the runtime to distribute across multiple machines instead of running in-process.&lt;/p&gt;

&lt;p&gt;Now the 50% rule applies. Your report generator runs on three nodes. One dies. The other two handle the load. You fix the failed node when convenient, not when panicked.&lt;/p&gt;

&lt;p&gt;Each step in this path delivers value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ember in-JVM&lt;/strong&gt;: Foundation laid, no new risk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ember multi-node&lt;/strong&gt;: Development and testing environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aether cluster&lt;/strong&gt;: Production fault tolerance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You control the pace. Extract another slice when ready. Expand the cluster when needed. The architecture grows with your confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Peeling Pattern
&lt;/h2&gt;

&lt;p&gt;Once your slice is running, you have a choice: leave the internals as-is, or gradually refactor them. The peeling pattern lets you do the latter without risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Wrap everything.&lt;/strong&gt; Your initial slice wraps the entire legacy method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Report&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;generateReport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReportRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyReportService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Phase 2: Peel the outer layer.&lt;/strong&gt; Refactor into a Sequencer, but keep each step wrapped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Report&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;generateReport&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReportRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyFetchData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyProcess&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyFormat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Phase 3: Peel deeper.&lt;/strong&gt; Take one wrapped step and expand it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RawData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ValidRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyFetchFromDb&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
        &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;legacyFetchFromApi&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;combineData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each phase keeps the code working. Tests pass at every step. You can stop anywhere—the system runs fine with mixed JBCT and wrapped legacy code. The &lt;code&gt;lift()&lt;/code&gt; calls mark exactly where legacy code remains, making progress visible and the remaining work obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Don't Need
&lt;/h2&gt;

&lt;p&gt;Traditional modernization projects require capabilities most middle-sized businesses don't have. Aether is different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No Kubernetes expertise.&lt;/strong&gt; Aether manages its own clustering. You don't need to learn pod configurations, service meshes, or container orchestration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No platform team.&lt;/strong&gt; The runtime handles deployment, discovery, and failover. Your existing operations team can manage it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No new infrastructure.&lt;/strong&gt; Start with Ember in your existing JVM. Add machines only when you're ready for fault tolerance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No retraining.&lt;/strong&gt; Same Java. Same IDE. Same debugging. Your developers write slice interfaces exactly like they write any other interface. The patterns are familiar; only the deployment model changes.&lt;/p&gt;

&lt;p&gt;The migration path is designed for teams that have a business to run, not a technology transformation to execute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Path Forward
&lt;/h2&gt;

&lt;p&gt;Once the foundation is working, possibilities open up.&lt;/p&gt;

&lt;p&gt;More slices mean more of your system becomes fault-tolerant. The relatively independent parts you migrated first are now proven. You understand the pattern. The next extraction is faster.&lt;/p&gt;

&lt;p&gt;Aether's operational model scales beyond manual management. A three-tier control system handles increasingly complex decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision trees&lt;/strong&gt; handle routine scaling—deterministic, predictable, fast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTM (predictive models)&lt;/strong&gt; detect patterns and scale preemptively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM agents&lt;/strong&gt; (planned) handle capacity planning and anomaly investigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're not just surviving anymore. You're building toward a system that manages itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The legacy Java system running your business doesn't need a complete rewrite. It needs a path forward that doesn't bet the company on an 18-month transformation project.&lt;/p&gt;

&lt;p&gt;Start with one slice. Run it in Ember alongside your existing code. Prove it works. Then decide: add fault tolerance, extract another slice, or pause and let the system prove itself in production.&lt;/p&gt;

&lt;p&gt;The 50% rule isn't a promise for someday. It's a capability you can reach in weeks, not years. Your business deserves infrastructure that survives failures. Now there's a path to get there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of &lt;a href="//../README.md"&gt;Java Backend Coding Technology&lt;/a&gt; - a methodology for writing predictable, testable backend code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>migration</category>
      <category>backend</category>
    </item>
    <item>
      <title>Slices: The Right Size for Microservices</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Sun, 18 Jan 2026 21:49:19 +0000</pubDate>
      <link>https://forem.com/siy/slices-the-right-size-for-microservices-c4</link>
      <guid>https://forem.com/siy/slices-the-right-size-for-microservices-c4</guid>
      <description>&lt;h1&gt;
  
  
  Slices: The Right Size for Microservices
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Granularity Trap
&lt;/h2&gt;

&lt;p&gt;Every team that adopts microservices eventually hits the same wall: how big should a service be?&lt;/p&gt;

&lt;p&gt;Go too small and you drown in network calls, distributed transactions, and deployment complexity. Your simple "get user profile" operation now involves five services, three of which are just proxies for database tables. Latency compounds. Debugging becomes archaeology.&lt;/p&gt;

&lt;p&gt;Go too large and you're back to the monolith. Different teams step on each other. Deployments require coordination. The "micro" in microservices becomes ironic.&lt;/p&gt;

&lt;p&gt;The standard advice—"one service per bounded context" or "services should be independently deployable"—sounds reasonable but provides no actionable guidance. Where does one context end and another begin? What exactly makes something "independently deployable"?&lt;/p&gt;

&lt;p&gt;Teams oscillate between extremes, refactoring services that are "too small" into larger ones, then splitting services that grew "too large." The cycle repeats because the fundamental question remains unanswered: what determines the right boundary?&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundaries Before Size
&lt;/h2&gt;

&lt;p&gt;The problem isn't size. It's boundaries.&lt;/p&gt;

&lt;p&gt;A well-defined boundary has specific properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear contract&lt;/strong&gt;: callers know exactly what they can request and what they'll receive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit dependencies&lt;/strong&gt;: the component declares what it needs from outside&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal freedom&lt;/strong&gt;: implementation details can change without affecting callers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Size follows from boundaries, not the other way around. A component is the right size when it fully owns its boundaries—when everything needed to fulfill its contract lives inside, and everything outside is accessed through explicit dependencies.&lt;/p&gt;

&lt;p&gt;This is where most microservice designs fail. They draw boundaries based on technical layers (API gateway, business logic, database access) or organizational structure (team ownership). Neither approach produces stable boundaries because neither focuses on the actual contracts between components.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Slice?
&lt;/h2&gt;

&lt;p&gt;A slice is a deployable unit defined by its contract. You write an interface with a single annotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GetOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;cancelOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CancelOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The annotation processor generates everything else—factory methods, dependency wiring, deployment metadata. You define the contract; the tooling handles the infrastructure.&lt;/p&gt;

&lt;p&gt;This interface is the boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Methods define the contract&lt;/strong&gt;: each takes a request, returns a promise of response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request/response types are explicit&lt;/strong&gt;: no hidden parameters, no ambient context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async by default&lt;/strong&gt;: &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt; handles both success and failure paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation lives behind this interface. It might be simple or complex. It might call other slices or be completely self-contained. The boundary doesn't care.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aether and Forge: Development Made Simple
&lt;/h2&gt;

&lt;p&gt;Slices run on Aether, a distributed runtime designed around slice contracts. You don't configure service discovery, serialization, or inter-slice communication—Aether handles it based on what the slice interfaces declare. Every inter-slice call eventually succeeds if the cluster is alive; the runtime manages retries, failover, and recovery transparently.&lt;/p&gt;

&lt;p&gt;Forge provides a development environment for testing slices under realistic conditions—load generation, chaos injection, backend simulation. Instead of deploying to staging to see how your slices behave under pressure, you run Forge locally and observe.&lt;/p&gt;

&lt;p&gt;The development experience stays simple: write &lt;code&gt;@Slice&lt;/code&gt; interfaces, implement them, test with Forge, deploy to Aether. The annotation processor generates all the boilerplate—factories, dependency wiring, routing metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies That Don't Lie
&lt;/h2&gt;

&lt;p&gt;Traditional service architectures bury dependencies in configuration files, environment variables, or runtime discovery. You find out what a service needs by reading its code, tracing its network calls, or waiting for it to fail in production.&lt;/p&gt;

&lt;p&gt;Slices declare dependencies in the interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Other methods...&lt;/span&gt;

    &lt;span class="c1"&gt;// Factory method declares dependencies explicitly&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Aspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The annotation processor generates the factory that wires everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceFactory&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;OrderServiceFactory&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="nf"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Aspect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceImpl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The factory method signature declares dependencies. No service locators, no runtime discovery, no configuration files that might or might not match reality. Dependencies are visible at compile time and verified before deployment.&lt;/p&gt;

&lt;p&gt;This explicitness matters. You can trace the dependency graph by reading code. You can test with substitutes by passing different implementations. Forge validates the entire graph before starting anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same Code, Different Environments
&lt;/h2&gt;

&lt;p&gt;The same slices run unchanged across three runtime modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ember&lt;/strong&gt;: Single-process runtime with multiple cluster nodes. Fast startup, simple debugging. Perfect for local development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forge&lt;/strong&gt;: Ember plus load generation and chaos injection. Test how slices behave under pressure without deploying anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aether&lt;/strong&gt;: Full distributed cluster. Production deployment with all the resilience guarantees.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your code doesn't know which mode it's running in. Slice interfaces, implementations, and dependencies stay identical. The runtime handles the difference—whether inter-slice calls are in-process or cross-network is transparent.&lt;/p&gt;

&lt;p&gt;This changes the development workflow. You write and debug in Ember. You stress-test in Forge. You deploy to Aether. At no point do you modify slice code to accommodate the environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Right-Sized by Definition
&lt;/h2&gt;

&lt;p&gt;With boundaries explicit and deployment flexible, the "right size" question dissolves.&lt;/p&gt;

&lt;p&gt;A slice is the right size when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Its interface captures a coherent set of operations&lt;/li&gt;
&lt;li&gt;Its dependencies accurately reflect what it needs&lt;/li&gt;
&lt;li&gt;Its implementation can fulfill its contract&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no minimum or maximum. An authentication slice might have two methods. An order processing slice might have twenty. Size follows from the domain, not from arbitrary rules about lines of code or team structure.&lt;/p&gt;

&lt;p&gt;More importantly, getting it wrong is recoverable. Split a slice that grew too complex? The boundary changes, but callers just see a new interface. Merge slices that were artificially separated? Same story. Refactoring slices is refactoring code, not rewriting infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The JBCT Connection
&lt;/h2&gt;

&lt;p&gt;Slices are where JBCT patterns live.&lt;/p&gt;

&lt;p&gt;Each slice method is a data transformation pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse input (validated request types)&lt;/li&gt;
&lt;li&gt;Gather data (dependencies, other slices)&lt;/li&gt;
&lt;li&gt;Process (business logic)&lt;/li&gt;
&lt;li&gt;Respond (typed response)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The six patterns—Leaf, Sequencer, Fork-Join, Condition, Iteration, Aspects—compose within and across slice methods. A slice is simply the deployment boundary around a set of related transformations.&lt;/p&gt;

&lt;p&gt;This is why the combination works. JBCT gives you consistent structure within slices. Slices give you consistent boundaries between them. Together, they eliminate the two sources of architectural entropy: inconsistent implementation patterns and unclear service boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The microservices granularity problem persists because it asks the wrong question. "How big should a service be?" has no good answer. "What are the contracts between components?" has a precise one.&lt;/p&gt;

&lt;p&gt;Slices shift focus from size to boundaries. Define the interface. Declare dependencies explicitly. Let deployment topology adapt to operational needs rather than dictating code structure.&lt;/p&gt;

&lt;p&gt;The result: boundaries that are clear by construction, dependencies that are visible by design, and deployment flexibility that doesn't require rewriting code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of &lt;a href="//../README.md"&gt;Java Backend Coding Technology&lt;/a&gt; - a methodology for writing predictable, testable backend code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>The Six Patterns That Cover Everything</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Wed, 14 Jan 2026 23:12:12 +0000</pubDate>
      <link>https://forem.com/siy/the-six-patterns-that-cover-everything-kmn</link>
      <guid>https://forem.com/siy/the-six-patterns-that-cover-everything-kmn</guid>
      <description>&lt;h1&gt;
  
  
  The Six Patterns That Cover Everything
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Complete Vocabulary
&lt;/h2&gt;

&lt;p&gt;Every data transformation you'll ever write falls into one of six patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leaf&lt;/strong&gt; - One thing. No substeps. Atomic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequencer&lt;/strong&gt; - This, then that. Output becomes input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork-Join&lt;/strong&gt; - These together, then combine. Independent operations merging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt; - Which path? Route based on value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iteration&lt;/strong&gt; - Same thing, many times. Transform a collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspects&lt;/strong&gt; - Wrap it. Add retry, timeout, logging around an operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Six patterns. They cover everything.&lt;/p&gt;

&lt;p&gt;Not "cover most cases." Not "work well for common scenarios." Everything. Every piece of request processing logic you'll ever write is one of these six, or a composition of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Only Six?
&lt;/h2&gt;

&lt;p&gt;These aren't design patterns someone invented. They're the fundamental ways data can flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Transform a value&lt;/strong&gt; (Leaf)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chain dependent transforms&lt;/strong&gt; (Sequencer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine independent transforms&lt;/strong&gt; (Fork-Join)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose between transforms&lt;/strong&gt; (Condition)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply transform to many values&lt;/strong&gt; (Iteration)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhance a transform&lt;/strong&gt; (Aspects)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no seventh option. Data either transforms, chains, combines, branches, iterates, or gets wrapped. That's the complete set of possibilities.&lt;/p&gt;

&lt;p&gt;This is why learning six patterns gives you everything. Not because JBCT is comprehensive - because reality is constrained.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strange Coincidence
&lt;/h2&gt;

&lt;p&gt;Here's what's remarkable: these same six patterns describe every business process.&lt;/p&gt;

&lt;p&gt;Think about any workflow in your domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leaf&lt;/strong&gt;: "Validate the email format" - one check, atomic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequencer&lt;/strong&gt;: "First verify identity, then check permissions, then grant access" - dependent chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork-Join&lt;/strong&gt;: "Get user profile, account balance, and recent transactions, then build the dashboard" - independent data gathering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt;: "If premium user, apply discount; otherwise, standard pricing" - routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iteration&lt;/strong&gt;: "For each item in cart, calculate tax" - collection processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspects&lt;/strong&gt;: "Log every payment attempt" - cross-cutting concern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Business processes and code patterns use the same vocabulary. This isn't coincidence. It's because both describe how information flows and transforms. Business logic IS data transformation - we just use different words for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Common Language
&lt;/h2&gt;

&lt;p&gt;This equivalence creates something powerful: a shared language between developers and business stakeholders.&lt;/p&gt;

&lt;p&gt;Watch the precision gain:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; "Get the user's stuff and show it"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; "Fork-Join: fetch profile, preferences, and history in parallel, then combine into dashboard view"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same requirement. One is vague. One is implementable.&lt;/p&gt;

&lt;p&gt;When a developer asks "Can these operations run in parallel?" they're really asking "Do these steps depend on each other's results?"&lt;/p&gt;

&lt;p&gt;When business says "First we verify, then we process, then we notify" - that's a Sequencer. Directly translatable to code.&lt;/p&gt;

&lt;p&gt;When business says "We need the user's profile, their preferences, and their history to show the dashboard" - that's a Fork-Join. Three independent fetches, one combined result.&lt;/p&gt;

&lt;p&gt;The translation becomes mechanical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Check if..." → &lt;strong&gt;Leaf&lt;/strong&gt; → Single validation&lt;/li&gt;
&lt;li&gt;"First... then... then..." → &lt;strong&gt;Sequencer&lt;/strong&gt; → &lt;code&gt;.flatMap()&lt;/code&gt; chain&lt;/li&gt;
&lt;li&gt;"Get X and Y and Z, then..." → &lt;strong&gt;Fork-Join&lt;/strong&gt; → &lt;code&gt;Promise.all()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;"If... otherwise..." → &lt;strong&gt;Condition&lt;/strong&gt; → Ternary/switch&lt;/li&gt;
&lt;li&gt;"For each..." → &lt;strong&gt;Iteration&lt;/strong&gt; → &lt;code&gt;.map()&lt;/code&gt; / loop&lt;/li&gt;
&lt;li&gt;"Always log/retry/timeout..." → &lt;strong&gt;Aspects&lt;/strong&gt; → Wrapper function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No translation layer. No impedance mismatch. The same six concepts, different vocabulary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gap Detection
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. When you model business processes using these patterns, gaps become visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing validation:&lt;/strong&gt;&lt;br&gt;
You're building a Sequencer: verify -&amp;gt; process -&amp;gt; notify. But what validates the input before "verify"? The pattern demands something produces the input for step one. If nothing does, you've found a gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unclear dependencies:&lt;/strong&gt;&lt;br&gt;
Business describes five things that need to happen. Are they a Sequencer (dependent chain) or Fork-Join (independent operations)? If they can't tell you which outputs feed which inputs, the process isn't fully defined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing error handling:&lt;/strong&gt;&lt;br&gt;
Every Leaf can fail. Every step in a Sequencer can fail. When you map business process to patterns, you naturally ask: "What happens when this fails?" If they don't know, you've found a gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inefficient flows:&lt;/strong&gt;&lt;br&gt;
Business describes a sequential process: get A, then get B, then get C, then combine. But if A, B, and C don't depend on each other, this should be Fork-Join, not Sequencer. The pattern reveals the inefficiency.&lt;/p&gt;

&lt;p&gt;The patterns don't just implement requirements - they validate them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Right Questions
&lt;/h2&gt;

&lt;p&gt;Once you think in patterns, the right questions emerge naturally:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At the start (Leaf/Validation):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How do we know this request is valid?"&lt;/li&gt;
&lt;li&gt;"What makes an email/phone/amount valid in your domain?"&lt;/li&gt;
&lt;li&gt;"What's the first thing we need to verify?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For sequences (Sequencer):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What do we need from step 1 to perform step 2?"&lt;/li&gt;
&lt;li&gt;"Can step 3 ever happen if step 2 fails?"&lt;/li&gt;
&lt;li&gt;"Is this order fixed, or could steps be reordered?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For parallel work (Fork-Join):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Do these operations depend on each other?"&lt;/li&gt;
&lt;li&gt;"Can we fetch user profile while also fetching their orders?"&lt;/li&gt;
&lt;li&gt;"What do we do if one succeeds and another fails?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For branching (Condition):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What determines which path we take?"&lt;/li&gt;
&lt;li&gt;"Are these paths mutually exclusive?"&lt;/li&gt;
&lt;li&gt;"Is there a default path?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For collections (Iteration):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Do we process all items or stop at first failure?"&lt;/li&gt;
&lt;li&gt;"Does order matter?"&lt;/li&gt;
&lt;li&gt;"Can items be processed independently (in parallel)?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For cross-cutting concerns (Aspects):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Should we retry on failure? How many times?"&lt;/li&gt;
&lt;li&gt;"Is there a timeout?"&lt;/li&gt;
&lt;li&gt;"What needs to be logged/measured?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're not inventing questions. The patterns generate them. Each pattern has a fixed set of things that must be defined. If they're not defined, you ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Patterns to Process Design
&lt;/h2&gt;

&lt;p&gt;The flow works both ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forward:&lt;/strong&gt; Business describes a process -&amp;gt; you identify patterns -&amp;gt; you implement code&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backward:&lt;/strong&gt; You see the patterns -&amp;gt; you notice gaps -&amp;gt; you ask questions -&amp;gt; business clarifies -&amp;gt; process improves&lt;/p&gt;

&lt;p&gt;This backward flow is underrated. Developers who think in patterns become process consultants. They don't just implement what they're told - they improve what they're told by making implicit assumptions explicit.&lt;/p&gt;

&lt;p&gt;"You said first A, then B, then C. But B doesn't use A's output. Can B and A happen at the same time? That would be faster."&lt;/p&gt;

&lt;p&gt;"You said validate the request. What exactly are we validating? Email format? Email exists in system? User is active? Each is a separate check."&lt;/p&gt;

&lt;p&gt;"You said handle the error. Which error? Network timeout? Invalid data? User not found? Each might need different handling."&lt;/p&gt;

&lt;p&gt;The patterns force precision. Vague requirements become concrete when you have to place them in one of six boxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;When developers and business share this vocabulary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requirements discussions become technical design sessions&lt;/li&gt;
&lt;li&gt;Gaps surface during conversation, not during implementation&lt;/li&gt;
&lt;li&gt;Code structure mirrors business process (because they're the same thing)&lt;/li&gt;
&lt;li&gt;Changes in business process map directly to code changes&lt;/li&gt;
&lt;li&gt;New team members understand both business and code faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Six patterns. Complete coverage. Shared language. Gap detection built in.&lt;/p&gt;

&lt;p&gt;This is why pattern-based thinking isn't just a coding technique. It's a communication framework that makes the implicit explicit and the vague precise.&lt;/p&gt;

&lt;p&gt;The irony? You'll spend an hour asking the right questions. The coding takes 30 seconds. Turns out the "coding technology" is mostly about not coding.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of &lt;a href="https://pragmatica.dev" rel="noopener noreferrer"&gt;Java Backend Coding Technology&lt;/a&gt; - a methodology for writing predictable, testable backend code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/siy/the-underlying-process-of-request-processing-1od4"&gt;The Underlying Process of Request Processing&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>functional</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
