<?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>Software's Industrialization Moment</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Sat, 02 May 2026 03:33:08 +0000</pubDate>
      <link>https://forem.com/siy/softwares-industrialization-moment-4pjn</link>
      <guid>https://forem.com/siy/softwares-industrialization-moment-4pjn</guid>
      <description>&lt;p&gt;When you design a circuit, you don't invent the resistor. You don't redesign the transistor for your application. You don't write a new SPI protocol because the existing one offends you. You compose standardized components -- resistors with known tolerances, ICs with documented behavior, protocols with published semantics -- into something specific to the problem. The design is creative work. The substrate is standardized.&lt;/p&gt;

&lt;p&gt;That standardization is not an accident. Electronics went through an industrialization arc. Standard component values (E-series, mid-twentieth century). Standard fabrication processes (silicon refinement, decade by decade). Standard interfaces (Ethernet 1980, I2C 1982, USB 1996). Tooling that codified design rules so the designer doesn't re-derive them on every project. The result is a profession where the design work is dense with creative tradeoffs, but the substrate beneath those tradeoffs is industrially deep.&lt;/p&gt;

&lt;p&gt;Software has been mostly going the other direction. Each generation invents new component vocabularies, new build systems, new design patterns to internalize. The substrate underneath the creative work has not been industrialized. It has been re-invented, badly, every five years.&lt;/p&gt;

&lt;p&gt;That is changing. The arc of industrialization that manufacturing and electronics walked first has lessons software can now use -- and the steps of that arc map cleanly to specific shifts that have arrived in software over the last decade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four moments that industrialized manufacturing
&lt;/h2&gt;

&lt;p&gt;Eli Whitney delivered a contract for ten thousand muskets in 1798 with a property that hadn't been guaranteed before: any lock fit any musket. Before Whitney, every musket lock was hand-fitted. A broken part wasn't replaced -- it was re-fitted by a craftsman who could improvise to the assembly. Whitney's parts didn't have to match the gun; they had to match a &lt;em&gt;spec&lt;/em&gt;. Variability became tolerance. Tolerance became the unit of standardization.&lt;/p&gt;

&lt;p&gt;Henry Ford's assembly line at Highland Park in 1913 took Whitney's principle further. Standardized parts let workers be standardized too. Any worker could do any station's task. The Model T didn't depend on which artisan happened to be in the shop on any given day. Quality and throughput stopped depending on tribal knowledge in the heads of senior craftsmen. The system was the technology, not any one worker's skill.&lt;/p&gt;

&lt;p&gt;The CAD/CAM revolution that began in the late 1950s and matured through the 1970s and 80s separated &lt;em&gt;what to make&lt;/em&gt; from &lt;em&gt;how to make it&lt;/em&gt;. Computer-Aided Design tools let engineers parametrize geometry rather than draw it freehand. Computer-Aided Manufacturing translated those parametric designs into machine instructions. Designer surprises -- will this fit, will this clear, will this be machinable -- collapsed because the spec was unambiguous. The design stayed creative; the path from design to fabrication became deterministic.&lt;/p&gt;

&lt;p&gt;Industrial robots arrived in 1961 with Unimate at General Motors, doing welds. The robot didn't replace the engineer. It took the part of fabrication that didn't need judgment. Engineers moved up: design, supervision, quality, problem-solving. The floor under the profession rose. The work of being an engineer changed.&lt;/p&gt;

&lt;p&gt;Each step took years to land. Each step raised the floor on what the profession could do. None of the steps replaced creative work -- they freed it from the parts that had been holding it back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping the arc to software
&lt;/h2&gt;

&lt;p&gt;Each step has a software equivalent. Most of them have arrived in the last decade or two, and the arrival times -- when you line them up -- tell you where software is right now in its compressed version of the same arc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whitney's interchangeable parts → standardized vocabulary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software's standardized vocabulary in any given codebase has historically been local. This team's idioms. This framework's conventions. This language's mainstream patterns. Across teams, interchangeability dropped sharply -- a developer joining a new codebase has spent decades onboarding into local vocabulary because there was no inter-codebase standard. JBCT (Java Backend Coding Technology) commits to a small named vocabulary -- three containers, six patterns, one boundary primitive -- that's the same in any codebase that uses it. Any developer who knows the vocabulary can read any JBCT codebase. Interchangeability of parts (code structures) and of people (developers).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ford's production line → JBCT as technology.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ford's deeper contribution wasn't speed. It was that the standardization itself was the &lt;em&gt;technology&lt;/em&gt;. The assembly line was a deliberately engineered system that produced consistent output regardless of which worker staffed any given station. JBCT positions itself the same way -- not a methodology you adopt to taste, but a deliberate technology that produces consistent code regardless of which engineer wrote any given module. The system is what makes consistency possible; consistency isn't expected to flow from individual discipline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CAD/CAM → JBDT.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JBDT (Java Backend Design Technology), the methodology articulated in &lt;em&gt;Java Backend Design Technology: A Process-First Methodology&lt;/em&gt;, is software's CAD/CAM. It separates &lt;em&gt;what the system should do&lt;/em&gt; from &lt;em&gt;how it's built&lt;/em&gt;. The designer answers a structured set of questions -- about boundaries, dependencies, modalities, and process shape -- and the answers are deterministic enough that the implementation isn't an open interpretation but a derivation. Designer surprises -- will this scale, will this be testable, will this be maintainable -- collapse because the spec is unambiguous. The design stays creative; the path from design to code becomes deterministic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Industrial robots → AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI assistants are the routine-work automation that arrived late in the manufacturing arc and is arriving now in software. They don't replace the designer. They do the parts of code production that are routine -- boilerplate, scaffolding, well-trodden compositions, test fixtures, documentation. Engineers move up: design, supervision, domain judgment, the parts of the work that benefit from human attention. The floor under the profession rises. The work of being an engineer changes.&lt;/p&gt;

&lt;p&gt;The four steps line up. JBCT is software's Ford-with-Whitney's-parts. JBDT is software's CAD/CAM. AI is software's industrial robots. The catalyst that has put all of these in conversation simultaneously is AI -- but AI is &lt;em&gt;not&lt;/em&gt; the industrialization. Large parts of JBCT existed in practice well before AI was useful -- patterns that were waiting to be named and unified. JBDT articulates the upstream design technology those patterns implied. Both reached their current articulated form with AI assistance, which is part of the point. AI didn't create the standardization; it crystallized standardization that was already there in practice. And that crystallization is what made the &lt;em&gt;next&lt;/em&gt; step -- mechanical-work automation -- immediately available on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;The pattern is the same across all four steps. The substrate gets standardized. The creative work concentrates on what only humans can do.&lt;/p&gt;

&lt;p&gt;In a JBCT codebase, the developer doesn't invent error-handling conventions, naming rules for types, or scaffolding for cross-cutting concerns. Those are standardized. The developer's attention goes to the domain -- what the business is trying to do, what the types should mean, where the boundaries genuinely lie.&lt;/p&gt;

&lt;p&gt;Under JBDT, the designer doesn't invent a fresh design process for each project. The eight questions, the dependency-data graph, the six patterns -- those are standardized. The designer's attention goes to the answers -- what's actually true about &lt;em&gt;this&lt;/em&gt; business, what &lt;em&gt;this&lt;/em&gt; domain genuinely requires.&lt;/p&gt;

&lt;p&gt;With AI assistance, the engineer doesn't write the boilerplate that the same engineer wrote a hundred times last year. The AI does. The engineer reviews, judges, integrates -- work that benefits from human attention because it requires judgment.&lt;/p&gt;

&lt;p&gt;The result is not less work. It is differently distributed work. The profession does not shrink -- it grows in the parts that scale with creative attention. This is what happened in manufacturing. It is what happened in electronics. It is what is happening now in software.&lt;/p&gt;

&lt;p&gt;This is the part the discourse usually misses. &lt;em&gt;Less art, more engineering&lt;/em&gt; is sometimes read as a directive to make software more rote, more constrained, less creative. The actual move is the opposite. It concentrates the creative work where creativity earns its keep, by removing the thousands of small decisions that didn't need to be creative in the first place. Naming a type. Choosing an error-handling pattern. Deciding how to thread observability through a use case. All of these are standardizable. None of them was the part of the work that drew anyone into the profession.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the analogy bends
&lt;/h2&gt;

&lt;p&gt;Software is not the same as manufacturing. The manufactured product is a physical thing whose properties don't change after it leaves the factory. Software's product is updated, extended, debugged, and reshaped continuously after delivery. The factory never closes. The bill of materials evolves. Knowledge work -- figuring out what to build, what it should mean, what edge cases lurk -- is more central in software than in cars or muskets, and that work resists the kind of standardization that reduces variability in physical parts.&lt;/p&gt;

&lt;p&gt;That is true. It is also true that &lt;em&gt;parts of&lt;/em&gt; software's production are standardizable in the same way physical parts are: the vocabulary, the patterns, the cross-cutting plumbing, the design questions. Electronics is the more honest analogy here. Electronics has the same continuous-update property in many senses -- firmware revisions, derivative designs, errata, board respins -- and electronics has industrially deep standardization despite that. The standardizable parts are the standardizable parts. The creative parts stay creative. The two layers don't compete.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still coming
&lt;/h2&gt;

&lt;p&gt;The manufacturing arc took roughly 150 years from Whitney to mature CAD/CAM. The electronics arc took about 70, depending on where you start counting. The software arc has had about 70 years and is now hitting industrial-robots time -- compressed, but recognizable.&lt;/p&gt;

&lt;p&gt;What's still missing in the software arc is the equivalent of standardized &lt;em&gt;interfaces&lt;/em&gt; -- the USB, I2C, Ethernet of software. Standardized parts (vocabulary) and standardized processes (methodology) are the prerequisites for standardized interfaces, and the work of articulating those is now becoming possible.&lt;/p&gt;

&lt;p&gt;The book in progress, &lt;em&gt;Process-First Design&lt;/em&gt;, articulates one substrate for the next layer of standardization -- process composition vocabulary that holds across altitudes, recovery-class taxonomy, design-question stages, and how those connect. Whether that particular substrate becomes the standard or something else does is not the question. The question is what the &lt;em&gt;appearance&lt;/em&gt; of any candidate signals: that the next layer has become workable, where a decade ago it would have been premature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;If you design a circuit today, you don't invent the resistor. You don't even think about not inventing it. The standardization is so deep that the question doesn't surface -- and the design work that does surface is dense with creative tradeoffs that the standardization made possible.&lt;/p&gt;

&lt;p&gt;Software is reaching that moment. The substrate is being standardized -- vocabulary, patterns, design questions, the parts of the work that didn't need to be invented fresh each time. The creativity concentrates where it earns its keep: domain modeling, system architecture, the judgments that benefit from human attention.&lt;/p&gt;

&lt;p&gt;Manufacturing took 150 years. Electronics took 70. Software is on compressed timelines, but the arc is recognizable. The substrate gets industrialized. The design stays creative.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is part of work on Process-First Design -- a forthcoming book on methodology for enterprise backend software. Earlier pieces in the series, including &lt;a href="https://medium.com/@sergiy-yevtushenko/saga-is-not-a-pattern-6973bdcebde5" rel="noopener noreferrer"&gt;&lt;em&gt;Saga Is Not a Pattern&lt;/em&gt;&lt;/a&gt;, develop specific implications of the principle described above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwaredesign</category>
      <category>methodology</category>
      <category>programming</category>
    </item>
    <item>
      <title>When Types Become the Business Language</title>
      <dc:creator>Sergiy Yevtushenko</dc:creator>
      <pubDate>Wed, 22 Apr 2026 20:27:00 +0000</pubDate>
      <link>https://forem.com/siy/when-types-become-the-business-language-1am0</link>
      <guid>https://forem.com/siy/when-types-become-the-business-language-1am0</guid>
      <description>&lt;p&gt;Software has a language problem. Not "natural language vs. code" -- that one is well-understood. The harder problem is the distance between the language a domain expert speaks and the language the program uses to represent what the expert said. Every sentence the expert uses -- "the customer may not exist," "placing the order might fail," "fulfillment happens later" -- is a statement about the domain. By the time it reaches the code, it has usually been flattened into control flow, null checks, try/catch blocks, and callbacks. The domain's meaning is still there, but it's no longer speakable in the code's own vocabulary.&lt;/p&gt;

&lt;p&gt;Two traditions have tried to close this gap. Both got partway. Neither finished.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Object-Oriented Design Promised
&lt;/h3&gt;

&lt;p&gt;OO was meant to model the domain. Classes would represent domain concepts. Behavior would live with the data that behavior operates on. A programmer reading a system would see &lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;Invoice&lt;/code&gt; -- names pulled directly from the business -- and find business logic inside each class.&lt;/p&gt;

&lt;p&gt;That's not what enterprise OO became. Shared entity models evolved into God objects serving many features badly. DTO-to-entity-to-DTO mapping layers accumulated because no single shape fit any single feature. Architecture discussions turned into debates about aggregate boundaries. The "where does the behavior live?" question never settled -- rich model, anemic model, transaction scripts, domain services. Each project picked differently; each team mixed approaches.&lt;/p&gt;

&lt;p&gt;Entities, it turned out, are the wrong unit. A &lt;code&gt;Seat&lt;/code&gt; in a booking flow is a row and a seat number. A &lt;code&gt;Seat&lt;/code&gt; in availability checking is an occupancy flag. A &lt;code&gt;Seat&lt;/code&gt; in pricing is a fare class. These are three different processes with three different notions of what a seat even is. Forcing them to share a structural definition is how entity models grow into God objects.&lt;/p&gt;

&lt;p&gt;The business doesn't think in entities first. It thinks in processes: &lt;em&gt;place an order, confirm availability, charge a card&lt;/em&gt;. Entities appear inside processes as participants, not as durable shared structures. OO's bet -- domain modeling through shared nouns -- couldn't close the language gap because the business doesn't speak in shared nouns.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Pure Functional Programming Offered
&lt;/h3&gt;

&lt;p&gt;Functional programming took a different route. Instead of modeling the domain with classes, it modeled &lt;em&gt;computations&lt;/em&gt; with types. Every value has a type. Every effect -- failure, absence, asynchrony, state, nondeterminism -- lives in a type that says so. The type system becomes precise enough to describe not just what a value is, but what can happen while producing it.&lt;/p&gt;

&lt;p&gt;That precision is real, and for programmers writing libraries or reasoning about concurrent programs it is valuable. But it answers a different question than the one the business is asking. A signature that tracks every effect a computation performs is transparent to the programmer -- it tells them exactly what happens inside. To the domain expert, it is opaque. Each additional type parameter is another channel through which machine-level detail leaks into code that was supposed to read as business logic.&lt;/p&gt;

&lt;p&gt;Pure FP is legible to &lt;em&gt;programmers&lt;/em&gt; in a way that OO isn't. It is not legible to the &lt;em&gt;domain&lt;/em&gt; in a way that OO at least gestured at. The language gap narrowed on one axis and widened on another.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two Halves of the Same Idea
&lt;/h3&gt;

&lt;p&gt;OO wanted types to carry business meaning but stopped at the nominal level: &lt;code&gt;Customer&lt;/code&gt; is a class that wraps a long and some strings. FP wanted types to carry semantic meaning but optimized for machine-level semantics: what effects, what errors, what environment.&lt;/p&gt;

&lt;p&gt;What if types carried &lt;em&gt;business&lt;/em&gt; semantics -- not just nominal ("this is a Customer") but structural ("this lookup might fail to find one")? What if the structural facts the type system expresses were the same facts the domain expert states out loud?&lt;/p&gt;

&lt;p&gt;This is the move that closes the gap. It isn't OO because behavior doesn't live with entities; it lives with processes. It isn't pure FP because the type system isn't tracking machine-level effects; it's tracking domain-level modalities. It is a middle ground that takes from each what actually serves legibility, and drops what doesn't.&lt;/p&gt;

&lt;p&gt;Java Backend Coding Technology (JBCT) is one way of writing this middle ground down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Shapes, One Principle
&lt;/h3&gt;

&lt;p&gt;Most functions don't have a special modality. They take inputs, return a value, always succeed, return immediately. Those stay plain: return type is &lt;code&gt;T&lt;/code&gt;. No container, no ceremony. This is the baseline -- the zero-deviation case.&lt;/p&gt;

&lt;p&gt;Three containers appear only when the computation genuinely has the modality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/strong&gt; -- the value may be missing. &lt;em&gt;"The customer may not exist."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/strong&gt; -- the computation may fail. &lt;em&gt;"Placing the order might fail."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/strong&gt; -- the value may be deferred. &lt;em&gt;"Fulfillment happens later."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four shapes total. One principle: every deviation from a total function gets a name; no deviation needs no name. Each container in a signature is a statement the business would recognize as true about the domain. Each absence is also a statement: this computation always completes with a value.&lt;/p&gt;

&lt;p&gt;Nothing about this surface tracks machine-level detail. A function returning &lt;code&gt;Promise&amp;lt;Order&amp;gt;&lt;/code&gt; does not disclose what database it reads, what HTTP calls it makes, what logs it writes. Those are facts about &lt;em&gt;how&lt;/em&gt; the order arrives. The type describes &lt;em&gt;what the domain guarantees&lt;/em&gt;: an order will eventually be placed, or the placement will fail. The business says the same sentence.&lt;/p&gt;

&lt;p&gt;The type vocabulary is small and the semantic payload is large -- because every element of it is a domain word. Minimal and expressive stop being opposites when every character in the signature is chosen for domain meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Six Patterns, One Vocabulary
&lt;/h3&gt;

&lt;p&gt;Types say what values are. They do not say how computations are shaped. For shape, JBCT names six recurring structures -- the forms that business processes take when written down in code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leaf&lt;/strong&gt; -- the smallest atomic step, one that can't be broken down into substeps (a value object constructed from raw input, a domain calculation, a database call, an HTTP request)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequencer&lt;/strong&gt; -- a sequence of steps where each depends on the previous&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork-Join&lt;/strong&gt; -- independent steps run together, combined when all complete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt; -- branching based on a decision&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iteration&lt;/strong&gt; -- the same step applied to many inputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspects&lt;/strong&gt; -- observational wrapping (logging, tracing, metrics) around another step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not invented abstractions. They are the shapes business processes actually take, recognizable to anyone who has drawn a BPMN diagram or mapped a workflow on a whiteboard. When programmer and stakeholder discuss a feature, they already use this vocabulary implicitly. JBCT makes it explicit in code: a Sequencer of three steps reads as "first this, then this, then this." A Fork-Join reads as "these two things, in parallel, then combine."&lt;/p&gt;

&lt;p&gt;Most functional styles express structure implicitly, through chains of combinators -- the operators carry the shape, the reader has to reconstruct it. JBCT names the shape first and lets the combinators drop into background plumbing. "Sequencer of three, the second of which is a Fork-Join" is a sentence the programmer speaks and the stakeholder understands. After a week of reading JBCT code, the combinators underneath fade to ambient detail; what remains in attention is the process shape.&lt;/p&gt;

&lt;p&gt;Each pattern carries semantic weight, not just structural shape. A Sequencer is a workflow where each step depends on the previous. A Condition is a domain decision point. An Iteration is a rule applied to many participants. Aspects are cross-cutting concerns -- audit, observability, authorization -- the business already thinks of separately from the process they wrap.&lt;/p&gt;

&lt;p&gt;The whole approach is shaped around a single design intent: every structural element should carry business meaning. Types name domain modalities. Patterns name domain process shapes. Value objects name domain concepts and enforce domain invariants. Leaf interfaces name domain operations. The intent doesn't always land perfectly -- some elements resist the reading, some boundaries are hard to draw -- but the resistance becomes a signal: wrong name, wrong place, or something that belongs on the technical side of a Leaf.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaves and the Quarantine Principle
&lt;/h3&gt;

&lt;p&gt;Leaves are atomic. Some of them are pure in-process operations -- a value object built from raw input, a price calculated from a rate card, a rule evaluated against a policy. These leaves are already in the business's language; there is nothing to quarantine.&lt;/p&gt;

&lt;p&gt;The other leaves cross into something external -- a database, an HTTP service, a message bus, a clock. This is where technical detail wants to leak into business code. JBCT's answer is a naming rule: a leaf that crosses a boundary is declared as an interface named for what the &lt;em&gt;business&lt;/em&gt; needs, not what the &lt;em&gt;technology&lt;/em&gt; provides. Not &lt;code&gt;PostgresUserDao&lt;/code&gt;. Not &lt;code&gt;HttpOrderClient&lt;/code&gt;. &lt;code&gt;UserRepository&lt;/code&gt;. &lt;code&gt;OrderPlacement&lt;/code&gt;. The interface is a domain statement; the implementation behind it is an implementation.&lt;/p&gt;

&lt;p&gt;This is why the business code doesn't need effect tracking in types. The effects don't appear in business code at all. They appear on the other side of named boundary leaves. The business layer reads as a composition of named business operations -- some atomic, some composed. The technical layer reads as a set of domain-named interfaces with technical implementations behind each. The two layers meet at the interface, and the interface speaks the business's language.&lt;/p&gt;

&lt;p&gt;Three containers, six patterns, one boundary primitive. The whole vocabulary fits on a napkin and carries enterprise complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Convergence
&lt;/h3&gt;

&lt;p&gt;This approach did not arrive fully formed. Practitioners in different ecosystems -- working independently, without coordination -- have been arriving at shapes of it for years. &lt;a href="https://dev.to/siy/the-quiet-consensus-2cel"&gt;Six of them, across F#, Rust, TypeScript, Scala, C#, and Java, describe the same structural adaptation with different vocabulary&lt;/a&gt;. The phrasing varies; the underlying move is the same: processes as the unit of decomposition, types as domain statements, technical detail pushed behind named boundaries.&lt;/p&gt;

&lt;p&gt;JBCT is one formalization of that convergence, in Java, with the containers and patterns named so that teams can talk about them without reinventing the vocabulary each time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Compiler as Participant
&lt;/h3&gt;

&lt;p&gt;What changes when the type system speaks the domain's language is the role of the compiler. In most type-system discourse the compiler is an overseer of machine correctness -- it catches wrong-shaped values, prevents null dereferences, enforces invariants that the machine cares about. The compiler is a gate.&lt;/p&gt;

&lt;p&gt;When the type system speaks the business's language, the compiler becomes something else. It becomes a participant in the domain conversation. A signature that says &lt;code&gt;Result&amp;lt;Customer&amp;gt; lookup(CustomerId id)&lt;/code&gt; is the code saying aloud, "I can fail to find this customer." If the caller forgets to handle the failure branch, the compiler points out that the domain has a case the caller hasn't addressed. It is not catching a machine error. It is asking the author to finish the domain sentence.&lt;/p&gt;

&lt;p&gt;That is the convergence that matters. Technical and domain languages stop being separate vocabularies that need to be translated at every boundary. They become the same language, spoken by the programmer, read by the stakeholder, enforced by the compiler. The gap the two traditions tried to close from opposite sides closes when the middle stops being empty.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;JBCT is documented at &lt;a href="https://pragmatica.dev" rel="noopener noreferrer"&gt;pragmatica.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwaredesign</category>
      <category>java</category>
      <category>functional</category>
      <category>domaindrivendesign</category>
    </item>
    <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>
  </channel>
</rss>
