<?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: Francesco Portus</title>
    <description>The latest articles on DEV Community by Francesco Portus (@portus84).</description>
    <link>https://dev.to/portus84</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%2F3769271%2F5d0663c6-8e45-47fa-903b-76694ad30c57.jpg</url>
      <title>DEV Community: Francesco Portus</title>
      <link>https://dev.to/portus84</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/portus84"/>
    <language>en</language>
    <item>
      <title>SmartOrder — Part 5: The Commons Layer — Shared Infrastructure Done Right</title>
      <dc:creator>Francesco Portus</dc:creator>
      <pubDate>Mon, 16 Mar 2026 16:58:08 +0000</pubDate>
      <link>https://dev.to/portus84/smartorder-part-5-the-commons-layer-shared-infrastructure-done-right-3nk8</link>
      <guid>https://dev.to/portus84/smartorder-part-5-the-commons-layer-shared-infrastructure-done-right-3nk8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;How a carefully designed shared library eliminates boilerplate across microservices — generics, AOP logging, custom Jackson modules, and a test framework you'll wish you had years ago.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part 4 explored the Inventory Service. Before moving forward, there's a layer that quietly powers everything — a layer most tutorials skip entirely. This post dives into &lt;strong&gt;&lt;code&gt;services/commons&lt;/code&gt;&lt;/strong&gt;: the shared infrastructure that both &lt;code&gt;order-service&lt;/code&gt; and &lt;code&gt;inventory-service&lt;/code&gt; depend on.&lt;/p&gt;

&lt;p&gt;Commons is not a dumping ground. It's a deliberate, multi-module library that solves real problems: generic CRUD, consistent serialization, AOP-based observability, OpenAPI cleanup, and test data lifecycle management. Let's take it apart.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services/commons" rel="noopener noreferrer"&gt;services/commons&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The three sub-modules at a glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services/commons
├── commons-business   ← domain-layer abstractions (no web dependency)
├── commons-service    ← web-layer infrastructure (HATEOAS, Jackson, AOP)
└── service-test       ← test utilities with transaction control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The split is intentional. &lt;code&gt;commons-business&lt;/code&gt; has no Spring MVC dependency — it can be pulled into any service without dragging in a web container. &lt;code&gt;commons-service&lt;/code&gt; assumes a web context and brings in HATEOAS, OpenAPI, and AOP support.&lt;/p&gt;




&lt;h2&gt;
  
  
  commons-business: Generic CRUD without the ceremony
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Entity contract
&lt;/h3&gt;

&lt;p&gt;Every domain object in SmartOrder implements a single 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="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="no"&gt;I&lt;/span&gt; &lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;I&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple. But it's the foundation for the entire generic CRUD stack. &lt;code&gt;Inventory&lt;/code&gt; uses &lt;code&gt;UUID&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt; uses &lt;code&gt;ObjectId&lt;/code&gt; — the abstraction doesn't care.&lt;/p&gt;

&lt;h3&gt;
  
  
  AbstractCrudService: one class to rule them all
&lt;/h3&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;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractCrudService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CrudRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CrudService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;I&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;protected&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="no"&gt;R&lt;/span&gt; &lt;span class="n"&gt;repository&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;I&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;S&lt;/span&gt; &lt;span class="n"&gt;entity&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;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;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;existing&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="nc"&gt;BeanUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ServiceUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getImmutableFields&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;S&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="n"&gt;existing&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;The &lt;code&gt;update&lt;/code&gt; method is the interesting part. It uses &lt;code&gt;BeanUtils.copyProperties&lt;/code&gt; but &lt;strong&gt;excludes immutable fields automatically&lt;/strong&gt;. No manually listing &lt;code&gt;"id"&lt;/code&gt;, &lt;code&gt;"createdDate"&lt;/code&gt;, &lt;code&gt;"lastModifiedDate"&lt;/code&gt; in every service.&lt;/p&gt;

&lt;h3&gt;
  
  
  ServiceUtils: reflection-based field protection
&lt;/h3&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;static&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;getImmutableFields&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Set&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;ignored&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;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
  &lt;span class="n"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getDeclaredFields&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ServiceUtils:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;isAuditingField&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;Field:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&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;ignored&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toArray&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;String&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;isAuditingField&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Field&lt;/span&gt; &lt;span class="n"&gt;field&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;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isAnnotationPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreatedDate&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="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isAnnotationPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LastModifiedDate&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any field annotated with &lt;code&gt;@CreatedDate&lt;/code&gt; or &lt;code&gt;@LastModifiedDate&lt;/code&gt; is automatically excluded from updates. This convention-over-configuration approach means services never accidentally overwrite audit timestamps — and it works without touching a single business class.&lt;/p&gt;

&lt;h3&gt;
  
  
  JPA vs Mongo — two concrete services, zero duplication
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For JPA (Inventory Service)&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;JpaCrudService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="no"&gt;I&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;AbstractCrudService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pageable&lt;/span&gt; &lt;span class="n"&gt;pageable&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;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageable&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="c1"&gt;// For MongoDB (Order Service)&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;MongoCrudService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="no"&gt;I&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;AbstractCrudService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MongoRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pageable&lt;/span&gt; &lt;span class="n"&gt;pageable&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;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageable&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;InventoryServiceImpl extends JpaCrudService&lt;/code&gt; and &lt;code&gt;OrderServiceImpl extends MongoCrudService&lt;/code&gt;. Polyglot persistence — one generic layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  commons-service: Where the real magic lives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Custom Jackson deserializers for Spring types
&lt;/h3&gt;

&lt;p&gt;This is one of the most underrated pieces of the codebase. Spring's &lt;code&gt;Page&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Pageable&lt;/code&gt;, &lt;code&gt;PagedModel&amp;lt;T&amp;gt;&lt;/code&gt;, and &lt;code&gt;EntityModel&amp;lt;T&amp;gt;&lt;/code&gt; don't serialize/deserialize symmetrically out of the box. Commons solves this with a set of custom Jackson modules that auto-register via &lt;code&gt;JacksonAutoConfiguration&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The module architecture is elegant:&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;AbstractDeserializerModule&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&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;SimpleModule&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;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;super&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rawType&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;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JavaType&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonDeserializer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deserializerFactory&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;void&lt;/span&gt; &lt;span class="nf"&gt;setupModule&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SetupContext&lt;/span&gt; &lt;span class="n"&gt;context&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="na"&gt;setupModule&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addDeserializers&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;Deserializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Base&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;JsonDeserializer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;findBeanDeserializer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;JavaType&lt;/span&gt; &lt;span class="n"&gt;javaType&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DeserializationConfig&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BeanDescription&lt;/span&gt; &lt;span class="n"&gt;beanDesc&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;rawType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;javaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRawClass&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;deserializerFactory&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;javaType&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;null&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module is just a one-liner that extends this base:&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;PageModule&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractDeserializerModule&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&amp;gt;&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;PageModule&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="nc"&gt;Page&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;javaType&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;PageDeserializer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;javaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containedTypeOrUnknown&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;PageDeserializer&lt;/code&gt; handles missing fields gracefully — no &lt;code&gt;content&lt;/code&gt; array? Returns an empty list. No &lt;code&gt;pageable&lt;/code&gt;? Returns &lt;code&gt;Pageable.unpaged()&lt;/code&gt;. No &lt;code&gt;totalElements&lt;/code&gt;? Falls back to content size.&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;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonParser&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DeserializationContext&lt;/span&gt; &lt;span class="n"&gt;ctxt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;ObjectNode&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readValueAsTree&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="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctxt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="nc"&gt;Pageable&lt;/span&gt; &lt;span class="n"&gt;pageable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractPageable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctxt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;totalElements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractTotalElements&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&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="nc"&gt;PageImpl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pageable&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalElements&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 same pattern applies to &lt;code&gt;PagedModel&lt;/code&gt;, &lt;code&gt;EntityModel&lt;/code&gt;, and &lt;code&gt;Pageable&lt;/code&gt;. Any service that pulls &lt;code&gt;commons-service&lt;/code&gt; gets &lt;strong&gt;symmetric serialization for free&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  AOP logging with zero configuration
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;LoggingAspect&lt;/code&gt; intercepts every method in your business packages and logs start, result, duration, and errors — with a &lt;strong&gt;correlation ID&lt;/strong&gt; automatically managed via MDC.&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;@Around&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"execution(* it.portus..*(..))"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" &amp;amp;&amp;amp; !within(it.portus.ms.commons..*)"&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;Object&lt;/span&gt; &lt;span class="nf"&gt;logMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProceedingJoinPoint&lt;/span&gt; &lt;span class="n"&gt;joinPoint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;correlationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getOrGenerateCorrelationId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&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;targetLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDebugEnabled&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;targetLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[correlationId: {}] {} - START with args: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;correlationId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formatArgs&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joinPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getArgs&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;Object&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;joinPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proceed&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;targetLogger&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;"[correlationId: {}] {} - SUCCESS in {} ms, result: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;correlationId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&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="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;targetLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[correlationId: {}] {} - ERROR in {} ms: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;correlationId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;MDC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CORRELATION_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configuration is smart: it scans for &lt;code&gt;@SpringBootApplication&lt;/code&gt; and &lt;code&gt;@ComponentScan&lt;/code&gt; packages at startup, excluding the commons package itself. You can also extend it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;aspect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;extra-packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;com.mycompany.special&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;@EnableAspectJAutoProxy&lt;/code&gt; annotation required. No bean declaration. Just add &lt;code&gt;commons-service&lt;/code&gt; and your methods are observed.&lt;/p&gt;

&lt;h3&gt;
  
  
  HATEOAS: pluralizing link relations
&lt;/h3&gt;

&lt;p&gt;A subtle but important detail. Spring HATEOAS by default uses class names for link relations. Commons registers a custom &lt;code&gt;PluralizingRelProvider&lt;/code&gt; that makes collection links grammatically correct:&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;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;LinkRelation&lt;/span&gt; &lt;span class="nf"&gt;getCollectionResourceRelFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;type&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;LinkRelation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;English&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plural&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="na"&gt;getSimpleName&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&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;Inventory&lt;/code&gt; → &lt;code&gt;"inventory"&lt;/code&gt; for item, &lt;code&gt;"inventories"&lt;/code&gt; for collection. &lt;code&gt;Order&lt;/code&gt; → &lt;code&gt;"order"&lt;/code&gt; / &lt;code&gt;"orders"&lt;/code&gt;. Uses the &lt;code&gt;evo-inflector&lt;/code&gt; library to handle English plural rules (&lt;code&gt;company&lt;/code&gt; → &lt;code&gt;companies&lt;/code&gt;, &lt;code&gt;box&lt;/code&gt; → &lt;code&gt;boxes&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  HATEOASLinkUtils: resolving placeholder base paths
&lt;/h3&gt;

&lt;p&gt;When services run behind a gateway, their self-links need to reflect the forwarded prefix — not the internal path. &lt;code&gt;HATEOASLinkUtils&lt;/code&gt; handles this transparently by reading &lt;code&gt;${property.name:/default}&lt;/code&gt; placeholders from &lt;code&gt;@RequestMapping&lt;/code&gt; annotations:&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;// Controller mapping:&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${openapi.order-service.base-path:/api/v1}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// At runtime, HATEOASLinkUtils resolves the placeholder from environment or ForwardedHeader:&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;Link&lt;/span&gt; &lt;span class="nf"&gt;buildLink&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;controllerClazz&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Environment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="n"&gt;rawLink&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;applyPlaceholder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controllerClazz&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rawLink&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 same helper resolves affordances — so &lt;code&gt;HAL-Forms&lt;/code&gt; metadata also gets correctly rewritten when accessed through the gateway. Clients never see internal URLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAPI schema cleanup
&lt;/h3&gt;

&lt;p&gt;Both services generate OpenAPI specs from code. Without intervention, Spring adds internal Spring types (&lt;code&gt;RepresentationModel&lt;/code&gt;, &lt;code&gt;Links&lt;/code&gt;, &lt;code&gt;Link&lt;/code&gt;, &lt;code&gt;PagedModel&lt;/code&gt;, etc.) to the schema — cluttering the API docs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DefaultSchemaProcessorImpl&lt;/code&gt; solves this with a post-processing pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Replace&lt;/strong&gt; certain schemas with custom definitions (e.g., &lt;code&gt;PageMetadata&lt;/code&gt; gets a cleaner DTO)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reorder&lt;/strong&gt; schemas — business schemas first, utility schemas last&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline&lt;/strong&gt; internal schemas (like &lt;code&gt;Links&lt;/code&gt;) wherever they're referenced&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove&lt;/strong&gt; internal schemas from the final components section&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;HateoasProcessorImpl&lt;/code&gt; extends this to handle HATEOAS-specific types and generates a correct &lt;code&gt;Links&lt;/code&gt; schema as &lt;code&gt;additionalProperties: { $ref: Link }&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="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolveSchemaFromClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clazz&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="nc"&gt;Links&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="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Schema&lt;/span&gt; &lt;span class="n"&gt;linkSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resolved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;referencedSchemas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Link"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;ObjectSchema&lt;/span&gt; &lt;span class="n"&gt;linksSchema&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;ObjectSchema&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;linksSchema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkSchema&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;linksSchema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDescription&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HATEOAS Links"&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;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linksSchema&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="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resolveSchemaFromClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clazz&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;
  
  
  service-test: A test framework in 3 files
&lt;/h2&gt;

&lt;p&gt;Most test frameworks force you to choose between loading all test data upfront or managing state manually. &lt;code&gt;service-test&lt;/code&gt; introduces a cleaner model with &lt;code&gt;@WithTestData&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The annotation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Target&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="nc"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;METHOD&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="nd"&gt;@Retention&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetentionPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RUNTIME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Inherited&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;WithTestData&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestDataLoader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[]&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;transactional&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kc"&gt;true&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;You provide a &lt;code&gt;TestDataLoader&lt;/code&gt; 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;@TestComponent&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&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;InventoryTestDataLoader&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;TestDataLoader&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;InventoryRepository&lt;/span&gt; &lt;span class="n"&gt;inventoryRepository&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;void&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;inventoryRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Instancio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Inventory&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ignore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Inventory:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ignore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Inventory:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getCreatedDate&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ignore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Inventory:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getLastModifiedDate&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&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;And annotate your test:&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;@DataJpaTest&lt;/span&gt;
&lt;span class="nd"&gt;@WithTestData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryTestDataLoader&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="nd"&gt;@ImportAutoConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JpaAutoConfiguration&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="nd"&gt;@Import&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="nc"&gt;InventoryServiceImpl&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// each test runs with 5 random inventories pre-loaded&lt;/span&gt;
  &lt;span class="c1"&gt;// automatically rolled back after each test&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How it works: WithTestDataListener
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;WithTestDataListener&lt;/code&gt; is registered as a &lt;code&gt;TestExecutionListener&lt;/code&gt; via &lt;code&gt;spring.factories&lt;/code&gt;. It hooks into &lt;code&gt;beforeTestMethod&lt;/code&gt; and &lt;code&gt;afterTestMethod&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;transactional = true&lt;/code&gt; (default): loads data within the existing test transaction — Spring rolls it back automatically&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;transactional = false&lt;/code&gt;: opens a &lt;strong&gt;new&lt;/strong&gt; &lt;code&gt;PROPAGATION_REQUIRES_NEW&lt;/code&gt; transaction, commits or rolls back after the test based on the &lt;code&gt;rollback&lt;/code&gt; flag
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&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;beforeTestMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestContext&lt;/span&gt; &lt;span class="n"&gt;testContext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;findAnnotation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testContext&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;ifPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="n"&gt;loadData&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;loadTestData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;annotation&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;annotation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transactional&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;loadData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Start a new transaction for non-transactional tests&lt;/span&gt;
    &lt;span class="nc"&gt;TransactionStatus&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;txManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;testContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TX_STATUS_KEY&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;loadData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&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;This gives you &lt;strong&gt;full control over test data lifecycle&lt;/strong&gt; without reaching for &lt;code&gt;@Sql&lt;/code&gt; scripts or Liquibase migrations in tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-configuration: zero-config by design
&lt;/h2&gt;

&lt;p&gt;All commons configuration registers itself via Spring Boot's &lt;code&gt;AutoConfiguration.imports&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;it.portus.ms.commons.config.CommonsAutoConfiguration&lt;/span&gt;
&lt;span class="err"&gt;it.portus.ms.commons.config.HateoasConfiguration&lt;/span&gt;
&lt;span class="err"&gt;it.portus.ms.commons.config.JacksonAutoConfiguration&lt;/span&gt;
&lt;span class="err"&gt;it.portus.ms.commons.config.LoggingAspectAutoConfiguration&lt;/span&gt;
&lt;span class="err"&gt;it.portus.ms.commons.config.MongoMappersConfiguration&lt;/span&gt;
&lt;span class="err"&gt;it.portus.ms.commons.config.OpenApiCustomizerConfiguration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every configuration class uses &lt;code&gt;@ConditionalOnClass&lt;/code&gt; guards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HateoasConfiguration&lt;/code&gt; only activates if &lt;code&gt;HateoasConfiguration&lt;/code&gt; (Spring HATEOAS) is on the classpath&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MongoMappersConfiguration&lt;/code&gt; only activates if &lt;code&gt;ObjectId&lt;/code&gt; (MongoDB) is present&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LoggingAspectAutoConfiguration&lt;/code&gt; only activates if AspectJ is present&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means &lt;strong&gt;services only pay for what they use&lt;/strong&gt;. The Inventory Service doesn't get Mongo mappers. The gateway doesn't get JPA auditing utilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  What makes this commons layer unusual
&lt;/h2&gt;

&lt;p&gt;Most "commons" in enterprise codebases accumulate over years and become a mess of utilities nobody owns. SmartOrder's commons avoids this with three rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No circular dependencies&lt;/strong&gt; — &lt;code&gt;commons-business&lt;/code&gt; knows nothing about &lt;code&gt;commons-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional everything&lt;/strong&gt; — configs guard themselves with &lt;code&gt;@ConditionalOnClass&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convention over configuration&lt;/strong&gt; — auditing exclusions, package scanning, and pluralization are automatic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: onboarding a new service means adding one Maven dependency. CRUD, HATEOAS, logging, OpenAPI cleanup, and test data management come along for free.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;With commons fully unpacked, the complete picture of SmartOrder's service architecture is clear. The next article will zoom out and look at the &lt;strong&gt;commons-service OpenAPI template customization&lt;/strong&gt; — how Mustache templates are layered and overridden to produce clean, HATEOAS-aware generated code across both services.&lt;/p&gt;

&lt;p&gt;Repo: → &lt;a href="https://github.com/portus84/smartorder-ms" rel="noopener noreferrer"&gt;github.com/portus84/smartorder-ms&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SmartOrder — Part 4: Inside the Inventory Service</title>
      <dc:creator>Francesco Portus</dc:creator>
      <pubDate>Mon, 09 Mar 2026 09:54:23 +0000</pubDate>
      <link>https://dev.to/portus84/smartorder-part-4-inside-the-inventory-service-106k</link>
      <guid>https://dev.to/portus84/smartorder-part-4-inside-the-inventory-service-106k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Event-driven stock management, HATEOAS, and handling complex inventory logic across microservices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part 3 followed an order from HTTP request to RabbitMQ message. The &lt;code&gt;OrderCreated&lt;/code&gt; event was left in the queue. This post focuses on &lt;strong&gt;how the Inventory Service consumes it, solves complex stock management challenges, and leverages shared commons infrastructure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Inventory Service&lt;/strong&gt; is a major bounded context in SmartOrder. Structurally similar to Order Service — three-module Maven layout, contract-first API, HATEOAS responses — it introduces several patterns to solve enterprise challenges: polyglot persistence, eventual consistency, and reliable event-driven workflows.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services/inventory-service" rel="noopener noreferrer"&gt;services/inventory-service&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Ownership and boundaries
&lt;/h2&gt;

&lt;p&gt;Inventory Service owns the stock state for each item — available, reserved, out of stock, discontinued. It &lt;strong&gt;does not&lt;/strong&gt; manage order lifecycle or pricing. Boundaries are strict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No cross-service joins&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No shared schemas&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No synchronous calls&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It consumes &lt;code&gt;OrderCreated&lt;/code&gt; events and reacts, enabling &lt;strong&gt;decoupled, eventually consistent microservices&lt;/strong&gt; — a cornerstone of SmartOrder architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  The domain model and commons integration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;
&lt;span class="nd"&gt;@Data&lt;/span&gt;
&lt;span class="nd"&gt;@Builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toBuilder&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="nd"&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@AllArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@Jacksonized&lt;/span&gt;
&lt;span class="nd"&gt;@EntityListeners&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AuditingEntityListener&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Inventory&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;portus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;business&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Entity&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="nd"&gt;@Id&lt;/span&gt;
  &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UUID&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;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;String&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@NotNull&lt;/span&gt;
  &lt;span class="nd"&gt;@Builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Default&lt;/span&gt;
  &lt;span class="nd"&gt;@Enumerated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EnumType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&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;InventoryStatus&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InventoryStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PENDING&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@CreatedDate&lt;/span&gt;
  &lt;span class="nd"&gt;@Setter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccessLevel&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="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updatable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;createdDate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@LastModifiedDate&lt;/span&gt;
  &lt;span class="nd"&gt;@Setter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccessLevel&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="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;lastModifiedDate&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;ul&gt;
&lt;li&gt;
&lt;code&gt;InventoryStatus&lt;/code&gt; drives the &lt;strong&gt;saga&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@CreatedDate&lt;/code&gt;/&lt;code&gt;@LastModifiedDate&lt;/code&gt; leverage &lt;strong&gt;commons-business auditing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Repository is intentionally thin:
&lt;/li&gt;
&lt;/ul&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;InventoryRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Inventory&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;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Polyglot persistence
&lt;/h2&gt;

&lt;p&gt;Inventory Service uses &lt;strong&gt;Spring Data JPA with H2/Postgres&lt;/strong&gt;, while Order Service uses MongoDB. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stock levels need &lt;strong&gt;atomic updates&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Reservations require &lt;strong&gt;transactional semantics&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;JPA auditing simplifies &lt;strong&gt;commons integration&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Commons modules provide &lt;strong&gt;base entities, mapper interfaces, and helpers&lt;/strong&gt;, ensuring consistent patterns across services.&lt;/p&gt;




&lt;h2&gt;
  
  
  Event-driven consumption
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Consumer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;orderCreatedConsumer&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;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orderConfirmationPublisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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 yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orderCreatedConsumer&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;bindings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;orderCreatedConsumer-in-0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;consumeOrderCreated&lt;/span&gt;
      &lt;span class="na"&gt;bindings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;consumeOrderCreated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pending-orders&lt;/span&gt;
          &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inventory-group&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;inventory-group&lt;/code&gt; ensures &lt;strong&gt;single processing per event across instances&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Stateless consumer, &lt;strong&gt;purely event-driven&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Commons provide &lt;strong&gt;shared DTOs and binding constants&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@UtilityClass&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;BindingNames&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="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;PUBLISH_ORDER_CONFIRMED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"publishOrderConfirmed"&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="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;PUBLISH_ORDER_OUT_OF_STOCK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"publishOrderOutOfStock"&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;
  
  
  Availability check and conditional event publishing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Slf4j&lt;/span&gt;
&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&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;OrderConfirmationPublisher&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;StreamBridge&lt;/span&gt; &lt;span class="n"&gt;streamBridge&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;InventoryService&lt;/span&gt; &lt;span class="n"&gt;inventoryService&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;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;available&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;checkAvailability&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrderId&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;available&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;streamBridge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BindingNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PUBLISH_ORDER_CONFIRMED&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;OrderConfirmedEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrderId&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;streamBridge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BindingNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PUBLISH_ORDER_OUT_OF_STOCK&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderOutOfStockEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrderId&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="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;ul&gt;
&lt;li&gt;Decision logic is &lt;strong&gt;imperative but fully decoupled via events&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Shared &lt;strong&gt;events-model&lt;/strong&gt; from commons ensures consistent serialization&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StreamBridge&lt;/code&gt; allows dynamic selection of output bindings&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  HATEOAS in REST responses
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Inventory&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getInventoryById&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="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;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;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;inventoryMapper:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toDTO&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;hateoasHelper:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toEntityModel&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;ResponseEntity:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&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="nc"&gt;InventoryNotFoundException&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;HATEOAS links generated via &lt;code&gt;hateoasHelper&lt;/code&gt; guide clients dynamically&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;patch/update operations without hardcoding URIs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Commons provide &lt;strong&gt;shared mapper interfaces, page mappers, HATEOAS helpers&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Event-driven workflow: solved challenges
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Order Service publishes &lt;code&gt;OrderCreated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inventory Service consumes it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OrderConfirmationPublisher&lt;/code&gt; decides based on stock&lt;/li&gt;
&lt;li&gt;Events published: &lt;code&gt;OrderConfirmedEvent&lt;/code&gt; or &lt;code&gt;OrderOutOfStockEvent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Order Service updates state&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Solved challenges:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven stock reservation&lt;/strong&gt; without distributed transactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic availability checks&lt;/strong&gt; leveraging JPA transactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared infrastructure&lt;/strong&gt; reduces boilerplate&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TODO: &lt;code&gt;checkAvailability&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&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;checkAvailability&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="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: validate each product item against actual stock&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;ul&gt;
&lt;li&gt;Placeholder always returns &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next step for developers:&lt;/strong&gt; integrate product line items from &lt;code&gt;OrderCreatedEvent&lt;/code&gt; and validate quantities per stock record&lt;/li&gt;
&lt;li&gt;This TODO is &lt;strong&gt;not part of this article's scope&lt;/strong&gt;, but essential for full inventory correctness&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Bootstrap and test data (developer hint)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@AutoConfiguration&lt;/span&gt;
&lt;span class="nd"&gt;@ConditionalOnClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instancio&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestDataLoaderConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="nc"&gt;CommandLineRunner&lt;/span&gt; &lt;span class="nf"&gt;loadTestData&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;service&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;args&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;saveAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instancio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Inventory&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="na"&gt;size&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;ignoreFields&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;create&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;ul&gt;
&lt;li&gt;Instancio generates seed data for local development&lt;/li&gt;
&lt;li&gt;Ensures &lt;strong&gt;consistent setup&lt;/strong&gt; without touching production&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next: commons modules
&lt;/h2&gt;

&lt;p&gt;Focus will be on &lt;strong&gt;&lt;code&gt;services/commons&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared &lt;strong&gt;business logic, DTOs, event definitions, and helpers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Sub-modules used across &lt;code&gt;order-service&lt;/code&gt; and &lt;code&gt;inventory-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entity base classes&lt;/li&gt;
&lt;li&gt;MapStruct mappers&lt;/li&gt;
&lt;li&gt;HATEOAS helpers&lt;/li&gt;
&lt;li&gt;Test data loaders&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Commons are key to scaling SmartOrder &lt;strong&gt;without duplicating boilerplate&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;Repo is open: → &lt;a href="https://github.com/portus84/smartorder-ms" rel="noopener noreferrer"&gt;github.com/portus84/smartorder-ms&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>java</category>
      <category>microservices</category>
    </item>
    <item>
      <title>SmartOrder — Part 3: Inside the Order Service</title>
      <dc:creator>Francesco Portus</dc:creator>
      <pubDate>Tue, 03 Mar 2026 10:21:25 +0000</pubDate>
      <link>https://dev.to/portus84/smartorder-part-3-inside-the-order-service-1k9k</link>
      <guid>https://dev.to/portus84/smartorder-part-3-inside-the-order-service-1k9k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;From OpenAPI contract to domain event — a hands-on walkthrough of one bounded context, end to end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've followed the series so far, you know the big picture: a multi-module Maven monorepo, a services layer that enforces DDD boundaries, an observability stack that boots with a single command. Good. Now let's open the hood.&lt;/p&gt;

&lt;p&gt;This post focuses on the &lt;strong&gt;Order Service&lt;/strong&gt; — the most central bounded context in SmartOrder. We'll trace a single request from the moment it hits the API contract all the way to the RabbitMQ event on the other side, looking at the real code along the way.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services/order-service" rel="noopener noreferrer"&gt;services/order-service&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What does the Order Service actually own?
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of code, it's worth asking: &lt;em&gt;what does this service know, and what is explicitly none of its business?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Order Service owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the lifecycle of an order (created → confirmed → shipped → delivered, or cancelled)&lt;/li&gt;
&lt;li&gt;the order's line items and quantities&lt;/li&gt;
&lt;li&gt;its own MongoDB collection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product details (that's the Product Service)&lt;/li&gt;
&lt;li&gt;stock levels (that's Inventory)&lt;/li&gt;
&lt;li&gt;pricing rules (at least not yet — fun contribution opportunity 👀)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This hard boundary is not just a design choice. It's enforced structurally. There's no cross-service database join, no shared schema, no sneaky &lt;code&gt;@FeignClient&lt;/code&gt; call to fetch product names inside a transaction. If another service's data is needed, you go through its API or you listen to its events. Period.&lt;/p&gt;




&lt;h2&gt;
  
  
  The module structure: three modules, three responsibilities
&lt;/h2&gt;

&lt;p&gt;The Order Service is itself a multi-module Maven project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order-service/
├── api/        ← OpenAPI contract, generated interfaces, DelegateImpl, HATEOAS helpers
├── bootstrap/  ← test data initialization
├── business/   ← domain model, Drools rules, application layer, infrastructure adapters
└── pom.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This split is deliberate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;api&lt;/code&gt;&lt;/strong&gt; is the public face of the service. It contains the OpenAPI spec, the Mustache-generated interfaces, the &lt;code&gt;OrdersApiDelegateImpl&lt;/code&gt; that bridges the HTTP layer to the application layer, and the HATEOAS helper classes. Nothing in here knows about MongoDB or RabbitMQ. If the contract changes, this is the only module that needs to recompile — and the rest of the codebase fails at the right place, at the right time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;bootstrap&lt;/code&gt;&lt;/strong&gt; handles test data initialization. When the full stack boots locally, you don't want to hit an empty database on every demo or integration test run. The bootstrap module seeds the MongoDB collection with a coherent initial dataset, so the service is immediately usable and the Grafana dashboards have something meaningful to display. Keeping it in its own module means that concern never leaks into production logic — no &lt;code&gt;if (isDev)&lt;/code&gt; scattered around the codebase. It can be excluded from production builds entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;business&lt;/code&gt;&lt;/strong&gt; is where the actual work happens. Domain model, application-layer use cases, infrastructure adapters (MongoDB repositories, RabbitMQ via Spring Cloud Stream), and — the part worth talking about at length — the Drools rule engine integration.&lt;/p&gt;

&lt;p&gt;The separation between &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;business&lt;/code&gt; means you can evolve the HTTP contract and the domain logic independently. Small monorepo trick, big dividend when the project grows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Business rules with Drools: the state machine lives in &lt;code&gt;.drl&lt;/code&gt; files
&lt;/h2&gt;

&lt;p&gt;Most Spring Boot services handle business rules the obvious way: a chain of &lt;code&gt;if&lt;/code&gt; statements in a service class. That works until the rules multiply, start interacting with each other, or need to change without touching Java code.&lt;/p&gt;

&lt;p&gt;SmartOrder takes a different approach in the &lt;code&gt;business&lt;/code&gt; module. Business rules — including the order state machine — are expressed as Drools &lt;code&gt;.drl&lt;/code&gt; files. Here's the rule that seeds the working memory with valid state transitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rule "Initialize valid transitions"
    salience 100
    ruleflow-group "validation"
    when
        // always fires — seeds the working memory with valid transitions
    then
        // PENDING → PENDING / CONFIRMED / CANCELLED / OUT_OF_STOCK
        insert(new ValidTransition(OrderStatus.PENDING, OrderStatus.PENDING));
        insert(new ValidTransition(OrderStatus.PENDING, OrderStatus.CONFIRMED));
        insert(new ValidTransition(OrderStatus.PENDING, OrderStatus.CANCELLED));
        insert(new ValidTransition(OrderStatus.PENDING, OrderStatus.OUT_OF_STOCK));

        // CONFIRMED → CONFIRMED / SHIPPED / CANCELLED
        insert(new ValidTransition(OrderStatus.CONFIRMED, OrderStatus.CONFIRMED));
        insert(new ValidTransition(OrderStatus.CONFIRMED, OrderStatus.SHIPPED));
        insert(new ValidTransition(OrderStatus.CONFIRMED, OrderStatus.CANCELLED));

        // SHIPPED → SHIPPED / DELIVERED
        insert(new ValidTransition(OrderStatus.SHIPPED, OrderStatus.SHIPPED));
        insert(new ValidTransition(OrderStatus.SHIPPED, OrderStatus.DELIVERED));
end

query "validTransitionsFor" ( OrderStatus $from )
    ValidTransition( current == $from, $vt := this )
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.drl&lt;/code&gt; is the &lt;strong&gt;single source of truth for the state machine&lt;/strong&gt;. There's no &lt;code&gt;switch&lt;/code&gt; statement, no &lt;code&gt;EnumSet&lt;/code&gt; somewhere else that could drift out of sync. When a new business requirement arrives — say, an &lt;code&gt;ON_HOLD&lt;/code&gt; state — you change the rule file, and every part of the system that queries valid transitions picks it up automatically.&lt;/p&gt;

&lt;p&gt;The Drools query at the bottom is also notable: it lets the application layer ask "what transitions are valid from this state?" and get back a list driven by the same rules, not by a hardcoded enum.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rule engine: Strategy pattern over raw sessions
&lt;/h3&gt;

&lt;p&gt;The integration on the Spring side is where it gets interesting. Rather than exposing &lt;code&gt;KieSession&lt;/code&gt; directly, the engine uses the Strategy pattern to encapsulate each use case:&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;@Slf4j&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&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;OrderRuleEngineImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;OrderRuleEngine&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;KieBase&lt;/span&gt; &lt;span class="n"&gt;orderKieBase&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;Order&lt;/span&gt; &lt;span class="nf"&gt;applyRules&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&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="nf"&gt;executeWithStrategy&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleProcessingStrategy&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;Order&lt;/span&gt; &lt;span class="nf"&gt;applyStatusTransition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="n"&gt;newStatus&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;executeWithStrategy&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StatusTransitionStrategy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newStatus&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getTransitionStatuses&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&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;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;KieSession&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderKieBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newKieSession&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;TransitionStrategy&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="n"&gt;session&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;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;executeWithStrategy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderRuleStrategy&lt;/span&gt; &lt;span class="n"&gt;strategy&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;KieSession&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderKieBase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newKieSession&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;strategy&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="n"&gt;session&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;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting here. &lt;code&gt;KieBase&lt;/code&gt; — not &lt;code&gt;KieContainer&lt;/code&gt; — is injected as a singleton: it holds the compiled rules and is thread-safe by design. &lt;code&gt;KieSession&lt;/code&gt; is created fresh per execution and closed immediately with &lt;code&gt;try-with-resources&lt;/code&gt;. Forgetting to close a &lt;code&gt;KieSession&lt;/code&gt; is a common source of memory leaks in Drools applications; the try-with-resources makes it impossible to forget.&lt;/p&gt;

&lt;p&gt;Each use case (&lt;code&gt;applyRules&lt;/code&gt;, &lt;code&gt;applyStatusTransition&lt;/code&gt;, &lt;code&gt;getTransitionStatuses&lt;/code&gt;) gets its own strategy class. Adding a new rule use case means adding a new strategy — the engine itself doesn't change.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contract-first: two YAML files, two roles
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;api&lt;/code&gt; module exists before any controller. Two distinct YAML files drive the whole thing, and understanding their roles is important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;config.spring.api-v1.yml&lt;/code&gt;&lt;/strong&gt; is the generator configuration — not the spec itself. It's the instruction set for the &lt;code&gt;openapi-generator-maven-plugin&lt;/code&gt;: which Spring features to activate, where to find the spec, which Mustache templates to use. Think of it as the build-time manifest.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/portus84/smartorder-ms/blob/develop/services/order-service/api/src/main/resources/openapi/config.spring.api-v1.yml" rel="noopener noreferrer"&gt;config.spring.api-v1.yml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;api-v1.yml&lt;/code&gt;&lt;/strong&gt; is the actual OpenAPI specification. It lives under &lt;code&gt;static/&lt;/code&gt; so it's served at runtime — point Swagger UI or any OpenAPI tool at the running service and you get the live spec. Here's the relevant excerpt for the orders endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/orders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;addOrder&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/CreateOrderRequest'&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;201'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Created"&lt;/span&gt;
          &lt;span class="na"&gt;x-hateoas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/hal+json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Order'&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;getOrders&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Success"&lt;/span&gt;
          &lt;span class="na"&gt;x-spring-page-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;it.portus.smartorder.ms.orderservice.api.v1.openapi.model.Order&lt;/span&gt;
          &lt;span class="na"&gt;x-hateoas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/prs.hal-forms+json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/PageOrder'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 &lt;a href="https://github.com/portus84/smartorder-ms/blob/develop/services/order-service/api/src/main/resources/static/api-v1.yml" rel="noopener noreferrer"&gt;api-v1.yml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things stand out. The &lt;code&gt;POST&lt;/code&gt; returns &lt;code&gt;application/hal+json&lt;/code&gt; — standard HATEOAS. The &lt;code&gt;GET&lt;/code&gt; returns &lt;code&gt;application/prs.hal-forms+json&lt;/code&gt; — HAL-FORMS, which also includes affordances describing what actions are available. And those &lt;code&gt;x-hateoas&lt;/code&gt; and &lt;code&gt;x-spring-page-type&lt;/code&gt; vendor extensions are not standard OpenAPI; they're the hook for the custom code generation described next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the standard generator falls short: custom extensions + Mustache templates
&lt;/h2&gt;

&lt;p&gt;Anyone who has tried to combine &lt;code&gt;openapi-generator&lt;/code&gt;, Spring HATEOAS and pagination will have hit the same wall: the standard plugin has no concept of &lt;code&gt;PagedModel&amp;lt;EntityModel&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;. Out of the box it generates &lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt;, which is useless for a HATEOAS-first API.&lt;/p&gt;

&lt;p&gt;SmartOrder solves this with two things working together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom vendor extensions in the spec&lt;/strong&gt; — &lt;code&gt;x-hateoas: true&lt;/code&gt; flags an endpoint as needing HATEOAS wrapping. &lt;code&gt;x-spring-page-type&lt;/code&gt; carries the fully-qualified entity class name for paginated responses. The generator ignores these (they're not in the spec), but custom Mustache templates can read them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom Mustache templates in &lt;code&gt;commons-service&lt;/code&gt;&lt;/strong&gt; — the generator config points to a shared template directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services/commons/commons-service/src/main/resources/openapi/templates/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services/commons/commons-service/src/main/resources/openapi/templates" rel="noopener noreferrer"&gt;templates/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These override the default generator output. When a template encounters &lt;code&gt;x-hateoas: true&lt;/code&gt;, it emits the correct Spring HATEOAS return type. The result, instead of &lt;code&gt;ResponseEntity&amp;lt;List&amp;lt;Order&amp;gt;&amp;gt;&lt;/code&gt;, is:&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;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PagedModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityModel&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;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the exact signature in &lt;code&gt;OrdersApiDelegateImpl&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="nd"&gt;@Component&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;OrdersApiDelegateImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;OrdersApiDelegate&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;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PagedModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityModel&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;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getOrders&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;size&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@Nullable&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// application layer call → page of orders → assembled into PagedModel&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;a href="https://github.com/portus84/smartorder-ms/blob/develop/services/order-service/api/src/main/java/it/portus/smartorder/ms/orderservice/api/controller/impl/OrdersApiDelegateImpl.java" rel="noopener noreferrer"&gt;OrdersApiDelegateImpl.java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The templates live in &lt;code&gt;commons-service&lt;/code&gt; so every other service in the platform reuses the same generation logic. Add &lt;code&gt;x-hateoas: true&lt;/code&gt; to a new endpoint anywhere in the repo and the right code comes out automatically, with no per-service configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  HATEOAS with affordances: more than just links
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;GET /orders&lt;/code&gt; endpoint returns &lt;code&gt;application/prs.hal-forms+json&lt;/code&gt;. That's not a typo — it's HAL-FORMS, which goes one step further than plain HATEOAS links by also including &lt;strong&gt;affordances&lt;/strong&gt;: machine-readable descriptions of available actions (DELETE, PUT) with their expected payloads.&lt;/p&gt;

&lt;p&gt;Look at &lt;code&gt;HateoasOrderHelper&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;EntityModel&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="nf"&gt;toEntityModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buildSelfLink&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&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;EntityModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HATEOASLinkUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildLinks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="no"&gt;CONTROLLER_CLAZZ&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;andAffordance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;afford&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebMvcLinkBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;afford&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;methodOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CONTROLLER_CLAZZ&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;deleteOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;()))))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;andAffordance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;afford&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebMvcLinkBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;afford&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;methodOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CONTROLLER_CLAZZ&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;updateOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;andAffordances&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buildUpdateAffordances&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;a href="https://github.com/portus84/smartorder-ms/blob/develop/services/order-service/api/src/main/java/it/portus/smartorder/ms/orderservice/api/hateoas/HateoasOrderHelper.java" rel="noopener noreferrer"&gt;HateoasOrderHelper.java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A HAL-FORMS client receiving this response knows not just where the resource is, but also that it can be deleted and updated, and what shape those requests should take. The client doesn't need out-of-band documentation to discover this — it's in the response. This is relatively rare to see implemented correctly in a public reference project.&lt;/p&gt;

&lt;p&gt;The valid state transitions are also built into the links dynamically, sourced from the Drools engine via &lt;code&gt;getTransitionStatuses&lt;/code&gt;. The link set changes as the order moves through its lifecycle, driven by the same rules that govern transitions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The domain event: outbox pattern, not wishful thinking
&lt;/h2&gt;

&lt;p&gt;Once an order is validated by Drools and persisted, the service needs to notify the rest of the platform. The naive approach — persist to MongoDB, then publish to RabbitMQ — has a well-known flaw: if the publish fails after a successful write, you have an order with no event. The system is inconsistent.&lt;/p&gt;

&lt;p&gt;SmartOrder solves this with a proper &lt;strong&gt;transactional outbox pattern&lt;/strong&gt;. The event is first saved to MongoDB as an &lt;code&gt;OrderOutboxEvent&lt;/code&gt; record, then sent asynchronously via &lt;code&gt;OutboxSender&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="nd"&gt;@Slf4j&lt;/span&gt;
&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&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;OutboxSender&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;OrderOutboxRepository&lt;/span&gt; &lt;span class="n"&gt;outboxRepository&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;StreamBridge&lt;/span&gt; &lt;span class="n"&gt;streamBridge&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;ObjectMapper&lt;/span&gt; &lt;span class="n"&gt;objectMapper&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;ExecutorService&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newFixedThreadPool&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="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderOutboxEvent&lt;/span&gt; &lt;span class="n"&gt;outboxEvent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runAsync&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;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outboxEvent&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;executor&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderOutboxEvent&lt;/span&gt; &lt;span class="n"&gt;event&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;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;eventClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEventType&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;eventPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPayload&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;eventClass&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;streamBridge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BindingNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PUBLISH_ORDER_CREATED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eventPayload&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;EventStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SENT&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;EventStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;outboxRepository&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="n"&gt;event&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;Exception&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="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error sending outbox event {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&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="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EventStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;outboxRepository&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="n"&gt;event&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 the send fails, the event stays in MongoDB with &lt;code&gt;FAILED&lt;/code&gt; status and can be retried. The outbox record is the durable guarantee — the message broker is best-effort on top of it.&lt;/p&gt;

&lt;p&gt;The events themselves use the &lt;strong&gt;CloudEvents&lt;/strong&gt; standard (&lt;code&gt;contentType: application/cloudevents+json&lt;/code&gt;), which means they're interoperable with any broker or consumer that supports the spec, not tied to RabbitMQ specifics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# async-api.yml (excerpt)&lt;/span&gt;
&lt;span class="na"&gt;channels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;orderEventsChannel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pending-orders&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;OrderCreatedMessage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/messages/OrderCreatedMessage'&lt;/span&gt;

&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OrderCreatedMessage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OrderCreatedEvent&lt;/span&gt;
      &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/cloudevents+json&lt;/span&gt;
      &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/OrderCreatedEvent'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Service discovery: Consul registration without thinking about it
&lt;/h2&gt;

&lt;p&gt;The Order Service doesn't know it exists in a cluster. Spring Cloud Consul handles registration automatically at startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;consul&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;discovery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;prefer-ip-address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;instance-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${spring.application.name}:${spring.application.instance_id:${random.value}}&lt;/span&gt;
        &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;instance-id&lt;/code&gt; with &lt;code&gt;${random.value}&lt;/code&gt; allows multiple replicas to register without collisions. &lt;code&gt;fail-fast: false&lt;/code&gt; is worth noting: the service starts even if Consul is temporarily unavailable, rather than refusing to boot — a pragmatic choice for local dev where startup order isn't guaranteed.&lt;/p&gt;

&lt;p&gt;When you open the Consul UI after a &lt;code&gt;docker-compose up&lt;/code&gt;, the service appears, goes green, and becomes routable through the gateway — no manual step required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4sjzzemz2ecs2rwqppo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4sjzzemz2ecs2rwqppo.png" alt="Consul UI showing order-service healthy" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability: Micrometer out of the box
&lt;/h2&gt;

&lt;p&gt;Every Spring Boot service in SmartOrder is instrumented with Micrometer. The Actuator and Prometheus endpoints are configured, and the Grafana dashboards in &lt;code&gt;docker/config-services/grafana&lt;/code&gt; pick them up automatically — the &lt;code&gt;bootstrap&lt;/code&gt; module ensures there's real data flowing from the start.&lt;/p&gt;

&lt;p&gt;What you get without writing anything: &lt;code&gt;http_server_requests_seconds&lt;/code&gt; latency histograms per endpoint, JVM memory metrics, and Spring Cloud Stream message throughput counters. Custom business metrics are four lines:&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;Counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders.created"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tier"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customerTier&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meterRegistry&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That counter shows up in Prometheus and Grafana with zero additional config. The infrastructure is already there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The full flow, end to end
&lt;/h2&gt;

&lt;p&gt;Let's put it all together. A client calls &lt;code&gt;POST /orders&lt;/code&gt; through the Gateway:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gateway&lt;/strong&gt; resolves the route from Consul, forwards to an Order Service instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;api&lt;/code&gt; module&lt;/strong&gt; — the &lt;code&gt;DelegateImpl&lt;/code&gt; receives the request via the OpenAPI-generated contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;business&lt;/code&gt; module&lt;/strong&gt; — the application layer runs the use case, passing the &lt;code&gt;Order&lt;/code&gt; aggregate to the Drools engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drools&lt;/strong&gt; inserts the order and its items as facts, fires all matching rules. If a constraint fails, the use case returns an error response immediately.&lt;/li&gt;
&lt;li&gt;If validation passes, the &lt;code&gt;Order&lt;/code&gt; is persisted to MongoDB. An &lt;code&gt;OrderOutboxEvent&lt;/code&gt; record is saved in the same write, then &lt;code&gt;OutboxSender&lt;/code&gt; dispatches the event to RabbitMQ asynchronously via Spring Cloud Stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response&lt;/strong&gt; goes back as &lt;code&gt;EntityModel&amp;lt;Order&amp;gt;&lt;/code&gt; with HAL-FORMS affordances and HTTP 201.&lt;/li&gt;
&lt;li&gt;Meanwhile, &lt;strong&gt;Inventory Service&lt;/strong&gt; consumes the &lt;code&gt;OrderCreated&lt;/code&gt; CloudEvent from RabbitMQ and reserves stock — fully decoupled, no synchronous dependency.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The consistency problem at step 5 — the classic "what if the broker is down?" — is already handled by the outbox pattern. The event record in MongoDB is the guarantee; the broker delivery is an implementation detail on top of it. A retry scheduler over &lt;code&gt;FAILED&lt;/code&gt; events is the natural next piece to add, and a good open contribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We've covered the full Order Service: its module boundaries, the Drools-driven state machine, the custom OpenAPI generator templates that produce real HATEOAS types, HAL-FORMS affordances, and the transactional outbox pattern for reliable event publishing.&lt;/p&gt;

&lt;p&gt;Next up: the &lt;strong&gt;Inventory Service&lt;/strong&gt; — the consumer side of that same &lt;code&gt;OrderCreated&lt;/code&gt; event, idempotency handling, and what happens when stock is insufficient.&lt;/p&gt;

&lt;p&gt;If something here sparked an idea, the repo is open. The &lt;code&gt;issues&lt;/code&gt; tab is quiet right now — that's a hint. 🙂&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/portus84/smartorder-ms" rel="noopener noreferrer"&gt;github.com/portus84/smartorder-ms&lt;/a&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>java</category>
      <category>microservices</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SmartOrder — Part 2: The Services Layer Architecture</title>
      <dc:creator>Francesco Portus</dc:creator>
      <pubDate>Wed, 25 Feb 2026 16:59:35 +0000</pubDate>
      <link>https://dev.to/portus84/smartorder-part-2-the-services-layer-architecture-49g7</link>
      <guid>https://dev.to/portus84/smartorder-part-2-the-services-layer-architecture-49g7</guid>
      <description>&lt;p&gt;In the previous article, I introduced &lt;strong&gt;SmartOrder&lt;/strong&gt; as a modern microservices reference platform designed around &lt;strong&gt;DDD, REST + HATEOAS, OpenAPI, AsyncAPI, and event-driven principles&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, we focus exclusively on the &lt;code&gt;services&lt;/code&gt; layer structure of the repository:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services" rel="noopener noreferrer"&gt;https://github.com/portus84/smartorder-ms/tree/develop/services&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;This is not a deep dive into individual services — those will be covered in dedicated articles.&lt;br&gt;&lt;br&gt;
Instead, this post explains the architectural blueprint behind the services module and why it is structured this way.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why a Single &lt;code&gt;services&lt;/code&gt; Aggregator Module?
&lt;/h2&gt;

&lt;p&gt;Unlike many microservices repositories that split each service into completely separate repositories, SmartOrder adopts a &lt;strong&gt;single multi-module Maven structure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This decision was intentional and driven by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blueprint visibility&lt;/li&gt;
&lt;li&gt;Consistent dependency management&lt;/li&gt;
&lt;li&gt;Architectural governance&lt;/li&gt;
&lt;li&gt;Easier local development&lt;/li&gt;
&lt;li&gt;Clear separation between shared infrastructure and business services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; directory acts as a &lt;strong&gt;bounded aggregation root&lt;/strong&gt; for all deployable microservices.&lt;/p&gt;

&lt;p&gt;It is not just a folder — it represents the &lt;strong&gt;runtime boundary of the platform&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  High-Level Structure
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; directory contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple Spring Boot microservices&lt;/li&gt;
&lt;li&gt;Each service as an independent Maven module&lt;/li&gt;
&lt;li&gt;A shared parent for dependency alignment&lt;/li&gt;
&lt;li&gt;Strict separation between business logic and infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services/
├── service-a
├── service-b
├── service-c
└── pom.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is independently deployable&lt;/li&gt;
&lt;li&gt;Has its own Spring Boot entry point&lt;/li&gt;
&lt;li&gt;Owns its own domain&lt;/li&gt;
&lt;li&gt;Exposes REST APIs&lt;/li&gt;
&lt;li&gt;Optionally publishes/subscribes to events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yet they share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Platform conventions&lt;/li&gt;
&lt;li&gt;Dependency versions&lt;/li&gt;
&lt;li&gt;Architectural constraints&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architectural Intent
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; module enforces several architectural rules:&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ Domain Isolation
&lt;/h3&gt;

&lt;p&gt;Each microservice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encapsulates its own domain model&lt;/li&gt;
&lt;li&gt;Does not access another service’s database&lt;/li&gt;
&lt;li&gt;Communicates via:

&lt;ul&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;Events (message broker)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This enforces true &lt;strong&gt;bounded contexts&lt;/strong&gt; as described in Domain-Driven Design.&lt;/p&gt;




&lt;h3&gt;
  
  
  2️⃣ API-First Design
&lt;/h3&gt;

&lt;p&gt;All services follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAPI specification for REST contracts&lt;/li&gt;
&lt;li&gt;AsyncAPI for event-driven contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs are explicit&lt;/li&gt;
&lt;li&gt;Contracts are versionable&lt;/li&gt;
&lt;li&gt;Consumer-driven development is possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Services are not “Spring controllers first” — they are &lt;strong&gt;contract-first&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  3️⃣ Clean Internal Layering
&lt;/h3&gt;

&lt;p&gt;Although we are not describing each service individually, the internal structure of services follows a consistent pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service/
├──api/
├──business/
├──bootstrap/
└── pom.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business logic isolation&lt;/li&gt;
&lt;li&gt;Framework independence&lt;/li&gt;
&lt;li&gt;Testability&lt;/li&gt;
&lt;li&gt;Technology replaceability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The services module is therefore not just a deployment unit — it is a &lt;strong&gt;DDD enforcement mechanism&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  4️⃣ Event-Driven Integration
&lt;/h3&gt;

&lt;p&gt;Services are designed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish domain events&lt;/li&gt;
&lt;li&gt;React to integration events&lt;/li&gt;
&lt;li&gt;Remain loosely coupled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Horizontal scalability&lt;/li&gt;
&lt;li&gt;Independent evolution&lt;/li&gt;
&lt;li&gt;Failure isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; layer is built assuming distributed system realities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network failures&lt;/li&gt;
&lt;li&gt;Eventual consistency&lt;/li&gt;
&lt;li&gt;Idempotency&lt;/li&gt;
&lt;li&gt;Observability requirements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Not Separate Repositories?
&lt;/h2&gt;

&lt;p&gt;A common question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not split each microservice into its own repository?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because SmartOrder is a &lt;strong&gt;reference platform&lt;/strong&gt;, not just a production system.&lt;/p&gt;

&lt;p&gt;The goal is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To show the blueprint clearly&lt;/li&gt;
&lt;li&gt;To allow readers to see cross-service patterns&lt;/li&gt;
&lt;li&gt;To simplify learning and experimentation&lt;/li&gt;
&lt;li&gt;To enforce architectural coherence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; module makes the architecture visible.&lt;/p&gt;

&lt;p&gt;It turns the repository into:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A living microservices architecture manual.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Governance Through Structure
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; directory enforces governance through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parent POM inheritance&lt;/li&gt;
&lt;li&gt;Dependency management alignment&lt;/li&gt;
&lt;li&gt;Consistent plugin configuration&lt;/li&gt;
&lt;li&gt;Shared coding conventions&lt;/li&gt;
&lt;li&gt;Architectural symmetry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version drift&lt;/li&gt;
&lt;li&gt;Accidental divergence&lt;/li&gt;
&lt;li&gt;Architecture erosion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The structure itself becomes a &lt;strong&gt;control mechanism&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Services as Independent Runtime Units
&lt;/h2&gt;

&lt;p&gt;Even though they live in the same repository, services are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Independently buildable&lt;/li&gt;
&lt;li&gt;Independently testable&lt;/li&gt;
&lt;li&gt;Independently deployable&lt;/li&gt;
&lt;li&gt;Independently scalable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI/CD pipelines clean&lt;/li&gt;
&lt;li&gt;Docker images separated&lt;/li&gt;
&lt;li&gt;Infrastructure definitions modular&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mono-repo is organizational.&lt;br&gt;&lt;br&gt;
The services remain microservices at runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Strategic Outcome
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; module achieves a balance between:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blueprint clarity&lt;/td&gt;
&lt;td&gt;Single structured repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime independence&lt;/td&gt;
&lt;td&gt;Separate Spring Boot services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architectural governance&lt;/td&gt;
&lt;td&gt;Parent POM + enforced layering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Event-driven + REST integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evolvability&lt;/td&gt;
&lt;td&gt;Domain isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This makes SmartOrder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Educational&lt;/li&gt;
&lt;li&gt;Production-ready&lt;/li&gt;
&lt;li&gt;Architecturally opinionated&lt;/li&gt;
&lt;li&gt;Extensible&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;In the next articles, we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explore individual services&lt;/li&gt;
&lt;li&gt;Analyze their domain boundaries&lt;/li&gt;
&lt;li&gt;Examine REST and event contracts&lt;/li&gt;
&lt;li&gt;Discuss persistence strategies&lt;/li&gt;
&lt;li&gt;Deep dive into integration patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to progressively unfold the SmartOrder architecture layer by layer.&lt;/p&gt;

&lt;p&gt;Stay tuned 🚀&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>softwareengineering</category>
      <category>api</category>
    </item>
    <item>
      <title>SmartOrder: A Modern Microservices Reference Platform</title>
      <dc:creator>Francesco Portus</dc:creator>
      <pubDate>Thu, 12 Feb 2026 17:30:12 +0000</pubDate>
      <link>https://dev.to/portus84/smartorder-a-modern-microservices-reference-platform-ng8</link>
      <guid>https://dev.to/portus84/smartorder-a-modern-microservices-reference-platform-ng8</guid>
      <description>&lt;h2&gt;
  
  
  SmartOrder — a production-ready microservices blueprint (for architects &amp;amp; devs)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A quick, hands-on tour of the SmartOrder reference platform: why it’s structured the way it is, how the Docker-based developer experience is designed, and where you can jump in and contribute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/portus84/smartorder-ms" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR — why this repo matters (put this first)
&lt;/h2&gt;

&lt;p&gt;It doesn't want to be just a toy. SmartOrder would like be a full microservices reference platform that aims to be a &lt;em&gt;blueprint&lt;/em&gt; for production-grade systems: service discovery, API gateway, messaging, observability, local developer tooling and reproducible environments — all wired together so you can boot everything with a single command.&lt;/p&gt;

&lt;p&gt;If you design, build or operate distributed systems, this repo gives you a realistic end-to-end example to read, extend and reuse.&lt;/p&gt;




&lt;h2&gt;
  
  
  Big-picture architecture (the elevator pitch)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Gateway&lt;/strong&gt;: Spring Cloud Gateway acts as a single entry point, dynamically routing using Consul and providing cross-cutting policies (CORS, circuit-breaker fallback, etc.). &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/gateway" rel="noopener noreferrer"&gt;GitHub+gateway&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service mesh-ish primitives&lt;/strong&gt;: Consul is used for service discovery and configuration (auto-registration, health checks). &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/docker" rel="noopener noreferrer"&gt;GitHub+docker&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Business microservices&lt;/strong&gt;: multiple standalone Spring Boot services (Order, Inventory, Product, …) expose HATEOAS-enabled REST APIs and are instrumented with Micrometer. &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services" rel="noopener noreferrer"&gt;GitHub+services&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asynchronous communication&lt;/strong&gt;: RabbitMQ for event-driven messaging (CQRS-friendly, eventual consistency patterns). Kafka is intentionally omitted to keep local dev simple. &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services" rel="noopener noreferrer"&gt;GitHub+services&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistence&lt;/strong&gt;: MongoDB per service for schema-less persistence where appropriate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability &amp;amp; dashboards&lt;/strong&gt;: a fully dockerized stack (Prometheus, Grafana, InfluxDB, Dozzle, Dashy) with pre-provisioned dashboards.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination is designed as a &lt;strong&gt;blueprint&lt;/strong&gt; — an opinionated, repeatable assembly of components you can copy into a real project and evolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Platform UIs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Gateway UI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqicckjuusp1efgat03nt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqicckjuusp1efgat03nt.png" alt="Gateway UI Screenshot" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Consul UI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4sjzzemz2ecs2rwqppo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4sjzzemz2ecs2rwqppo.png" alt="Consul UI Screenshot" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashy
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ukfh51w9ltju4kwk76n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ukfh51w9ltju4kwk76n.png" alt="Dashy UI Screenshot" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dozzle
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdpger3hrmxb5oig5zok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdpger3hrmxb5oig5zok.png" alt="Dozzle UI Screenshot" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Grafana UI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpzslg6wqnb1epajn89f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpzslg6wqnb1epajn89f.png" alt="Grafana UI Screenshot" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a &lt;em&gt;large Maven project&lt;/em&gt; (and why not many separate repos)
&lt;/h2&gt;

&lt;p&gt;Maintainers often face a choice: many tiny repositories (one per service) vs a single repository (or multi-module Maven project) that groups related services. This repository chooses the latter to deliver a few concrete advantages for a blueprint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single versioned snapshot&lt;/strong&gt; — everything boots together and the Docker Compose orchestrations match code checked into the repo. That makes local reproducibility and tutorial-style onboarding trivial.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Synchronized dependency management&lt;/strong&gt; — parent POMs and shared dependencyManagement / pluginManagement reduce version drift across services and simplify CI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Coordinated local dev environment&lt;/strong&gt; — the &lt;code&gt;docker/&lt;/code&gt; folder and &lt;code&gt;docker-compose.all.yml&lt;/code&gt; orchestrate the full ecosystem so you can run the whole stack with the correct versions of monitoring, messaging and persistence. Trying to coordinate cross-repo snapshots for a demo/blueprint is brittle; a single multi-service Maven project keeps the blueprint faithful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blueprint clarity&lt;/strong&gt; — grouping the services makes it easier to show architecture diagrams, end-to-end flows, and example scenarios without jumping between dozens of independent repos.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(If you prefer a polyrepo for production microservices at scale, you can still take the blueprint and split services later. The repo is intentionally organized to make that extraction straightforward.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker &amp;amp; local dev — the reproducible playground
&lt;/h2&gt;

&lt;p&gt;The Docker setup is central — it’s not an afterthought. The repo exposes a structured &lt;code&gt;docker/&lt;/code&gt; layout and multiple compose files so you can selectively boot only what you need or everything at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker
├── config-services
│   ├── dashy
│   ├── grafana
│   ├── influxdb
│   ├── jmeter
│   ├── prometheus
├── docker-compose.all.yml
├── docker-compose.monitoring.yml
├── docker-compose.persistence.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker-compose.all.yml&lt;/code&gt; orchestrates the entire ecosystem (gateway, services, RabbitMQ, Consul, MongoDB, observability tools). The intent: &lt;strong&gt;one command to reproduce a realistic environment&lt;/strong&gt; for debugging, load testing, or demoing features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the full stack (example):&lt;/strong&gt;&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;# from repo root (example)&lt;/span&gt;
docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker/docker-compose.all.yml up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Exact command and compose file names are in the &lt;code&gt;docker/&lt;/code&gt; folder in the repo.) &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/docker" rel="noopener noreferrer"&gt;GitHub+docker&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Domain-Driven Design (DDD), REST + HATEOAS, OpenAPI &amp;amp; AsyncAPI
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The project follows &lt;strong&gt;DDD&lt;/strong&gt; thinking: services own bounded contexts and encapsulate business responsibilities (orders, inventory, product). That makes the model boundaries and data ownership explicit to anyone reading the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;REST + HATEOAS&lt;/strong&gt;: APIs are exposed with HATEOAS-friendly responses so clients can discover resources and transitions (links) instead of relying solely on out-of-band docs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenAPI / AsyncAPI&lt;/strong&gt;: the repo is organized to document synchronous REST APIs with OpenAPI and asynchronous channels with AsyncAPI (where applicable). That makes automated client generation and message contract validation straightforward.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(You’ll find OpenAPI or API docs and AsyncAPI artifacts in the docs folder and service modules; see the repo link below.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/portus84/smartorder-ms/blob/develop/services/order-service/api/src/main/resources/openapi/config.spring.api-v1.yml" rel="noopener noreferrer"&gt;GitHub+openapi+order&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/portus84/smartorder-ms/blob/develop/services/inventory-service/api/src/main/resources/openapi/config.spring.api-v1.yml" rel="noopener noreferrer"&gt;GitHub+openapi-inventory&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/portus84/smartorder-ms/blob/develop/services/events-model/src/main/resources/static/async-api.yml" rel="noopener noreferrer"&gt;GitHub+async-api&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing, quality gates and CI
&lt;/h2&gt;

&lt;p&gt;The project is set up with attention to quality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unit tests and integration tests&lt;/strong&gt; are used to validate both service logic and interactions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;sonar-project.properties&lt;/code&gt; is present — the project is prepared to be analyzed with SonarCloud/ SonarQube for code smells, bugs, and coverage tracking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coverage and CI badges are included to communicate health at a glance (see README). The repo is a good place to experiment with mutation testing, contract tests (for messaging), and end-to-end test strategies.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example — small request flow (Order → Inventory)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Client hits the Gateway: &lt;code&gt;POST /api/orders&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gateway routes to Order Service (route registered via Consul).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Order Service persists the order into its bounded-context DB (Mongo).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Order Service publishes an &lt;code&gt;OrderCreated&lt;/code&gt; event to RabbitMQ.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inventory Service consumes &lt;code&gt;OrderCreated&lt;/code&gt;, reserves stock, and may publish &lt;code&gt;StockReserved&lt;/code&gt; or &lt;code&gt;StockFailed&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clients can follow HATEOAS links to query order status or next steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This kind of event-driven choreography is implemented in the services and wired up via the Dockerized messaging broker so you can step through it locally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why you should read (and contribute)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Architects&lt;/strong&gt;: read the composition choices (service discovery via Consul, why RabbitMQ for a demo blueprint, observability-first stack) to get ideas for your next architecture review or to compare trade-offs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developers&lt;/strong&gt;: the repo contains tangible examples (Spring Boot apps, HATEOAS patterns, Micrometer instrumentation, Docker Compose orchestration) you can clone and run to learn by doing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Contributors&lt;/strong&gt;: tests, docs, and monitoring dashboards are all designed to be extended. Help wanted items you can contribute: sample OpenAPI/AsyncAPI files, additional integration tests (contract tests for messages), example CI workflows, or language-agnostic client examples.&lt;/p&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to look in the repo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;README.md&lt;/code&gt; — quick architecture overview and goals. &lt;a href="https://github.com/portus84/smartorder-ms" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker/&lt;/code&gt; — all the compose files and monitoring stacks (the reproducible dev environment). &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/docker" rel="noopener noreferrer"&gt;GitHub+docker&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;services/&lt;/code&gt; — the individual Spring Boot applications (Order, Inventory, Product, …). &lt;a href="https://github.com/portus84/smartorder-ms/tree/develop/services" rel="noopener noreferrer"&gt;GitHub+services&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How you can help (suggested first PRs)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add/extend &lt;strong&gt;AsyncAPI&lt;/strong&gt; docs for message contracts so consumers/producers can be code-generated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add &lt;strong&gt;contract tests&lt;/strong&gt; for event messages (e.g., Pact or custom consumer-driven contracts).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Harden the &lt;strong&gt;CI&lt;/strong&gt; workflow with matrixed tests and coverage reporting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improve &lt;strong&gt;examples&lt;/strong&gt; for extracting a single service into its own repo (migration guide).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add sample &lt;strong&gt;client SDKs&lt;/strong&gt; generated from OpenAPI for one service.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final notes &amp;amp; follow-ups
&lt;/h2&gt;

&lt;p&gt;This post is the first of a short series that will walk through the repository in more detail: next posts will deep-dive into the Docker composition and observability dashboard, then into DDD and the messaging contracts, and finally into testing strategies and a contributor’s guide — coming as soon as possible.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Enjoy exploring the blueprint — contributions welcome!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>java</category>
      <category>eventdriven</category>
    </item>
  </channel>
</rss>
