<?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>DEV Community: Kamen</title>
    <description>The latest articles on DEV Community by Kamen (@kamenivanov).</description>
    <link>https://dev.to/kamenivanov</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4043234%2Fc0c83e8a-752a-435b-b342-4a44ed85593a.png</url>
      <title>DEV Community: Kamen</title>
      <link>https://dev.to/kamenivanov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kamenivanov"/>
    <language>en</language>
    <item>
      <title>Anatomy of the Domain Module: Throw Away Anemic Models and Ban `@ManyToMany` (Chapter 2)</title>
      <dc:creator>Kamen</dc:creator>
      <pubDate>Thu, 23 Jul 2026 07:37:53 +0000</pubDate>
      <link>https://dev.to/kamenivanov/anatomy-of-the-domain-module-throw-away-anemic-models-and-ban-manytomany-chapter-2-31ff</link>
      <guid>https://dev.to/kamenivanov/anatomy-of-the-domain-module-throw-away-anemic-models-and-ban-manytomany-chapter-2-31ff</guid>
      <description>&lt;p&gt;Years ago, when I was starting my career, I religiously followed the standard textbooks. The ones that teach you that entities should be anemic (just data holders) and that the "service layer" should orchestrate everything.&lt;/p&gt;

&lt;p&gt;The result? After just а couple of months in production, the &lt;code&gt;@Service&lt;/code&gt; classes bloated into thousands of lines of spaghetti code, filled with endless validation checks, state mutations, and structural mess. The domain entities completely lost their integrity—anyone could mutate their state from anywhere.&lt;/p&gt;

&lt;p&gt;And if you throw physical mappings like @ManyToMany into the data access layer later? You are guaranteed to face N+1 query issues, tight coupling, and a schema so tangled that splitting it into microservices feels like performing open-heart surgery.&lt;/p&gt;

&lt;p&gt;In this article, we will break down the design of our &lt;code&gt;domain&lt;/code&gt; module. It is written in &lt;strong&gt;modern Java&lt;/strong&gt;, remains completely free of Spring dependencies, guarantees encapsulation through a &lt;strong&gt;Rich Domain Model&lt;/strong&gt;, and runs unit tests in milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pure OOP (Without Spring Data Magic)
&lt;/h2&gt;

&lt;p&gt;When writing a clean domain model, we must isolate the infrastructure. Instead of relying on Spring Data JPA’s auditing framework at such an early stage, we define the rules for creation, modification, and naming directly within our domain's Java hierarchy.&lt;/p&gt;

&lt;p&gt;Here is the blueprint of our base domain classes:&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;// Every domain object that requires creation audit tracking&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;AbstractCreatable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IdType&lt;/span&gt;&lt;span class="o"&gt;&amp;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;IdType&lt;/span&gt; &lt;span class="n"&gt;id&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;Instant&lt;/span&gt; &lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;AbstractCreatable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IdType&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="k"&gt;this&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;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createdAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every domain object that also tracks modification timestamps&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;AbstractUpdatable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IdType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractCreatable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IdType&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Instant&lt;/span&gt; &lt;span class="n"&gt;updatedAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;AbstractUpdatable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IdType&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="kd"&gt;super&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updatedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every domain object requiring a full audit trail (who created and updated it)&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;AbstractAuditable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IdType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractUpdatable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IdType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;createdById&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;updatedById&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;AbstractAuditable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IdType&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="kd"&gt;super&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="o"&gt;}&lt;/span&gt;  

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;AbstractAuditable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IdType&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;createdById&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
        &lt;span class="kd"&gt;super&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createdById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createdById&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;updatedById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;createdById&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// At creation, updatedBy matches creator&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;h3&gt;
  
  
  💡 But wait... why initialize timestamps directly in the constructor?
&lt;/h3&gt;

&lt;p&gt;Because your domain models must be self-contained and fully functional from the very millisecond they are instantiated. Since our &lt;code&gt;domain&lt;/code&gt; module is a pure Java library with zero infrastructure awareness, we cannot and should not rely on database triggers or external framework lifecycles.&lt;/p&gt;

&lt;p&gt;By setting &lt;code&gt;Instant.now()&lt;/code&gt; directly in the constructor, we guarantee that the domain object is always complete, valid, and immediately testable in-memory, without waiting for some future persistence layer to "hydrate" its state. It also prevents &lt;code&gt;NullPointerException&lt;/code&gt; bugs when sorting or processing newly instantiated domain events in memory before database transactions.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Death to &lt;code&gt;@ManyToMany&lt;/code&gt; – Designing Decoupled Relationships
&lt;/h2&gt;

&lt;p&gt;In real-world, highly scalable systems, physically coupling two independent business contexts (like &lt;code&gt;Product&lt;/code&gt; and &lt;code&gt;Category&lt;/code&gt;) via Hibernate mappings is an architectural dead-end.&lt;/p&gt;

&lt;p&gt;Therefore, in our architecture, &lt;strong&gt;&lt;code&gt;@ManyToMany&lt;/code&gt; relationships are strictly banned&lt;/strong&gt;. Instead, we model them as completely decoupled Aggregates that communicate solely through identifiers (UUIDs) using a dedicated &lt;strong&gt;Relationship Entity&lt;/strong&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="c1"&gt;// Aggregate Root #1: Category&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;Category&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractAuditable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;&amp;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;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
        &lt;span class="c1"&gt;// POJO  &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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The decoupled junction: CategoryAssignment (Relationship Entity)&lt;/span&gt;
&lt;span class="c1"&gt;// Since relationship assignments are append-only, inheriting from AbstractCreatable is perfect!&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;CategoryAssignment&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractCreatable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Pure UUID instead of Product&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;categoryId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Pure UUID instead of Category&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;assignedById&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// For auditing purpose, who made the association&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CategoryAssignment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;categoryId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;assignedById&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;categoryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categoryId&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;assignedById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assignedById&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;h3&gt;
  
  
  Why is this "Distributed Ready"?
&lt;/h3&gt;

&lt;p&gt;If tomorrow your business decides to migrate Categories to a separate microservice (e.g., a dedicated &lt;code&gt;Catalog Service&lt;/code&gt;), &lt;strong&gt;the code of your Product domain won't change by a single line&lt;/strong&gt;. The Product database will simply store the &lt;code&gt;categoryId&lt;/code&gt; as a plain UUID received via Kafka or a REST payload, without caring about the internal database tables or Hibernate proxies of the neighboring context.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. When Are Direct Relationships Allowed? (Aggregate Root &amp;amp; 1-to-1)
&lt;/h2&gt;

&lt;p&gt;While we decouple large business units via UUIDs, how do we handle tightly bound details?&lt;/p&gt;

&lt;p&gt;If an entity (e.g., &lt;code&gt;ProductSpecification&lt;/code&gt;) has no independent business meaning and shares 100% of the lifecycle of the main object (&lt;code&gt;Product&lt;/code&gt;), they become part of the same &lt;strong&gt;Aggregate Root&lt;/strong&gt;. When the product is deleted, the specification must be deleted cascadingly.&lt;/p&gt;

&lt;p&gt;Here is how we model this in our domain, where &lt;code&gt;Product&lt;/code&gt; holds a direct reference to &lt;code&gt;ProductSpecification&lt;/code&gt; but acts as the strict transactional 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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductSpecification&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractUpdatable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;&amp;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;String&lt;/span&gt; &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProductSpecification&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&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;dimensions&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dimensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimensions&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;weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weight&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Aggregate Root: Product controlling the lifecycle of its nested entities&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;Product&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractAuditable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;&amp;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;String&lt;/span&gt; &lt;span class="n"&gt;name&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;String&lt;/span&gt; &lt;span class="n"&gt;sku&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;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;price&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;ProductStatus&lt;/span&gt; &lt;span class="n"&gt;status&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;ProductSpecification&lt;/span&gt; &lt;span class="n"&gt;specifications&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Strongly bound 1-to-1&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&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;name&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;sku&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;ProductSpecification&lt;/span&gt; &lt;span class="n"&gt;specifications&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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;sku&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&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;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&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;specifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specifications&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DRAFT&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt; &lt;span class="n"&gt;nextStatus&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;canTransitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextStatus&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;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Business rule violated: Cannot transition from "&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;status&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" to "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nextStatus&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nextStatus&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;h2&gt;
  
  
  4. State Machine: Inside the Enum or in an External State Manager?
&lt;/h2&gt;

&lt;p&gt;A common debate among senior engineers is: &lt;em&gt;Where should the state transition logic live—inside the Enum or delegated to an external State Manager?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The architectural answer is clear: &lt;strong&gt;State transition rules belong to the Domain (inside the Enum), while an external State Manager/orchestrator should only handle structural side-effects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you leak state validation rules outside, you anemicize &lt;code&gt;Product&lt;/code&gt; and allow any developer to bypass the state machine, mutating the object into an invalid state.&lt;/p&gt;

&lt;p&gt;Here is our encapsulated &lt;code&gt;ProductStatus&lt;/code&gt; enum:&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;enum&lt;/span&gt; &lt;span class="nc"&gt;ProductStatus&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;DRAFT&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="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;canTransitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt; &lt;span class="n"&gt;next&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;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ACTIVE&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ARCHIVED&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="no"&gt;ACTIVE&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="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;canTransitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt; &lt;span class="n"&gt;next&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;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;OUT_OF_STOCK&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ARCHIVED&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="no"&gt;OUT_OF_STOCK&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="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;canTransitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt; &lt;span class="n"&gt;next&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;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ACTIVE&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ARCHIVED&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="no"&gt;ARCHIVED&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="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;canTransitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt; &lt;span class="n"&gt;next&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="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Terminal state. No transitions allowed!&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;abstract&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;canTransitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt; &lt;span class="n"&gt;next&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 about the external State Machine? It acts purely as an orchestrator. Once the domain layer successfully performs the transition, the orchestrator commits the transactions and triggers the side-effects (e.g., publishing a &lt;code&gt;ProductActivatedEvent&lt;/code&gt; to Kafka or clearing a cache).&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The 5-Millisecond Unit Test
&lt;/h2&gt;

&lt;p&gt;Since our domain module does not import a single &lt;code&gt;@Component&lt;/code&gt;, &lt;code&gt;@Service&lt;/code&gt;, or &lt;code&gt;@Autowired&lt;/code&gt; annotation, we don't need a slow boot context to validate our business rules.&lt;/p&gt;

&lt;p&gt;We test pure Java objects, and the execution completes in &lt;strong&gt;under 5 milliseconds&lt;/strong&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;class&lt;/span&gt; &lt;span class="nc"&gt;ProductDomainTestCase&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="kd"&gt;final&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="no"&gt;CREATOR_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&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="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ProductSpecification&lt;/span&gt; &lt;span class="n"&gt;spec&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;ProductSpecification&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;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  

    &lt;span class="nd"&gt;@BeforeEach&lt;/span&gt;  
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;product&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;Product&lt;/span&gt;&lt;span class="o"&gt;(...);&lt;/span&gt;  
    &lt;span class="o"&gt;}&lt;/span&gt;  

    &lt;span class="c1"&gt;// --- DRAFT Status Transitions ---  &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;shouldAllowTransitionFromDraftToActive&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transitionTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ACTIVE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  
        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ACTIVE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&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;By keeping the business layer pure, you establish a highly testable, robust, and decoupled domain core. Frameworks, databases, and network protocols are just external details plugged in at a later stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next?
&lt;/h3&gt;

&lt;p&gt;In the next part, we will dive into the very foundation of Data Access Layer design — the &lt;strong&gt;DAO API Module&lt;/strong&gt; — and discuss how to keep it 100% pure without a single specific implementation (hibernate, mysql, oracle sql, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  Codebase &amp;amp; Architecture Blueprint
&lt;/h3&gt;

&lt;p&gt;The entire evolutionary architecture of this project is tracked using strict Git tags. To clone the repository and switch exactly to the state established in Chapter 2, use the following link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository (Tag: &lt;code&gt;chapter-02-domain&lt;/code&gt;):&lt;/strong&gt; &lt;a href="https://github.com/KamenIvanov/advanced-spring-multimodule/tree/chapter-02-domain" rel="noopener noreferrer"&gt;advanced-spring-multimodule&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: All core modules are configured with strict compilation-level boundaries. Compile and run &lt;code&gt;mvn clean install&lt;/code&gt; to see the structure in action. Maven version 3.9.* and Java 25 are required.&lt;/p&gt;




&lt;p&gt;◀️ &lt;a href="https://kamenivanov.substack.com/p/why-package-structures-wont-save?r=8p8mow" rel="noopener noreferrer"&gt;Read Chapter 1: Stop returning Spring Data Page from your REST endpoints&lt;/a&gt;&lt;br&gt;
▶️ Read Chapter 3: The Dao API Module (Coming soon) &lt;/p&gt;




&lt;p&gt;📨 &lt;strong&gt;Liked this architecture blueprint?&lt;/strong&gt; This article is part of my &lt;strong&gt;Evolutionary Architecture&lt;/strong&gt; series. I publish deep-dive technical pieces every week. &lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://kamenivanov.substack.com" rel="noopener noreferrer"&gt;Subscribe to my Substack Newsletter&lt;/a&gt;&lt;/strong&gt; to get full source code repositories (Git tags) and new chapters straight to your inbox!&lt;/p&gt;

</description>
      <category>java</category>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Why Package Structures Won't Save Your Spring Boot Architecture (And How Maven Enforces It) (Chapter 1)</title>
      <dc:creator>Kamen</dc:creator>
      <pubDate>Thu, 23 Jul 2026 07:29:50 +0000</pubDate>
      <link>https://dev.to/kamenivanov/why-package-structures-wont-save-your-spring-boot-architecture-and-how-maven-enforces-it-40k4</link>
      <guid>https://dev.to/kamenivanov/why-package-structures-wont-save-your-spring-boot-architecture-and-how-maven-enforces-it-40k4</guid>
      <description>&lt;h2&gt;
  
  
  How We Turned Architectural Guidelines Into Compilation Errors
&lt;/h2&gt;

&lt;p&gt;Have you ever found yourself doing a Friday afternoon Code Review, only to discover that someone injected the &lt;code&gt;EntityManager&lt;/code&gt; directly into a REST controller, writing raw SQL strings, and mapping rows manually with a loop? Or worse, have you seen a &lt;code&gt;@RequestParam&lt;/code&gt; accepting &lt;code&gt;org.springframework.data.domain.Pageable&lt;/code&gt; while the controller returns a raw &lt;code&gt;Page&amp;lt;Entity&amp;gt;&lt;/code&gt; directly to the frontend?&lt;/p&gt;

&lt;p&gt;Congratulations. You have just exposed your database schema to the entire world and completely bypassed the concept of an &lt;strong&gt;Anti-Corruption Layer (ACL)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Illusion of Package Control
&lt;/h2&gt;

&lt;p&gt;When an application is structured within a single module and we rely strictly on package separation (&lt;code&gt;.controller&lt;/code&gt;, &lt;code&gt;.service&lt;/code&gt;, &lt;code&gt;.repository&lt;/code&gt;), we live in an illusion of architectural control. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;The hard truth:&lt;/strong&gt; Packages do not stop anyone. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the heat of a tight deadline, when &lt;em&gt;"things just need to work,"&lt;/em&gt; Java package visibility rules will not prevent a junior or stressed developer from committing architectural crimes that you will be debugging for months.&lt;/p&gt;

&lt;p&gt;Here is how we solved this problem in our team by breaking down the system into &lt;strong&gt;highly specialized Maven modules&lt;/strong&gt;, effectively turning architectural guidelines into compilation errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architectural Blueprint: Divide, Conquer, and Version
&lt;/h2&gt;

&lt;p&gt;The first step toward true isolation was radical: &lt;strong&gt;we extracted the API contract into a completely separate Git repository.&lt;/strong&gt; Why? Because your API contract version should be independent of your backend implementation. If we fix a bug tomorrow in the core business logic, it makes absolutely no sense to bump the version of the REST/Event contract if nothing changed there. The frontend team and QA engineers need a stable contract to work against, completely shielded from our internal refactoring.&lt;/p&gt;

&lt;p&gt;Next, we split the main backend project into highly specialized Maven modules. When I first proposed this, the team was highly skeptical: &lt;em&gt;"It's too complex,"&lt;/em&gt; &lt;em&gt;"Why do we need this? We already have packages."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, I built a quick proof-of-concept. Instead of relying on a developer’s goodwill, we shifted the enforcement of architectural boundaries directly to the compiler.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dependency Topology
&lt;/h3&gt;

&lt;p&gt;Here is what the real dependency topology looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── RestService API (Git Repo 1)
│ &amp;nbsp; ├── dto (Jackson &amp;amp; Swagger)
│ &amp;nbsp; └── events (Event Contracts)
│ &amp;nbsp; └── rest-api (The API intefaces from which documentation is generated)
│
└── Backend (Git Repo 2)
    ├── domain (Pure Domain Models &amp;amp; Core Logic (No Frameworks))
&amp;nbsp; &amp;nbsp; ├── business-logic (Core Business Logic (Depends only on domain &amp;amp; APIs))
&amp;nbsp; &amp;nbsp; ├── dao-api (Database Access Interfaces (No JPA/Spring Data))
&amp;nbsp; &amp;nbsp; ├── dao-impl (Actual DB Integration (Spring Data JPA, Hibernate))
&amp;nbsp; &amp;nbsp; ├── bridge-api (External Services Communication APIs)
&amp;nbsp; &amp;nbsp; ├── bridge-impl (Actual Integration with External APIs)
&amp;nbsp; &amp;nbsp; ├── integration-tests (Testing layer via Testcontainers (Docker-based))
&amp;nbsp; &amp;nbsp; └── application (Spring Boot Bootstrapper)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this structure, the heart of the system — the &lt;code&gt;business-logic&lt;/code&gt; module — depends strictly on the interfaces defined in &lt;code&gt;dao-api&lt;/code&gt; and &lt;code&gt;bridge-api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It has &lt;strong&gt;zero access&lt;/strong&gt; to &lt;code&gt;dao-impl&lt;/code&gt; or &lt;code&gt;bridge-impl&lt;/code&gt;. Your core business logic does not have &lt;code&gt;spring-boot-starter-data-jpa&lt;/code&gt;, Hibernate, Kafka, or Redisson in its classpath.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let Maven Keep Your Code Reviews Clean
&lt;/h2&gt;

&lt;p&gt;Once we introduced this change, it didn’t take long for the team to realize its power.&lt;/p&gt;

&lt;p&gt;If a developer attempts to inject the &lt;code&gt;EntityManager&lt;/code&gt; or write raw SQL queries inside the core business logic tomorrow, &lt;strong&gt;the code simply will not compile&lt;/strong&gt;. The build will break right on their local machine. &lt;/p&gt;

&lt;p&gt;To circumvent this, they would have to deliberately go into the &lt;code&gt;pom.xml&lt;/code&gt; of &lt;code&gt;business-logic&lt;/code&gt; and introduce a dependency on the database module — an action that would instantly trigger a massive red flag during any Code Review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immediate Benefits in an Enterprise Environment
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No More Cyclic Dependencies:&lt;/strong&gt; Maven physically forbids module A from depending on B if B already depends on A.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightning-Fast Unit Tests:&lt;/strong&gt; Because the business logic is entirely decoupled from infrastructure frameworks, unit tests are written effortlessly. We only mock pure Java interfaces, and the tests execute in milliseconds. No one can use the &lt;em&gt;"tests take too much time"&lt;/em&gt; excuse anymore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pure Infrastructure Interchangeability:&lt;/strong&gt; The &lt;code&gt;business-logic&lt;/code&gt; module interacts solely with the contract in &lt;code&gt;dao-api&lt;/code&gt;. It doesn't know — nor does it care — whether the data underneath comes from MySQL (via &lt;code&gt;dao-impl&lt;/code&gt;), is cached in Redis, or is being streamed via Kafka (via &lt;code&gt;bridge-impl&lt;/code&gt;). The implementations are wired together at the very top layer — in the &lt;code&gt;application&lt;/code&gt; module.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion: Is it Overengineering?
&lt;/h2&gt;

&lt;p&gt;If you are building a small CRUD app with five tables, this approach is undoubtedly overengineering. But if you are building an Enterprise system designed for long-term maintainability, high team velocity, and strict domain boundaries, you cannot afford to build your house on sand.&lt;/p&gt;

&lt;p&gt;Relying purely on folder structures means that sooner or later, under pressure, someone will break the rules. Transitioning to a multi-module design requires more initial boilerplate, but it &lt;strong&gt;eliminates 70% of long-term architectural decay&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next?
&lt;/h3&gt;

&lt;p&gt;In the next part, we will dive into the very foundation of this design — the &lt;strong&gt;Domain Module&lt;/strong&gt; — and discuss how to keep it 100% pure (POJO) without allowing a single JPA or Hibernate annotation to pollute your business models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codebase &amp;amp; Architecture Blueprint
&lt;/h3&gt;

&lt;p&gt;The entire evolutionary architecture of this project is tracked using strict Git tags. To clone the repository and switch exactly to the baseline state established in Chapter 1, use the following link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository (Tag: &lt;code&gt;chapter-01-baseline&lt;/code&gt;):&lt;/strong&gt; &lt;a href="https://github.com/KamenIvanov/advanced-spring-multimodule/tree/chapter-01-baseline" rel="noopener noreferrer"&gt;advanced-spring-multimodule&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: All core modules are configured with strict compilation-level boundaries. Compile and run &lt;code&gt;mvn clean install&lt;/code&gt; to see the structure in action. Maven version 3.9.* and Java 25 are required.&lt;/p&gt;




&lt;p&gt;▶️ Read Chapter 2: &lt;a href="https://kamenivanov.substack.com/p/anatomy-of-the-domain-module-throw" rel="noopener noreferrer"&gt;The Domain module&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📨 &lt;strong&gt;Liked this architecture blueprint?&lt;/strong&gt; This article is part of my &lt;strong&gt;Evolutionary Architecture&lt;/strong&gt; series. I publish deep-dive technical pieces every week. &lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://kamenivanov.substack.com" rel="noopener noreferrer"&gt;Subscribe to my Substack Newsletter&lt;/a&gt;&lt;/strong&gt; to get full source code repositories (Git tags) and new chapters straight to your inbox!&lt;/p&gt;

</description>
      <category>java</category>
      <category>softwarearchitecure</category>
      <category>springboot</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
