<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://cristorvalds.netlify.app/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cristorvalds.netlify.app/" rel="alternate" type="text/html" /><updated>2026-07-03T15:09:04+07:00</updated><id>https://cristorvalds.netlify.app/feed.xml</id><title type="html">CrisTorvalds</title><subtitle>CRIS-TORVALDS</subtitle><author><name>CrisDaDev</name></author><entry><title type="html">T24 - core banking</title><link href="https://cristorvalds.netlify.app/t24/" rel="alternate" type="text/html" title="T24 - core banking" /><published>2026-06-29T19:29:39+07:00</published><updated>2026-06-29T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/t24</id><content type="html" xml:base="https://cristorvalds.netlify.app/t24/"><![CDATA[<p>T24 - hay còn gọi là <strong>Temenos Transact</strong> là core banking system của Temenos - công ty Thụy Sĩ, vendor lớn nhất thế giới về
phần mềm ngân hàng. Hơn 3,000 ngân hàng tại 150 quốc gia dùng sản phẩm Temenos, bao gồm nhiều ngân hàng lớn ở Việt Nam và Đông Nam Á.</p>

<p>“Core banking” nghĩa là gì? Đây là hệ thống xử lý mọi giao dịch tài chính cốt lõi: mở tài khoản, gửi/rút tiền, chuyển khoản,
cho vay, tính lãi, quản lý sổ cái. Nó là “source of truth” cho mọi data tài chính — tất cả channels (mobile app, internet
banking, ATM, teller) đều đọc/ghi vào core banking.</p>

<p>Tại sao developer cần hiểu T24?</p>

<ul>
  <li>Integrate digital channels (mobile banking, web) với T24</li>
  <li>Build middleware/adapter giữa modern stack và T24</li>
  <li>Hiểu data model và business logic mà core banking enforce</li>
  <li>Debug production issues khi data flow qua T24</li>
</ul>

<h3 id="-kiến-trúc-t24"># kiến trúc t24</h3>

<p>T24 sinh ra từ thập niên 90 — kiến trúc khác xa <code class="language-plaintext highlighter-rouge">modern microservices</code>. Hiểu context lịch sử giúp bạn không “judge” nó bằng
<code class="language-plaintext highlighter-rouge">modern standards</code> mà biết cách work with it.</p>

<p><img src="/assets/img/blog/t24-architecture.png" alt="t24-architecture" /></p>

<h4 id="-đặc-điểm-kiến-trúc"># đặc điểm kiến trúc</h4>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>T24</th>
      <th>Modern comparison</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Language</td>
      <td>TAFJ (Java-based framework cho T24 logic) + proprietary scripting</td>
      <td>Spring Boot Java</td>
    </tr>
    <tr>
      <td>Database</td>
      <td>jBASE (NoSQL hash-file DB) hoặc Oracle/SQL Server</td>
      <td>PostgreSQL/MongoDB</td>
    </tr>
    <tr>
      <td>Data model</td>
      <td>Multi-value fields (1 field chứa nhiều giá trị)</td>
      <td>Normalized tables</td>
    </tr>
    <tr>
      <td>API</td>
      <td>OFS messages (text-based) + REST (newer)</td>
      <td>REST/gRPC</td>
    </tr>
    <tr>
      <td>Batch</td>
      <td>COB (Close of Business) — nightly batch processing</td>
      <td>Event-driven/streaming</td>
    </tr>
    <tr>
      <td>Customization</td>
      <td>“Local routines” + “VERSION overrides”</td>
      <td>Configuration + custom code</td>
    </tr>
  </tbody>
</table>

<h3 id="-core-concepts-developer-cần-biết"># core concepts developer cần biết</h3>

<h4 id="-applications--tables"># applications &amp; tables</h4>

<p>T24 tổ chức data theo “Applications” (tương đương bảng/collection):</p>

<table>
  <thead>
    <tr>
      <th>Application</th>
      <th>Mô tả</th>
      <th>Ví dụ ID</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>CUSTOMER</td>
      <td>Thông tin khách hàng</td>
      <td>CUS1234567</td>
    </tr>
    <tr>
      <td>ACCOUNT</td>
      <td>Tài khoản (tiết kiệm, vãng lai)</td>
      <td>VN0010001234567</td>
    </tr>
    <tr>
      <td>FUNDS.TRANSFER</td>
      <td>Giao dịch chuyển khoản</td>
      <td>FT2024010112345</td>
    </tr>
    <tr>
      <td>TELLER</td>
      <td>Giao dịch teller (gửi/rút)</td>
      <td>TT2024010100001</td>
    </tr>
    <tr>
      <td>LIMIT</td>
      <td>Hạn mức tín dụng</td>
      <td>LI000001234</td>
    </tr>
    <tr>
      <td>AA.ARRANGEMENT</td>
      <td>Arrangement Architecture (sản phẩm)</td>
      <td>AA2024010100001</td>
    </tr>
    <tr>
      <td>STMT.ENTRY</td>
      <td>Statement entries (sổ phụ)</td>
      <td>—</td>
    </tr>
  </tbody>
</table>

<h4 id="-record-id-pattern"># record id pattern</h4>

<p><img src="/assets/img/blog/record-pattern.png" alt="record-pattern" /></p>

<p>Mỗi <code class="language-plaintext highlighter-rouge">record</code> có <code class="language-plaintext highlighter-rouge">ID</code> unique, thường encode metadata:</p>

<ul>
  <li>FUNDS.TRANSFER ID: FT24010ABCDE
    <ul>
      <li>FT: Prefix (application type)</li>
      <li>24: Year</li>
      <li>010: Julian day (Jan 10)</li>
      <li>ABCDE: Sequential/random suffix</li>
    </ul>
  </li>
  <li>ACCOUNT ID: VN0010001234567
    <ul>
      <li>VN: Country</li>
      <li>001: Branch code</li>
      <li>0001234567: Sequential number</li>
    </ul>
  </li>
</ul>

<h4 id="-multi-value-fields"># multi-value fields</h4>

<p>T24 dùng <code class="language-plaintext highlighter-rouge">jBASE</code> — NoSQL database với <code class="language-plaintext highlighter-rouge">multi-value</code> fields. Một field có thể chứa array of values, separated bởi special delimiters.
Đây là concept xa lạ nhất với developer quen relational DB.</p>

<p>CUSTOMER record (simplified):</p>

<ul>
  <li>ID: CUS1234567</li>
  <li>MNEMONIC: NGUYENVANA</li>
  <li>SHORT.NAME: “NGUYEN VAN A”</li>
  <li>
    <table>
      <tbody>
        <tr>
          <td>NAME.1: “NGUYEN VAN A”</td>
          <td>“NGUYEN VAN A (Mr.)” ← Multi-value: 2 values</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>
    <table>
      <tbody>
        <tr>
          <td>STREET: “123 Le Loi”</td>
          <td>“Ward 1” ← Multi-value: 2 lines</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>
    <table>
      <tbody>
        <tr>
          <td>PHONE.1: “0901234567”</td>
          <td>“0281234567” ← Multi-value: 2 phones</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>
    <table>
      <tbody>
        <tr>
          <td>RELATION.CODE: “SPOUSE”</td>
          <td>“PARENT” ← Multi-value</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>
    <table>
      <tbody>
        <tr>
          <td>REL.CUSTOMER: “CUS7654321”</td>
          <td>“CUS9876543” ← Associated values</td>
        </tr>
      </tbody>
    </table>
  </li>
</ul>

<p>Mapping to SQL equivalent:</p>

<ul>
  <li>CUSTOMER table + CUSTOMER_PHONES table + CUSTOMER_RELATIONS table</li>
  <li>(normalized) vs T24 (all in 1 record with multi-values)</li>
</ul>

<h4 id="-cob-close-of-business--nightly-batch"># cob (close of business) — nightly batch</h4>

<p>Mỗi đêm, T24 chạy <code class="language-plaintext highlighter-rouge">COB</code>: tính lãi, apply charges, aging loans, generate statements, update balances.
Đây là quá trình rất NẶNG — có thể mất 2-8 tiếng. Trong COB, system restricted (read-only hoặc offline).</p>

<p>Ngày làm việc T24:</p>

<ul>
  <li>08:00 - 17:00: Online processing (real-time transactions)</li>
  <li>17:00 - 18:00: Pre-COB (preparation, cut-off)</li>
  <li>18:00 - 02:00: COB running (batch: interest calc, aging, statements)</li>
  <li>02:00 - 08:00: Post-COB (system ready for next day)</li>
</ul>

<p>Business Date vs System Date:</p>

<ul>
  <li>T24 có concept “TODAY” = business date (may differ from system date)</li>
  <li>Sau COB complete, TODAY advances to next business day</li>
  <li>Weekend: TODAY stays Friday until Monday COB</li>
</ul>

<h3 id="-integration--cách-kết-nối-với-t24"># integration — cách kết nối với t24</h3>

<h4 id="-ofs-open-financial-service--legacy-but-still-dominant"># ofs (open financial service) — legacy but still dominant</h4>

<p>OFS là text-based messaging protocol. Bạn gửi OFS message → T24 parse → execute → return response. Format human-readable nhưng verbose.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// OFS Request: Tạo chuyển khoản
FUNDS.TRANSFER,CSP.INPUT/I/PROCESS,
   TRANSACTION.TYPE::=AC,
   DEBIT.ACCT.NO::=VN0010001234567,
   DEBIT.AMOUNT::=5000000,
   CREDIT.ACCT.NO::=VN0010009876543,
   DEBIT.CURRENCY::=VND

// OFS Response (success)
FT2401050001/1

// OFS Response (error)
//FUNDS.TRANSFER,CSP.INPUT/I/PROCESS/-1,
   DEBIT.ACCT.NO::=VN0010001234567:"INSUFFICIENT BALANCE"
</code></pre></div></div>

<h4 id="-temenos-connect--rest-apis-modern"># temenos connect / rest apis (modern)</h4>

<p>Temenos Transact (newer versions) expose REST APIs qua Temenos Connect:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Modern integration: REST call to T24</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">T24AccountService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">WebClient</span> <span class="n">t24Client</span><span class="o">;</span>

   <span class="kd">public</span> <span class="nc">AccountBalance</span> <span class="nf">getBalance</span><span class="o">(</span><span class="nc">String</span> <span class="n">accountId</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">t24Client</span><span class="o">.</span><span class="na">get</span><span class="o">()</span>
           <span class="o">.</span><span class="na">uri</span><span class="o">(</span><span class="s">"/api/v1/holdings/accounts/{accountId}"</span><span class="o">,</span> <span class="n">accountId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"Authorization"</span><span class="o">,</span> <span class="s">"Bearer "</span> <span class="o">+</span> <span class="n">getServiceToken</span><span class="o">())</span>
           <span class="o">.</span><span class="na">retrieve</span><span class="o">()</span>
           <span class="o">.</span><span class="na">bodyToMono</span><span class="o">(</span><span class="nc">T24AccountResponse</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">mapToBalance</span><span class="o">)</span>
           <span class="o">.</span><span class="na">block</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="nc">TransferResult</span> <span class="nf">createTransfer</span><span class="o">(</span><span class="nc">TransferRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">T24TransferPayload</span> <span class="n">payload</span> <span class="o">=</span> <span class="nc">T24TransferPayload</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">debitAccountId</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getFromAccount</span><span class="o">())</span>
           <span class="o">.</span><span class="na">creditAccountId</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getToAccount</span><span class="o">())</span>
           <span class="o">.</span><span class="na">amount</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getAmount</span><span class="o">())</span>
           <span class="o">.</span><span class="na">currency</span><span class="o">(</span><span class="s">"VND"</span><span class="o">)</span>
           <span class="o">.</span><span class="na">transactionType</span><span class="o">(</span><span class="s">"AC"</span><span class="o">)</span>
           <span class="o">.</span><span class="na">build</span><span class="o">();</span>

       <span class="k">return</span> <span class="n">t24Client</span><span class="o">.</span><span class="na">post</span><span class="o">()</span>
           <span class="o">.</span><span class="na">uri</span><span class="o">(</span><span class="s">"/api/v1/order/payments/transfers"</span><span class="o">)</span>
           <span class="o">.</span><span class="na">bodyValue</span><span class="o">(</span><span class="n">payload</span><span class="o">)</span>
           <span class="o">.</span><span class="na">retrieve</span><span class="o">()</span>
           <span class="o">.</span><span class="na">onStatus</span><span class="o">(</span><span class="nl">HttpStatusCode:</span><span class="o">:</span><span class="n">is4xxClientError</span><span class="o">,</span> <span class="n">response</span> <span class="o">-&gt;</span>
               <span class="n">response</span><span class="o">.</span><span class="na">bodyToMono</span><span class="o">(</span><span class="nc">T24ErrorResponse</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
                   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">error</span> <span class="o">-&gt;</span> <span class="nc">Mono</span><span class="o">.</span><span class="na">error</span><span class="o">(</span>
                       <span class="k">new</span> <span class="nf">T24BusinessException</span><span class="o">(</span><span class="n">error</span><span class="o">.</span><span class="na">getErrorCode</span><span class="o">(),</span> <span class="n">error</span><span class="o">.</span><span class="na">getMessage</span><span class="o">()))))</span>
           <span class="o">.</span><span class="na">bodyToMono</span><span class="o">(</span><span class="nc">T24TransferResponse</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">mapToResult</span><span class="o">)</span>
           <span class="o">.</span><span class="na">block</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-tafj-t24-application-framework-for-java"># tafj (t24 application framework for java)</h4>

<p>TAFJ cho phép viết T24 routines bằng Java (thay vì proprietary language). Developer customize T24 behavior bằng Java classes deployed vào T24 runtime.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// T24 Local Routine (runs INSIDE T24 engine)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LR_ValidateTransfer</span> <span class="kd">extends</span> <span class="nc">LocalRoutine</span> <span class="o">{</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">String</span> <span class="n">debitAccount</span> <span class="o">=</span> <span class="n">getFieldValue</span><span class="o">(</span><span class="s">"DEBIT.ACCT.NO"</span><span class="o">);</span>
       <span class="nc">BigDecimal</span> <span class="n">amount</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="n">getFieldValue</span><span class="o">(</span><span class="s">"DEBIT.AMOUNT"</span><span class="o">));</span>

       <span class="c1">// Custom business validation</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">amount</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"10000000000"</span><span class="o">))</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
           <span class="n">setError</span><span class="o">(</span><span class="s">"DEBIT.AMOUNT"</span><span class="o">,</span> <span class="s">"Amount exceeds single transaction limit"</span><span class="o">);</span>
           <span class="k">return</span><span class="o">;</span>
       <span class="o">}</span>

       <span class="c1">// Check sanctions</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">sanctionService</span><span class="o">.</span><span class="na">isBlocked</span><span class="o">(</span><span class="n">debitAccount</span><span class="o">))</span> <span class="o">{</span>
           <span class="n">setError</span><span class="o">(</span><span class="s">"DEBIT.ACCT.NO"</span><span class="o">,</span> <span class="s">"Account is blocked by compliance"</span><span class="o">);</span>
       <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-data-model--hiểu-cách-t24-lưu-data"># data model — hiểu cách t24 lưu data</h3>

<h4 id="-account-structure"># account structure</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ACCOUNT (tài khoản)
├── ID: VN0010001234567
├── CUSTOMER: CUS1234567 (link to customer)
├── CATEGORY: 6001 (savings) / 1001 (current)
├── CURRENCY: VND
├── WORKING.BALANCE: 50,000,000 (available balance)
├── ONLINE.ACTUAL.BAL: 55,000,000 (ledger balance, including holds)
├── OPENING.DATE: 20200115
├── ACCOUNT.TITLE.1: "NGUYEN VAN A - SAVINGS"
└── INTEREST.RATE: 4.5 (annual %)

Balances in T24:
- WORKING.BALANCE = Available for withdrawal (after holds, blocks)
- ONLINE.ACTUAL.BAL = Ledger balance (all posted transactions)
- ONLINE.CLEARED.BAL = Cleared funds only (excluding uncleared cheques)
</code></pre></div></div>

<h4 id="-transaction-flow"># transaction flow</h4>

<p><img src="/assets/img/blog/transaction-flow.png" alt="transaction-flow" /></p>

<ul>
  <li>
    <p>STMT.ENTRY (Statement Entry): Đây là các bản ghi hạch toán chi tiết. Khi ACCOUNT bị thay đổi số dư, T24 sẽ tự động sinh ra
các STMT.ENTRY tương ứng để phục vụ cho việc in sao kê và đối soát (Reconciliation) sau này.</p>
  </li>
  <li>
    <p>Tính toàn vẹn dữ liệu (No state change): Trong cơ chế của T24 Core Engine, nếu bất kỳ bước kiểm tra nào thất bại hoặc
xảy ra lỗi hệ thống trong quá trình ghi (giai đoạn PASS), toàn bộ giao dịch sẽ bị ROLLBACK để đảm bảo tài khoản không bị trừ tiền oan.</p>
  </li>
</ul>

<h3 id="-challenges-khi-integrate-với-t24"># challenges khi integrate với t24</h3>

<table>
  <thead>
    <tr>
      <th>Challenge</th>
      <th>Vấn đề</th>
      <th>Cách giải quyết</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Latency</td>
      <td>OFS calls: 200ms-2s per request</td>
      <td>Caching layer, batch queries</td>
    </tr>
    <tr>
      <td>COB downtime</td>
      <td>4-8 hours offline mỗi đêm</td>
      <td>Queue requests, retry after COB</td>
    </tr>
    <tr>
      <td>Multi-value mapping</td>
      <td>1 field = N values, không type-safe</td>
      <td>Robust parsing library, validation</td>
    </tr>
    <tr>
      <td>Error messages</td>
      <td>Cryptic codes (“EB-ACCT.BAL.INSUF”)</td>
      <td>Error code mapping table</td>
    </tr>
    <tr>
      <td>Idempotency</td>
      <td>OFS không native idempotent</td>
      <td>Generate unique refs, check before submit</td>
    </tr>
    <tr>
      <td>Testing</td>
      <td>T24 environment limited, expensive</td>
      <td>Mock T24 responses, contract testing</td>
    </tr>
    <tr>
      <td>Performance</td>
      <td>Concurrent OFS sessions limited</td>
      <td>Connection pooling, throttling</td>
    </tr>
    <tr>
      <td>Data freshness</td>
      <td>Balances update after COB steps</td>
      <td>Real-time fields vs batch fields</td>
    </tr>
  </tbody>
</table>

<h4 id="-middleware-pattern-recommended"># middleware pattern (recommended)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Mobile App → API Gateway → [Your Middleware] → T24
                               │
                               ├── Cache (Redis): account info, rates
                               ├── Queue (RabbitMQ): async operations
                               ├── Transform: modern JSON ↔ OFS format
                               ├── Retry: handle T24 timeouts
                               ├── Circuit Breaker: handle T24 down
                               └── Audit: log all T24 interactions
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Middleware service: abstracts T24 complexity from digital channels</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AccountMiddlewareService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">T24Client</span> <span class="n">t24Client</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RedisTemplate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">AccountDTO</span><span class="o">&gt;</span> <span class="n">cache</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CircuitBreakerFactory</span> <span class="n">circuitBreakerFactory</span><span class="o">;</span>

   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Duration</span> <span class="no">CACHE_TTL</span> <span class="o">=</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">5</span><span class="o">);</span>

   <span class="kd">public</span> <span class="nc">AccountDTO</span> <span class="nf">getAccount</span><span class="o">(</span><span class="nc">String</span> <span class="n">accountId</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// 1. Check cache first</span>
       <span class="nc">AccountDTO</span> <span class="n">cached</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="s">"account:"</span> <span class="o">+</span> <span class="n">accountId</span><span class="o">);</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">cached</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="n">cached</span><span class="o">;</span>

       <span class="c1">// 2. Call T24 with circuit breaker</span>
       <span class="nc">CircuitBreaker</span> <span class="n">cb</span> <span class="o">=</span> <span class="n">circuitBreakerFactory</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="s">"t24-accounts"</span><span class="o">);</span>
       <span class="nc">AccountDTO</span> <span class="n">account</span> <span class="o">=</span> <span class="n">cb</span><span class="o">.</span><span class="na">run</span><span class="o">(</span>
           <span class="o">()</span> <span class="o">-&gt;</span> <span class="n">t24Client</span><span class="o">.</span><span class="na">getAccount</span><span class="o">(</span><span class="n">accountId</span><span class="o">),</span>
           <span class="n">throwable</span> <span class="o">-&gt;</span> <span class="o">{</span>
               <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"T24 unavailable for account: {}"</span><span class="o">,</span> <span class="n">accountId</span><span class="o">,</span> <span class="n">throwable</span><span class="o">);</span>
               <span class="k">throw</span> <span class="k">new</span> <span class="nf">CoreBankingUnavailableException</span><span class="o">(</span><span class="s">"Cannot retrieve account"</span><span class="o">);</span>
           <span class="o">}</span>
       <span class="o">);</span>

       <span class="c1">// 3. Cache result</span>
       <span class="n">cache</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="s">"account:"</span> <span class="o">+</span> <span class="n">accountId</span><span class="o">,</span> <span class="n">account</span><span class="o">,</span> <span class="no">CACHE_TTL</span><span class="o">);</span>
       <span class="k">return</span> <span class="n">account</span><span class="o">;</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-t24-trong-context-modern-banking"># t24 trong context modern banking</h3>

<p>T24 là “old but gold” — xử lý correctly hàng tỷ giao dịch/ngày. Nhưng nó KHÔNG phải platform cho digital experiences. Modern stack wrap around T24:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User Experience Layer: Mobile/Web apps (React, Angular, Flutter)
        │
Engagement Layer: BFF, personalization, analytics (Spring Boot, Node.js)
        │
Integration Layer: API management, transformation, caching (Kong, Middleware)
        │
Core Banking: T24 (source of truth, transaction processing, compliance)
</code></pre></div></div>

<p>Xu hướng: Ngân hàng giữ T24 cho <code class="language-plaintext highlighter-rouge">"system of record"</code> (sổ cái, compliance, batch processing) nhưng build modern layers trên
top cho <code class="language-plaintext highlighter-rouge">customer-facing features</code>. Dần dần, specific modules có thể migrate ra — payments qua modern payment hub, lending
qua modern origination platform — nhưng T24 vẫn là backbone cho nhiều thập kỷ nữa.</p>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[T24 - hay còn gọi là Temenos Transact là core banking system của Temenos - công ty Thụy Sĩ, vendor lớn nhất thế giới về phần mềm ngân hàng. Hơn 3,000 ngân hàng tại 150 quốc gia dùng sản phẩm Temenos, bao gồm nhiều ngân hàng lớn ở Việt Nam và Đông Nam Á.]]></summary></entry><entry><title type="html">activiti 8</title><link href="https://cristorvalds.netlify.app/activiti/" rel="alternate" type="text/html" title="activiti 8" /><published>2026-06-28T19:29:39+07:00</published><updated>2026-06-28T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/activiti</id><content type="html" xml:base="https://cristorvalds.netlify.app/activiti/"><![CDATA[<h3 id="-activiti-là-gì-và-tại-sao-cần-nó"># activiti là gì và tại sao cần nó?</h3>

<p>Bạn có thể code mọi workflow bằng if-else, state machine, hay event-driven architecture. Nhưng khi business process có 20+ steps,
conditional branches, parallel paths, human approvals, timeouts, error handling và cần thay đổi thường xuyên mà KHÔNG
redeploy code — bạn cần một <strong>process engine</strong>.</p>

<p>Activiti là BPMN 2.0 engine chạy trên <code class="language-plaintext highlighter-rouge">JVM</code>. Nó nhận file <code class="language-plaintext highlighter-rouge">BPMN XML</code> (vẽ bằng visual designer), parse thành executable logic
và điều phối execution xuyên suốt process lifecycle. Developer không code flow control — chỉ code individual tasks (delegates).
Flow logic nằm trong BPMN diagram mà BA có thể thay đổi.</p>

<p><strong>Ví dụ</strong>: Một ngân hàng dùng Activiti cho quy trình duyệt khoản vay. Khi khách hàng submit đơn trên web portal,
BPMN XML được execute — tự động gọi credit scoring API, route đến nhân viên phê duyệt phù hợp, gửi notification và track SLA.</p>

<h4 id="-activiti-vs-camunda-vs-flowable"># activiti vs camunda vs flowable</h4>

<p>Cả 3 đều fork từ cùng codebase (jBPM → Activiti 5 → fork). Điểm khác biệt:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Activiti 8</th>
      <th>Camunda 7/8</th>
      <th>Flowable 7</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>License</td>
      <td>Apache 2.0</td>
      <td>Community + Enterprise</td>
      <td>Apache 2.0</td>
    </tr>
    <tr>
      <td>Cloud-native</td>
      <td>Activiti Cloud (K8s)</td>
      <td>Camunda 8 (Zeebe)</td>
      <td>Flowable Cloud</td>
    </tr>
    <tr>
      <td>Spring Boot</td>
      <td>Native integration</td>
      <td>Good integration</td>
      <td>Native integration</td>
    </tr>
    <tr>
      <td>Runtime model</td>
      <td>Shared DB (RDBMS)</td>
      <td>Shared DB / Event-sourced (C8)</td>
      <td>Shared DB</td>
    </tr>
    <tr>
      <td>Used by</td>
      <td>✓ (many enterprises)</td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>

<h3 id="-architecture--activiti-bên-trong-làm-gì"># architecture — activiti bên trong làm gì?</h3>

<p>Khi bạn gọi <code class="language-plaintext highlighter-rouge">runtimeService.startProcessInstanceByKey("loan-approval")</code>, đây là điều xảy ra bên dưới:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Engine đọc Process Definition từ ACT_RE_PROCDEF table
2. Tạo Process Instance (row trong ACT_RU_EXECUTION)
3. Tạo execution token ở Start Event
4. Token di chuyển theo Sequence Flow
5. Gặp Service Task → gọi Java Delegate → execute business logic
6. Gặp User Task → INSERT vào ACT_RU_TASK → DỪNG (wait state)
7. Khi user complete task → token tiếp tục di chuyển
8. Gặp Gateway → evaluate conditions → chọn paths
9. Gặp End Event → complete instance → move to history tables (ACT_HI_*)
</code></pre></div></div>

<h4 id="-database-tables"># database tables</h4>

<p>Activiti tạo ~28 tables, chia thành prefixes:</p>

<table>
  <thead>
    <tr>
      <th>Prefix</th>
      <th>Tên</th>
      <th>Mục đích</th>
      <th>Ví dụ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ACT<em>RE</em>*</td>
      <td>Repository</td>
      <td>Process definitions, deployments</td>
      <td>ACT_RE_PROCDEF, ACT_RE_DEPLOYMENT</td>
    </tr>
    <tr>
      <td>ACT<em>RU</em>*</td>
      <td>Runtime</td>
      <td>Running instances, tasks, variables, jobs</td>
      <td>ACT_RU_EXECUTION, ACT_RU_TASK</td>
    </tr>
    <tr>
      <td>ACT<em>HI</em>*</td>
      <td>History</td>
      <td>Completed instances, audit trail</td>
      <td>ACT_HI_PROCINST, ACT_HI_ACTINST</td>
    </tr>
    <tr>
      <td>ACT<em>GE</em>*</td>
      <td>General</td>
      <td>Byte arrays, properties</td>
      <td>ACT_GE_BYTEARRAY</td>
    </tr>
    <tr>
      <td>ACT<em>ID</em>*</td>
      <td>Identity</td>
      <td>Users, groups (optional)</td>
      <td>ACT_ID_USER, ACT_ID_GROUP</td>
    </tr>
  </tbody>
</table>

<p><strong>Quan trọng cho performance</strong>: <code class="language-plaintext highlighter-rouge">ACT_RU_*</code> tables chỉ chứa data đang running — khi instance complete, data chuyển sang <code class="language-plaintext highlighter-rouge">ACT_HI_*</code>. Nếu bạn có 1 triệu instances hoàn thành, <code class="language-plaintext highlighter-rouge">ACT_RU_EXECUTION</code> vẫn nhỏ (chỉ instances đang chạy). Nhưng <code class="language-plaintext highlighter-rouge">ACT_HI_*</code> sẽ phình to — cần archiving strategy.</p>

<h3 id="-spring-boot-integration--setup"># spring boot integration — setup</h3>

<h4 id="-dependencies"># dependencies</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
   <span class="nt">&lt;groupId&gt;</span>org.activiti<span class="nt">&lt;/groupId&gt;</span>
   <span class="nt">&lt;artifactId&gt;</span>activiti-spring-boot-starter<span class="nt">&lt;/artifactId&gt;</span>
   <span class="nt">&lt;version&gt;</span>8.6.0<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<h4 id="-configuration"># configuration</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spring</span><span class="pi">:</span>
  <span class="na">activiti</span><span class="pi">:</span>
    <span class="na">database-schema-update</span><span class="pi">:</span> <span class="no">true</span> <span class="c1"># Auto create/update schema</span>
    <span class="na">history-level</span><span class="pi">:</span> <span class="s">full</span> <span class="c1"># Lưu full audit (activity, variable, task details)</span>
    <span class="na">async-executor-activate</span><span class="pi">:</span> <span class="no">true</span> <span class="c1"># Enable async job execution</span>
    <span class="na">check-process-definitions</span><span class="pi">:</span> <span class="no">false</span> <span class="c1"># Không auto-deploy từ classpath</span>

  <span class="na">datasource</span><span class="pi">:</span>
    <span class="na">url</span><span class="pi">:</span> <span class="s">jdbc:postgresql://localhost:5432/bpm_db</span>
    <span class="na">username</span><span class="pi">:</span> <span class="s">${DB_USERNAME}</span>
    <span class="na">password</span><span class="pi">:</span> <span class="s">${DB_PASSWORD}</span>
    <span class="na">hikari</span><span class="pi">:</span>
      <span class="na">maximum-pool-size</span><span class="pi">:</span> <span class="m">30</span> <span class="c1"># Activiti cần nhiều connections cho async jobs</span>
</code></pre></div></div>

<h4 id="-configuration-class"># configuration class</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ActivitiConfig</span> <span class="o">{</span>

   <span class="nd">@Bean</span>
   <span class="kd">public</span> <span class="nc">SpringProcessEngineConfiguration</span> <span class="nf">processEngineConfiguration</span><span class="o">(</span>
           <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">,</span>
           <span class="nc">PlatformTransactionManager</span> <span class="n">transactionManager</span><span class="o">,</span>
           <span class="nc">List</span><span class="o">&lt;</span><span class="nc">JavaDelegate</span><span class="o">&gt;</span> <span class="n">delegates</span><span class="o">)</span> <span class="o">{</span>

       <span class="nc">SpringProcessEngineConfiguration</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SpringProcessEngineConfiguration</span><span class="o">();</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setDataSource</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setTransactionManager</span><span class="o">(</span><span class="n">transactionManager</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setDatabaseSchemaUpdate</span><span class="o">(</span><span class="s">"true"</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorActivate</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setHistoryLevel</span><span class="o">(</span><span class="nc">HistoryLevel</span><span class="o">.</span><span class="na">FULL</span><span class="o">);</span>

       <span class="c1">// Custom ID generator (UUID thay vì sequential)</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setIdGenerator</span><span class="o">(</span><span class="k">new</span> <span class="nc">StrongUuidGenerator</span><span class="o">());</span>

       <span class="c1">// Async executor tuning</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorCorePoolSize</span><span class="o">(</span><span class="mi">5</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorMaxPoolSize</span><span class="o">(</span><span class="mi">20</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorDefaultAsyncJobAcquireWaitTime</span><span class="o">(</span><span class="mi">5000</span><span class="o">);</span>

       <span class="k">return</span> <span class="n">config</span><span class="o">;</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Activiti tự động detect Spring beans và cho phép reference chúng trong BPMN expressions (<code class="language-plaintext highlighter-rouge">${beanName.method()}</code>). Không cần register thủ công — Spring ApplicationContext = Activiti’s bean registry.</p>

<h3 id="-core-services--api-chính-bạn-cần-biết"># core services — api chính bạn cần biết</h3>

<p>Activiti expose 7 services chính. Trong daily work, bạn dùng 4-5 cái đầu tiên thường xuyên nhất.</p>

<h4 id="-repositoryservice--quản-lý-process-definitions"># repositoryservice — quản lý process definitions</h4>

<p>Think of this as “deployment &amp; versioning.” Mỗi BPMN file deploy = 1 ProcessDefinition. Deploy lại cùng key = version mới.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProcessDefinitionService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RepositoryService</span> <span class="n">repositoryService</span><span class="o">;</span>

   <span class="cm">/**
    * Deploy BPMN XML. Mỗi lần deploy cùng key → version tăng.
    * Instances đang chạy KHÔNG bị ảnh hưởng — giữ version cũ.
    * Instances mới start sẽ dùng version mới nhất.
    */</span>
   <span class="kd">public</span> <span class="nc">DeploymentResult</span> <span class="nf">deploy</span><span class="o">(</span><span class="nc">String</span> <span class="n">bpmnXml</span><span class="o">,</span> <span class="nc">String</span> <span class="n">processName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">workspaceId</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Deployment</span> <span class="n">deployment</span> <span class="o">=</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">createDeployment</span><span class="o">()</span>
           <span class="o">.</span><span class="na">name</span><span class="o">(</span><span class="n">processName</span><span class="o">)</span>
           <span class="o">.</span><span class="na">tenantId</span><span class="o">(</span><span class="n">workspaceId</span><span class="o">)</span>  <span class="c1">// Multi-tenant isolation</span>
           <span class="o">.</span><span class="na">addString</span><span class="o">(</span><span class="n">processName</span> <span class="o">+</span> <span class="s">".bpmn20.xml"</span><span class="o">,</span> <span class="n">bpmnXml</span><span class="o">)</span>
           <span class="o">.</span><span class="na">deploy</span><span class="o">();</span>

       <span class="c1">// Lấy definition mới deploy</span>
       <span class="nc">ProcessDefinition</span> <span class="n">definition</span> <span class="o">=</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">createProcessDefinitionQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">deploymentId</span><span class="o">(</span><span class="n">deployment</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>

       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Deployed process | name={} | version={} | deploymentId={}"</span><span class="o">,</span>
           <span class="n">definition</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">definition</span><span class="o">.</span><span class="na">getVersion</span><span class="o">(),</span> <span class="n">deployment</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>

       <span class="k">return</span> <span class="k">new</span> <span class="nf">DeploymentResult</span><span class="o">(</span><span class="n">deployment</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">definition</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">definition</span><span class="o">.</span><span class="na">getVersion</span><span class="o">());</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Lấy tất cả versions của 1 process definition.
    * Hữu ích cho rollback: activate version cũ, suspend version mới.
    */</span>
   <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ProcessDefinitionDTO</span><span class="o">&gt;</span> <span class="nf">getVersions</span><span class="o">(</span><span class="nc">String</span> <span class="n">processKey</span><span class="o">,</span> <span class="nc">String</span> <span class="n">workspaceId</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">createProcessDefinitionQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processDefinitionKey</span><span class="o">(</span><span class="n">processKey</span><span class="o">)</span>
           <span class="o">.</span><span class="na">processDefinitionTenantId</span><span class="o">(</span><span class="n">workspaceId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orderByProcessDefinitionVersion</span><span class="o">().</span><span class="na">desc</span><span class="o">()</span>
           <span class="o">.</span><span class="na">list</span><span class="o">()</span>
           <span class="o">.</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">)</span>
           <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Suspend definition → không thể start instances mới.
    * Useful khi phát hiện bug trong process, cần "pause" nó.
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">suspend</span><span class="o">(</span><span class="nc">String</span> <span class="n">definitionId</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">repositoryService</span><span class="o">.</span><span class="na">suspendProcessDefinitionById</span><span class="o">(</span><span class="n">definitionId</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
       <span class="c1">// suspendProcessInstances=true → suspend cả running instances</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Suspended process definition: {}"</span><span class="o">,</span> <span class="n">definitionId</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Lấy BPMN XML từ deployed definition.
    * Useful cho: hiển thị diagram, audit, export.
    */</span>
   <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getBpmnXml</span><span class="o">(</span><span class="nc">String</span> <span class="n">definitionId</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">ProcessDefinition</span> <span class="n">def</span> <span class="o">=</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">getProcessDefinition</span><span class="o">(</span><span class="n">definitionId</span><span class="o">);</span>
       <span class="k">try</span> <span class="o">(</span><span class="nc">InputStream</span> <span class="n">is</span> <span class="o">=</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span>
               <span class="n">def</span><span class="o">.</span><span class="na">getDeploymentId</span><span class="o">(),</span> <span class="n">def</span><span class="o">.</span><span class="na">getResourceName</span><span class="o">()))</span> <span class="o">{</span>
           <span class="k">return</span> <span class="k">new</span> <span class="nf">String</span><span class="o">(</span><span class="n">is</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(),</span> <span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">);</span>
       <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Failed to read BPMN resource"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
       <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-runtimeservice--start--control-instances"># runtimeservice — start &amp; control instances</h4>

<p>Đây là service bạn dùng nhiều nhất. Start processes, signal events, get/set variables.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProcessInstanceService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RuntimeService</span> <span class="n">runtimeService</span><span class="o">;</span>

   <span class="cm">/**
    * Start process instance.
    * businessKey = unique identifier từ business domain (order ID, loan ID).
    * Variables = initial data cho process.
    */</span>
   <span class="kd">public</span> <span class="nc">ProcessInstanceDTO</span> <span class="nf">startProcess</span><span class="o">(</span><span class="nc">StartProcessRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">variables</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>
       <span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"applicantName"</span><span class="o">,</span> <span class="n">request</span><span class="o">.</span><span class="na">getApplicantName</span><span class="o">());</span>
       <span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"loanAmount"</span><span class="o">,</span> <span class="n">request</span><span class="o">.</span><span class="na">getLoanAmount</span><span class="o">());</span>
       <span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"applicationDate"</span><span class="o">,</span> <span class="nc">LocalDate</span><span class="o">.</span><span class="na">now</span><span class="o">().</span><span class="na">toString</span><span class="o">());</span>
       <span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"workspaceId"</span><span class="o">,</span> <span class="n">request</span><span class="o">.</span><span class="na">getWorkspaceId</span><span class="o">());</span>

       <span class="nc">ProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">startProcessInstanceByKey</span><span class="o">(</span>
           <span class="n">request</span><span class="o">.</span><span class="na">getProcessKey</span><span class="o">(),</span>       <span class="c1">// BPMN process ID (VD: "loan-approval")</span>
           <span class="n">request</span><span class="o">.</span><span class="na">getBusinessKey</span><span class="o">(),</span>      <span class="c1">// Business identifier (VD: "LOAN-2024-001")</span>
           <span class="n">variables</span>
       <span class="o">);</span>

       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Process started | key={} | instanceId={} | businessKey={}"</span><span class="o">,</span>
           <span class="n">request</span><span class="o">.</span><span class="na">getProcessKey</span><span class="o">(),</span> <span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">request</span><span class="o">.</span><span class="na">getBusinessKey</span><span class="o">());</span>

       <span class="k">return</span> <span class="nf">toDTO</span><span class="o">(</span><span class="n">instance</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Gửi signal/message đến running instance.
    * Use case: external system callback, webhook response.
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">sendMessage</span><span class="o">(</span><span class="nc">String</span> <span class="n">messageName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">businessKey</span><span class="o">,</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">payload</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// Find execution đang wait message</span>
       <span class="nc">Execution</span> <span class="n">execution</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">createExecutionQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">messageEventSubscriptionName</span><span class="o">(</span><span class="n">messageName</span><span class="o">)</span>
           <span class="o">.</span><span class="na">processInstanceBusinessKey</span><span class="o">(</span><span class="n">businessKey</span><span class="o">)</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>

       <span class="k">if</span> <span class="o">(</span><span class="n">execution</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">ProcessNotFoundException</span><span class="o">(</span>
               <span class="s">"No process waiting for message: "</span> <span class="o">+</span> <span class="n">messageName</span> <span class="o">+</span> <span class="s">" with key: "</span> <span class="o">+</span> <span class="n">businessKey</span><span class="o">);</span>
       <span class="o">}</span>

       <span class="n">runtimeService</span><span class="o">.</span><span class="na">messageEventReceived</span><span class="o">(</span><span class="n">messageName</span><span class="o">,</span> <span class="n">execution</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">payload</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Message delivered | message={} | businessKey={}"</span><span class="o">,</span> <span class="n">messageName</span><span class="o">,</span> <span class="n">businessKey</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Get variables từ running instance.
    * Variables = shared state giữa tasks trong cùng instance.
    */</span>
   <span class="kd">public</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="nf">getVariables</span><span class="o">(</span><span class="nc">String</span> <span class="n">instanceId</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">getVariables</span><span class="o">(</span><span class="n">instanceId</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Set/update variable dynamically.
    * Use case: external event update data giữa chừng process.
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setVariable</span><span class="o">(</span><span class="nc">String</span> <span class="n">instanceId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">varName</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">runtimeService</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="n">instanceId</span><span class="o">,</span> <span class="n">varName</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Cancel running instance.
    * Use case: customer cancel application, admin abort stuck process.
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">cancel</span><span class="o">(</span><span class="nc">String</span> <span class="n">instanceId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">runtimeService</span><span class="o">.</span><span class="na">deleteProcessInstance</span><span class="o">(</span><span class="n">instanceId</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Process cancelled | instanceId={} | reason={}"</span><span class="o">,</span> <span class="n">instanceId</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-taskservice--user-tasks-human-interaction"># taskservice — user tasks (human interaction)</h4>

<p>User Tasks là “pause points” — process dừng, chờ người thao tác. TaskService quản lý: query tasks cho user, claim/release, complete với output variables.</p>

<p>Concept quan trọng: <strong>Assignee vs Candidate</strong>. Assignee = task thuộc về 1 người cụ thể. Candidate Groups = task available cho nhóm (ai claim trước thì người đó làm). Giống email vs shared inbox.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserTaskService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">TaskService</span> <span class="n">taskService</span><span class="o">;</span>

   <span class="cm">/**
    * Lấy tasks assigned cho user cụ thể.
    * "Inbox" của user — tasks đang chờ user hoàn thành.
    */</span>
   <span class="kd">public</span> <span class="nc">Page</span><span class="o">&lt;</span><span class="nc">TaskDTO</span><span class="o">&gt;</span> <span class="nf">getMyTasks</span><span class="o">(</span><span class="nc">String</span> <span class="n">userId</span><span class="o">,</span> <span class="nc">Pageable</span> <span class="n">pageable</span><span class="o">)</span> <span class="o">{</span>
       <span class="kt">long</span> <span class="n">total</span> <span class="o">=</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">taskAssignee</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">count</span><span class="o">();</span>

       <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Task</span><span class="o">&gt;</span> <span class="n">tasks</span> <span class="o">=</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">taskAssignee</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orderByTaskCreateTime</span><span class="o">().</span><span class="na">desc</span><span class="o">()</span>
           <span class="o">.</span><span class="na">listPage</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span> <span class="n">pageable</span><span class="o">.</span><span class="na">getOffset</span><span class="o">(),</span> <span class="n">pageable</span><span class="o">.</span><span class="na">getPageSize</span><span class="o">());</span>

       <span class="nc">List</span><span class="o">&lt;</span><span class="nc">TaskDTO</span><span class="o">&gt;</span> <span class="n">dtos</span> <span class="o">=</span> <span class="n">tasks</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">).</span><span class="na">toList</span><span class="o">();</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nc">PageImpl</span><span class="o">&lt;&gt;(</span><span class="n">dtos</span><span class="o">,</span> <span class="n">pageable</span><span class="o">,</span> <span class="n">total</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Lấy tasks available cho group (chưa ai claim).
    * "Shared queue" — ai rảnh thì nhận.
    */</span>
   <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">TaskDTO</span><span class="o">&gt;</span> <span class="nf">getGroupTasks</span><span class="o">(</span><span class="nc">String</span> <span class="n">groupId</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">taskCandidateGroup</span><span class="o">(</span><span class="n">groupId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">taskUnassigned</span><span class="o">()</span>  <span class="c1">// Chưa ai claim</span>
           <span class="o">.</span><span class="na">orderByTaskCreateTime</span><span class="o">().</span><span class="na">desc</span><span class="o">()</span>
           <span class="o">.</span><span class="na">list</span><span class="o">()</span>
           <span class="o">.</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">)</span>
           <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Claim task từ group queue.
    * Sau khi claim, task không còn visible cho người khác trong group.
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">claim</span><span class="o">(</span><span class="nc">String</span> <span class="n">taskId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">userId</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">taskService</span><span class="o">.</span><span class="na">claim</span><span class="o">(</span><span class="n">taskId</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Task claimed | taskId={} | userId={}"</span><span class="o">,</span> <span class="n">taskId</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Unclaim — trả task về queue (user bận, không thể handle).
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">unclaim</span><span class="o">(</span><span class="nc">String</span> <span class="n">taskId</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">taskService</span><span class="o">.</span><span class="na">unclaim</span><span class="o">(</span><span class="n">taskId</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Complete task với output variables.
    * Variables này available cho downstream tasks/gateways.
    *
    * Ví dụ: Approver complete với decision="approve", comments="Looks good"
    * → Gateway đọc ${decision == 'approve'} → route accordingly
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">complete</span><span class="o">(</span><span class="nc">String</span> <span class="n">taskId</span><span class="o">,</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">variables</span><span class="o">,</span> <span class="nc">String</span> <span class="n">userId</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// Verify task assigned to this user</span>
       <span class="nc">Task</span> <span class="n">task</span> <span class="o">=</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">taskId</span><span class="o">(</span><span class="n">taskId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">taskAssignee</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>

       <span class="k">if</span> <span class="o">(</span><span class="n">task</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">TaskNotFoundException</span><span class="o">(</span><span class="s">"Task not found or not assigned to you: "</span> <span class="o">+</span> <span class="n">taskId</span><span class="o">);</span>
       <span class="o">}</span>

       <span class="n">taskService</span><span class="o">.</span><span class="na">complete</span><span class="o">(</span><span class="n">taskId</span><span class="o">,</span> <span class="n">variables</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Task completed | taskId={} | taskName={} | userId={} | vars={}"</span><span class="o">,</span>
           <span class="n">taskId</span><span class="o">,</span> <span class="n">task</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">userId</span><span class="o">,</span> <span class="n">variables</span><span class="o">.</span><span class="na">keySet</span><span class="o">());</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Delegate task cho người khác.
    * Use case: manager delegate cho subordinate.
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">delegate</span><span class="o">(</span><span class="nc">String</span> <span class="n">taskId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">fromUserId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">toUserId</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">taskService</span><span class="o">.</span><span class="na">delegateTask</span><span class="o">(</span><span class="n">taskId</span><span class="o">,</span> <span class="n">toUserId</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Task delegated | taskId={} | from={} | to={}"</span><span class="o">,</span> <span class="n">taskId</span><span class="o">,</span> <span class="n">fromUserId</span><span class="o">,</span> <span class="n">toUserId</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Add comment to task (audit trail).
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">addComment</span><span class="o">(</span><span class="nc">String</span> <span class="n">taskId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">userId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Task</span> <span class="n">task</span> <span class="o">=</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">().</span><span class="na">taskId</span><span class="o">(</span><span class="n">taskId</span><span class="o">).</span><span class="na">singleResult</span><span class="o">();</span>
       <span class="n">taskService</span><span class="o">.</span><span class="na">addComment</span><span class="o">(</span><span class="n">taskId</span><span class="o">,</span> <span class="n">task</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">(),</span> <span class="n">message</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="kd">private</span> <span class="nc">TaskDTO</span> <span class="nf">toDTO</span><span class="o">(</span><span class="nc">Task</span> <span class="n">task</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">variables</span> <span class="o">=</span> <span class="n">taskService</span><span class="o">.</span><span class="na">getVariables</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
       <span class="k">return</span> <span class="nc">TaskDTO</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">id</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">name</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getName</span><span class="o">())</span>
           <span class="o">.</span><span class="na">assignee</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getAssignee</span><span class="o">())</span>
           <span class="o">.</span><span class="na">createTime</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getCreateTime</span><span class="o">().</span><span class="na">toInstant</span><span class="o">().</span><span class="na">atZone</span><span class="o">(</span><span class="nc">ZoneId</span><span class="o">.</span><span class="na">systemDefault</span><span class="o">()).</span><span class="na">toLocalDateTime</span><span class="o">())</span>
           <span class="o">.</span><span class="na">dueDate</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getDueDate</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">task</span><span class="o">.</span><span class="na">getDueDate</span><span class="o">().</span><span class="na">toInstant</span><span class="o">().</span><span class="na">atZone</span><span class="o">(</span><span class="nc">ZoneId</span><span class="o">.</span><span class="na">systemDefault</span><span class="o">()).</span><span class="na">toLocalDate</span><span class="o">()</span> <span class="o">:</span> <span class="kc">null</span><span class="o">)</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">processDefinitionId</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getProcessDefinitionId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">formKey</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getFormKey</span><span class="o">())</span>
           <span class="o">.</span><span class="na">variables</span><span class="o">(</span><span class="n">variables</span><span class="o">)</span>
           <span class="o">.</span><span class="na">build</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-historyservice--audit-trail--analytics"># historyservice — audit trail &amp; analytics</h4>

<p>Mọi thứ đã xảy ra được lưu trong history. Khi process complete, runtime tables clean up nhưng history giữ lại. Đây là data source cho: audit compliance, performance analytics, bottleneck detection.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProcessHistoryService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">HistoryService</span> <span class="n">historyService</span><span class="o">;</span>

   <span class="cm">/**
    * Lấy toàn bộ history của 1 process instance.
    * Ai làm gì, khi nào, mất bao lâu — full timeline.
    */</span>
   <span class="kd">public</span> <span class="nc">ProcessHistoryDTO</span> <span class="nf">getInstanceHistory</span><span class="o">(</span><span class="nc">String</span> <span class="n">instanceId</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">HistoricProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">historyService</span><span class="o">.</span><span class="na">createHistoricProcessInstanceQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">instanceId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>

       <span class="nc">List</span><span class="o">&lt;</span><span class="nc">HistoricActivityInstance</span><span class="o">&gt;</span> <span class="n">activities</span> <span class="o">=</span> <span class="n">historyService</span><span class="o">.</span><span class="na">createHistoricActivityInstanceQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">instanceId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orderByHistoricActivityInstanceStartTime</span><span class="o">().</span><span class="na">asc</span><span class="o">()</span>
           <span class="o">.</span><span class="na">list</span><span class="o">();</span>

       <span class="nc">List</span><span class="o">&lt;</span><span class="nc">HistoricTaskInstance</span><span class="o">&gt;</span> <span class="n">tasks</span> <span class="o">=</span> <span class="n">historyService</span><span class="o">.</span><span class="na">createHistoricTaskInstanceQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">instanceId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orderByHistoricTaskInstanceStartTime</span><span class="o">().</span><span class="na">asc</span><span class="o">()</span>
           <span class="o">.</span><span class="na">list</span><span class="o">();</span>

       <span class="k">return</span> <span class="nc">ProcessHistoryDTO</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">instanceId</span><span class="o">(</span><span class="n">instanceId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">startTime</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getStartTime</span><span class="o">())</span>
           <span class="o">.</span><span class="na">endTime</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getEndTime</span><span class="o">())</span>
           <span class="o">.</span><span class="na">durationMs</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getDurationInMillis</span><span class="o">())</span>
           <span class="o">.</span><span class="na">status</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getEndTime</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="s">"COMPLETED"</span> <span class="o">:</span> <span class="s">"RUNNING"</span><span class="o">)</span>
           <span class="o">.</span><span class="na">activities</span><span class="o">(</span><span class="n">activities</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toActivityDTO</span><span class="o">).</span><span class="na">toList</span><span class="o">())</span>
           <span class="o">.</span><span class="na">tasks</span><span class="o">(</span><span class="n">tasks</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toTaskDTO</span><span class="o">).</span><span class="na">toList</span><span class="o">())</span>
           <span class="o">.</span><span class="na">build</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Phân tích performance: average duration per task.
    * Phát hiện bottleneck: task nào mất thời gian nhiều nhất?
    */</span>
   <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">TaskPerformanceDTO</span><span class="o">&gt;</span> <span class="nf">getTaskPerformance</span><span class="o">(</span><span class="nc">String</span> <span class="n">processKey</span><span class="o">,</span> <span class="nc">LocalDate</span> <span class="n">from</span><span class="o">,</span> <span class="nc">LocalDate</span> <span class="n">to</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">historyService</span><span class="o">.</span><span class="na">createHistoricTaskInstanceQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processDefinitionKey</span><span class="o">(</span><span class="n">processKey</span><span class="o">)</span>
           <span class="o">.</span><span class="na">taskCompletedAfter</span><span class="o">(</span><span class="n">toDate</span><span class="o">(</span><span class="n">from</span><span class="o">))</span>
           <span class="o">.</span><span class="na">taskCompletedBefore</span><span class="o">(</span><span class="n">toDate</span><span class="o">(</span><span class="n">to</span><span class="o">))</span>
           <span class="o">.</span><span class="na">list</span><span class="o">()</span>
           <span class="o">.</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">groupingBy</span><span class="o">(</span><span class="nl">HistoricTaskInstance:</span><span class="o">:</span><span class="n">getName</span><span class="o">))</span>
           <span class="o">.</span><span class="na">entrySet</span><span class="o">().</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">entry</span> <span class="o">-&gt;</span> <span class="o">{</span>
               <span class="nc">List</span><span class="o">&lt;</span><span class="nc">HistoricTaskInstance</span><span class="o">&gt;</span> <span class="n">tasks</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span>
               <span class="kt">double</span> <span class="n">avgDuration</span> <span class="o">=</span> <span class="n">tasks</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
                   <span class="o">.</span><span class="na">mapToLong</span><span class="o">(</span><span class="n">t</span> <span class="o">-&gt;</span> <span class="n">t</span><span class="o">.</span><span class="na">getDurationInMillis</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">t</span><span class="o">.</span><span class="na">getDurationInMillis</span><span class="o">()</span> <span class="o">:</span> <span class="mi">0</span><span class="o">)</span>
                   <span class="o">.</span><span class="na">average</span><span class="o">().</span><span class="na">orElse</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
               <span class="k">return</span> <span class="k">new</span> <span class="nf">TaskPerformanceDTO</span><span class="o">(</span>
                   <span class="n">entry</span><span class="o">.</span><span class="na">getKey</span><span class="o">(),</span>
                   <span class="n">tasks</span><span class="o">.</span><span class="na">size</span><span class="o">(),</span>
                   <span class="o">(</span><span class="kt">long</span><span class="o">)</span> <span class="n">avgDuration</span><span class="o">,</span>
                   <span class="n">tasks</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">mapToLong</span><span class="o">(</span><span class="nl">HistoricTaskInstance:</span><span class="o">:</span><span class="n">getDurationInMillis</span><span class="o">).</span><span class="na">max</span><span class="o">().</span><span class="na">orElse</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
               <span class="o">);</span>
           <span class="o">})</span>
           <span class="o">.</span><span class="na">sorted</span><span class="o">(</span><span class="nc">Comparator</span><span class="o">.</span><span class="na">comparingLong</span><span class="o">(</span><span class="nl">TaskPerformanceDTO:</span><span class="o">:</span><span class="n">avgDurationMs</span><span class="o">).</span><span class="na">reversed</span><span class="o">())</span>
           <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-java-delegates--nơi-business-logic-sống"># java delegates — nơi business logic sống</h3>

<p>Java Delegate là bridge giữa BPMN diagram và Java code. Mỗi Service Task trong diagram gọi 1 delegate. Delegate nhận execution context (variables, process info), thực hiện logic, và set output variables.</p>

<h3 id="design-principles-cho-delegates">Design principles cho delegates:</h3>

<ol>
  <li><strong>Single Responsibility</strong> — 1 delegate làm 1 việc. Đừng nhồi 500 dòng logic vào 1 delegate.</li>
  <li><strong>Idempotent</strong> — Engine có thể retry. <code class="language-plaintext highlighter-rouge">chargePayment()</code> phải check “đã charge chưa?” trước khi charge.</li>
  <li><strong>Fast</strong> — Synchronous delegates block engine thread. Nếu cần gọi external API mất 30s, dùng async task.</li>
  <li><strong>Stateless</strong> — Delegate là Spring singleton. Không giữ state giữa các lần gọi. Dùng process variables.</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Delegate tính toán credit score.
*
* Input variables: customerId, loanAmount
* Output variables: creditScore, riskLevel, autoApproved
*
* BPMN reference: activiti:delegateExpression="${creditScoreDelegate}"
*/</span>
<span class="nd">@Component</span><span class="o">(</span><span class="s">"creditScoreDelegate"</span><span class="o">)</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CreditScoreDelegate</span> <span class="kd">implements</span> <span class="nc">JavaDelegate</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CreditScoringClient</span> <span class="n">scoringClient</span><span class="o">;</span>  <span class="c1">// Gọi external service</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">MetricsService</span> <span class="n">metrics</span><span class="o">;</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">DelegateExecution</span> <span class="n">execution</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// 1. Read input variables</span>
       <span class="nc">String</span> <span class="n">customerId</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"customerId"</span><span class="o">);</span>
       <span class="nc">BigDecimal</span> <span class="n">loanAmount</span> <span class="o">=</span> <span class="o">(</span><span class="nc">BigDecimal</span><span class="o">)</span> <span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"loanAmount"</span><span class="o">);</span>
       <span class="nc">String</span> <span class="n">processId</span> <span class="o">=</span> <span class="n">execution</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">();</span>

       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"[processId={}] Calculating credit score | customerId={} | amount={}"</span><span class="o">,</span>
           <span class="n">processId</span><span class="o">,</span> <span class="n">customerId</span><span class="o">,</span> <span class="n">loanAmount</span><span class="o">);</span>

       <span class="c1">// 2. Execute business logic</span>
       <span class="kt">long</span> <span class="n">start</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">nanoTime</span><span class="o">();</span>
       <span class="k">try</span> <span class="o">{</span>
           <span class="nc">CreditScoreResult</span> <span class="n">result</span> <span class="o">=</span> <span class="n">scoringClient</span><span class="o">.</span><span class="na">calculate</span><span class="o">(</span><span class="n">customerId</span><span class="o">,</span> <span class="n">loanAmount</span><span class="o">);</span>

           <span class="c1">// 3. Set output variables (available for downstream tasks/gateways)</span>
           <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"creditScore"</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">getScore</span><span class="o">());</span>
           <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"riskLevel"</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">getRiskLevel</span><span class="o">().</span><span class="na">name</span><span class="o">());</span>
           <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"autoApproved"</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">getScore</span><span class="o">()</span> <span class="o">&gt;=</span> <span class="mi">750</span><span class="o">);</span>
           <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"scoringDetails"</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">getDetails</span><span class="o">());</span>

           <span class="kt">long</span> <span class="n">durationMs</span> <span class="o">=</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">nanoTime</span><span class="o">()</span> <span class="o">-</span> <span class="n">start</span><span class="o">)</span> <span class="o">/</span> <span class="mi">1_000_000</span><span class="o">;</span>
           <span class="n">metrics</span><span class="o">.</span><span class="na">recordTaskDuration</span><span class="o">(</span><span class="s">"credit_score_calculation"</span><span class="o">,</span> <span class="n">durationMs</span><span class="o">);</span>

           <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"[processId={}] Credit score calculated | score={} | risk={} | autoApproved={} | {}ms"</span><span class="o">,</span>
               <span class="n">processId</span><span class="o">,</span> <span class="n">result</span><span class="o">.</span><span class="na">getScore</span><span class="o">(),</span> <span class="n">result</span><span class="o">.</span><span class="na">getRiskLevel</span><span class="o">(),</span>
               <span class="n">result</span><span class="o">.</span><span class="na">getScore</span><span class="o">()</span> <span class="o">&gt;=</span> <span class="mi">750</span><span class="o">,</span> <span class="n">durationMs</span><span class="o">);</span>

       <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ScoringServiceUnavailableException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
           <span class="c1">// Throw BpmnError → caught by boundary error event trên diagram</span>
           <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"[processId={}] Scoring service unavailable: {}"</span><span class="o">,</span> <span class="n">processId</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">BpmnError</span><span class="o">(</span><span class="s">"SCORING_UNAVAILABLE"</span><span class="o">,</span>
               <span class="s">"Credit scoring service is temporarily unavailable"</span><span class="o">);</span>

       <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvalidCustomerException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
           <span class="c1">// Business error → route to rejection path</span>
           <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"rejectionReason"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">BpmnError</span><span class="o">(</span><span class="s">"INVALID_CUSTOMER"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
       <span class="o">}</span>
       <span class="c1">// RuntimeException (NPE, etc.) → task fails, engine creates incident</span>
       <span class="c1">// → requires manual intervention or job retry</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-delegate-expression-vs-class-vs-expression"># delegate expression vs class vs expression</h4>

<p>3 cách reference delegate trong BPMN XML:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 1. delegateExpression: Spring bean name (RECOMMENDED) --&gt;</span>
<span class="c">&lt;!-- Bean phải implement JavaDelegate --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">activiti:delegateExpression=</span><span class="s">"${creditScoreDelegate}"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- 2. class: Full qualified class name (instantiated by engine, NOT Spring-managed) --&gt;</span>
<span class="c">&lt;!-- KHÔNG inject Spring beans được (no @Autowired) --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">activiti:class=</span><span class="s">"com.example.bpm.delegate.CreditScoreDelegate"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- 3. expression: Call method trên Spring bean (không cần implement JavaDelegate) --&gt;</span>
<span class="c">&lt;!-- Return value tự động set vào biến nếu có resultVariable --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">activiti:expression=</span><span class="s">"${orderService.calculateTotal(execution)}"</span>
            <span class="na">activiti:resultVariable=</span><span class="s">"totalAmount"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<p><strong>Recommendation</strong>: Luôn dùng <code class="language-plaintext highlighter-rouge">delegateExpression</code> — Spring-managed, injectable, testable.</p>

<h3 id="-async-execution--job-executor"># async execution &amp; job executor</h3>

<p>Mặc định, Service Tasks chạy <strong>synchronous</strong> — cùng thread với process execution. Nghĩa là nếu delegate gọi external API mất 30 giây, engine thread bị block 30 giây. Với 10 concurrent instances → 10 threads bị block → thread pool exhausted → mọi process trong engine freeze.</p>

<p><strong>Async tasks</strong> giải quyết: engine tạo “job” trong database và return ngay. Job Executor (background thread pool) pick up jobs và execute async. Nếu fail → retry với exponential backoff. Nếu retry hết → move to deadletter queue.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Async service task: engine không block, delegate chạy bởi job executor --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"callExternalApi"</span>
            <span class="na">activiti:delegateExpression=</span><span class="s">"${externalApiDelegate}"</span>
            <span class="na">activiti:async=</span><span class="s">"true"</span>
            <span class="na">activiti:exclusive=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
<span class="c">&lt;!--
 async="true": tạo job thay vì execute inline
 exclusive="true": không chạy song song với jobs cùng instance (tránh optimistic lock)
--&gt;</span>
</code></pre></div></div>

<h4 id="-khi-nào-dùng-async"># khi nào dùng async?</h4>

<table>
  <thead>
    <tr>
      <th>Scenario</th>
      <th>Sync</th>
      <th>Async</th>
      <th>Lý do</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Tính toán nhanh <code class="language-plaintext highlighter-rouge">(&lt;100ms)</code></td>
      <td>✓</td>
      <td> </td>
      <td>Overhead job creation không đáng</td>
    </tr>
    <tr>
      <td>Gọi external API</td>
      <td> </td>
      <td>✓</td>
      <td>Network latency unpredictable</td>
    </tr>
    <tr>
      <td>Gửi email/notification</td>
      <td> </td>
      <td>✓</td>
      <td>Không cần block process flow</td>
    </tr>
    <tr>
      <td>Database query nội bộ</td>
      <td>✓</td>
      <td> </td>
      <td>Nhanh, cùng transaction</td>
    </tr>
    <tr>
      <td>File processing (large)</td>
      <td> </td>
      <td>✓</td>
      <td>CPU-intensive, có thể OOM</td>
    </tr>
    <tr>
      <td>Sau parallel gateway fork</td>
      <td> </td>
      <td>✓</td>
      <td>Mỗi path chạy independent job</td>
    </tr>
  </tbody>
</table>

<h4 id="-job-executor-configuration"># job executor configuration</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AsyncExecutorConfig</span> <span class="o">{</span>

   <span class="nd">@Bean</span>
   <span class="kd">public</span> <span class="nc">SpringProcessEngineConfiguration</span> <span class="nf">engineConfig</span><span class="o">(</span><span class="nc">DataSource</span> <span class="n">ds</span><span class="o">,</span>
           <span class="nc">PlatformTransactionManager</span> <span class="n">txManager</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">SpringProcessEngineConfiguration</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SpringProcessEngineConfiguration</span><span class="o">();</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setDataSource</span><span class="o">(</span><span class="n">ds</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setTransactionManager</span><span class="o">(</span><span class="n">txManager</span><span class="o">);</span>

       <span class="c1">// Async executor settings</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorActivate</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorCorePoolSize</span><span class="o">(</span><span class="mi">5</span><span class="o">);</span>        <span class="c1">// Minimum threads</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorMaxPoolSize</span><span class="o">(</span><span class="mi">20</span><span class="o">);</span>       <span class="c1">// Maximum threads</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorDefaultAsyncJobAcquireWaitTime</span><span class="o">(</span><span class="mi">5000</span><span class="o">);</span>  <span class="c1">// Poll interval (ms)</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorDefaultTimerJobAcquireWaitTime</span><span class="o">(</span><span class="mi">5000</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncFailedJobWaitTime</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>         <span class="c1">// Seconds between retries</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorMaxAsyncJobsDuePerAcquisition</span><span class="o">(</span><span class="mi">5</span><span class="o">);</span>     <span class="c1">// Jobs per batch</span>

       <span class="c1">// Retry configuration</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setDefaultFailedJobWaitTime</span><span class="o">(</span><span class="mi">300</span><span class="o">);</span>      <span class="c1">// 5 minutes between retries</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorResetExpiredJobsInterval</span><span class="o">(</span><span class="mi">60000</span><span class="o">);</span>      <span class="c1">// 1 minute</span>

       <span class="k">return</span> <span class="n">config</span><span class="o">;</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-handling-failed-jobs"># handling failed jobs</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">FailedJobService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ManagementService</span> <span class="n">managementService</span><span class="o">;</span>

   <span class="cm">/**
    * Lấy jobs đang stuck (retries exhausted).
    * Monitor dashboard nên alert khi có deadletter jobs.
    */</span>
   <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">DeadLetterJobDTO</span><span class="o">&gt;</span> <span class="nf">getDeadLetterJobs</span><span class="o">()</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">managementService</span><span class="o">.</span><span class="na">createDeadLetterJobQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">orderByJobCreateTime</span><span class="o">().</span><span class="na">desc</span><span class="o">()</span>
           <span class="o">.</span><span class="na">list</span><span class="o">()</span>
           <span class="o">.</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">job</span> <span class="o">-&gt;</span> <span class="nc">DeadLetterJobDTO</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
               <span class="o">.</span><span class="na">jobId</span><span class="o">(</span><span class="n">job</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
               <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">job</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">())</span>
               <span class="o">.</span><span class="na">exceptionMessage</span><span class="o">(</span><span class="n">job</span><span class="o">.</span><span class="na">getExceptionMessage</span><span class="o">())</span>
               <span class="o">.</span><span class="na">createTime</span><span class="o">(</span><span class="n">job</span><span class="o">.</span><span class="na">getCreateTime</span><span class="o">())</span>
               <span class="o">.</span><span class="na">retries</span><span class="o">(</span><span class="n">job</span><span class="o">.</span><span class="na">getRetries</span><span class="o">())</span>
               <span class="o">.</span><span class="na">build</span><span class="o">())</span>
           <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="cm">/**
    * Retry dead letter job (sau khi fix root cause).
    * Move job từ deadletter → executable queue.
    */</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">retryDeadLetterJob</span><span class="o">(</span><span class="nc">String</span> <span class="n">jobId</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">managementService</span><span class="o">.</span><span class="na">moveDeadLetterJobToExecutableJob</span><span class="o">(</span><span class="n">jobId</span><span class="o">,</span> <span class="mi">3</span><span class="o">);</span> <span class="c1">// 3 retries</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Dead letter job moved to executable | jobId={}"</span><span class="o">,</span> <span class="n">jobId</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-multi-tenancy--workspace-isolation"># multi-tenancy — workspace isolation</h3>

<p>Trong SaaS platforms, mỗi organization/tenant cần isolation. Process definitions và instances phải separated — tenant A không thấy processes của tenant B. Activiti hỗ trợ multi-tenancy native qua <code class="language-plaintext highlighter-rouge">tenantId</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TenantAwareProcessService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RepositoryService</span> <span class="n">repositoryService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RuntimeService</span> <span class="n">runtimeService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">TaskService</span> <span class="n">taskService</span><span class="o">;</span>

   <span class="c1">// Deploy với tenant isolation</span>
   <span class="kd">public</span> <span class="nc">String</span> <span class="nf">deploy</span><span class="o">(</span><span class="nc">String</span> <span class="n">bpmnXml</span><span class="o">,</span> <span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">workspaceId</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Deployment</span> <span class="n">deployment</span> <span class="o">=</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">createDeployment</span><span class="o">()</span>
           <span class="o">.</span><span class="na">name</span><span class="o">(</span><span class="n">name</span><span class="o">)</span>
           <span class="o">.</span><span class="na">tenantId</span><span class="o">(</span><span class="n">workspaceId</span><span class="o">)</span>  <span class="c1">// ← Tenant isolation</span>
           <span class="o">.</span><span class="na">addString</span><span class="o">(</span><span class="n">name</span> <span class="o">+</span> <span class="s">".bpmn20.xml"</span><span class="o">,</span> <span class="n">bpmnXml</span><span class="o">)</span>
           <span class="o">.</span><span class="na">deploy</span><span class="o">();</span>
       <span class="k">return</span> <span class="n">deployment</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="c1">// Start instance — tự động inherit tenant từ definition</span>
   <span class="kd">public</span> <span class="nc">String</span> <span class="nf">startProcess</span><span class="o">(</span><span class="nc">String</span> <span class="n">processKey</span><span class="o">,</span> <span class="nc">String</span> <span class="n">workspaceId</span><span class="o">,</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">vars</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">ProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">startProcessInstanceByKeyAndTenantId</span><span class="o">(</span>
           <span class="n">processKey</span><span class="o">,</span> <span class="n">vars</span><span class="o">,</span> <span class="n">workspaceId</span><span class="o">);</span>  <span class="c1">// ← Tenant-scoped</span>
       <span class="k">return</span> <span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="c1">// Query tasks — chỉ trong workspace</span>
   <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Task</span><span class="o">&gt;</span> <span class="nf">getTasks</span><span class="o">(</span><span class="nc">String</span> <span class="n">userId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">workspaceId</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">taskAssignee</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">taskTenantId</span><span class="o">(</span><span class="n">workspaceId</span><span class="o">)</span>  <span class="c1">// ← Tenant filter</span>
           <span class="o">.</span><span class="na">list</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-testing-processes"># testing processes</h3>

<p>Testing BPMN processes = testing cả flow logic (diagram) + business logic (delegates). Unit test delegates riêng, integration test toàn process.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@SpringBootTest</span>
<span class="nd">@ActiveProfiles</span><span class="o">(</span><span class="s">"test"</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">LoanApprovalProcessTest</span> <span class="o">{</span>

   <span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">RuntimeService</span> <span class="n">runtimeService</span><span class="o">;</span>
   <span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">TaskService</span> <span class="n">taskService</span><span class="o">;</span>
   <span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">HistoryService</span> <span class="n">historyService</span><span class="o">;</span>
   <span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">RepositoryService</span> <span class="n">repositoryService</span><span class="o">;</span>

   <span class="nd">@MockBean</span> <span class="kd">private</span> <span class="nc">CreditScoringClient</span> <span class="n">creditScoringClient</span><span class="o">;</span>
   <span class="nd">@MockBean</span> <span class="kd">private</span> <span class="nc">NotificationService</span> <span class="n">notificationService</span><span class="o">;</span>

   <span class="nd">@BeforeEach</span>
   <span class="kt">void</span> <span class="nf">deployProcess</span><span class="o">()</span> <span class="o">{</span>
       <span class="n">repositoryService</span><span class="o">.</span><span class="na">createDeployment</span><span class="o">()</span>
           <span class="o">.</span><span class="na">addClasspathResource</span><span class="o">(</span><span class="s">"processes/loan-approval.bpmn20.xml"</span><span class="o">)</span>
           <span class="o">.</span><span class="na">deploy</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">loanApproval_highCreditScore_autoApproved</span><span class="o">()</span> <span class="o">{</span>
       <span class="c1">// Given: high credit score → auto-approve path</span>
       <span class="n">when</span><span class="o">(</span><span class="n">creditScoringClient</span><span class="o">.</span><span class="na">calculate</span><span class="o">(</span><span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">()))</span>
           <span class="o">.</span><span class="na">thenReturn</span><span class="o">(</span><span class="k">new</span> <span class="nc">CreditScoreResult</span><span class="o">(</span><span class="mi">800</span><span class="o">,</span> <span class="nc">RiskLevel</span><span class="o">.</span><span class="na">LOW</span><span class="o">));</span>

       <span class="c1">// When: start process</span>
       <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">variables</span> <span class="o">=</span> <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
           <span class="s">"customerId"</span><span class="o">,</span> <span class="s">"CUST-001"</span><span class="o">,</span>
           <span class="s">"loanAmount"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"500000000"</span><span class="o">),</span>
           <span class="s">"applicantName"</span><span class="o">,</span> <span class="s">"Nguyen Van A"</span>
       <span class="o">);</span>
       <span class="nc">ProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">startProcessInstanceByKey</span><span class="o">(</span>
           <span class="s">"loan-approval"</span><span class="o">,</span> <span class="s">"LOAN-001"</span><span class="o">,</span> <span class="n">variables</span><span class="o">);</span>

       <span class="c1">// Then: process should complete automatically (no user task needed)</span>
       <span class="nc">HistoricProcessInstance</span> <span class="n">completed</span> <span class="o">=</span> <span class="n">historyService</span><span class="o">.</span><span class="na">createHistoricProcessInstanceQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">finished</span><span class="o">()</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>

       <span class="n">assertThat</span><span class="o">(</span><span class="n">completed</span><span class="o">).</span><span class="na">isNotNull</span><span class="o">();</span>
       <span class="n">assertThat</span><span class="o">(</span><span class="n">completed</span><span class="o">.</span><span class="na">getEndTime</span><span class="o">()).</span><span class="na">isNotNull</span><span class="o">();</span>

       <span class="c1">// Verify notification sent</span>
       <span class="n">verify</span><span class="o">(</span><span class="n">notificationService</span><span class="o">).</span><span class="na">sendApprovalNotification</span><span class="o">(</span><span class="s">"CUST-001"</span><span class="o">,</span> <span class="s">"LOAN-001"</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">loanApproval_lowCreditScore_requiresManualReview</span><span class="o">()</span> <span class="o">{</span>
       <span class="c1">// Given: low credit score → manual review path</span>
       <span class="n">when</span><span class="o">(</span><span class="n">creditScoringClient</span><span class="o">.</span><span class="na">calculate</span><span class="o">(</span><span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">()))</span>
           <span class="o">.</span><span class="na">thenReturn</span><span class="o">(</span><span class="k">new</span> <span class="nc">CreditScoreResult</span><span class="o">(</span><span class="mi">550</span><span class="o">,</span> <span class="nc">RiskLevel</span><span class="o">.</span><span class="na">HIGH</span><span class="o">));</span>

       <span class="c1">// When: start process</span>
       <span class="nc">ProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">startProcessInstanceByKey</span><span class="o">(</span>
           <span class="s">"loan-approval"</span><span class="o">,</span> <span class="s">"LOAN-002"</span><span class="o">,</span> <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
               <span class="s">"customerId"</span><span class="o">,</span> <span class="s">"CUST-002"</span><span class="o">,</span>
               <span class="s">"loanAmount"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"2000000000"</span><span class="o">)</span>
           <span class="o">));</span>

       <span class="c1">// Then: should create user task for manual review</span>
       <span class="nc">Task</span> <span class="n">reviewTask</span> <span class="o">=</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">taskDefinitionKey</span><span class="o">(</span><span class="s">"manualReviewTask"</span><span class="o">)</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>

       <span class="n">assertThat</span><span class="o">(</span><span class="n">reviewTask</span><span class="o">).</span><span class="na">isNotNull</span><span class="o">();</span>
       <span class="n">assertThat</span><span class="o">(</span><span class="n">reviewTask</span><span class="o">.</span><span class="na">getName</span><span class="o">()).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="s">"Manual Review"</span><span class="o">);</span>

       <span class="c1">// Complete review with approval</span>
       <span class="n">taskService</span><span class="o">.</span><span class="na">complete</span><span class="o">(</span><span class="n">reviewTask</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
           <span class="s">"decision"</span><span class="o">,</span> <span class="s">"approve"</span><span class="o">,</span>
           <span class="s">"reviewComments"</span><span class="o">,</span> <span class="s">"High amount but good collateral"</span>
       <span class="o">));</span>

       <span class="c1">// Verify process completed</span>
       <span class="nc">HistoricProcessInstance</span> <span class="n">completed</span> <span class="o">=</span> <span class="n">historyService</span><span class="o">.</span><span class="na">createHistoricProcessInstanceQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">finished</span><span class="o">()</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>
       <span class="n">assertThat</span><span class="o">(</span><span class="n">completed</span><span class="o">).</span><span class="na">isNotNull</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">loanApproval_scoringServiceDown_errorPath</span><span class="o">()</span> <span class="o">{</span>
       <span class="c1">// Given: scoring service throws error</span>
       <span class="n">when</span><span class="o">(</span><span class="n">creditScoringClient</span><span class="o">.</span><span class="na">calculate</span><span class="o">(</span><span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">()))</span>
           <span class="o">.</span><span class="na">thenThrow</span><span class="o">(</span><span class="k">new</span> <span class="nc">ScoringServiceUnavailableException</span><span class="o">(</span><span class="s">"Service down"</span><span class="o">));</span>

       <span class="c1">// When: start process</span>
       <span class="nc">ProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">startProcessInstanceByKey</span><span class="o">(</span>
           <span class="s">"loan-approval"</span><span class="o">,</span> <span class="s">"LOAN-003"</span><span class="o">,</span> <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
               <span class="s">"customerId"</span><span class="o">,</span> <span class="s">"CUST-003"</span><span class="o">,</span>
               <span class="s">"loanAmount"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"100000000"</span><span class="o">)</span>
           <span class="o">));</span>

       <span class="c1">// Then: should route to error handling path (manual scoring task)</span>
       <span class="nc">Task</span> <span class="n">manualScoringTask</span> <span class="o">=</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">processInstanceId</span><span class="o">(</span><span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">taskDefinitionKey</span><span class="o">(</span><span class="s">"manualScoringTask"</span><span class="o">)</span>
           <span class="o">.</span><span class="na">singleResult</span><span class="o">();</span>

       <span class="n">assertThat</span><span class="o">(</span><span class="n">manualScoringTask</span><span class="o">).</span><span class="na">isNotNull</span><span class="o">();</span>
       <span class="n">assertThat</span><span class="o">(</span><span class="n">manualScoringTask</span><span class="o">.</span><span class="na">getName</span><span class="o">()).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="s">"Manual Credit Assessment"</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-production-monitoring--troubleshooting"># production monitoring &amp; troubleshooting</h3>

<h4 id="-metrics-to-track"># metrics to track</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProcessMetricsCollector</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RuntimeService</span> <span class="n">runtimeService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ManagementService</span> <span class="n">managementService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">HistoryService</span> <span class="n">historyService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">MeterRegistry</span> <span class="n">meterRegistry</span><span class="o">;</span>

   <span class="nd">@Scheduled</span><span class="o">(</span><span class="n">fixedRate</span> <span class="o">=</span> <span class="mi">30000</span><span class="o">)</span>  <span class="c1">// Every 30s</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">collectMetrics</span><span class="o">()</span> <span class="o">{</span>
       <span class="c1">// Active instances</span>
       <span class="kt">long</span> <span class="n">activeInstances</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">createProcessInstanceQuery</span><span class="o">().</span><span class="na">count</span><span class="o">();</span>
       <span class="n">meterRegistry</span><span class="o">.</span><span class="na">gauge</span><span class="o">(</span><span class="s">"bpmn.instances.active"</span><span class="o">,</span> <span class="n">activeInstances</span><span class="o">);</span>

       <span class="c1">// Suspended instances (paused, needs attention)</span>
       <span class="kt">long</span> <span class="n">suspendedInstances</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">createProcessInstanceQuery</span><span class="o">().</span><span class="na">suspended</span><span class="o">().</span><span class="na">count</span><span class="o">();</span>
       <span class="n">meterRegistry</span><span class="o">.</span><span class="na">gauge</span><span class="o">(</span><span class="s">"bpmn.instances.suspended"</span><span class="o">,</span> <span class="n">suspendedInstances</span><span class="o">);</span>

       <span class="c1">// Pending user tasks (work backlog)</span>
       <span class="kt">long</span> <span class="n">pendingTasks</span> <span class="o">=</span> <span class="n">managementService</span><span class="o">.</span><span class="na">createJobQuery</span><span class="o">().</span><span class="na">count</span><span class="o">();</span>
       <span class="n">meterRegistry</span><span class="o">.</span><span class="na">gauge</span><span class="o">(</span><span class="s">"bpmn.tasks.pending"</span><span class="o">,</span> <span class="n">pendingTasks</span><span class="o">);</span>

       <span class="c1">// Failed jobs (needs intervention)</span>
       <span class="kt">long</span> <span class="n">failedJobs</span> <span class="o">=</span> <span class="n">managementService</span><span class="o">.</span><span class="na">createDeadLetterJobQuery</span><span class="o">().</span><span class="na">count</span><span class="o">();</span>
       <span class="n">meterRegistry</span><span class="o">.</span><span class="na">gauge</span><span class="o">(</span><span class="s">"bpmn.jobs.deadletter"</span><span class="o">,</span> <span class="n">failedJobs</span><span class="o">);</span>

       <span class="c1">// Timer jobs waiting (scheduled future work)</span>
       <span class="kt">long</span> <span class="n">timerJobs</span> <span class="o">=</span> <span class="n">managementService</span><span class="o">.</span><span class="na">createTimerJobQuery</span><span class="o">().</span><span class="na">count</span><span class="o">();</span>
       <span class="n">meterRegistry</span><span class="o">.</span><span class="na">gauge</span><span class="o">(</span><span class="s">"bpmn.jobs.timer"</span><span class="o">,</span> <span class="n">timerJobs</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-common-issues--solutions"># common issues &amp; solutions</h4>

<table>
  <thead>
    <tr>
      <th>Symptom</th>
      <th>Root Cause</th>
      <th>Solution</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Process stuck at service task</td>
      <td>Delegate throws uncaught exception</td>
      <td>Check ACT_RU_JOB exceptions, fix delegate, retry</td>
    </tr>
    <tr>
      <td>User task not appearing</td>
      <td>Wrong assignee/candidateGroup</td>
      <td>Check BPMN definition, verify user in correct group</td>
    </tr>
    <tr>
      <td>Process very slow</td>
      <td>Synchronous external calls</td>
      <td>Make service tasks async</td>
    </tr>
    <tr>
      <td>“Optimistic locking exception”</td>
      <td>Concurrent updates on same instance</td>
      <td>Use <code class="language-plaintext highlighter-rouge">exclusive="true"</code> on async tasks</td>
    </tr>
    <tr>
      <td>History tables huge</td>
      <td>No cleanup/archiving</td>
      <td>Schedule history cleanup job</td>
    </tr>
    <tr>
      <td>Deadletter jobs piling up</td>
      <td>External service permanently down</td>
      <td>Fix service, then moveDeadLetterJobToExecutable</td>
    </tr>
  </tbody>
</table>

<h3 id="-kết-luận"># kết luận</h3>

<p>Activiti là powerful tool nhưng có learning curve. Key takeaways cho developer:</p>

<ul>
  <li><strong>BPMN diagram = executable code</strong> — respect it like production code (version, test, review)</li>
  <li><strong>Delegates = your code</strong> — keep them small, idempotent, testable</li>
  <li><strong>Async cho I/O</strong> — đừng block engine threads với external calls</li>
  <li><strong>Monitor deadletter jobs</strong> — silent failures kill production</li>
  <li><strong>Multi-tenancy từ đầu</strong> — retrofit tenant isolation sau = nightmare</li>
  <li><strong>Test full flows</strong> — unit test delegates + integration test process paths</li>
  <li><strong>History = gold mine</strong> — dùng cho analytics, bottleneck detection, compliance</li>
</ul>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="activiti" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[# activiti là gì và tại sao cần nó?]]></summary></entry><entry><title type="html">backbase — digital banking platform</title><link href="https://cristorvalds.netlify.app/backbase/" rel="alternate" type="text/html" title="backbase — digital banking platform" /><published>2026-06-15T19:29:39+07:00</published><updated>2026-06-15T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/backbase</id><content type="html" xml:base="https://cristorvalds.netlify.app/backbase/"><![CDATA[<h3 id="-backbase-là-gì"># backbase là gì?</h3>

<p><strong>Backbase</strong> là platform thương mại <em>(commercial)</em> chuyên xây dựng digital banking experiences.
Thay vì ngân hàng tự build từ zero (mất 2-5 năm), <strong>Backbase</strong> cung cấp ready-made platform với UI components, backend services
và integration layer - ngân hàng chỉ việc customize và deploy trong 6-12 tháng.</p>

<p>Nói đơn giản: <strong>Backbase = “Shopify for banks.”</strong> Bạn không build e-commerce engine từ scratch, bạn dùng Shopify rồi customize.
Tương tự, ngân hàng không build digital banking platform từ scratch, dùng Backbase rồi customize branding, features, integrations.</p>

<p><strong>Target</strong>: Retail banking (cá nhân), SME banking, Wealth management, Corporate banking.
<strong>Customers</strong>: Hơn 150 ngân hàng toàn cầu — HSBC, Lloyds, Rabobank, Banorte…</p>

<h3 id="-kiến-trúc-tổng-quan"># kiến trúc tổng quan</h3>

<p>Backbase theo mô hình <strong>MACH Architecture</strong> (Microservices, API-first, Cloud-native, Headless)</p>

<p><img src="/assets/img/blog/backbase-arch.png" alt="backbase-arch" /></p>

<h3 id="-core-concepts"># core concepts</h3>

<h4 id="-dbs-digital-banking-services"># dbs (digital banking services)</h4>

<p>DBS là API layer giữa Backbase platform và core banking systems. Nó abstract core banking complexity thành standardized REST APIs.
Ngân hàng đổi core banking (T24 → Finacle) → chỉ cần rewrite integration adapter, Backbase apps KHÔNG thay đổi.</p>

<h4 id="-model-bank"># model bank</h4>

<p>Backbase ship “Model Bank” - reference implementation sẵn sàng chạy. Ngân hàng clone Model Bank rồi customize thay vì build from scratch.
Giống starter template.</p>

<h4 id="-widgets--journeys"># widgets &amp; journeys</h4>

<p><strong>Widget</strong>: UI component nhỏ, reusable (account summary, transaction list, transfer form).</p>

<p><strong>Journey</strong>: Full user flow (onboarding journey, loan application journey) = sequence of widgets + orchestration logic.</p>

<h4 id="-entitlements"># entitlements</h4>

<p>Permission system multi-level: User → Service Agreement → Function Group → Privileges.</p>

<p>Ví dụ: User A thuộc company X, có Function Group “Payments Maker” với privilege “Create Domestic Payment” lên đến 500 triệu.</p>

<h3 id="-tech-stack"># tech stack</h3>

<table>
  <thead>
    <tr>
      <th>Layer</th>
      <th>Technology</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Frontend</td>
      <td>Angular (web), iOS/Android native</td>
    </tr>
    <tr>
      <td>BFF</td>
      <td>Spring Boot (Experience Services)</td>
    </tr>
    <tr>
      <td>Backend</td>
      <td>Spring Boot microservices (Java 17+)</td>
    </tr>
    <tr>
      <td>API Gateway</td>
      <td>Custom (Edge Service) hoặc Kong/Nginx</td>
    </tr>
    <tr>
      <td>Database</td>
      <td>MySQL/PostgreSQL per service</td>
    </tr>
    <tr>
      <td>Messaging</td>
      <td>ActiveMQ / Kafka</td>
    </tr>
    <tr>
      <td>Identity</td>
      <td>Keycloak (IAM), OAuth2/OpenID Connect</td>
    </tr>
    <tr>
      <td>Containerization</td>
      <td>Docker + Kubernetes</td>
    </tr>
    <tr>
      <td>Build</td>
      <td>Maven, Jenkins/GitLab CI</td>
    </tr>
    <tr>
      <td>Documentation</td>
      <td>OpenAPI 3.0 (mỗi service có spec)</td>
    </tr>
  </tbody>
</table>

<h4 id="-spring-boot-services-anatomy"># spring boot services anatomy</h4>

<p>Mỗi Backbase capability service là Spring Boot application với structure:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>capability-service/
├── src/main/java/
│   ├── config/          # Spring configuration
│   ├── rest/            # REST controllers (auto-generated from OpenAPI)
│   ├── service/         # Business logic
│   ├── mapper/          # DTO ↔ Domain mapping
│   ├── integration/     # Integration with core banking
│   ├── persistence/     # JPA entities, repositories
│   └── listener/        # Event listeners
├── src/main/resources/
│   ├── application.yml
│   └── api/             # OpenAPI specs
└── pom.xml
</code></pre></div></div>

<h3 id="-integration-patterns--kết-nối-core-banking"># integration patterns — kết nối core banking</h3>

<p>Backbase KHÔNG phải core banking — nó là engagement layer. Data thật (accounts, balances, transactions) nằm ở core
banking system (T24, Finacle, Temenos Infinity). Backbase pull/push data qua Integration Services.</p>

<h4 id="-ingestion-pattern-batchevent-driven"># ingestion pattern (batch/event-driven)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Core Banking → Event/Batch → Backbase DBS (persist) → Backbase Apps (fast reads)
</code></pre></div></div>

<p>Data ingested vào Backbase’s own database cho fast reads. Users truy cập Backbase DB (milliseconds) thay vì hit core banking trực tiếp (seconds).</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Ingestion service: pull transactions from core banking, persist to Backbase DB</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TransactionIngestionService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CoreBankingClient</span> <span class="n">coreBankingClient</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">TransactionRepository</span> <span class="n">backbaseRepository</span><span class="o">;</span>

   <span class="nd">@Scheduled</span><span class="o">(</span><span class="n">fixedRate</span> <span class="o">=</span> <span class="mi">300000</span><span class="o">)</span>  <span class="c1">// Every 5 minutes</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">ingestTransactions</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">LocalDateTime</span> <span class="n">lastSync</span> <span class="o">=</span> <span class="n">backbaseRepository</span><span class="o">.</span><span class="na">getLastSyncTimestamp</span><span class="o">();</span>

       <span class="nc">List</span><span class="o">&lt;</span><span class="nc">CoreTransaction</span><span class="o">&gt;</span> <span class="n">newTransactions</span> <span class="o">=</span> <span class="n">coreBankingClient</span>
           <span class="o">.</span><span class="na">getTransactionsSince</span><span class="o">(</span><span class="n">lastSync</span><span class="o">);</span>

       <span class="nc">List</span><span class="o">&lt;</span><span class="nc">BackbaseTransaction</span><span class="o">&gt;</span> <span class="n">mapped</span> <span class="o">=</span> <span class="n">newTransactions</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">mapToBackbase</span><span class="o">)</span>
           <span class="o">.</span><span class="na">toList</span><span class="o">();</span>

       <span class="n">backbaseRepository</span><span class="o">.</span><span class="na">saveAll</span><span class="o">(</span><span class="n">mapped</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Ingested {} transactions from core banking"</span><span class="o">,</span> <span class="n">mapped</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-pass-through-pattern-real-time"># pass-through pattern (real-time)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Backbase App → Backbase Service → Core Banking (real-time call) → Response
</code></pre></div></div>

<p>Cho operations cần real-time data (create payment, check real-time balance).</p>

<h3 id="-customization--extending-backbase"># customization — extending backbase</h3>

<p>Ngân hàng customize Backbase qua:</p>

<ol>
  <li><strong>Behavior Extensions</strong>: Override/extend service behavior qua Spring Boot hooks</li>
  <li><strong>Custom Services</strong>: Thêm microservice mới cho bank-specific features</li>
  <li><strong>Widget Customization</strong>: Fork/extend Angular widgets</li>
  <li><strong>Integration Adapters</strong>: Connect tới bank’s specific core banking</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Extending Backbase Payment service behavior</span>
<span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomPaymentConfig</span> <span class="o">{</span>

   <span class="nd">@Bean</span>
   <span class="nd">@Primary</span>  <span class="c1">// Override default implementation</span>
   <span class="kd">public</span> <span class="nc">PaymentValidator</span> <span class="nf">customPaymentValidator</span><span class="o">(</span>
           <span class="nc">SanctionScreeningClient</span> <span class="n">sanctionClient</span><span class="o">,</span>
           <span class="nc">FraudDetectionClient</span> <span class="n">fraudClient</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nf">EnhancedPaymentValidator</span><span class="o">(</span><span class="n">sanctionClient</span><span class="o">,</span> <span class="n">fraudClient</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Custom pre-processing hook</span>
<span class="nd">@Component</span>
<span class="nd">@Order</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">FraudCheckInterceptor</span> <span class="kd">implements</span> <span class="nc">PaymentPreProcessor</span> <span class="o">{</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">PaymentOrder</span> <span class="n">payment</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">FraudScore</span> <span class="n">score</span> <span class="o">=</span> <span class="n">fraudService</span><span class="o">.</span><span class="na">evaluate</span><span class="o">(</span><span class="n">payment</span><span class="o">);</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">score</span><span class="o">.</span><span class="na">isHighRisk</span><span class="o">())</span> <span class="o">{</span>
           <span class="n">payment</span><span class="o">.</span><span class="na">setStatus</span><span class="o">(</span><span class="nc">PaymentStatus</span><span class="o">.</span><span class="na">PENDING_REVIEW</span><span class="o">);</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">PaymentHeldException</span><span class="o">(</span><span class="s">"Held for fraud review"</span><span class="o">);</span>
       <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-so-sánh-backbase-vs-build-from-scratch"># so sánh backbase vs build-from-scratch</h3>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>Backbase</th>
      <th>Custom Build</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Time to market</td>
      <td>6-12 months</td>
      <td>2-5 years</td>
    </tr>
    <tr>
      <td>Cost upfront</td>
      <td>License fee (high)</td>
      <td>Development cost (very high)</td>
    </tr>
    <tr>
      <td>Maintenance</td>
      <td>Vendor updates + custom</td>
      <td>100% your responsibility</td>
    </tr>
    <tr>
      <td>Flexibility</td>
      <td>Within platform constraints</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <td>Best for</td>
      <td>Banks wanting speed</td>
      <td>Banks with unique requirements</td>
    </tr>
    <tr>
      <td>Risk</td>
      <td>Vendor lock-in</td>
      <td>Technical debt, team dependency</td>
    </tr>
    <tr>
      <td>Compliance</td>
      <td>Pre-certified (PCI, etc.)</td>
      <td>Must certify yourself</td>
    </tr>
  </tbody>
</table>

<p><strong>Khi nào chọn Backbase</strong>: Ngân hàng truyền thống muốn digital transformation nhanh, có budget cho license, chấp nhận platform constraints.</p>

<p><strong>Khi nào custom build</strong>: Fintech/neobank với business model unique, cần full control, team kỹ thuật mạnh, time-to-market không phải priority #1.</p>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="backbase" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[# backbase là gì?]]></summary></entry><entry><title type="html">bpmn 2.0 — business process model and notation</title><link href="https://cristorvalds.netlify.app/bpmn/" rel="alternate" type="text/html" title="bpmn 2.0 — business process model and notation" /><published>2026-06-14T19:29:39+07:00</published><updated>2026-06-14T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/bpmn</id><content type="html" xml:base="https://cristorvalds.netlify.app/bpmn/"><![CDATA[<h3 id="-giới-thiệu"># giới thiệu</h3>

<p>BPMN 2.0 (Business Process Model and Notation) là tiêu chuẩn quốc tế của OMG (Object Management Group) cho việc mô hình hóa quy trình nghiệp vụ. Nó cung cấp ký hiệu đồ họa mà cả business analyst lẫn developer đều đọc được — cầu nối giữa “nghiệp vụ muốn gì” và “hệ thống làm gì.”</p>

<p>Trong enterprise systems (ngân hàng, bảo hiểm, logistics), BPMN là core technology — platform cho phép thiết kế, deploy, và execute workflows thông qua engine (Activiti, Camunda, Flowable). Process đi qua lifecycle: design → deploy → execute → monitor, tất cả dựa trên BPMN 2.0 XML.</p>

<p>Tại sao senior developer cần hiểu BPMN?</p>

<ul>
  <li>Đọc/review process definitions do BA tạo</li>
  <li>Implement service tasks, listeners, delegates</li>
  <li>Debug process execution issues</li>
  <li>Optimize process performance</li>
  <li>Thiết kế integration giữa BPMN engine và microservices</li>
</ul>

<h3 id="-kiến-trúc-bpmn-20"># kiến trúc bpmn 2.0</h3>

<p>Hãy tưởng tượng bạn vẽ flowchart cho quy trình duyệt khoản vay. Bạn cần: điểm bắt đầu (khách hàng nộp đơn), các bước xử lý (thẩm định, phê duyệt), điểm quyết định (duyệt hay từ chối?), và điểm kết thúc (giải ngân hoặc thông báo từ chối). BPMN 2.0 chuẩn hóa tất cả những ký hiệu này — bất kỳ ai đọc diagram đều hiểu cùng 1 ý nghĩa, bất kể tool hay vendor nào.</p>

<p>Điểm quan trọng mà nhiều developer bỏ qua: BPMN 2.0 không chỉ là diagram. Nó là <strong>executable specification</strong>. File BPMN XML vừa là diagram vừa là code — engine (Activiti, Camunda, Flowable) parse XML này và thực thi nó. Diagram đẹp trên UI và code chạy trên server là CÙNG MỘT FILE.</p>

<p>BPMN 2.0 chia thành 5 nhóm elements chính</p>

<p><img src="/assets/img/blog/bpmn-elements.png" alt="bpmn-elements" /></p>

<p>Bạn không cần thuộc lòng tất cả — 80% process dùng lặp đi lặp lại khoảng 10-15 elements. Phần tiếp theo đi sâu vào từng nhóm với ví dụ thực tế.</p>

<h3 id="-events--điều-gì-xảy-ra"># events — “điều gì xảy ra”</h3>

<p>Events là những <strong>sự kiện</strong> xảy ra trong vòng đời process. Chúng được biểu diễn bằng hình tròn — và chính viền của hình tròn cho bạn biết loại event:</p>

<ul>
  <li>Viền mảnh (thin) = Start Event</li>
  <li>Viền đôi (double) = Intermediate Event</li>
  <li>Viền dày (bold) = End Event</li>
</ul>

<p>Bên trong hình tròn, icon cho biết <strong>trigger type</strong>: đồng hồ (timer), phong bì (message), sấm sét (signal), lửa (error)…</p>

<p>Một điểm quan trọng mà nhiều người bỏ qua: events chia thành <strong>Catching</strong> (chờ sự kiện đến) và <strong>Throwing</strong> (phát ra sự kiện). Catching event = “tôi đang đợi cái gì đó xảy ra.” Throwing event = “tôi thông báo cho thế giới biết cái gì đó đã xảy ra.” Phân biệt này cực kỳ quan trọng khi design inter-process communication.</p>

<p>Trong thực tế, bạn sẽ gặp nhiều nhất:</p>

<ul>
  <li><strong>Timer events</strong>: deadline cho approval tasks (48h không duyệt → escalate)</li>
  <li><strong>Message events</strong>: nhận webhook từ external system, hoặc inter-service triggers</li>
  <li><strong>Error events</strong>: payment failed → rollback → chuyển sang manual handling</li>
</ul>

<h4 id="-start-events--khởi-đầu-process"># start events — khởi đầu process</h4>

<table>
  <thead>
    <tr>
      <th>Event</th>
      <th>Ký hiệu</th>
      <th>Mô tả</th>
      <th>Use case</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>None Start</td>
      <td>(empty circle)</td>
      <td>Manually triggered</td>
      <td>User click “Start Process”</td>
    </tr>
    <tr>
      <td>Timer Start</td>
      <td>với clock</td>
      <td>Triggered theo schedule</td>
      <td>Daily report generation</td>
    </tr>
    <tr>
      <td>Message Start</td>
      <td>với envelope</td>
      <td>Triggered bởi message</td>
      <td>Order received from API</td>
    </tr>
    <tr>
      <td>Signal Start</td>
      <td>với triangle</td>
      <td>Triggered bởi broadcast signal</td>
      <td>System-wide event</td>
    </tr>
    <tr>
      <td>Conditional Start</td>
      <td>với paper</td>
      <td>Triggered khi condition = true</td>
      <td>KPI threshold exceeded</td>
    </tr>
  </tbody>
</table>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- None Start Event --&gt;</span>
<span class="nt">&lt;startEvent</span> <span class="na">id=</span><span class="s">"start"</span> <span class="na">name=</span><span class="s">"Start Process"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Timer Start Event: chạy mỗi ngày lúc 8:00 AM --&gt;</span>
<span class="nt">&lt;startEvent</span> <span class="na">id=</span><span class="s">"timerStart"</span> <span class="na">name=</span><span class="s">"Daily Trigger"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;timerEventDefinition&gt;</span>
       <span class="nt">&lt;timeCycle&gt;</span>0 0 8 * * ?<span class="nt">&lt;/timeCycle&gt;</span>
   <span class="nt">&lt;/timerEventDefinition&gt;</span>
<span class="nt">&lt;/startEvent&gt;</span>

<span class="c">&lt;!-- Message Start Event --&gt;</span>
<span class="nt">&lt;startEvent</span> <span class="na">id=</span><span class="s">"msgStart"</span> <span class="na">name=</span><span class="s">"Order Received"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;messageEventDefinition</span> <span class="na">messageRef=</span><span class="s">"orderMessage"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/startEvent&gt;</span>
</code></pre></div></div>

<h3 id="-intermediate-events--giữa-process"># intermediate events — giữa process</h3>

<p>Intermediate events xảy ra giữa start và end — chờ event, ném signal, delay process.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Timer Intermediate (wait/delay) --&gt;</span>
<span class="nt">&lt;intermediateCatchEvent</span> <span class="na">id=</span><span class="s">"wait3days"</span> <span class="na">name=</span><span class="s">"Wait 3 Days"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;timerEventDefinition&gt;</span>
       <span class="nt">&lt;timeDuration&gt;</span>P3D<span class="nt">&lt;/timeDuration&gt;</span>  <span class="c">&lt;!-- ISO 8601: 3 days --&gt;</span>
   <span class="nt">&lt;/timerEventDefinition&gt;</span>
<span class="nt">&lt;/intermediateCatchEvent&gt;</span>

<span class="c">&lt;!-- Message Intermediate Catch (wait for message) --&gt;</span>
<span class="nt">&lt;intermediateCatchEvent</span> <span class="na">id=</span><span class="s">"waitApproval"</span> <span class="na">name=</span><span class="s">"Wait for Approval"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;messageEventDefinition</span> <span class="na">messageRef=</span><span class="s">"approvalMessage"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/intermediateCatchEvent&gt;</span>

<span class="c">&lt;!-- Signal Throw (broadcast to all processes listening) --&gt;</span>
<span class="nt">&lt;intermediateThrowEvent</span> <span class="na">id=</span><span class="s">"notifyAll"</span> <span class="na">name=</span><span class="s">"Notify All"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;signalEventDefinition</span> <span class="na">signalRef=</span><span class="s">"processCompleteSignal"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/intermediateThrowEvent&gt;</span>

<span class="c">&lt;!-- Boundary Timer (attached to task — deadline/timeout) --&gt;</span>
<span class="nt">&lt;boundaryEvent</span> <span class="na">id=</span><span class="s">"deadline"</span> <span class="na">attachedToRef=</span><span class="s">"reviewTask"</span> <span class="na">cancelActivity=</span><span class="s">"true"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;timerEventDefinition&gt;</span>
       <span class="nt">&lt;timeDuration&gt;</span>PT48H<span class="nt">&lt;/timeDuration&gt;</span>  <span class="c">&lt;!-- 48 hours timeout --&gt;</span>
   <span class="nt">&lt;/timerEventDefinition&gt;</span>
<span class="nt">&lt;/boundaryEvent&gt;</span>
<span class="c">&lt;!-- Nếu reviewTask không xong trong 48h → cancel task, đi đường khác --&gt;</span>
</code></pre></div></div>

<h4 id="-end-events--kết-thúc-process"># end events — kết thúc process</h4>

<table>
  <thead>
    <tr>
      <th>Event</th>
      <th>Mô tả</th>
      <th>Effect</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>None End</td>
      <td>Process instance kết thúc bình thường</td>
      <td>Terminate path</td>
    </tr>
    <tr>
      <td>Terminate End</td>
      <td>Kết thúc TẤT CẢ paths (parallel) ngay lập tức</td>
      <td>Kill toàn bộ instance</td>
    </tr>
    <tr>
      <td>Error End</td>
      <td>Throw error (catch bởi boundary error event)</td>
      <td>Error propagation</td>
    </tr>
    <tr>
      <td>Message End</td>
      <td>Gửi message khi kết thúc</td>
      <td>Notify external</td>
    </tr>
    <tr>
      <td>Signal End</td>
      <td>Broadcast signal khi kết thúc</td>
      <td>Notify all listeners</td>
    </tr>
  </tbody>
</table>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Normal End --&gt;</span>
<span class="nt">&lt;endEvent</span> <span class="na">id=</span><span class="s">"end"</span> <span class="na">name=</span><span class="s">"Process Complete"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Terminate End: kill tất cả parallel paths --&gt;</span>
<span class="nt">&lt;endEvent</span> <span class="na">id=</span><span class="s">"terminateEnd"</span> <span class="na">name=</span><span class="s">"Abort All"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;terminateEventDefinition/&gt;</span>
<span class="nt">&lt;/endEvent&gt;</span>

<span class="c">&lt;!-- Error End: throw error lên parent process --&gt;</span>
<span class="nt">&lt;endEvent</span> <span class="na">id=</span><span class="s">"errorEnd"</span> <span class="na">name=</span><span class="s">"Payment Failed"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;errorEventDefinition</span> <span class="na">errorRef=</span><span class="s">"paymentError"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/endEvent&gt;</span>
</code></pre></div></div>

<h3 id="-activities--công-việc-được-thực-hiện"># activities — “công việc được thực hiện”</h3>

<p>Nếu Events là “điều gì xảy ra” và Gateways là “đi đường nào”, thì Activities là nơi <strong>công việc thực sự được hoàn thành</strong>. Đây là phần developer tương tác nhiều nhất — vì mỗi Activity có thể map thành code bạn viết.</p>

<p>Activities biểu diễn bằng hình chữ nhật bo góc. Icon ở góc trên-trái cho biết type: người (user task), bánh răng (service task), script (script task)…</p>

<p>Điểm mấu chốt cho developer: <strong>User Task</strong> tạo “wait state” — process dừng lại, chờ con người hoàn thành form/approval. <strong>Service Task</strong> là nơi bạn plug Java code vào — gọi API, tính toán, send notification. Đây là integration point giữa BPMN diagram và Spring Boot services.</p>

<p>Trong hầu hết BPMN projects, mỗi Service Task delegate tới một Spring bean. Business Analyst vẽ diagram, developer implement delegates. Hai bên làm việc parallel mà không block nhau — đó là sức mạnh của BPMN.</p>

<h4 id="-task-types"># task types</h4>

<table>
  <thead>
    <tr>
      <th>Task Type</th>
      <th>Icon</th>
      <th>Mô tả</th>
      <th>Implementation</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>User Task</td>
      <td>👤</td>
      <td>Con người thực hiện (form, approval)</td>
      <td>Assigned to user/group</td>
    </tr>
    <tr>
      <td>Service Task</td>
      <td>⚙️</td>
      <td>Hệ thống tự động thực hiện</td>
      <td>Java class, REST call</td>
    </tr>
    <tr>
      <td>Script Task</td>
      <td>📜</td>
      <td>Execute script (Groovy, JS)</td>
      <td>Inline code</td>
    </tr>
    <tr>
      <td>Send Task</td>
      <td>✉️</td>
      <td>Gửi message</td>
      <td>Message broker, email</td>
    </tr>
    <tr>
      <td>Receive Task</td>
      <td>📨</td>
      <td>Chờ nhận message</td>
      <td>Wait state</td>
    </tr>
    <tr>
      <td>Business Rule Task</td>
      <td>📋</td>
      <td>Execute business rules</td>
      <td>DMN decision table</td>
    </tr>
    <tr>
      <td>Manual Task</td>
      <td>✋</td>
      <td>Task ngoài hệ thống (physical)</td>
      <td>No system interaction</td>
    </tr>
    <tr>
      <td>Call Activity</td>
      <td>↗️</td>
      <td>Gọi process khác (reusable)</td>
      <td>Sub-process reference</td>
    </tr>
  </tbody>
</table>

<h4 id="-user-task--human-interaction"># user task — human interaction</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;userTask</span> <span class="na">id=</span><span class="s">"approveRequest"</span> <span class="na">name=</span><span class="s">"Approve Loan Request"</span>
         <span class="na">activiti:assignee=</span><span class="s">"${applicant}"</span>
         <span class="na">activiti:candidateGroups=</span><span class="s">"loan-approvers"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;documentation&gt;</span>Review loan application and approve/reject<span class="nt">&lt;/documentation&gt;</span>

   <span class="c">&lt;!-- Form properties (hiển thị form cho user) --&gt;</span>
   <span class="nt">&lt;extensionElements&gt;</span>
       <span class="nt">&lt;activiti:formProperty</span> <span class="na">id=</span><span class="s">"decision"</span> <span class="na">name=</span><span class="s">"Decision"</span>
           <span class="na">type=</span><span class="s">"enum"</span> <span class="na">required=</span><span class="s">"true"</span><span class="nt">&gt;</span>
           <span class="nt">&lt;activiti:value</span> <span class="na">id=</span><span class="s">"approve"</span> <span class="na">name=</span><span class="s">"Approve"</span><span class="nt">/&gt;</span>
           <span class="nt">&lt;activiti:value</span> <span class="na">id=</span><span class="s">"reject"</span> <span class="na">name=</span><span class="s">"Reject"</span><span class="nt">/&gt;</span>
       <span class="nt">&lt;/activiti:formProperty&gt;</span>
       <span class="nt">&lt;activiti:formProperty</span> <span class="na">id=</span><span class="s">"comments"</span> <span class="na">name=</span><span class="s">"Comments"</span>
           <span class="na">type=</span><span class="s">"string"</span> <span class="na">required=</span><span class="s">"false"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;/extensionElements&gt;</span>
<span class="nt">&lt;/userTask&gt;</span>
</code></pre></div></div>

<h4 id="-service-task--automated-work"># service task — automated work</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Java Delegate --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"calculateScore"</span> <span class="na">name=</span><span class="s">"Calculate Credit Score"</span>
            <span class="na">activiti:class=</span><span class="s">"com.example.bpm.delegate.CreditScoreDelegate"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Expression (call Spring bean method) --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"sendNotification"</span> <span class="na">name=</span><span class="s">"Send Email"</span>
            <span class="na">activiti:expression=</span><span class="s">"${notificationService.sendEmail(execution)}"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Delegate Expression (Spring bean) --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"processPayment"</span> <span class="na">name=</span><span class="s">"Process Payment"</span>
            <span class="na">activiti:delegateExpression=</span><span class="s">"${paymentDelegate}"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- HTTP Task (REST call) --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"callExternalApi"</span> <span class="na">name=</span><span class="s">"Verify Identity"</span>
            <span class="na">activiti:type=</span><span class="s">"http"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;extensionElements&gt;</span>
       <span class="nt">&lt;activiti:field</span> <span class="na">name=</span><span class="s">"requestMethod"</span> <span class="na">stringValue=</span><span class="s">"POST"</span><span class="nt">/&gt;</span>
       <span class="nt">&lt;activiti:field</span> <span class="na">name=</span><span class="s">"requestUrl"</span>
           <span class="na">stringValue=</span><span class="s">"http://identity-service/api/v1/verify"</span><span class="nt">/&gt;</span>
       <span class="nt">&lt;activiti:field</span> <span class="na">name=</span><span class="s">"requestBody"</span>
           <span class="na">expression=</span><span class="s">"${execution.getVariable('requestPayload')}"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;/extensionElements&gt;</span>
<span class="nt">&lt;/serviceTask&gt;</span>
</code></pre></div></div>

<h4 id="-java-delegate-implementation"># java delegate implementation</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span><span class="s">"creditScoreDelegate"</span><span class="o">)</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CreditScoreDelegate</span> <span class="kd">implements</span> <span class="nc">JavaDelegate</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CreditScoringService</span> <span class="n">scoringService</span><span class="o">;</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">DelegateExecution</span> <span class="n">execution</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">String</span> <span class="n">customerId</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"customerId"</span><span class="o">);</span>
       <span class="nc">BigDecimal</span> <span class="n">loanAmount</span> <span class="o">=</span> <span class="o">(</span><span class="nc">BigDecimal</span><span class="o">)</span> <span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"loanAmount"</span><span class="o">);</span>

       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Calculating credit score | customerId={} | loanAmount={}"</span><span class="o">,</span>
           <span class="n">customerId</span><span class="o">,</span> <span class="n">loanAmount</span><span class="o">);</span>

       <span class="nc">CreditScore</span> <span class="n">score</span> <span class="o">=</span> <span class="n">scoringService</span><span class="o">.</span><span class="na">calculate</span><span class="o">(</span><span class="n">customerId</span><span class="o">,</span> <span class="n">loanAmount</span><span class="o">);</span>

       <span class="c1">// Set process variables cho downstream tasks</span>
       <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"creditScore"</span><span class="o">,</span> <span class="n">score</span><span class="o">.</span><span class="na">getValue</span><span class="o">());</span>
       <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"riskLevel"</span><span class="o">,</span> <span class="n">score</span><span class="o">.</span><span class="na">getRiskLevel</span><span class="o">().</span><span class="na">name</span><span class="o">());</span>
       <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"autoApproved"</span><span class="o">,</span> <span class="n">score</span><span class="o">.</span><span class="na">getValue</span><span class="o">()</span> <span class="o">&gt;=</span> <span class="mi">750</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-sub-process--embedded-process-logic"># sub-process — embedded process logic</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Embedded Sub-Process: nhóm related tasks, shared error handling --&gt;</span>
<span class="nt">&lt;subProcess</span> <span class="na">id=</span><span class="s">"paymentSubProcess"</span> <span class="na">name=</span><span class="s">"Payment Processing"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;startEvent</span> <span class="na">id=</span><span class="s">"payStart"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"validatePayment"</span> <span class="na">name=</span><span class="s">"Validate"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"executePayment"</span> <span class="na">name=</span><span class="s">"Execute"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"confirmPayment"</span> <span class="na">name=</span><span class="s">"Confirm"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;endEvent</span> <span class="na">id=</span><span class="s">"payEnd"</span><span class="nt">/&gt;</span>

   <span class="c">&lt;!-- Sequence flows within sub-process --&gt;</span>
   <span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"payStart"</span> <span class="na">targetRef=</span><span class="s">"validatePayment"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"validatePayment"</span> <span class="na">targetRef=</span><span class="s">"executePayment"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"executePayment"</span> <span class="na">targetRef=</span><span class="s">"confirmPayment"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"confirmPayment"</span> <span class="na">targetRef=</span><span class="s">"payEnd"</span><span class="nt">/&gt;</span>

   <span class="c">&lt;!-- Error boundary on entire sub-process --&gt;</span>
   <span class="nt">&lt;boundaryEvent</span> <span class="na">id=</span><span class="s">"paymentError"</span> <span class="na">attachedToRef=</span><span class="s">"paymentSubProcess"</span><span class="nt">&gt;</span>
       <span class="nt">&lt;errorEventDefinition</span> <span class="na">errorRef=</span><span class="s">"paymentFailedError"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;/boundaryEvent&gt;</span>
<span class="nt">&lt;/subProcess&gt;</span>
</code></pre></div></div>

<h4 id="-multi-instance---loopparallel-execution"># multi-instance - loop/parallel execution</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Sequential: process items one by one --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"processItem"</span> <span class="na">name=</span><span class="s">"Process Item"</span>
            <span class="na">activiti:delegateExpression=</span><span class="s">"${itemProcessor}"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;multiInstanceLoopCharacteristics</span> <span class="na">isSequential=</span><span class="s">"true"</span>
       <span class="na">activiti:collection=</span><span class="s">"${orderItems}"</span>
       <span class="na">activiti:elementVariable=</span><span class="s">"currentItem"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/serviceTask&gt;</span>

<span class="c">&lt;!-- Parallel: process ALL items simultaneously --&gt;</span>
<span class="nt">&lt;userTask</span> <span class="na">id=</span><span class="s">"approveByAll"</span> <span class="na">name=</span><span class="s">"Approve By All Managers"</span>
         <span class="na">activiti:candidateGroups=</span><span class="s">"managers"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;multiInstanceLoopCharacteristics</span> <span class="na">isSequential=</span><span class="s">"false"</span>
       <span class="na">activiti:collection=</span><span class="s">"${approverList}"</span>
       <span class="na">activiti:elementVariable=</span><span class="s">"approver"</span><span class="nt">&gt;</span>
       <span class="c">&lt;!-- Complete when 60% approved --&gt;</span>
       <span class="nt">&lt;completionCondition&gt;</span>${nrOfCompletedInstances / nrOfInstances &gt;= 0.6}<span class="nt">&lt;/completionCondition&gt;</span>
   <span class="nt">&lt;/multiInstanceLoopCharacteristics&gt;</span>
<span class="nt">&lt;/userTask&gt;</span>
</code></pre></div></div>

<h3 id="-gateways---quyết-định--hợp-nhất"># gateways - “quyết định &amp; hợp nhất”</h3>

<p>Gateway là “ngã tư” trong process flow. Khi execution token đến gateway, nó phải quyết định: đi đường nào? Chia ra bao nhiêu đường? Chờ bao nhiêu đường hợp lại mới tiếp tục?</p>

<p>Hình kim cương (◇) là ký hiệu - và icon bên trong cho biết loại:</p>

<ul>
  <li><strong>X</strong> (hoặc trống) = Exclusive (XOR): chọn đúng 1 đường, giống if-else</li>
  <li><strong>+</strong> = Parallel (AND): đi TẤT CẢ đường cùng lúc, chờ TẤT CẢ ở merge</li>
  <li><strong>O</strong> = Inclusive (OR): đi 1 hoặc nhiều đường tùy conditions</li>
</ul>

<p>Sai lầm phổ biến nhất: <strong>quên gateway merge</strong>. Bạn split bằng Parallel Gateway → 3 paths chạy song song → nhưng nếu không có Parallel Gateway ở cuối để merge, process sẽ tạo 3 “token” riêng biệt chạy đến end event, gây duplicate completion events hoặc race conditions.</p>

<p>Một gateway CÓ THỂ vừa split vừa merge (mixed gateway), nhưng best practice là tách rõ: 1 gateway cho fork, 1 gateway cho join — dễ đọc và debug hơn nhiều.</p>

<h4 id="-exclusive-gateway-xor--chọn-1-trong-n-paths"># exclusive gateway (xor) — chọn 1 trong n paths</h4>

<p>Giống if-else: chỉ 1 path được chọn dựa trên condition.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;exclusiveGateway</span> <span class="na">id=</span><span class="s">"decisionGateway"</span> <span class="na">name=</span><span class="s">"Approve or Reject?"</span><span class="nt">/&gt;</span>

<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"decisionGateway"</span> <span class="na">targetRef=</span><span class="s">"approvedPath"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;conditionExpression</span> <span class="na">xsi:type=</span><span class="s">"tFormalExpression"</span><span class="nt">&gt;</span>
       ${decision == 'approve'}
   <span class="nt">&lt;/conditionExpression&gt;</span>
<span class="nt">&lt;/sequenceFlow&gt;</span>

<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"decisionGateway"</span> <span class="na">targetRef=</span><span class="s">"rejectedPath"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;conditionExpression</span> <span class="na">xsi:type=</span><span class="s">"tFormalExpression"</span><span class="nt">&gt;</span>
       ${decision == 'reject'}
   <span class="nt">&lt;/conditionExpression&gt;</span>
<span class="nt">&lt;/sequenceFlow&gt;</span>

<span class="c">&lt;!-- Default flow (khi không condition nào match) --&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"decisionGateway"</span> <span class="na">targetRef=</span><span class="s">"manualReview"</span>
             <span class="na">id=</span><span class="s">"defaultFlow"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;exclusiveGateway</span> <span class="na">id=</span><span class="s">"decisionGateway"</span> <span class="na">default=</span><span class="s">"defaultFlow"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<h4 id="-parallel-gateway-and--chạy-tất-cả-paths-đồng-thời"># parallel gateway (and) — chạy tất cả paths đồng thời</h4>

<p>Fork: tạo parallel execution. Join: chờ TẤT CẢ paths hoàn thành.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Fork: split into parallel paths --&gt;</span>
<span class="nt">&lt;parallelGateway</span> <span class="na">id=</span><span class="s">"forkGateway"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"forkGateway"</span> <span class="na">targetRef=</span><span class="s">"sendEmail"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"forkGateway"</span> <span class="na">targetRef=</span><span class="s">"updateInventory"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"forkGateway"</span> <span class="na">targetRef=</span><span class="s">"generateInvoice"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Tất cả 3 tasks chạy ĐỒNG THỜI --&gt;</span>

<span class="c">&lt;!-- Join: wait for ALL to complete --&gt;</span>
<span class="nt">&lt;parallelGateway</span> <span class="na">id=</span><span class="s">"joinGateway"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"sendEmail"</span> <span class="na">targetRef=</span><span class="s">"joinGateway"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"updateInventory"</span> <span class="na">targetRef=</span><span class="s">"joinGateway"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"generateInvoice"</span> <span class="na">targetRef=</span><span class="s">"joinGateway"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Chỉ tiếp tục khi cả 3 xong --&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"joinGateway"</span> <span class="na">targetRef=</span><span class="s">"completeOrder"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<h4 id="-inclusive-gateway-or--chạy-1-hoặc-nhiều-paths"># inclusive gateway (or) — chạy 1 hoặc nhiều paths</h4>

<p>Linh hoạt hơn XOR (chỉ 1) và AND (tất cả). Chạy mọi paths có condition = true.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;inclusiveGateway</span> <span class="na">id=</span><span class="s">"notifyGateway"</span> <span class="na">name=</span><span class="s">"Notification Channels"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Chạy tất cả paths có condition true --&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"notifyGateway"</span> <span class="na">targetRef=</span><span class="s">"sendSms"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;conditionExpression&gt;</span>${customer.phoneVerified == true}<span class="nt">&lt;/conditionExpression&gt;</span>
<span class="nt">&lt;/sequenceFlow&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"notifyGateway"</span> <span class="na">targetRef=</span><span class="s">"sendEmail"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;conditionExpression&gt;</span>${customer.email != null}<span class="nt">&lt;/conditionExpression&gt;</span>
<span class="nt">&lt;/sequenceFlow&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"notifyGateway"</span> <span class="na">targetRef=</span><span class="s">"pushNotification"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;conditionExpression&gt;</span>${customer.deviceToken != null}<span class="nt">&lt;/conditionExpression&gt;</span>
<span class="nt">&lt;/sequenceFlow&gt;</span>

<span class="c">&lt;!-- Merge: chờ tất cả activated paths hoàn thành --&gt;</span>
<span class="nt">&lt;inclusiveGateway</span> <span class="na">id=</span><span class="s">"notifyJoin"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<h4 id="-event-based-gateway--chờ-event-nào-đến-trước"># event-based gateway — chờ event nào đến trước</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Chờ: hoặc customer respond, hoặc timeout 7 ngày --&gt;</span>
<span class="nt">&lt;eventBasedGateway</span> <span class="na">id=</span><span class="s">"waitGateway"</span><span class="nt">/&gt;</span>

<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"waitGateway"</span> <span class="na">targetRef=</span><span class="s">"customerResponse"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;intermediateCatchEvent</span> <span class="na">id=</span><span class="s">"customerResponse"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;messageEventDefinition</span> <span class="na">messageRef=</span><span class="s">"responseMessage"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/intermediateCatchEvent&gt;</span>

<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"waitGateway"</span> <span class="na">targetRef=</span><span class="s">"timeout"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;intermediateCatchEvent</span> <span class="na">id=</span><span class="s">"timeout"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;timerEventDefinition&gt;</span>
       <span class="nt">&lt;timeDuration&gt;</span>P7D<span class="nt">&lt;/timeDuration&gt;</span>
   <span class="nt">&lt;/timerEventDefinition&gt;</span>
<span class="nt">&lt;/intermediateCatchEvent&gt;</span>
<span class="c">&lt;!-- Event nào đến TRƯỚC → đi path đó, cancel paths còn lại --&gt;</span>
</code></pre></div></div>

<h3 id="-process-variables--expressions"># process variables &amp; expressions</h3>

<p>Process variables là “bộ nhớ” của process instance. Mỗi instance có riêng set variables — giống HTTP session nhưng cho workflow. Variables truyền data giữa tasks: task A set <code class="language-plaintext highlighter-rouge">creditScore = 750</code>, gateway B đọc <code class="language-plaintext highlighter-rouge">${creditScore &gt;= 700}</code> để quyết định approve hay reject.</p>

<p>Điều developer cần nhớ: variables được <strong>serialized và persist vào database</strong>. Mỗi lần set variable = 1 row INSERT/UPDATE. Đừng store large objects (file content, huge lists) — chỉ store IDs, references, summary data. Large data nên lưu ở document store (MongoDB) và chỉ truyền ID qua process variable.</p>

<p>Variable scope cũng quan trọng: variable set ở parent process có thể đọc ở sub-process, nhưng variable set trong sub-process mặc định không visible ở parent (trừ khi map explicitly). Hiểu scope giúp tránh bugs “variable not found” khó debug.</p>

<p>Expression language trong Activiti là <strong>UEL (Unified Expression Language)</strong> — syntax giống JSP EL. Bạn có thể gọi Spring bean methods trực tiếp: <code class="language-plaintext highlighter-rouge">${orderService.calculateTotal(orderId)}</code>. Engine resolve bean name từ Spring ApplicationContext.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Set variable khi start process</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">variables</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>
<span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"applicantName"</span><span class="o">,</span> <span class="s">"Nguyen Van A"</span><span class="o">);</span>
<span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"loanAmount"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"500000000"</span><span class="o">));</span>
<span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"applicationDate"</span><span class="o">,</span> <span class="nc">LocalDate</span><span class="o">.</span><span class="na">now</span><span class="o">());</span>
<span class="n">variables</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"documents"</span><span class="o">,</span> <span class="n">documentList</span><span class="o">);</span>

<span class="nc">ProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">startProcessInstanceByKey</span><span class="o">(</span>
   <span class="s">"loan-approval"</span><span class="o">,</span> <span class="n">businessKey</span><span class="o">,</span> <span class="n">variables</span><span class="o">);</span>

<span class="c1">// Read/write trong Java Delegate</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">DelegateExecution</span> <span class="n">execution</span><span class="o">)</span> <span class="o">{</span>
   <span class="nc">BigDecimal</span> <span class="n">amount</span> <span class="o">=</span> <span class="o">(</span><span class="nc">BigDecimal</span><span class="o">)</span> <span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"loanAmount"</span><span class="o">);</span>
   <span class="n">execution</span><span class="o">.</span><span class="na">setVariable</span><span class="o">(</span><span class="s">"interestRate"</span><span class="o">,</span> <span class="n">calculateRate</span><span class="o">(</span><span class="n">amount</span><span class="o">));</span>

   <span class="c1">// Transient variable (không persist, chỉ tồn tại trong memory)</span>
   <span class="n">execution</span><span class="o">.</span><span class="na">setTransientVariable</span><span class="o">(</span><span class="s">"tempCalculation"</span><span class="o">,</span> <span class="n">intermediateResult</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// Expression trong BPMN XML</span>
<span class="c1">// UEL (Unified Expression Language)</span>
<span class="err">$</span><span class="o">{</span><span class="n">applicantName</span><span class="o">}</span>                          <span class="c1">// Read variable</span>
<span class="err">$</span><span class="o">{</span><span class="n">loanAmount</span> <span class="o">&gt;</span> <span class="mi">1000000000</span><span class="o">}</span>                <span class="c1">// Condition</span>
<span class="err">$</span><span class="o">{</span><span class="n">riskLevel</span> <span class="o">==</span> <span class="err">'</span><span class="no">HIGH</span><span class="err">'</span> <span class="o">?</span> <span class="err">'</span><span class="n">manual</span><span class="err">'</span> <span class="o">:</span> <span class="err">'</span><span class="n">auto</span><span class="err">'</span><span class="o">}</span> <span class="c1">// Ternary</span>
<span class="err">$</span><span class="o">{</span><span class="n">orderService</span><span class="o">.</span><span class="na">calculateTotal</span><span class="o">(</span><span class="n">orderId</span><span class="o">)}</span>    <span class="c1">// Call Spring bean method</span>
<span class="err">#</span><span class="o">{</span><span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="err">'</span><span class="n">status</span><span class="err">'</span><span class="o">)}</span>         <span class="c1">// Alternative syntax</span>
</code></pre></div></div>

<h3 id="-listeners--lifecycle-hooks"># listeners &amp; lifecycle hooks</h3>

<p>Listeners là “event hooks” của BPMN engine — giống <code class="language-plaintext highlighter-rouge">@EventListener</code> trong Spring nhưng cho process lifecycle. Chúng cho phép bạn chạy code tại các điểm cụ thể: khi task được tạo, khi task complete, khi process start/end, khi execution đi qua một sequence flow.</p>

<p>Tại sao cần listeners thay vì đặt logic trong delegates? Vì listeners là <strong>cross-cutting concerns</strong>: audit logging, metrics collection, notification dispatch, SLA monitoring. Bạn không muốn mỗi delegate phải gọi <code class="language-plaintext highlighter-rouge">auditService.log()</code> — thay vào đó, 1 listener lắng nghe tất cả tasks và log tự động.</p>

<p>Có 2 loại chính:</p>

<ul>
  <li><strong>Execution Listener</strong>: hook vào flow execution (start/end của process, activity, sequence flow)</li>
  <li><strong>Task Listener</strong>: hook vào user task lifecycle (create, assign, complete, delete)</li>
</ul>

<p>Trong production systems, listeners thường dùng cho:</p>

<ul>
  <li>Gửi notification khi task assign cho user</li>
  <li>Log audit trail cho compliance</li>
  <li>Update dashboard/analytics real-time</li>
  <li>Set deadline/SLA timer</li>
  <li>Auto-assign task dựa trên workload balancing</li>
</ul>

<h4 id="-execution-listeners--hook-vào-process-flow"># execution listeners — hook vào process flow</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;userTask</span> <span class="na">id=</span><span class="s">"approveTask"</span> <span class="na">name=</span><span class="s">"Approve"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;extensionElements&gt;</span>
       <span class="c">&lt;!-- Chạy khi task START --&gt;</span>
       <span class="nt">&lt;activiti:executionListener</span> <span class="na">event=</span><span class="s">"start"</span>
           <span class="na">delegateExpression=</span><span class="s">"${taskAuditListener}"</span><span class="nt">/&gt;</span>
       <span class="c">&lt;!-- Chạy khi task END --&gt;</span>
       <span class="nt">&lt;activiti:executionListener</span> <span class="na">event=</span><span class="s">"end"</span>
           <span class="na">expression=</span><span class="s">"${metricsService.recordTaskCompletion(execution)}"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;/extensionElements&gt;</span>
<span class="nt">&lt;/userTask&gt;</span>

<span class="c">&lt;!-- Process-level listener --&gt;</span>
<span class="nt">&lt;process</span> <span class="na">id=</span><span class="s">"loanProcess"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;extensionElements&gt;</span>
       <span class="nt">&lt;activiti:executionListener</span> <span class="na">event=</span><span class="s">"start"</span>
           <span class="na">delegateExpression=</span><span class="s">"${processStartListener}"</span><span class="nt">/&gt;</span>
       <span class="nt">&lt;activiti:executionListener</span> <span class="na">event=</span><span class="s">"end"</span>
           <span class="na">delegateExpression=</span><span class="s">"${processEndListener}"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;/extensionElements&gt;</span>
<span class="nt">&lt;/process&gt;</span>
</code></pre></div></div>

<h4 id="-task-listeners--specific-to-user-tasks"># task listeners — specific to user tasks</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;userTask</span> <span class="na">id=</span><span class="s">"reviewTask"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;extensionElements&gt;</span>
       <span class="c">&lt;!-- Khi task được CREATE (assigned to someone) --&gt;</span>
       <span class="nt">&lt;activiti:taskListener</span> <span class="na">event=</span><span class="s">"create"</span>
           <span class="na">delegateExpression=</span><span class="s">"${taskAssignmentListener}"</span><span class="nt">/&gt;</span>
       <span class="c">&lt;!-- Khi task COMPLETE --&gt;</span>
       <span class="nt">&lt;activiti:taskListener</span> <span class="na">event=</span><span class="s">"complete"</span>
           <span class="na">expression=</span><span class="s">"${notificationService.notifyCompletion(task)}"</span><span class="nt">/&gt;</span>
       <span class="c">&lt;!-- Khi task bị DELETE (cancelled) --&gt;</span>
       <span class="nt">&lt;activiti:taskListener</span> <span class="na">event=</span><span class="s">"delete"</span>
           <span class="na">delegateExpression=</span><span class="s">"${taskCancellationListener}"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;/extensionElements&gt;</span>
<span class="nt">&lt;/userTask&gt;</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span><span class="s">"taskAssignmentListener"</span><span class="o">)</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TaskAssignmentListener</span> <span class="kd">implements</span> <span class="nc">TaskListener</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">NotificationService</span> <span class="n">notificationService</span><span class="o">;</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">notify</span><span class="o">(</span><span class="nc">DelegateTask</span> <span class="n">task</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">String</span> <span class="n">assignee</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">getAssignee</span><span class="o">();</span>
       <span class="nc">String</span> <span class="n">taskName</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span>
       <span class="nc">String</span> <span class="n">processId</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">getProcessInstanceId</span><span class="o">();</span>

       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Task assigned | task={} | assignee={} | processId={}"</span><span class="o">,</span>
           <span class="n">taskName</span><span class="o">,</span> <span class="n">assignee</span><span class="o">,</span> <span class="n">processId</span><span class="o">);</span>

       <span class="n">notificationService</span><span class="o">.</span><span class="na">sendTaskNotification</span><span class="o">(</span><span class="n">assignee</span><span class="o">,</span>
           <span class="s">"New task assigned: "</span> <span class="o">+</span> <span class="n">taskName</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-error-handling-in-bpmn"># error handling in bpmn</h3>

<p>Đây là phần nhiều team làm sai — hoặc không handle errors trong BPMN (let it crash), hoặc handle sai cách (try-catch trong delegate rồi swallow exception). BPMN có error handling mechanism riêng, và nó rất powerful khi dùng đúng.</p>

<p>Concept chính: <strong>Boundary Error Event</strong>. Bạn “gắn” error event lên task — nếu task throw <code class="language-plaintext highlighter-rouge">BpmnError</code>, execution token rẽ sang error path thay vì tiếp tục normal flow. Giống try-catch nhưng ở level process diagram — BA nhìn thấy error handling logic trực quan.</p>

<p>Quan trọng: chỉ <code class="language-plaintext highlighter-rouge">BpmnError</code> (throw từ Java Delegate) được catch bởi boundary event. Các exceptions khác (<code class="language-plaintext highlighter-rouge">NullPointerException</code>, <code class="language-plaintext highlighter-rouge">RuntimeException</code>…) KHÔNG bị catch — chúng fail task và cần retry/manual intervention. Đây là design choice: <code class="language-plaintext highlighter-rouge">BpmnError</code> = “business error tôi dự đoán được”, other exceptions = “bug cần fix.”</p>

<p><strong>Compensation</strong> (undo pattern) là advanced feature cho distributed operations. Khi process gồm nhiều steps đã complete (reserve inventory → charge payment → book shipping), nếu step cuối fail, bạn cần undo các steps trước. BPMN compensation = SAGA pattern — mỗi task có 1 compensation handler để rollback.</p>

<h4 id="-boundary-error-event--catch-errors-từ-task"># boundary error event — catch errors từ task</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"callPayment"</span> <span class="na">name=</span><span class="s">"Process Payment"</span>
            <span class="na">activiti:delegateExpression=</span><span class="s">"${paymentDelegate}"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Boundary error event: catch payment failure --&gt;</span>
<span class="nt">&lt;boundaryEvent</span> <span class="na">id=</span><span class="s">"paymentFailed"</span> <span class="na">attachedToRef=</span><span class="s">"callPayment"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;errorEventDefinition</span> <span class="na">errorRef=</span><span class="s">"PAYMENT_FAILED"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/boundaryEvent&gt;</span>

<span class="c">&lt;!-- Khi paymentDelegate throw BpmnError("PAYMENT_FAILED") --&gt;</span>
<span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"paymentFailed"</span> <span class="na">targetRef=</span><span class="s">"handlePaymentFailure"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"handlePaymentFailure"</span> <span class="na">name=</span><span class="s">"Handle Payment Failure"</span>
            <span class="na">activiti:delegateExpression=</span><span class="s">"${paymentFailureHandler}"</span><span class="nt">/&gt;</span>
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span><span class="s">"paymentDelegate"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PaymentDelegate</span> <span class="kd">implements</span> <span class="nc">JavaDelegate</span> <span class="o">{</span>
   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">DelegateExecution</span> <span class="n">execution</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">try</span> <span class="o">{</span>
           <span class="n">paymentService</span><span class="o">.</span><span class="na">process</span><span class="o">(</span><span class="n">execution</span><span class="o">.</span><span class="na">getVariable</span><span class="o">(</span><span class="s">"paymentId"</span><span class="o">));</span>
       <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">PaymentDeclinedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
           <span class="c1">// Throw BpmnError → caught by boundary error event</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">BpmnError</span><span class="o">(</span><span class="s">"PAYMENT_FAILED"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
       <span class="o">}</span>
       <span class="c1">// Các exceptions KHÔNG phải BpmnError → escalate lên process level</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-compensation--undo-completed-work-saga-pattern"># compensation — undo completed work (saga pattern)</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Task đã complete thành công --&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"reserveInventory"</span> <span class="na">name=</span><span class="s">"Reserve Inventory"</span>
            <span class="na">activiti:delegateExpression=</span><span class="s">"${inventoryReserveDelegate}"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Compensation handler: undo reservation nếu process cần rollback --&gt;</span>
<span class="nt">&lt;boundaryEvent</span> <span class="na">id=</span><span class="s">"compensateReserve"</span> <span class="na">attachedToRef=</span><span class="s">"reserveInventory"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;compensateEventDefinition/&gt;</span>
<span class="nt">&lt;/boundaryEvent&gt;</span>
<span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"cancelReservation"</span> <span class="na">name=</span><span class="s">"Cancel Reservation"</span>
            <span class="na">isForCompensation=</span><span class="s">"true"</span>
            <span class="na">activiti:delegateExpression=</span><span class="s">"${inventoryReleaseDelegate}"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;association</span> <span class="na">sourceRef=</span><span class="s">"compensateReserve"</span> <span class="na">targetRef=</span><span class="s">"cancelReservation"</span><span class="nt">/&gt;</span>

<span class="c">&lt;!-- Trigger compensation khi cần undo --&gt;</span>
<span class="nt">&lt;intermediateThrowEvent</span> <span class="na">id=</span><span class="s">"undoAll"</span> <span class="na">name=</span><span class="s">"Compensate All"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;compensateEventDefinition/&gt;</span>
<span class="nt">&lt;/intermediateThrowEvent&gt;</span>
</code></pre></div></div>

<h3 id="-activiti-integration-trong-spring-boot"># activiti integration trong spring boot</h3>

<p>Đây là phần kết nối lý thuyết BPMN với thực tế code. Activiti là BPMN engine chạy trên Spring Boot — nó parse BPMN XML, tạo database schema để track process state, và execute flow logic.</p>

<p>Khi bạn deploy 1 BPMN file, Activiti lưu process definition vào database. Khi start process instance, engine tạo “execution token” chạy qua diagram — token dừng ở wait states (user tasks, message catch events), tiến khi conditions met. Mỗi token có riêng variables và state, persist qua server restart.</p>

<p>Trong kiến trúc microservices, thường có 1 dedicated service host Activiti engine. Các services khác gọi API để deploy definitions, start instances, complete tasks. Engine trigger Service Tasks bằng cách gọi Java Delegates — những Spring beans bạn viết.</p>

<p>Key services cần biết:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">RepositoryService</code>: Deploy, query process definitions</li>
  <li><code class="language-plaintext highlighter-rouge">RuntimeService</code>: Start instances, signal events, set variables</li>
  <li><code class="language-plaintext highlighter-rouge">TaskService</code>: Query/complete user tasks, claim/delegate</li>
  <li><code class="language-plaintext highlighter-rouge">HistoryService</code>: Query completed instances, audit trail</li>
  <li><code class="language-plaintext highlighter-rouge">ManagementService</code>: Jobs, deadletter jobs, engine config</li>
</ul>

<h4 id="-process-engine-configuration"># process engine configuration</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ActivitiConfig</span> <span class="o">{</span>

   <span class="nd">@Bean</span>
   <span class="kd">public</span> <span class="nc">SpringProcessEngineConfiguration</span> <span class="nf">processEngineConfiguration</span><span class="o">(</span>
           <span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">,</span>
           <span class="nc">PlatformTransactionManager</span> <span class="n">transactionManager</span><span class="o">)</span> <span class="o">{</span>

       <span class="nc">SpringProcessEngineConfiguration</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SpringProcessEngineConfiguration</span><span class="o">();</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setDataSource</span><span class="o">(</span><span class="n">dataSource</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setTransactionManager</span><span class="o">(</span><span class="n">transactionManager</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setDatabaseSchemaUpdate</span><span class="o">(</span><span class="s">"true"</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setAsyncExecutorActivate</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
       <span class="n">config</span><span class="o">.</span><span class="na">setHistoryLevel</span><span class="o">(</span><span class="nc">HistoryLevel</span><span class="o">.</span><span class="na">FULL</span><span class="o">);</span>
       <span class="k">return</span> <span class="n">config</span><span class="o">;</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-deploy--execute-process"># deploy &amp; execute process</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProcessService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RepositoryService</span> <span class="n">repositoryService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RuntimeService</span> <span class="n">runtimeService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">TaskService</span> <span class="n">taskService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">HistoryService</span> <span class="n">historyService</span><span class="o">;</span>

   <span class="c1">// Deploy process definition</span>
   <span class="kd">public</span> <span class="nc">String</span> <span class="nf">deploy</span><span class="o">(</span><span class="nc">String</span> <span class="n">bpmnXml</span><span class="o">,</span> <span class="nc">String</span> <span class="n">processName</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Deployment</span> <span class="n">deployment</span> <span class="o">=</span> <span class="n">repositoryService</span><span class="o">.</span><span class="na">createDeployment</span><span class="o">()</span>
           <span class="o">.</span><span class="na">name</span><span class="o">(</span><span class="n">processName</span><span class="o">)</span>
           <span class="o">.</span><span class="na">addString</span><span class="o">(</span><span class="n">processName</span> <span class="o">+</span> <span class="s">".bpmn20.xml"</span><span class="o">,</span> <span class="n">bpmnXml</span><span class="o">)</span>
           <span class="o">.</span><span class="na">deploy</span><span class="o">();</span>
       <span class="k">return</span> <span class="n">deployment</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="c1">// Start process instance</span>
   <span class="kd">public</span> <span class="nc">String</span> <span class="nf">startProcess</span><span class="o">(</span><span class="nc">String</span> <span class="n">processKey</span><span class="o">,</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">variables</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">ProcessInstance</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">runtimeService</span><span class="o">.</span><span class="na">startProcessInstanceByKey</span><span class="o">(</span>
           <span class="n">processKey</span><span class="o">,</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">(),</span> <span class="n">variables</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Process started | key={} | instanceId={}"</span><span class="o">,</span> <span class="n">processKey</span><span class="o">,</span> <span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
       <span class="k">return</span> <span class="n">instance</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="c1">// Complete user task</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">completeTask</span><span class="o">(</span><span class="nc">String</span> <span class="n">taskId</span><span class="o">,</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">variables</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">taskService</span><span class="o">.</span><span class="na">complete</span><span class="o">(</span><span class="n">taskId</span><span class="o">,</span> <span class="n">variables</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="c1">// Query active tasks for user</span>
   <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">TaskDTO</span><span class="o">&gt;</span> <span class="nf">getMyTasks</span><span class="o">(</span><span class="nc">String</span> <span class="n">userId</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">taskService</span><span class="o">.</span><span class="na">createTaskQuery</span><span class="o">()</span>
           <span class="o">.</span><span class="na">taskAssignee</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orderByTaskCreateTime</span><span class="o">().</span><span class="na">desc</span><span class="o">()</span>
           <span class="o">.</span><span class="na">list</span><span class="o">()</span>
           <span class="o">.</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">)</span>
           <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-best-practices"># best practices</h3>

<p>Sau nhiều năm làm việc với BPMN engines trong production, đây là những lessons learned đau thương nhất:</p>

<p><strong>Về thiết kế process:</strong></p>

<ol>
  <li>
    <p><strong>Keep processes simple</strong> — Nếu process diagram quá phức tạp (&gt;30 elements), tách thành sub-processes hoặc call activities. Rule of thumb: nếu diagram không fit 1 màn hình, nó quá phức tạp. BA và developer đều cần đọc được trong 30 giây.</p>
  </li>
  <li>
    <p><strong>Happy path trước, exceptions sau</strong> — Vẽ main flow trước cho nó chạy. Sau đó mới thêm error boundaries, timeouts, compensation. Đừng cố handle mọi edge case lúc ban đầu.</p>
  </li>
  <li>
    <p><strong>Version process definitions</strong> — Mỗi lần deploy = version mới. Running instances giữ version cũ và chạy đến khi complete. Không bao giờ update definition đang có instances running — tạo version mới.</p>
  </li>
</ol>

<p><strong>Về implementation:</strong></p>

<ol>
  <li>
    <p><strong>Service tasks phải idempotent</strong> — Process engine có thể retry failed tasks (especially với async executor). Nếu <code class="language-plaintext highlighter-rouge">chargePayment()</code> không idempotent, customer bị charge 2 lần. Dùng idempotency keys.</p>
  </li>
  <li>
    <p><strong>Boundary timer trên MỌI user task</strong> — Không có task nào nên wait forever. 48h không approve → escalate lên manager. 7 ngày không response → auto-cancel. Đây là SLA enforcement.</p>
  </li>
  <li>
    <p><strong>Async cho heavy service tasks</strong> — <code class="language-plaintext highlighter-rouge">activiti:async="true"</code> cho tasks nặng (call external API, process files). Nếu không, 1 slow task block toàn bộ execution thread pool → ảnh hưởng tất cả process instances.</p>
  </li>
  <li>
    <p><strong>Variables nhẹ, reference nặng</strong> — Không store 10MB file content trong process variable. Store file ID, fetch khi cần. Variables persist vào RDBMS — bloat chúng = slow queries toàn engine.</p>
  </li>
</ol>

<p><strong>Về operations:</strong></p>

<ol>
  <li>
    <p><strong>Monitor stuck instances</strong> — Dashboard alert cho: instances running &gt; X days, tasks unassigned &gt; 24h, failed jobs in deadletter queue. Đây là silent failures — không ai biết trừ khi bạn monitor.</p>
  </li>
  <li>
    <p><strong>Test processes end-to-end</strong> — Unit test từng delegate, integration test toàn process flow. Activiti cung cấp <code class="language-plaintext highlighter-rouge">ActivitiRule</code> cho testing — deploy process, start instance, assert state at each step.</p>
  </li>
  <li><strong>Compensation cho distributed ops</strong> — Khi process span nhiều services (reserve inventory + charge payment + ship order), dùng SAGA pattern thay vì distributed transactions. BPMN compensation events = native SAGA support.</li>
  <li><strong>Monitor execution</strong> — Track duration, stuck tasks, error rates per process definition</li>
  <li><strong>Compensation cho distributed operations</strong> — Saga pattern thay vì distributed transactions</li>
</ol>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="bpmn" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[# giới thiệu]]></summary></entry><entry><title type="html">clean architecture</title><link href="https://cristorvalds.netlify.app/clean-architecture/" rel="alternate" type="text/html" title="clean architecture" /><published>2026-06-06T19:29:39+07:00</published><updated>2026-06-06T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/clean-architecture</id><content type="html" xml:base="https://cristorvalds.netlify.app/clean-architecture/"><![CDATA[<h3 id="-vấn-đề-clean-architecture-giải-quyết"># vấn đề clean architecture giải quyết</h3>

<p>Bạn đã bao giờ gặp codebase mà:</p>

<ul>
  <li>Đổi database từ PostgreSQL sang MongoDB = rewrite 60% code?</li>
  <li>Viết unit test cho business logic phải mock 15 dependencies?</li>
  <li>Service class 2000 dòng vì mix business rules, HTTP calls, database queries, validation?</li>
  <li>Không thể test offline vì code depend trực tiếp vào external APIs?</li>
</ul>

<p>Root cause: <strong>business logic bị coupling với infrastructure details</strong>. Service biết nó dùng PostgreSQL. Controller biết format response. Use case biết cách gọi REST API. Khi infrastructure thay đổi, business logic phải thay đổi theo.</p>

<p>Clean Architecture (Robert C. Martin, 2012) giải quyết bằng <strong>Dependency Rule</strong>: source code dependencies chỉ point inward. Inner layers (business rules) KHÔNG biết outer layers (frameworks, databases, UI) tồn tại.</p>

<h3 id="-the-dependency-rule--quy-tắc-duy-nhất"># the dependency rule — quy tắc duy nhất</h3>

<p><img src="/assets/img/blog/clean-architecture-rule.png" alt="clean-architecture-rule" /></p>

<p>4 layers từ trong ra ngoài:</p>

<table>
  <thead>
    <tr>
      <th>Layer</th>
      <th>Chứa gì</th>
      <th>Ví dụ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Entities</td>
      <td>Core business objects + rules</td>
      <td>Order, Money, CreditScore</td>
    </tr>
    <tr>
      <td>Use Cases</td>
      <td>Application-specific business rules</td>
      <td>CreateOrderUseCase, ApproveL oanUseCase</td>
    </tr>
    <tr>
      <td>Interface Adapters</td>
      <td>Convert data format giữa layers</td>
      <td>Controllers, Presenters, Gateways</td>
    </tr>
    <tr>
      <td>Frameworks &amp; Drivers</td>
      <td>External tools, libs, infra</td>
      <td>Spring Boot, PostgreSQL, RabbitMQ</td>
    </tr>
  </tbody>
</table>

<p><strong>Key insight</strong>: Entities không import Use Case classes. Use Cases không import Controller classes. Controller không import Spring-specific gì vào Use Case. Interfaces (ports) ở inner layer, implementations (adapters) ở outer layer.</p>

<h3 id="-ports--adapters-hexagonal-architecture"># ports &amp; adapters (hexagonal architecture)</h3>

<p>Clean Architecture và Hexagonal Architecture (Alistair Cockburn) là cùng 1 idea, khác cách trình bày. Hexagonal dùng metaphor “ports” (interfaces inner layer định nghĩa) và “adapters” (implementations outer layer cung cấp).</p>

<p><img src="/assets/img/blog/hexagonal-architecture.png" alt="hexagonal-architecture" /></p>

<p><strong>Inbound Ports</strong> (driving): Use Case interfaces — ai muốn “dùng” application phải qua đây.
<strong>Outbound Ports</strong> (driven): Repository/Gateway interfaces — application cần gì từ bên ngoài khai báo ở đây.
<strong>Adapters</strong>: Implementations cụ thể (REST controller, JPA repository, SMTP email sender).</p>

<h3 id="-implementation-trong-spring-boot"># implementation trong spring boot</h3>

<h4 id="-package-structure"># package structure</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>com.example.order/
├── domain/                          # ← INNER: Zero Spring dependencies
│   ├── model/
│   │   ├── Order.java              # Entity (POJO, business rules bên trong)
│   │   ├── OrderItem.java
│   │   ├── Money.java              # Value Object
│   │   └── OrderStatus.java
│   ├── port/
│   │   ├── in/                     # Inbound Ports (Use Case interfaces)
│   │   │   ├── CreateOrderUseCase.java
│   │   │   ├── GetOrderUseCase.java
│   │   │   └── CancelOrderUseCase.java
│   │   └── out/                    # Outbound Ports (driven interfaces)
│   │       ├── OrderRepository.java       # Persistence port
│   │       ├── PaymentGateway.java        # External service port
│   │       ├── InventoryGateway.java
│   │       └── NotificationPort.java
│   └── service/                    # Use Case implementations
│       ├── CreateOrderService.java
│       ├── GetOrderService.java
│       └── CancelOrderService.java
│
├── adapter/                         # ← OUTER: Spring, JPA, HTTP dependencies OK
│   ├── in/                         # Inbound Adapters (driving)
│   │   ├── web/
│   │   │   ├── OrderController.java       # REST adapter
│   │   │   ├── CreateOrderRequest.java    # Web-specific DTO
│   │   │   └── OrderResponse.java
│   │   └── messaging/
│   │       └── OrderEventListener.java    # RabbitMQ adapter
│   └── out/                        # Outbound Adapters (driven)
│       ├── persistence/
│       │   ├── OrderJpaEntity.java        # JPA entity (≠ domain Order)
│       │   ├── OrderJpaRepository.java    # Spring Data interface
│       │   └── OrderPersistenceAdapter.java  # Implements domain's OrderRepository
│       ├── payment/
│       │   └── StripePaymentAdapter.java  # Implements PaymentGateway
│       └── notification/
│           └── SmtpNotificationAdapter.java
│
└── config/                          # Spring wiring
   └── BeanConfig.java
</code></pre></div></div>

<h4 id="-domain-layer--zero-framework-dependencies"># domain layer — zero framework dependencies</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Domain Entity — pure Java, NO Spring annotations, NO JPA annotations</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Order</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderId</span> <span class="n">id</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CustomerId</span> <span class="n">customerId</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderItem</span><span class="o">&gt;</span> <span class="n">items</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">OrderStatus</span> <span class="n">status</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">Money</span> <span class="n">totalAmount</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">LocalDateTime</span> <span class="n">createdAt</span><span class="o">;</span>

   <span class="c1">// Business rules LIVE HERE</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">confirm</span><span class="o">()</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">status</span> <span class="o">!=</span> <span class="nc">OrderStatus</span><span class="o">.</span><span class="na">PENDING</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalOrderStateException</span><span class="o">(</span>
               <span class="s">"Cannot confirm order in status: "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">status</span><span class="o">);</span>
       <span class="o">}</span>
       <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">items</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">EmptyOrderException</span><span class="o">(</span><span class="s">"Order must have at least 1 item"</span><span class="o">);</span>
       <span class="o">}</span>
       <span class="k">this</span><span class="o">.</span><span class="na">status</span> <span class="o">=</span> <span class="nc">OrderStatus</span><span class="o">.</span><span class="na">CONFIRMED</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">cancel</span><span class="o">(</span><span class="nc">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">status</span> <span class="o">==</span> <span class="nc">OrderStatus</span><span class="o">.</span><span class="na">SHIPPED</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalOrderStateException</span><span class="o">(</span><span class="s">"Cannot cancel shipped order"</span><span class="o">);</span>
       <span class="o">}</span>
       <span class="k">this</span><span class="o">.</span><span class="na">status</span> <span class="o">=</span> <span class="nc">OrderStatus</span><span class="o">.</span><span class="na">CANCELLED</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="nc">Money</span> <span class="nf">calculateTotal</span><span class="o">()</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">items</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">item</span> <span class="o">-&gt;</span> <span class="n">item</span><span class="o">.</span><span class="na">getPrice</span><span class="o">().</span><span class="na">multiply</span><span class="o">(</span><span class="n">item</span><span class="o">.</span><span class="na">getQuantity</span><span class="o">()))</span>
           <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="nc">Money</span><span class="o">.</span><span class="na">ZERO</span><span class="o">,</span> <span class="nl">Money:</span><span class="o">:</span><span class="n">add</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="c1">// No setters — state changes only through business methods</span>
<span class="o">}</span>

<span class="c1">// Value Object — immutable, equality by value</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">Money</span><span class="o">(</span><span class="nc">BigDecimal</span> <span class="n">amount</span><span class="o">,</span> <span class="nc">String</span> <span class="n">currency</span><span class="o">)</span> <span class="o">{</span>
   <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Money</span> <span class="no">ZERO</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Money</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">,</span> <span class="s">"VND"</span><span class="o">);</span>

   <span class="kd">public</span> <span class="nc">Money</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">amount</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">ZERO</span><span class="o">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Amount cannot be negative"</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="nc">Money</span> <span class="nf">add</span><span class="o">(</span><span class="nc">Money</span> <span class="n">other</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(!</span><span class="k">this</span><span class="o">.</span><span class="na">currency</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">other</span><span class="o">.</span><span class="na">currency</span><span class="o">))</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">CurrencyMismatchException</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">currency</span><span class="o">,</span> <span class="n">other</span><span class="o">.</span><span class="na">currency</span><span class="o">);</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nf">Money</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">amount</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">other</span><span class="o">.</span><span class="na">amount</span><span class="o">),</span> <span class="k">this</span><span class="o">.</span><span class="na">currency</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="nc">Money</span> <span class="nf">multiply</span><span class="o">(</span><span class="kt">int</span> <span class="n">quantity</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nf">Money</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">amount</span><span class="o">.</span><span class="na">multiply</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">quantity</span><span class="o">)),</span> <span class="k">this</span><span class="o">.</span><span class="na">currency</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-ports--interfaces-domain-cần"># ports — interfaces domain cần</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Inbound Port: Use Case interface</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CreateOrderUseCase</span> <span class="o">{</span>
   <span class="nc">OrderId</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">CreateOrderCommand</span> <span class="n">command</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// Command object (input to use case)</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">CreateOrderCommand</span><span class="o">(</span>
   <span class="nc">CustomerId</span> <span class="n">customerId</span><span class="o">,</span>
   <span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderItemCommand</span><span class="o">&gt;</span> <span class="n">items</span><span class="o">,</span>
   <span class="nc">PaymentMethod</span> <span class="n">paymentMethod</span>
<span class="o">)</span> <span class="o">{}</span>

<span class="c1">// Outbound Port: persistence</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">OrderRepository</span> <span class="o">{</span>
   <span class="nc">Order</span> <span class="nf">save</span><span class="o">(</span><span class="nc">Order</span> <span class="n">order</span><span class="o">);</span>
   <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="nf">findById</span><span class="o">(</span><span class="nc">OrderId</span> <span class="n">id</span><span class="o">);</span>
   <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="nf">findByCustomerId</span><span class="o">(</span><span class="nc">CustomerId</span> <span class="n">customerId</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// Outbound Port: external service</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">PaymentGateway</span> <span class="o">{</span>
   <span class="nc">PaymentResult</span> <span class="nf">charge</span><span class="o">(</span><span class="nc">OrderId</span> <span class="n">orderId</span><span class="o">,</span> <span class="nc">Money</span> <span class="n">amount</span><span class="o">,</span> <span class="nc">PaymentMethod</span> <span class="n">method</span><span class="o">);</span>
   <span class="kt">void</span> <span class="nf">refund</span><span class="o">(</span><span class="nc">OrderId</span> <span class="n">orderId</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">NotificationPort</span> <span class="o">{</span>
   <span class="kt">void</span> <span class="nf">sendOrderConfirmation</span><span class="o">(</span><span class="nc">CustomerId</span> <span class="n">customerId</span><span class="o">,</span> <span class="nc">OrderId</span> <span class="n">orderId</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-use-case-implementation"># use case implementation</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Use Case: orchestrate domain objects + ports</span>
<span class="c1">// NO Spring annotations here (optional: can add @Service for convenience)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CreateOrderService</span> <span class="kd">implements</span> <span class="nc">CreateOrderUseCase</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderRepository</span> <span class="n">orderRepository</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">PaymentGateway</span> <span class="n">paymentGateway</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">InventoryGateway</span> <span class="n">inventoryGateway</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">NotificationPort</span> <span class="n">notificationPort</span><span class="o">;</span>

   <span class="kd">public</span> <span class="nf">CreateOrderService</span><span class="o">(</span><span class="nc">OrderRepository</span> <span class="n">orderRepository</span><span class="o">,</span>
                             <span class="nc">PaymentGateway</span> <span class="n">paymentGateway</span><span class="o">,</span>
                             <span class="nc">InventoryGateway</span> <span class="n">inventoryGateway</span><span class="o">,</span>
                             <span class="nc">NotificationPort</span> <span class="n">notificationPort</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">this</span><span class="o">.</span><span class="na">orderRepository</span> <span class="o">=</span> <span class="n">orderRepository</span><span class="o">;</span>
       <span class="k">this</span><span class="o">.</span><span class="na">paymentGateway</span> <span class="o">=</span> <span class="n">paymentGateway</span><span class="o">;</span>
       <span class="k">this</span><span class="o">.</span><span class="na">inventoryGateway</span> <span class="o">=</span> <span class="n">inventoryGateway</span><span class="o">;</span>
       <span class="k">this</span><span class="o">.</span><span class="na">notificationPort</span> <span class="o">=</span> <span class="n">notificationPort</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="nc">OrderId</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">CreateOrderCommand</span> <span class="n">command</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// 1. Create domain object (business rules validated in constructor)</span>
       <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="nc">Order</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">customerId</span><span class="o">(),</span> <span class="n">command</span><span class="o">.</span><span class="na">items</span><span class="o">());</span>

       <span class="c1">// 2. Check inventory (through port — doesn't know HOW it's checked)</span>
       <span class="n">inventoryGateway</span><span class="o">.</span><span class="na">checkAvailability</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getItems</span><span class="o">());</span>

       <span class="c1">// 3. Charge payment (through port — doesn't know it's Stripe/VnPay)</span>
       <span class="nc">PaymentResult</span> <span class="n">payment</span> <span class="o">=</span> <span class="n">paymentGateway</span><span class="o">.</span><span class="na">charge</span><span class="o">(</span>
           <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">order</span><span class="o">.</span><span class="na">calculateTotal</span><span class="o">(),</span> <span class="n">command</span><span class="o">.</span><span class="na">paymentMethod</span><span class="o">());</span>

       <span class="k">if</span> <span class="o">(!</span><span class="n">payment</span><span class="o">.</span><span class="na">isSuccess</span><span class="o">())</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">PaymentFailedException</span><span class="o">(</span><span class="n">payment</span><span class="o">.</span><span class="na">getErrorMessage</span><span class="o">());</span>
       <span class="o">}</span>

       <span class="c1">// 4. Confirm order (domain business rule)</span>
       <span class="n">order</span><span class="o">.</span><span class="na">confirm</span><span class="o">();</span>

       <span class="c1">// 5. Persist (through port — doesn't know it's PostgreSQL/MongoDB)</span>
       <span class="n">orderRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">order</span><span class="o">);</span>

       <span class="c1">// 6. Notify (through port — doesn't know it's email/push/SMS)</span>
       <span class="n">notificationPort</span><span class="o">.</span><span class="na">sendOrderConfirmation</span><span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">customerId</span><span class="o">(),</span> <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>

       <span class="k">return</span> <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-adapter-layer--framework-specific-implementations"># adapter layer — framework-specific implementations</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Inbound Adapter: REST Controller</span>
<span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/api/v1/orders"</span><span class="o">)</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderController</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CreateOrderUseCase</span> <span class="n">createOrderUseCase</span><span class="o">;</span>  <span class="c1">// Depend on PORT, not implementation</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">GetOrderUseCase</span> <span class="n">getOrderUseCase</span><span class="o">;</span>

   <span class="nd">@PostMapping</span>
   <span class="nd">@ResponseStatus</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">CREATED</span><span class="o">)</span>
   <span class="kd">public</span> <span class="nc">OrderResponse</span> <span class="nf">create</span><span class="o">(</span><span class="nd">@Valid</span> <span class="nd">@RequestBody</span> <span class="nc">CreateOrderRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// Convert web DTO → domain command</span>
       <span class="nc">CreateOrderCommand</span> <span class="n">command</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CreateOrderCommand</span><span class="o">(</span>
           <span class="k">new</span> <span class="nf">CustomerId</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getCustomerId</span><span class="o">()),</span>
           <span class="n">request</span><span class="o">.</span><span class="na">getItems</span><span class="o">().</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toItemCommand</span><span class="o">).</span><span class="na">toList</span><span class="o">(),</span>
           <span class="n">request</span><span class="o">.</span><span class="na">getPaymentMethod</span><span class="o">()</span>
       <span class="o">);</span>

       <span class="c1">// Call use case (doesn't know about HTTP, JSON, Spring)</span>
       <span class="nc">OrderId</span> <span class="n">orderId</span> <span class="o">=</span> <span class="n">createOrderUseCase</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">command</span><span class="o">);</span>

       <span class="c1">// Convert domain result → web response</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nf">OrderResponse</span><span class="o">(</span><span class="n">orderId</span><span class="o">.</span><span class="na">value</span><span class="o">(),</span> <span class="s">"Order created successfully"</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Outbound Adapter: JPA persistence</span>
<span class="nd">@Component</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderPersistenceAdapter</span> <span class="kd">implements</span> <span class="nc">OrderRepository</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderJpaRepository</span> <span class="n">jpaRepository</span><span class="o">;</span>  <span class="c1">// Spring Data</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderMapper</span> <span class="n">mapper</span><span class="o">;</span>                <span class="c1">// Domain ↔ JPA entity</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="nc">Order</span> <span class="nf">save</span><span class="o">(</span><span class="nc">Order</span> <span class="n">order</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">OrderJpaEntity</span> <span class="n">entity</span> <span class="o">=</span> <span class="n">mapper</span><span class="o">.</span><span class="na">toJpaEntity</span><span class="o">(</span><span class="n">order</span><span class="o">);</span>
       <span class="nc">OrderJpaEntity</span> <span class="n">saved</span> <span class="o">=</span> <span class="n">jpaRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">entity</span><span class="o">);</span>
       <span class="k">return</span> <span class="n">mapper</span><span class="o">.</span><span class="na">toDomain</span><span class="o">(</span><span class="n">saved</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="nf">findById</span><span class="o">(</span><span class="nc">OrderId</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">jpaRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">.</span><span class="na">value</span><span class="o">())</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">mapper:</span><span class="o">:</span><span class="n">toDomain</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Outbound Adapter: Payment gateway (Stripe)</span>
<span class="nd">@Component</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">StripePaymentAdapter</span> <span class="kd">implements</span> <span class="nc">PaymentGateway</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">StripeClient</span> <span class="n">stripeClient</span><span class="o">;</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="nc">PaymentResult</span> <span class="nf">charge</span><span class="o">(</span><span class="nc">OrderId</span> <span class="n">orderId</span><span class="o">,</span> <span class="nc">Money</span> <span class="n">amount</span><span class="o">,</span> <span class="nc">PaymentMethod</span> <span class="n">method</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">StripeChargeRequest</span> <span class="n">stripeRequest</span> <span class="o">=</span> <span class="nc">StripeChargeRequest</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">amount</span><span class="o">(</span><span class="n">amount</span><span class="o">.</span><span class="na">amount</span><span class="o">().</span><span class="na">movePointRight</span><span class="o">(</span><span class="mi">2</span><span class="o">).</span><span class="na">longValue</span><span class="o">())</span>  <span class="c1">// cents</span>
           <span class="o">.</span><span class="na">currency</span><span class="o">(</span><span class="n">amount</span><span class="o">.</span><span class="na">currency</span><span class="o">().</span><span class="na">toLowerCase</span><span class="o">())</span>
           <span class="o">.</span><span class="na">idempotencyKey</span><span class="o">(</span><span class="n">orderId</span><span class="o">.</span><span class="na">value</span><span class="o">().</span><span class="na">toString</span><span class="o">())</span>
           <span class="o">.</span><span class="na">build</span><span class="o">();</span>

       <span class="nc">StripeChargeResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">stripeClient</span><span class="o">.</span><span class="na">createCharge</span><span class="o">(</span><span class="n">stripeRequest</span><span class="o">);</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nf">PaymentResult</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">isSucceeded</span><span class="o">(),</span> <span class="n">response</span><span class="o">.</span><span class="na">getErrorMessage</span><span class="o">());</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-wiring--spring-config"># wiring — spring config</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderBeanConfig</span> <span class="o">{</span>

   <span class="nd">@Bean</span>
   <span class="kd">public</span> <span class="nc">CreateOrderUseCase</span> <span class="nf">createOrderUseCase</span><span class="o">(</span>
           <span class="nc">OrderRepository</span> <span class="n">orderRepository</span><span class="o">,</span>
           <span class="nc">PaymentGateway</span> <span class="n">paymentGateway</span><span class="o">,</span>
           <span class="nc">InventoryGateway</span> <span class="n">inventoryGateway</span><span class="o">,</span>
           <span class="nc">NotificationPort</span> <span class="n">notificationPort</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nf">CreateOrderService</span><span class="o">(</span>
           <span class="n">orderRepository</span><span class="o">,</span> <span class="n">paymentGateway</span><span class="o">,</span> <span class="n">inventoryGateway</span><span class="o">,</span> <span class="n">notificationPort</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-testing-benefits--the-payoff"># testing benefits — the payoff</h3>

<p>Clean Architecture shines khi testing: domain logic test với ZERO infrastructure.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Unit test Use Case — mock ports, test business logic</span>
<span class="kd">class</span> <span class="nc">CreateOrderServiceTest</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderRepository</span> <span class="n">orderRepository</span> <span class="o">=</span> <span class="n">mock</span><span class="o">(</span><span class="nc">OrderRepository</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">PaymentGateway</span> <span class="n">paymentGateway</span> <span class="o">=</span> <span class="n">mock</span><span class="o">(</span><span class="nc">PaymentGateway</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">InventoryGateway</span> <span class="n">inventoryGateway</span> <span class="o">=</span> <span class="n">mock</span><span class="o">(</span><span class="nc">InventoryGateway</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">NotificationPort</span> <span class="n">notificationPort</span> <span class="o">=</span> <span class="n">mock</span><span class="o">(</span><span class="nc">NotificationPort</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CreateOrderUseCase</span> <span class="n">useCase</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CreateOrderService</span><span class="o">(</span>
       <span class="n">orderRepository</span><span class="o">,</span> <span class="n">paymentGateway</span><span class="o">,</span> <span class="n">inventoryGateway</span><span class="o">,</span> <span class="n">notificationPort</span><span class="o">);</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">createOrder_success</span><span class="o">()</span> <span class="o">{</span>
       <span class="n">when</span><span class="o">(</span><span class="n">paymentGateway</span><span class="o">.</span><span class="na">charge</span><span class="o">(</span><span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">()))</span>
           <span class="o">.</span><span class="na">thenReturn</span><span class="o">(</span><span class="nc">PaymentResult</span><span class="o">.</span><span class="na">success</span><span class="o">());</span>
       <span class="n">when</span><span class="o">(</span><span class="n">orderRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">any</span><span class="o">())).</span><span class="na">thenAnswer</span><span class="o">(</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="n">i</span><span class="o">.</span><span class="na">getArgument</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span>

       <span class="nc">OrderId</span> <span class="n">result</span> <span class="o">=</span> <span class="n">useCase</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">validCommand</span><span class="o">());</span>

       <span class="n">assertThat</span><span class="o">(</span><span class="n">result</span><span class="o">).</span><span class="na">isNotNull</span><span class="o">();</span>
       <span class="n">verify</span><span class="o">(</span><span class="n">orderRepository</span><span class="o">).</span><span class="na">save</span><span class="o">(</span><span class="n">argThat</span><span class="o">(</span><span class="n">order</span> <span class="o">-&gt;</span>
           <span class="n">order</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()</span> <span class="o">==</span> <span class="nc">OrderStatus</span><span class="o">.</span><span class="na">CONFIRMED</span><span class="o">));</span>
       <span class="n">verify</span><span class="o">(</span><span class="n">notificationPort</span><span class="o">).</span><span class="na">sendOrderConfirmation</span><span class="o">(</span><span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">());</span>
   <span class="o">}</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">createOrder_paymentFailed_throwsException</span><span class="o">()</span> <span class="o">{</span>
       <span class="n">when</span><span class="o">(</span><span class="n">paymentGateway</span><span class="o">.</span><span class="na">charge</span><span class="o">(</span><span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">(),</span> <span class="n">any</span><span class="o">()))</span>
           <span class="o">.</span><span class="na">thenReturn</span><span class="o">(</span><span class="nc">PaymentResult</span><span class="o">.</span><span class="na">failed</span><span class="o">(</span><span class="s">"Insufficient funds"</span><span class="o">));</span>

       <span class="n">assertThatThrownBy</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">useCase</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">validCommand</span><span class="o">()))</span>
           <span class="o">.</span><span class="na">isInstanceOf</span><span class="o">(</span><span class="nc">PaymentFailedException</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>

       <span class="n">verify</span><span class="o">(</span><span class="n">orderRepository</span><span class="o">,</span> <span class="n">never</span><span class="o">()).</span><span class="na">save</span><span class="o">(</span><span class="n">any</span><span class="o">());</span>  <span class="c1">// Order NOT persisted</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Domain entity unit test — ZERO mocking</span>
<span class="kd">class</span> <span class="nc">OrderTest</span> <span class="o">{</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">confirm_pendingOrder_changesStatusToConfirmed</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="nc">Order</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">customerId</span><span class="o">,</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">item1</span><span class="o">,</span> <span class="n">item2</span><span class="o">));</span>
       <span class="n">order</span><span class="o">.</span><span class="na">confirm</span><span class="o">();</span>
       <span class="n">assertThat</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="nc">OrderStatus</span><span class="o">.</span><span class="na">CONFIRMED</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">confirm_emptyOrder_throwsException</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="nc">Order</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">customerId</span><span class="o">,</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">());</span>
       <span class="n">assertThatThrownBy</span><span class="o">(</span><span class="nl">order:</span><span class="o">:</span><span class="n">confirm</span><span class="o">)</span>
           <span class="o">.</span><span class="na">isInstanceOf</span><span class="o">(</span><span class="nc">EmptyOrderException</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="nd">@Test</span>
   <span class="kt">void</span> <span class="nf">cancel_shippedOrder_throwsException</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="n">createShippedOrder</span><span class="o">();</span>
       <span class="n">assertThatThrownBy</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">order</span><span class="o">.</span><span class="na">cancel</span><span class="o">(</span><span class="s">"Changed mind"</span><span class="o">))</span>
           <span class="o">.</span><span class="na">isInstanceOf</span><span class="o">(</span><span class="nc">IllegalOrderStateException</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-pragmatic-clean-architecture--đừng-over-engineer"># pragmatic clean architecture — đừng over-engineer</h3>

<p>Clean Architecture là guideline, không phải religion. Trong thực tế:</p>

<p><strong>Khi nào FULL Clean Architecture:</strong></p>

<ul>
  <li>Core domain phức tạp (fintech, insurance, healthcare)</li>
  <li>Nhiều inbound channels (REST, gRPC, messaging, scheduled)</li>
  <li>Khả năng swap infrastructure (đổi DB, payment provider)</li>
  <li>Team lớn, cần clear boundaries</li>
</ul>

<p><strong>Khi nào SIMPLIFIED (3-layer đủ rồi):</strong></p>

<ul>
  <li>CRUD apps đơn giản</li>
  <li>Prototypes, MVPs</li>
  <li>Team nhỏ (1-3 devs)</li>
  <li>Ít business logic (pass-through APIs)</li>
</ul>

<p><strong>Common simplifications:</strong></p>

<ul>
  <li>Domain entities CÓ THỂ dùng JPA annotations (pragmatic trade-off)</li>
  <li>Use Cases CÓ THỂ là <code class="language-plaintext highlighter-rouge">@Service</code> classes (Spring convenient)</li>
  <li>Skip mapper layer nếu domain model ≈ persistence model</li>
  <li>Combine inbound port + use case (interface + impl cùng class)</li>
</ul>

<p>Mục tiêu không phải “pure Clean Architecture” — mà là <strong>business logic testable mà không cần infrastructure</strong>. Nếu bạn đạt được điều đó với ít layers hơn, đó cũng là thành công.</p>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="software-development" /><category term="system-design" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[# vấn đề clean architecture giải quyết]]></summary></entry><entry><title type="html">map vs flatMap trong Java — Stream, Optional &amp;amp; CompletableFuture</title><link href="https://cristorvalds.netlify.app/map-vs-flat-map/" rel="alternate" type="text/html" title="map vs flatMap trong Java — Stream, Optional &amp;amp; CompletableFuture" /><published>2026-06-03T19:29:39+07:00</published><updated>2026-06-03T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/map-vs-flat-map</id><content type="html" xml:base="https://cristorvalds.netlify.app/map-vs-flat-map/"><![CDATA[<h3 id="-tại-sao-hay-nhầm"># tại sao hay nhầm?</h3>

<p>Cả <code class="language-plaintext highlighter-rouge">map</code> và <code class="language-plaintext highlighter-rouge">flatMap</code> đều transform giá trị bên trong container (Stream, Optional, CompletableFuture). Khác biệt duy nhất: <strong>flatMap “bóc” thêm 1 lớp wrapper</strong> mà map không làm.</p>

<p>Hiểu đơn giản:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">map(f)</code>: apply f → wrap kết quả vào container → có thể tạo nested container</li>
  <li><code class="language-plaintext highlighter-rouge">flatMap(f)</code>: apply f (f tự trả container) → KHÔNG wrap thêm → flatten</li>
</ul>

<h3 id="-stream-map-vs-flatmap"># stream: map vs flatMap</h3>

<h4 id="-map--11-transformation"># map — 1:1 transformation</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Mỗi element → 1 element mới</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">names</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"john"</span><span class="o">,</span> <span class="s">"jane"</span><span class="o">,</span> <span class="s">"bob"</span><span class="o">);</span>

<span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">upper</span> <span class="o">=</span> <span class="n">names</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">toUpperCase</span><span class="o">)</span>    <span class="c1">// "john" → "JOHN"</span>
   <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
<span class="c1">// ["JOHN", "JANE", "BOB"]</span>

<span class="c1">// map với function trả về object khác</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="n">orders</span> <span class="o">=</span> <span class="o">...;</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="no">UUID</span><span class="o">&gt;</span> <span class="n">customerIds</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Order:</span><span class="o">:</span><span class="n">getCustomerId</span><span class="o">)</span>   <span class="c1">// Order → UUID</span>
   <span class="o">.</span><span class="na">distinct</span><span class="o">()</span>
   <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
</code></pre></div></div>

<h4 id="-flatmap--1n-transformation-flatten-nested-collections"># flatMap — 1:N transformation (flatten nested collections)</h4>

<p>Khi function trả về collection/stream, <code class="language-plaintext highlighter-rouge">map</code> tạo <code class="language-plaintext highlighter-rouge">Stream&lt;Stream&lt;T&gt;&gt;</code> — nested. <code class="language-plaintext highlighter-rouge">flatMap</code> flatten thành <code class="language-plaintext highlighter-rouge">Stream&lt;T&gt;</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Mỗi order có nhiều items → lấy TẤT CẢ items từ TẤT CẢ orders</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="n">orders</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">order1</span><span class="o">,</span> <span class="n">order2</span><span class="o">,</span> <span class="n">order3</span><span class="o">);</span>

<span class="c1">// map → Stream&lt;List&lt;OrderItem&gt;&gt; (nested!)</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderItem</span><span class="o">&gt;&gt;</span> <span class="n">nested</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Order:</span><span class="o">:</span><span class="n">getItems</span><span class="o">)</span>  <span class="c1">// Order → List&lt;OrderItem&gt;</span>
   <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
<span class="c1">// [[item1, item2], [item3], [item4, item5]] — NESTED</span>

<span class="c1">// flatMap → Stream&lt;OrderItem&gt; (flat!)</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderItem</span><span class="o">&gt;</span> <span class="n">allItems</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">order</span> <span class="o">-&gt;</span> <span class="n">order</span><span class="o">.</span><span class="na">getItems</span><span class="o">().</span><span class="na">stream</span><span class="o">())</span>  <span class="c1">// Order → Stream&lt;OrderItem&gt;</span>
   <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
<span class="c1">// [item1, item2, item3, item4, item5] — FLAT</span>
</code></pre></div></div>

<h4 id="-real-world-examples"># real-world examples</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Lấy tất cả tags từ tất cả products</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">allTags</span> <span class="o">=</span> <span class="n">products</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">product</span> <span class="o">-&gt;</span> <span class="n">product</span><span class="o">.</span><span class="na">getTags</span><span class="o">().</span><span class="na">stream</span><span class="o">())</span>
   <span class="o">.</span><span class="na">distinct</span><span class="o">()</span>
   <span class="o">.</span><span class="na">sorted</span><span class="o">()</span>
   <span class="o">.</span><span class="na">toList</span><span class="o">();</span>

<span class="c1">// Lấy tất cả permissions từ tất cả roles của user</span>
<span class="nc">Set</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">permissions</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="na">getRoles</span><span class="o">().</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">role</span> <span class="o">-&gt;</span> <span class="n">role</span><span class="o">.</span><span class="na">getPermissions</span><span class="o">().</span><span class="na">stream</span><span class="o">())</span>
   <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toSet</span><span class="o">());</span>

<span class="c1">// Parse CSV lines → individual fields</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">allFields</span> <span class="o">=</span> <span class="n">csvLines</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">line</span> <span class="o">-&gt;</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">stream</span><span class="o">(</span><span class="n">line</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s">","</span><span class="o">)))</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">trim</span><span class="o">)</span>
   <span class="o">.</span><span class="na">toList</span><span class="o">();</span>

<span class="c1">// Multi-level flatten: workspace → projects → tasks</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Task</span><span class="o">&gt;</span> <span class="n">allTasks</span> <span class="o">=</span> <span class="n">workspaces</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">ws</span> <span class="o">-&gt;</span> <span class="n">ws</span><span class="o">.</span><span class="na">getProjects</span><span class="o">().</span><span class="na">stream</span><span class="o">())</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">project</span> <span class="o">-&gt;</span> <span class="n">project</span><span class="o">.</span><span class="na">getTasks</span><span class="o">().</span><span class="na">stream</span><span class="o">())</span>
   <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">task</span> <span class="o">-&gt;</span> <span class="n">task</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()</span> <span class="o">==</span> <span class="nc">TaskStatus</span><span class="o">.</span><span class="na">OPEN</span><span class="o">)</span>
   <span class="o">.</span><span class="na">toList</span><span class="o">();</span>
</code></pre></div></div>

<h3 id="-optional-map-vs-flatmap"># optional: map vs flatMap</h3>

<p>Khi function trả về <code class="language-plaintext highlighter-rouge">Optional</code>, <code class="language-plaintext highlighter-rouge">map</code> tạo <code class="language-plaintext highlighter-rouge">Optional&lt;Optional&lt;T&gt;&gt;</code>. <code class="language-plaintext highlighter-rouge">flatMap</code> giữ <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code>.</p>

<h4 id="-map--function-trả-về-plain-value"># map — function trả về plain value</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">user</span> <span class="o">=</span> <span class="n">userRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">userId</span><span class="o">);</span>

<span class="c1">// map: User → String (plain value)</span>
<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">email</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">User:</span><span class="o">:</span><span class="n">getEmail</span><span class="o">);</span>
<span class="c1">// Optional&lt;"john@mail.com"&gt; hoặc Optional.empty()</span>

<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">upperName</span> <span class="o">=</span> <span class="n">user</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">User:</span><span class="o">:</span><span class="n">getName</span><span class="o">)</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">toUpperCase</span><span class="o">);</span>
</code></pre></div></div>

<h4 id="-flatmap--function-trả-về-optional"># flatMap — function trả về Optional</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// findById trả Optional → cần flatMap</span>
<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">user</span> <span class="o">=</span> <span class="n">userRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">userId</span><span class="o">);</span>

<span class="c1">// map → Optional&lt;Optional&lt;Address&gt;&gt;</span>
<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Address</span><span class="o">&gt;&gt;</span> <span class="n">nested</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">User:</span><span class="o">:</span><span class="n">getAddress</span><span class="o">);</span>
<span class="c1">// Nếu User::getAddress return Optional&lt;Address&gt;</span>

<span class="c1">// flatMap → Optional&lt;Address&gt;</span>
<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Address</span><span class="o">&gt;</span> <span class="n">address</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="nl">User:</span><span class="o">:</span><span class="n">getAddress</span><span class="o">);</span>

<span class="c1">// Chain multiple Optional operations</span>
<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">city</span> <span class="o">=</span> <span class="n">userRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>      <span class="c1">// Optional&lt;User&gt;</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="nl">User:</span><span class="o">:</span><span class="n">getAddress</span><span class="o">)</span>                                <span class="c1">// Optional&lt;Address&gt;</span>
   <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="nl">Address:</span><span class="o">:</span><span class="n">getCity</span><span class="o">)</span>                                <span class="c1">// Optional&lt;String&gt;</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">toUpperCase</span><span class="o">);</span>                                <span class="c1">// Optional&lt;String&gt;</span>
</code></pre></div></div>

<h4 id="-practical-pattern"># practical pattern</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Service method returning Optional</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="nf">getProductWithCategory</span><span class="o">(</span><span class="no">UUID</span> <span class="n">productId</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">productId</span><span class="o">)</span>           <span class="c1">// Optional&lt;Product&gt;</span>
       <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">product</span> <span class="o">-&gt;</span>
           <span class="n">categoryRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">product</span><span class="o">.</span><span class="na">getCategoryId</span><span class="o">())</span>  <span class="c1">// Optional&lt;Category&gt;</span>
               <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">category</span> <span class="o">-&gt;</span> <span class="n">toDTO</span><span class="o">(</span><span class="n">product</span><span class="o">,</span> <span class="n">category</span><span class="o">)));</span>      <span class="c1">// Optional&lt;ProductDTO&gt;</span>
<span class="o">}</span>

<span class="c1">// Avoid nested optionals</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="nf">getWorkspaceName</span><span class="o">(</span><span class="no">UUID</span> <span class="n">userId</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">userRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>                  <span class="c1">// Optional&lt;User&gt;</span>
       <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">user</span> <span class="o">-&gt;</span> <span class="n">workspaceRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getDefaultWorkspaceId</span><span class="o">()))</span>  <span class="c1">// Optional&lt;Workspace&gt;</span>
       <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Workspace:</span><span class="o">:</span><span class="n">getName</span><span class="o">);</span>                           <span class="c1">// Optional&lt;String&gt;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-completablefuture-thenapply-vs-thencompose"># CompletableFuture: thenApply vs thenCompose</h3>

<p>Trong CompletableFuture, pattern giống hệt:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">thenApply</code> = <code class="language-plaintext highlighter-rouge">map</code> (function trả plain value)</li>
  <li><code class="language-plaintext highlighter-rouge">thenCompose</code> = <code class="language-plaintext highlighter-rouge">flatMap</code> (function trả CompletableFuture)</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">userFuture</span> <span class="o">=</span> <span class="nc">CompletableFuture</span><span class="o">.</span><span class="na">supplyAsync</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">fetchUser</span><span class="o">(</span><span class="n">userId</span><span class="o">));</span>

<span class="c1">// thenApply (= map): User → String</span>
<span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">nameFuture</span> <span class="o">=</span> <span class="n">userFuture</span>
   <span class="o">.</span><span class="na">thenApply</span><span class="o">(</span><span class="nl">User:</span><span class="o">:</span><span class="n">getName</span><span class="o">);</span>  <span class="c1">// plain value</span>

<span class="c1">// thenApply khi function trả Future → nested!</span>
<span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;&gt;</span> <span class="n">nested</span> <span class="o">=</span> <span class="n">userFuture</span>
   <span class="o">.</span><span class="na">thenApply</span><span class="o">(</span><span class="n">user</span> <span class="o">-&gt;</span> <span class="n">fetchOrderAsync</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getId</span><span class="o">()));</span>  <span class="c1">// Returns CF → nested CF&lt;CF&lt;Order&gt;&gt;</span>

<span class="c1">// thenCompose (= flatMap): flatten</span>
<span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="n">orderFuture</span> <span class="o">=</span> <span class="n">userFuture</span>
   <span class="o">.</span><span class="na">thenCompose</span><span class="o">(</span><span class="n">user</span> <span class="o">-&gt;</span> <span class="n">fetchOrderAsync</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getId</span><span class="o">()));</span>  <span class="c1">// Returns CF → flat CF&lt;Order&gt;</span>

<span class="c1">// Chain</span>
<span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="nc">CompletableFuture</span>
   <span class="o">.</span><span class="na">supplyAsync</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">fetchUser</span><span class="o">(</span><span class="n">userId</span><span class="o">))</span>               <span class="c1">// CF&lt;User&gt;</span>
   <span class="o">.</span><span class="na">thenCompose</span><span class="o">(</span><span class="n">user</span> <span class="o">-&gt;</span> <span class="n">fetchOrdersAsync</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getId</span><span class="o">()))</span> <span class="c1">// CF&lt;List&lt;Order&gt;&gt;</span>
   <span class="o">.</span><span class="na">thenApply</span><span class="o">(</span><span class="n">orders</span> <span class="o">-&gt;</span> <span class="n">orders</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">+</span> <span class="s">" orders"</span><span class="o">);</span>     <span class="c1">// CF&lt;String&gt;</span>
</code></pre></div></div>

<h3 id="-lời-kết"># lời kết</h3>

<table>
  <thead>
    <tr>
      <th>Context</th>
      <th>map equivalent</th>
      <th>flatMap equivalent</th>
      <th>Khi nào dùng flatMap</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Stream</td>
      <td>map()</td>
      <td>flatMap()</td>
      <td>Function trả Stream/Collection</td>
    </tr>
    <tr>
      <td>Optional</td>
      <td>map()</td>
      <td>flatMap()</td>
      <td>Function trả Optional</td>
    </tr>
    <tr>
      <td>CompletableFuture</td>
      <td>thenApply()</td>
      <td>thenCompose()</td>
      <td>Function trả CompletableFuture</td>
    </tr>
    <tr>
      <td>Reactor Mono</td>
      <td>map()</td>
      <td>flatMap()</td>
      <td>Function trả Mono/Flux</td>
    </tr>
  </tbody>
</table>

<p><strong>Rule đơn giản</strong>: Nếu function bạn truyền vào ĐÃ wrap kết quả trong container (Optional, Stream, Future…) → dùng flatMap. Nếu function trả plain value → dùng map.</p>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[# tại sao hay nhầm?]]></summary></entry><entry><title type="html">rabbitmq foundation</title><link href="https://cristorvalds.netlify.app/rabbitmq2/" rel="alternate" type="text/html" title="rabbitmq foundation" /><published>2026-05-30T19:29:39+07:00</published><updated>2026-05-30T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/rabbitmq2</id><content type="html" xml:base="https://cristorvalds.netlify.app/rabbitmq2/"><![CDATA[<blockquote>
  <p><em>“Any sufficiently complex distributed system contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a message queue.”</em>
— Paraphrased from Greenspun’s Tenth Rule</p>
</blockquote>

<p>Tôi đã từng deploy một hệ thống microservices mà các service gọi nhau bằng REST synchronous. Mọi thứ chạy ngon lành cho đến khi traffic tăng gấp 10 lần vào Black Friday. Order Service gọi Inventory Service, Inventory gọi Payment, Payment gọi Notification… Một service chết, cả chuỗi domino sụp đổ. Latency p99 từ 200ms nhảy lên 15 giây. Đó là lúc tôi thực sự hiểu tại sao message queue tồn tại.</p>

<p>Bài viết này là tất cả những gì tôi ước mình biết sớm hơn về RabbitMQ — không phải tutorial “Hello World”, mà là deep dive thực sự từ kiến trúc bên trong cho đến production patterns với Java và Spring Boot.</p>

<h3 id="-message-queue-là-gì"># Message Queue là gì</h3>

<p>Hãy tưởng tượng bạn đang ở một nhà hàng đông đúc.</p>

<p><strong>Không có message queue</strong> = Khách hàng đứng trước bếp, chờ đầu bếp nấu xong mới order tiếp. Một món phức tạp → cả hàng đứng chờ.</p>

<p><strong>Có message queue</strong> = Khách hàng viết order lên giấy, đặt vào kệ. Đầu bếp lấy order từ kệ theo thứ tự. Khách hàng tự do ngồi xuống. Nhiều đầu bếp có thể lấy order song song.</p>

<p>Cái “kệ order” đó chính là message queue.</p>

<h4 id="-vấn-đề-thực-tế-mà-message-queue-giải-quyết"># vấn đề thực tế mà Message Queue giải quyết</h4>

<p><img src="/assets/img/blog/rest-block.png" alt="rest-block" /></p>

<p>Vấn đề:</p>

<ul>
  <li>Temporal coupling: Tất cả service phải online cùng lúc</li>
  <li>Latency chồng chất: 100ms + 100ms + 100ms + 100ms = 400ms minimum</li>
  <li>Cascading failure: Payment chết → Inventory timeout → Order fail</li>
  <li>Không scale được: Peak traffic = tất cả service phải scale cùng lúc</li>
</ul>

<p>Với message queue:</p>

<p><img src="/assets/img/blog/message-queue.png" alt="message-queue" /></p>

<p>Lợi ích:</p>

<ul>
  <li>Decoupling: Order Service không cần biết ai consume</li>
  <li>Async: Response ngay lập tức, xử lý background</li>
  <li>Buffer: Traffic spike? Message queue giữ lại, consumer xử lý dần</li>
  <li>Resilience: Service chết? Message vẫn nằm trong queue, xử lý khi service sống lại</li>
</ul>

<h4 id="-3-lý-do-cốt-lõi-để-áp-dụng-message-queue"># 3 lý do cốt lõi để áp dụng Message Queue</h4>

<ul>
  <li><strong>Decoupling (Giảm độ liên kết):</strong> Hệ thống gửi tin (Producer) không cần quan tâm hệ thống nhận (Consumer) là ai, ở đâu, hay đang viết bằng ngôn ngữ gì. Bạn có thể thêm bớt Consumer thoải mái mà không cần động vào code của Producer.</li>
  <li><strong>Buffering (Đệm &amp; giảm tải):</strong> Khi lượng request đổ về quá nhanh vượt quá khả năng xử lý của Consumer, Queue đóng vai trò như một bộ đệm hấp thụ lực. Thay vì Consumer bị sập do quá tải (overwhelm), nó cứ thong thả lấy message ra xử lý theo đúng năng lực của mình.</li>
  <li><strong>Resilience (Khả năng phục hồi):</strong> Lỡ Consumer có đột ngột lăn ra chết? Không vấn đề gì cả. Message vẫn nằm an toàn trong Queue. Khi Consumer sống lại, nó sẽ tiếp tục xử lý từ đúng vị trí bị gián đoạn.</li>
</ul>

<h3 id="-rabbitmq--bức-tranh-toàn-cảnh"># RabbitMQ — Bức tranh toàn cảnh</h3>

<h4 id="-rabbitmq-là-gì"># RabbitMQ là gì?</h4>

<p>RabbitMQ là một open-source message broker implement AMQP (Advanced Message Queuing Protocol). Được viết bằng Erlang — một ngôn ngữ được thiết kế cho hệ thống telecom với yêu cầu uptime 99.999% (five nines).</p>

<p>Tại sao lại là Erlang? Ngôn ngữ này sở hữu những đặc tính cực kỳ phù hợp cho Message Broker:</p>

<ul>
  <li>Hỗ trợ hàng triệu process siêu nhẹ (lightweight processes) chạy đồng thời.</li>
  <li>Cơ chế Hot code swapping (update code hệ thống trực tiếp mà không cần reboot).</li>
  <li>Hỗ trợ tính toán phân tán (distributed computing) ngay từ lõi.</li>
  <li>Khả năng chịu lỗi (fault tolerance) cực tốt nhờ mô hình Supervisor Trees.</li>
</ul>

<p>Nhờ nền tảng Erlang vững chắc này, RabbitMQ có thể xử lý hàng trăm nghìn message mỗi giây với độ trễ (latency) cực kỳ thấp.</p>

<h4 id="-lịch-sử-phát-triển"># lịch sử phát triển</h4>

<ul>
  <li><strong>2007</strong>: Rabbit Technologies Ltd chính thức ra mắt RabbitMQ.</li>
  <li><strong>2010</strong>: VMware mua lại Rabbit Technologies.</li>
  <li><strong>2013</strong>: Dự án được chuyển giao cho Pivotal Software (một nhánh tách ra từ VMware).</li>
  <li><strong>2019</strong>: VMware mua lại Pivotal, đưa RabbitMQ trở lại mái nhà xưa.</li>
  <li><strong>Hiện tại</strong>: Thuộc sở hữu của Broadcom (sau thương vụ thâu tóm VMware) nhưng vẫn giữ định hướng open-source với cộng đồng cực kỳ lớn mạnh.</li>
</ul>

<h4 id="-những-ông-lớn-nào-đang-dùng-rabbitmq"># những ông lớn nào đang dùng rabbitmq?</h4>

<ul>
  <li><strong>Bloomberg</strong>: Xử lý hàng triệu message tài chính mỗi giây với yêu cầu độ tin cậy tuyệt đối.</li>
  <li><strong>Reddit</strong>: Vận hành hệ thống push notification quy mô lớn.</li>
  <li><strong>Mozilla</strong>: Quản lý dịch vụ push notification trên trình duyệt Firefox.</li>
  <li><strong>Zalando</strong>: Trái tim của pipeline xử lý đơn hàng thương mại điện tử.</li>
  <li><strong>Và rất nhiều startup đến enterprise khác…</strong></li>
</ul>

<h3 id="-kiến-trúc-bên-trong-rabbitmq"># kiến trúc bên trong rabbitmq</h3>

<p>Nếu nắm chắc phần này, các vấn đề cấu hình hay debug sau này sẽ dễ thở hơn rất nhiều.</p>

<h4 id="-các-thành-phần-cốt-lõi"># các thành phần cốt lõi</h4>

<p><img src="/assets/img/blog/core-pattern.png" alt="core-pattern" /></p>

<h4 id="-các-thành-phần"># các thành phần</h4>

<p><strong>Producer (Publisher)</strong>
Ứng dụng gửi message. Cần lưu ý là Producer <strong>không gửi tin nhắn trực tiếp vào Queue</strong> — nó bắt buộc phải gửi thông qua một thành phần trung gian gọi là Exchange. Đây là điểm khác biệt quan trọng của RabbitMQ so với nhiều message queue khác.</p>

<p><strong>Exchange</strong>
Được ví như bộ não định tuyến. Nó nhận message từ Producer và quyết định sẽ chuyển tiếp tin nhắn đó vào Queue nào dựa trên:</p>

<ul>
  <li><strong>Exchange Type</strong> (loại exchange: direct, topic, fanout, headers).</li>
  <li><strong>Routing Key</strong> (nhãn đính kèm trên message).</li>
  <li><strong>Binding Rules</strong> (quy tắc liên kết giữa Exchange và Queue).</li>
</ul>

<p>Hãy tưởng tượng Exchange như một bưu điện: nó nhìn vào địa chỉ trên phong bì (routing key) để phân phối thư vào đúng hòm thư (queue).</p>

<p><strong>Binding</strong>
Là luật liên kết giữa Exchange và Queue. Một binding sẽ định nghĩa: “Hãy chuyển các message có routing key X này vào queue Y”.</p>

<p><strong>Queue</strong>
Nơi lưu trữ message cho đến khi có Consumer lấy đi xử lý. Một Queue có các thuộc tính quan trọng:</p>

<ul>
  <li><strong>Durable</strong>: Đảm bảo an toàn dữ liệu. Message sẽ được ghi xuống ổ đĩa để không bị mất khi RabbitMQ broker khởi động lại.</li>
  <li><strong>Exclusive</strong>: Chỉ cho phép kết nối hiện tại sử dụng và tự động xóa khi kết nối này đóng lại.</li>
  <li><strong>Auto-delete</strong>: Tự động biến mất khi Consumer cuối cùng ngắt kết nối (unsubscribe).</li>
  <li><strong>TTL (Time-To-Live)</strong>: Đặt thời gian sống cho message, quá thời hạn này message sẽ tự động bị hủy.</li>
</ul>

<p><strong>Consumer</strong>
Ứng dụng kết nối tới Queue để nhận và xử lý message. Có hai cơ chế nhận tin nhắn:</p>

<ul>
  <li><strong>Push (Basic.Consume)</strong>: Broker chủ động đẩy message xuống cho Consumer khi có tin mới — đây là cơ chế phổ biến nhất nhờ hiệu năng cao.</li>
  <li><strong>Pull (Basic.Get)</strong>: Consumer chủ động kéo message về — ít khi dùng vì tốn tài nguyên và độ trễ cao.</li>
</ul>

<h4 id="-connection--channel"># connection &amp; channel</h4>

<p><img src="/assets/img/blog/connection-channel.png" alt="connection-channel" /></p>

<p><strong>Connection</strong>: Là một kết nối vật lý (TCP connection) từ ứng dụng tới RabbitMQ broker. Việc mở mới một Connection rất tốn tài nguyên (phải thực hiện bắt tay TCP, xác thực, bắt tay bảo mật TLS…). Do đó, ta nên giữ kết nối này bền vững thay vì tạo mới liên tục.</p>

<p><strong>Channel</strong>: Là một kết nối ảo (virtual connection) chạy trên một Connection vật lý. Tạo và đóng Channel cực kỳ nhanh và tốn rất ít tài nguyên. Mỗi luồng (thread) xử lý trong code của bạn nên sử dụng một Channel riêng.</p>

<blockquote>
  <p><strong>Kinh nghiệm thực tế</strong>: Mỗi ứng dụng chỉ nên duy trì một Connection duy nhất, và tạo ra một Channel cho mỗi thread xử lý. Tuyệt đối không chia sẻ (share) Channel giữa các thread vì nó không được thiết kế để thread-safe.</p>
</blockquote>

<h4 id="-virtual-host-vhost"># virtual host (vhost)</h4>

<p>Có vai trò tương tự như database schema hay namespace giúp phân vùng tài nguyên. Mỗi vhost sở hữu các Exchange, Queue, và Binding hoàn toàn độc lập. Chúng ta thường dùng vhost để cô lập các môi trường (dev, staging, prod) hoặc phân tách quyền truy cập giữa các team/dự án khác nhau trên cùng một cụm RabbitMQ Cluster.</p>

<pre><code class="language-mermaid">RabbitMQ Broker
├── vhost: /production
│   ├── exchange: order.exchange
│   ├── queue: order.processing
│   └── queue: order.notification
├── vhost: /staging
│   ├── exchange: order.exchange
│   └── queue: order.processing
└── vhost: /development
    └── ...
</code></pre>

<h3 id="-amqp-protocol"># amqp protocol</h3>

<p>AMQP (Advanced Message Queuing Protocol) là giao thức truyền tin ở tầng mạng (wire-level) mà RabbitMQ đang sử dụng. Nắm được cách thức hoạt động của giao thức này sẽ giúp bạn dễ dàng debug và tối ưu hóa hệ thống khi gặp sự cố.</p>

<h4 id="-vòng-đời-của-một-message"># vòng đời của một message</h4>

<p><img src="/assets/img/blog/message-lifecycle.png" alt="message-lifecycle" /></p>

<h4 id="-message-properties-thuộc-tính-tin-nhắn"># message properties (thuộc tính tin nhắn)</h4>

<p>Mỗi message AMQP được chia làm hai phần: <strong>properties</strong> (metadata cấu hình) và <strong>body</strong> (nội dung payload chứa dữ liệu thực tế).</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Các properties quan trọng thường dùng:</span>
<span class="nc">MessageProperties</span> <span class="n">properties</span> <span class="o">=</span> <span class="nc">MessagePropertiesBuilder</span><span class="o">.</span><span class="na">newInstance</span><span class="o">()</span>
    <span class="o">.</span><span class="na">setContentType</span><span class="o">(</span><span class="s">"application/json"</span><span class="o">)</span>       <span class="c1">// Định dạng dữ liệu của body</span>
    <span class="o">.</span><span class="na">setContentEncoding</span><span class="o">(</span><span class="s">"UTF-8"</span><span class="o">)</span>              <span class="c1">// Bảng mã hóa ký tự</span>
    <span class="o">.</span><span class="na">setDeliveryMode</span><span class="o">(</span><span class="nc">MessageDeliveryMode</span><span class="o">.</span><span class="na">PERSISTENT</span><span class="o">)</span> <span class="c1">// 1=tạm thời, 2=lưu đĩa (survive restart)</span>
    <span class="o">.</span><span class="na">setPriority</span><span class="o">(</span><span class="mi">5</span><span class="o">)</span>                           <span class="c1">// Độ ưu tiên của tin nhắn từ 0-9</span>
    <span class="o">.</span><span class="na">setCorrelationId</span><span class="o">(</span><span class="s">"abc-123"</span><span class="o">)</span>              <span class="c1">// Dùng để định danh request-response (RPC)</span>
    <span class="o">.</span><span class="na">setReplyTo</span><span class="o">(</span><span class="s">"response.queue"</span><span class="o">)</span>             <span class="c1">// Queue nhận phản hồi</span>
    <span class="o">.</span><span class="na">setExpiration</span><span class="o">(</span><span class="s">"60000"</span><span class="o">)</span>                   <span class="c1">// TTL thời gian hết hạn (milliseconds)</span>
    <span class="o">.</span><span class="na">setMessageId</span><span class="o">(</span><span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">())</span> <span class="c1">// ID duy nhất chống lặp tin</span>
    <span class="o">.</span><span class="na">setTimestamp</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">())</span>                 <span class="c1">// Thời gian tạo tin nhắn</span>
    <span class="o">.</span><span class="na">setType</span><span class="o">(</span><span class="s">"OrderCreatedEvent"</span><span class="o">)</span>             <span class="c1">// Tên loại sự kiện</span>
    <span class="o">.</span><span class="na">setAppId</span><span class="o">(</span><span class="s">"order-service"</span><span class="o">)</span>                <span class="c1">// ID ứng dụng gửi tin</span>
    <span class="o">.</span><span class="na">build</span><span class="o">();</span>
</code></pre></div></div>

<h4 id="-acknowledgment--cơ-chế-xác-nhận-tin-nhắn"># acknowledgment — cơ chế xác nhận tin nhắn</h4>

<p>Đây là phần cực kỳ dễ bị bỏ sót nhưng lại là tác nhân gây ra hàng loạt bug nghiêm trọng trên môi trường Production.</p>

<p><strong>Auto Ack (autoAck=true)</strong>:</p>

<ul>
  <li>Message sẽ bị xóa khỏi Queue ngay khi vừa được gửi đến Consumer.</li>
  <li>Lỡ Consumer sập (crash) khi đang xử lý giữa chừng? Tin nhắn đó coi như mất vĩnh viễn.</li>
  <li>Chỉ nên dùng cho những message không quá quan trọng như log hoặc metrics.</li>
</ul>

<p><strong>Manual Ack (autoAck=false)</strong>:</p>

<ul>
  <li>Consumer bắt buộc phải chủ động gửi tín hiệu xác nhận (ack) về cho broker sau khi đã xử lý xong tin nhắn.</li>
  <li>Nếu Consumer gặp sự cố trước khi kịp gửi ack, RabbitMQ sẽ tự động đẩy tin nhắn đó quay lại queue để Consumer khác (hoặc chính nó sau khi khởi động lại) xử lý tiếp.</li>
  <li><strong>Đây là cấu hình mặc định bắt buộc phải dùng cho môi trường Production.</strong></li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Ba loại xác nhận cơ bản:</span>
<span class="n">channel</span><span class="o">.</span><span class="na">basicAck</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>   <span class="c1">// Xử lý thành công, cho phép xóa tin nhắn khỏi queue</span>
<span class="n">channel</span><span class="o">.</span><span class="na">basicNack</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>  <span class="c1">// Thất bại, yêu cầu đẩy lại vào queue (requeue) để thử lại</span>
<span class="n">channel</span><span class="o">.</span><span class="na">basicReject</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>      <span class="c1">// Thất bại, loại bỏ tin nhắn (hoặc chuyển sang DLX) không requeue</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Cảnh báo (Gotcha):</strong> Quên gửi ack là một trong những nguyên nhân phổ biến nhất dẫn đến memory leak trên RabbitMQ. Message sẽ bị treo ở trạng thái “Unacked” vô thời hạn, khiến Broker giữ lại trong RAM và không thể giải phóng bộ nhớ. Trước đây mình từng tốn cả buổi chiều chỉ để debug lỗi ngớ ngẩn này.</p>
</blockquote>

<h3 id="-exchange-types--bộ-não-định-tuyến"># exchange types — bộ não định tuyến</h3>

<p>Đây có lẽ là tính năng linh hoạt và thú vị nhất của RabbitMQ khi so sánh với các message queue khác. Chúng ta có 4 loại Exchange chính tương ứng với các bài toán định tuyến khác nhau:</p>

<h4 id="-direct-exchange"># direct exchange</h4>

<p>Định tuyến chính xác dựa theo Routing Key. Message chỉ được chuyển đến Queue có Binding Key khớp hoàn toàn (exact match) với Routing Key của tin nhắn.</p>

<p><img src="/assets/img/blog/direct-exchange.png" alt="direct-exchange" /></p>

<p><strong>Use case</strong>: Phân phối tác vụ (task distribution), điều hướng tin nhắn đến đúng các service chuyên biệt.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Khai báo</span>
<span class="n">channel</span><span class="o">.</span><span class="na">exchangeDeclare</span><span class="o">(</span><span class="s">"order.direct"</span><span class="o">,</span> <span class="nc">BuiltinExchangeType</span><span class="o">.</span><span class="na">DIRECT</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">channel</span><span class="o">.</span><span class="na">queueDeclare</span><span class="o">(</span><span class="s">"order-processing"</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="n">channel</span><span class="o">.</span><span class="na">queueBind</span><span class="o">(</span><span class="s">"order-processing"</span><span class="o">,</span> <span class="s">"order.direct"</span><span class="o">,</span> <span class="s">"order.created"</span><span class="o">);</span>

<span class="c1">// Gửi tin nhắn</span>
<span class="n">channel</span><span class="o">.</span><span class="na">basicPublish</span><span class="o">(</span><span class="s">"order.direct"</span><span class="o">,</span> <span class="s">"order.created"</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">message</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>
</code></pre></div></div>

<h4 id="-fanout-exchange"># fanout exchange</h4>

<p>Cơ chế Broadcast — gửi message tới <strong>tất cả</strong> các Queue đang liên kết với nó mà không cần quan tâm đến Routing Key.</p>

<p><img src="/assets/img/blog/fanout-exchange.png" alt="fanout-exchange" /></p>

<p><strong>Use case</strong>: Phát tán sự kiện (event broadcasting), gửi các thông báo cho nhiều service chạy bất đồng bộ cùng lúc.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Khai báo</span>
<span class="n">channel</span><span class="o">.</span><span class="na">exchangeDeclare</span><span class="o">(</span><span class="s">"notification.fanout"</span><span class="o">,</span> <span class="nc">BuiltinExchangeType</span><span class="o">.</span><span class="na">FANOUT</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">channel</span><span class="o">.</span><span class="na">queueBind</span><span class="o">(</span><span class="s">"email-queue"</span><span class="o">,</span> <span class="s">"notification.fanout"</span><span class="o">,</span> <span class="s">""</span><span class="o">);</span> <span class="c1">// routing key bị bỏ qua</span>

<span class="c1">// Publish — routing key đặt trống</span>
<span class="n">channel</span><span class="o">.</span><span class="na">basicPublish</span><span class="o">(</span><span class="s">"notification.fanout"</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">message</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>
</code></pre></div></div>

<h4 id="-topic-exchange"># topic exchange</h4>

<p>Định tuyến linh hoạt thông qua khớp mẫu (pattern matching) với Routing Key bằng các ký tự đại diện (wildcard). Các từ trong Routing Key phân tách với nhau bằng dấu chấm (<code class="language-plaintext highlighter-rouge">.</code>), kết hợp với:</p>

<ul>
  <li>Ký tự <code class="language-plaintext highlighter-rouge">*</code> (star): Thay thế cho đúng 1 từ.</li>
  <li>Ký tự <code class="language-plaintext highlighter-rouge">#</code> (hash): Thay thế cho 0 hoặc nhiều từ.</li>
</ul>

<p><img src="/assets/img/blog/topic-exchange.png" alt="topic-exchange" /></p>

<p><strong>Use case</strong>: Định tuyến tin nhắn linh hoạt dựa trên nhiều tiêu chí lọc nâng cao.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Khai báo</span>
<span class="n">channel</span><span class="o">.</span><span class="na">exchangeDeclare</span><span class="o">(</span><span class="s">"events.topic"</span><span class="o">,</span> <span class="nc">BuiltinExchangeType</span><span class="o">.</span><span class="na">TOPIC</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">channel</span><span class="o">.</span><span class="na">queueBind</span><span class="o">(</span><span class="s">"vn-orders"</span><span class="o">,</span> <span class="s">"events.topic"</span><span class="o">,</span> <span class="s">"order.*.vn"</span><span class="o">);</span>
<span class="n">channel</span><span class="o">.</span><span class="na">queueBind</span><span class="o">(</span><span class="s">"all-orders"</span><span class="o">,</span> <span class="s">"events.topic"</span><span class="o">,</span> <span class="s">"order.#"</span><span class="o">);</span>
<span class="n">channel</span><span class="o">.</span><span class="na">queueBind</span><span class="o">(</span><span class="s">"all-events"</span><span class="o">,</span> <span class="s">"events.topic"</span><span class="o">,</span> <span class="s">"#"</span><span class="o">);</span>

<span class="c1">// Publish</span>
<span class="n">channel</span><span class="o">.</span><span class="na">basicPublish</span><span class="o">(</span><span class="s">"events.topic"</span><span class="o">,</span> <span class="s">"order.created.vn"</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">message</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>
</code></pre></div></div>

<h4 id="-headers-exchange"># headers exchange</h4>

<p>Định tuyến dựa trên thông tin trong thuộc tính Headers của message thay vị Routing Key. Loại này ít khi được dùng vì hiệu năng kém nhất, nhưng lại cực kỳ mạnh mẽ khi cần filter phức tạp.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Liên kết queue kèm điều kiện match header</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>
<span class="n">headers</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"x-match"</span><span class="o">,</span> <span class="s">"all"</span><span class="o">);</span>  <span class="c1">// "all" = tất cả điều kiện phải đúng (AND), "any" = chỉ cần 1 điều kiện (OR)</span>
<span class="n">headers</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"format"</span><span class="o">,</span> <span class="s">"pdf"</span><span class="o">);</span>
<span class="n">headers</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"type"</span><span class="o">,</span> <span class="s">"report"</span><span class="o">);</span>
<span class="n">channel</span><span class="o">.</span><span class="na">queueBind</span><span class="o">(</span><span class="s">"pdf-reports"</span><span class="o">,</span> <span class="s">"docs.headers"</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="n">headers</span><span class="o">);</span>

<span class="c1">// Publish tin nhắn kèm header tương ứng</span>
<span class="no">AMQP</span><span class="o">.</span><span class="na">BasicProperties</span> <span class="n">props</span> <span class="o">=</span> <span class="k">new</span> <span class="no">AMQP</span><span class="o">.</span><span class="na">BasicProperties</span><span class="o">.</span><span class="na">Builder</span><span class="o">()</span>
    <span class="o">.</span><span class="na">headers</span><span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"format"</span><span class="o">,</span> <span class="s">"pdf"</span><span class="o">,</span> <span class="s">"type"</span><span class="o">,</span> <span class="s">"report"</span><span class="o">))</span>
    <span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="n">channel</span><span class="o">.</span><span class="na">basicPublish</span><span class="o">(</span><span class="s">"docs.headers"</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="n">props</span><span class="o">,</span> <span class="n">message</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span>
</code></pre></div></div>

<h4 id="-so-sánh-nhanh"># so sánh nhanh</h4>

<table>
  <thead>
    <tr>
      <th>Exchange</th>
      <th>Cơ chế định tuyến</th>
      <th>Tốc độ xử lý</th>
      <th>Use Case thực tế</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Direct</td>
      <td>Khớp chính xác</td>
      <td>Nhanh nhất</td>
      <td>Điểm - Điểm, hàng đợi tác vụ</td>
    </tr>
    <tr>
      <td>Fanout</td>
      <td>Gửi hàng loạt</td>
      <td>Rất nhanh</td>
      <td>Phát tán sự kiện (Pub/Sub)</td>
    </tr>
    <tr>
      <td>Topic</td>
      <td>Khớp theo mẫu</td>
      <td>Nhanh</td>
      <td>Định tuyến linh hoạt, lọc sự kiện</td>
    </tr>
    <tr>
      <td>Headers</td>
      <td>Khớp theo Header</td>
      <td>Chậm nhất</td>
      <td>Bộ lọc định tuyến phức tạp</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p><strong>Kinh nghiệm thực tế</strong>: 90% các bài toán hàng ngày của bạn chỉ cần dùng Direct và Topic Exchange là đủ. Fanout hữu ích khi cần broadcast (ví dụ gửi notification). Còn Headers Exchange thì hầu như rất hiếm khi sờ tới.</p>
</blockquote>

<h3 id="-spring-boot--rabbitmq"># spring boot + rabbitmq!</h3>

<p>Để dễ hình dung hơn, chúng ta sẽ cùng nhau xây dựng một hệ thống xử lý đơn hàng (Order Processing) hoàn chỉnh để thấy cách RabbitMQ hoạt động trong thực tế thế nào.</p>

<h4 id="-setup-dự-án"># setup dự án</h4>

<p><strong>pom.xml</strong> — Khai báo các dependency cần thiết:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependencies&gt;</span>
    <span class="c">&lt;!-- Spring Boot Starter --&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-web<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>

    <span class="c">&lt;!-- Spring AMQP — RabbitMQ integration --&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-amqp<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>

    <span class="c">&lt;!-- JSON serialization --&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>com.fasterxml.jackson.core<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>jackson-databind<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>

    <span class="c">&lt;!-- Lombok — giảm boilerplate --&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>org.projectlombok<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>lombok<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;optional&gt;</span>true<span class="nt">&lt;/optional&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
<span class="nt">&lt;/dependencies&gt;</span>
</code></pre></div></div>

<p><strong>application.yml</strong> — Cấu hình kết nối:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spring</span><span class="pi">:</span>
  <span class="na">rabbitmq</span><span class="pi">:</span>
    <span class="na">host</span><span class="pi">:</span> <span class="s">localhost</span>
    <span class="na">port</span><span class="pi">:</span> <span class="m">5672</span>
    <span class="na">username</span><span class="pi">:</span> <span class="s">guest</span>
    <span class="na">password</span><span class="pi">:</span> <span class="s">guest</span>
    <span class="na">virtual-host</span><span class="pi">:</span> <span class="s">/</span>
    <span class="c1"># Connection pooling</span>
    <span class="na">connection-timeout</span><span class="pi">:</span> <span class="m">5000</span>
    <span class="c1"># Listener configuration</span>
    <span class="na">listener</span><span class="pi">:</span>
      <span class="na">simple</span><span class="pi">:</span>
        <span class="na">acknowledge-mode</span><span class="pi">:</span> <span class="s">manual</span> <span class="c1"># QUAN TRỌNG: Manual ack cho production</span>
        <span class="na">prefetch</span><span class="pi">:</span> <span class="m">10</span> <span class="c1"># Số message consumer nhận trước khi ack</span>
        <span class="na">concurrency</span><span class="pi">:</span> <span class="m">3</span> <span class="c1"># Min consumer threads</span>
        <span class="na">max-concurrency</span><span class="pi">:</span> <span class="m">10</span> <span class="c1"># Max consumer threads</span>
        <span class="na">retry</span><span class="pi">:</span>
          <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
          <span class="na">initial-interval</span><span class="pi">:</span> <span class="m">1000</span> <span class="c1"># 1 giây</span>
          <span class="na">max-interval</span><span class="pi">:</span> <span class="m">10000</span> <span class="c1"># 10 giây</span>
          <span class="na">multiplier</span><span class="pi">:</span> <span class="m">2.0</span> <span class="c1"># Exponential backoff</span>
          <span class="na">max-attempts</span><span class="pi">:</span> <span class="m">3</span>
    <span class="c1"># Publisher confirms</span>
    <span class="na">publisher-confirm-type</span><span class="pi">:</span> <span class="s">correlated</span> <span class="c1"># Async publisher confirms</span>
    <span class="na">publisher-returns</span><span class="pi">:</span> <span class="no">true</span> <span class="c1"># Return unroutable messages</span>
</code></pre></div></div>

<h4 id="-rabbitmq-config"># rabbitmq config</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.example.order.config</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">org.springframework.amqp.core.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.rabbit.connection.ConnectionFactory</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.rabbit.core.RabbitTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.support.converter.Jackson2JsonMessageConverter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.support.converter.MessageConverter</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Bean</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Configuration</span><span class="o">;</span>

<span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RabbitMQConfig</span> <span class="o">{</span>

    <span class="c1">// ==================== Constants ====================</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">ORDER_EXCHANGE</span> <span class="o">=</span> <span class="s">"order.exchange"</span><span class="o">;</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">ORDER_QUEUE</span> <span class="o">=</span> <span class="s">"order.processing.queue"</span><span class="o">;</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">ORDER_ROUTING_KEY</span> <span class="o">=</span> <span class="s">"order.created"</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">NOTIFICATION_EXCHANGE</span> <span class="o">=</span> <span class="s">"notification.exchange"</span><span class="o">;</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EMAIL_QUEUE</span> <span class="o">=</span> <span class="s">"notification.email.queue"</span><span class="o">;</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">SMS_QUEUE</span> <span class="o">=</span> <span class="s">"notification.sms.queue"</span><span class="o">;</span>

    <span class="c1">// Dead Letter</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DLX_EXCHANGE</span> <span class="o">=</span> <span class="s">"dlx.exchange"</span><span class="o">;</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DLX_QUEUE</span> <span class="o">=</span> <span class="s">"dlx.queue"</span><span class="o">;</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DLX_ROUTING_KEY</span> <span class="o">=</span> <span class="s">"dlx.routing"</span><span class="o">;</span>

    <span class="c1">// ==================== Exchanges ====================</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">TopicExchange</span> <span class="nf">orderExchange</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">ExchangeBuilder</span>
                <span class="o">.</span><span class="na">topicExchange</span><span class="o">(</span><span class="no">ORDER_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">FanoutExchange</span> <span class="nf">notificationExchange</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">ExchangeBuilder</span>
                <span class="o">.</span><span class="na">fanoutExchange</span><span class="o">(</span><span class="no">NOTIFICATION_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">DirectExchange</span> <span class="nf">deadLetterExchange</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">ExchangeBuilder</span>
                <span class="o">.</span><span class="na">directExchange</span><span class="o">(</span><span class="no">DLX_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">// ==================== Queues ====================</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Queue</span> <span class="nf">orderQueue</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">QueueBuilder</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="no">ORDER_QUEUE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-exchange"</span><span class="o">,</span> <span class="no">DLX_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-routing-key"</span><span class="o">,</span> <span class="no">DLX_ROUTING_KEY</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-message-ttl"</span><span class="o">,</span> <span class="mi">300000</span><span class="o">)</span> <span class="c1">// 5 phút TTL</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Queue</span> <span class="nf">emailQueue</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">QueueBuilder</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="no">EMAIL_QUEUE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-exchange"</span><span class="o">,</span> <span class="no">DLX_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-routing-key"</span><span class="o">,</span> <span class="no">DLX_ROUTING_KEY</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Queue</span> <span class="nf">smsQueue</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">QueueBuilder</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="no">SMS_QUEUE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-exchange"</span><span class="o">,</span> <span class="no">DLX_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-routing-key"</span><span class="o">,</span> <span class="no">DLX_ROUTING_KEY</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Queue</span> <span class="nf">deadLetterQueue</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">QueueBuilder</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="no">DLX_QUEUE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">// ==================== Bindings ====================</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Binding</span> <span class="nf">orderBinding</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">BindingBuilder</span>
                <span class="o">.</span><span class="na">bind</span><span class="o">(</span><span class="n">orderQueue</span><span class="o">())</span>
                <span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="n">orderExchange</span><span class="o">())</span>
                <span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="s">"order.#"</span><span class="o">);</span> <span class="c1">// Match tất cả order events</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Binding</span> <span class="nf">emailBinding</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">BindingBuilder</span>
                <span class="o">.</span><span class="na">bind</span><span class="o">(</span><span class="n">emailQueue</span><span class="o">())</span>
                <span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="n">notificationExchange</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Binding</span> <span class="nf">smsBinding</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">BindingBuilder</span>
                <span class="o">.</span><span class="na">bind</span><span class="o">(</span><span class="n">smsQueue</span><span class="o">())</span>
                <span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="n">notificationExchange</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Binding</span> <span class="nf">deadLetterBinding</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">BindingBuilder</span>
                <span class="o">.</span><span class="na">bind</span><span class="o">(</span><span class="n">deadLetterQueue</span><span class="o">())</span>
                <span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="n">deadLetterExchange</span><span class="o">())</span>
                <span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="no">DLX_ROUTING_KEY</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// ==================== Message Converter ====================</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">MessageConverter</span> <span class="nf">jsonMessageConverter</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Jackson2JsonMessageConverter</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">// ==================== RabbitTemplate ====================</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">RabbitTemplate</span> <span class="nf">rabbitTemplate</span><span class="o">(</span><span class="nc">ConnectionFactory</span> <span class="n">connectionFactory</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">RabbitTemplate</span> <span class="n">template</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RabbitTemplate</span><span class="o">(</span><span class="n">connectionFactory</span><span class="o">);</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setMessageConverter</span><span class="o">(</span><span class="n">jsonMessageConverter</span><span class="o">());</span>

        <span class="c1">// Publisher Confirms — biết chắc message đã đến Exchange</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setConfirmCallback</span><span class="o">((</span><span class="n">correlationData</span><span class="o">,</span> <span class="n">ack</span><span class="o">,</span> <span class="n">cause</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">ack</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Message delivered to exchange: {}"</span><span class="o">,</span> <span class="n">correlationData</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Message FAILED to deliver to exchange: {}, cause: {}"</span><span class="o">,</span>
                        <span class="n">correlationData</span><span class="o">,</span> <span class="n">cause</span><span class="o">);</span>
                <span class="c1">// TODO: Retry logic hoặc save to DB</span>
            <span class="o">}</span>
        <span class="o">});</span>

        <span class="c1">// Publisher Returns — message không route được đến queue nào</span>
        <span class="n">template</span><span class="o">.</span><span class="na">setReturnsCallback</span><span class="o">(</span><span class="n">returned</span> <span class="o">-&gt;</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Message returned: exchange={}, routingKey={}, replyCode={}, replyText={}"</span><span class="o">,</span>
                    <span class="n">returned</span><span class="o">.</span><span class="na">getExchange</span><span class="o">(),</span>
                    <span class="n">returned</span><span class="o">.</span><span class="na">getRoutingKey</span><span class="o">(),</span>
                    <span class="n">returned</span><span class="o">.</span><span class="na">getReplyCode</span><span class="o">(),</span>
                    <span class="n">returned</span><span class="o">.</span><span class="na">getReplyText</span><span class="o">());</span>
        <span class="o">});</span>

        <span class="n">template</span><span class="o">.</span><span class="na">setMandatory</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="c1">// Enable returns cho unroutable messages</span>
        <span class="k">return</span> <span class="n">template</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// ==================== Listener Container Factory ====================</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">SimpleRabbitListenerContainerFactory</span> <span class="nf">rabbitListenerContainerFactory</span><span class="o">(</span>
            <span class="nc">ConnectionFactory</span> <span class="n">connectionFactory</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">SimpleRabbitListenerContainerFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleRabbitListenerContainerFactory</span><span class="o">();</span>
        <span class="n">factory</span><span class="o">.</span><span class="na">setConnectionFactory</span><span class="o">(</span><span class="n">connectionFactory</span><span class="o">);</span>
        <span class="n">factory</span><span class="o">.</span><span class="na">setMessageConverter</span><span class="o">(</span><span class="n">jsonMessageConverter</span><span class="o">());</span>
        <span class="n">factory</span><span class="o">.</span><span class="na">setAcknowledgeMode</span><span class="o">(</span><span class="nc">AcknowledgeMode</span><span class="o">.</span><span class="na">MANUAL</span><span class="o">);</span>
        <span class="n">factory</span><span class="o">.</span><span class="na">setPrefetchCount</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>
        <span class="n">factory</span><span class="o">.</span><span class="na">setConcurrentConsumers</span><span class="o">(</span><span class="mi">3</span><span class="o">);</span>
        <span class="n">factory</span><span class="o">.</span><span class="na">setMaxConcurrentConsumers</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">factory</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-domain-models"># domain models</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.example.order.model</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">lombok.AllArgsConstructor</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.Builder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.Data</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.NoArgsConstructor</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.Serializable</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.LocalDateTime</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>

<span class="nd">@Data</span>
<span class="nd">@Builder</span>
<span class="nd">@NoArgsConstructor</span>
<span class="nd">@AllArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderEvent</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="nc">String</span> <span class="n">orderId</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">customerId</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">customerEmail</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">customerPhone</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderItem</span><span class="o">&gt;</span> <span class="n">items</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">totalAmount</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">currency</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">OrderStatus</span> <span class="n">status</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">LocalDateTime</span> <span class="n">createdAt</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">eventType</span><span class="o">;</span> <span class="c1">// "created", "paid", "shipped", "cancelled"</span>

    <span class="nd">@Data</span>
    <span class="nd">@Builder</span>
    <span class="nd">@NoArgsConstructor</span>
    <span class="nd">@AllArgsConstructor</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">OrderItem</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>
        <span class="kd">private</span> <span class="nc">String</span> <span class="n">productId</span><span class="o">;</span>
        <span class="kd">private</span> <span class="nc">String</span> <span class="n">productName</span><span class="o">;</span>
        <span class="kd">private</span> <span class="kt">int</span> <span class="n">quantity</span><span class="o">;</span>
        <span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">price</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">enum</span> <span class="nc">OrderStatus</span> <span class="o">{</span>
        <span class="no">PENDING</span><span class="o">,</span> <span class="no">CONFIRMED</span><span class="o">,</span> <span class="no">PAID</span><span class="o">,</span> <span class="no">SHIPPED</span><span class="o">,</span> <span class="no">DELIVERED</span><span class="o">,</span> <span class="no">CANCELLED</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-producer--gửi-message"># producer — gửi message</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.example.order.producer</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.example.order.config.RabbitMQConfig</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.example.order.model.OrderEvent</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.RequiredArgsConstructor</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.rabbit.connection.CorrelationData</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.rabbit.core.RabbitTemplate</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Service</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.util.UUID</span><span class="o">;</span>

<span class="nd">@Slf4j</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderProducer</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RabbitTemplate</span> <span class="n">rabbitTemplate</span><span class="o">;</span>

    <span class="cm">/**
     * Publish order event đến Topic Exchange.
     * Routing key format: order.{eventType}
     * Ví dụ: order.created, order.paid, order.shipped
     */</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">publishOrderEvent</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">routingKey</span> <span class="o">=</span> <span class="s">"order."</span> <span class="o">+</span> <span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">();</span>
        <span class="nc">String</span> <span class="n">correlationId</span> <span class="o">=</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span>

        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Publishing order event: orderId={}, type={}, correlationId={}"</span><span class="o">,</span>
                <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">(),</span> <span class="n">correlationId</span><span class="o">);</span>

        <span class="nc">CorrelationData</span> <span class="n">correlationData</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CorrelationData</span><span class="o">(</span><span class="n">correlationId</span><span class="o">);</span>

        <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span>
                <span class="nc">RabbitMQConfig</span><span class="o">.</span><span class="na">ORDER_EXCHANGE</span><span class="o">,</span>
                <span class="n">routingKey</span><span class="o">,</span>
                <span class="n">event</span><span class="o">,</span>
                <span class="n">message</span> <span class="o">-&gt;</span> <span class="o">{</span>
                    <span class="c1">// Custom message properties</span>
                    <span class="n">message</span><span class="o">.</span><span class="na">getMessageProperties</span><span class="o">().</span><span class="na">setMessageId</span><span class="o">(</span><span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">());</span>
                    <span class="n">message</span><span class="o">.</span><span class="na">getMessageProperties</span><span class="o">().</span><span class="na">setCorrelationId</span><span class="o">(</span><span class="n">correlationId</span><span class="o">);</span>
                    <span class="n">message</span><span class="o">.</span><span class="na">getMessageProperties</span><span class="o">().</span><span class="na">setHeader</span><span class="o">(</span><span class="s">"source"</span><span class="o">,</span> <span class="s">"order-service"</span><span class="o">);</span>
                    <span class="n">message</span><span class="o">.</span><span class="na">getMessageProperties</span><span class="o">().</span><span class="na">setHeader</span><span class="o">(</span><span class="s">"version"</span><span class="o">,</span> <span class="s">"1.0"</span><span class="o">);</span>
                    <span class="k">return</span> <span class="n">message</span><span class="o">;</span>
                <span class="o">},</span>
                <span class="n">correlationData</span>
        <span class="o">);</span>

        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Order event published successfully: orderId={}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="cm">/**
     * Broadcast notification đến tất cả notification consumers.
     * Dùng Fanout Exchange — tất cả queue bound sẽ nhận message.
     */</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">broadcastNotification</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Broadcasting notification for order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>

        <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span>
                <span class="nc">RabbitMQConfig</span><span class="o">.</span><span class="na">NOTIFICATION_EXCHANGE</span><span class="o">,</span>
                <span class="s">""</span><span class="o">,</span> <span class="c1">// Fanout exchange bỏ qua routing key</span>
                <span class="n">event</span>
        <span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-consumer--nhận-và-xử-lý-message"># consumer — nhận và xử lý message</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.example.order.consumer</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.example.order.model.OrderEvent</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.rabbitmq.client.Channel</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.rabbit.annotation.RabbitListener</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.support.AmqpHeaders</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.messaging.handler.annotation.Header</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.messaging.handler.annotation.Payload</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Component</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.io.IOException</span><span class="o">;</span>

<span class="nd">@Slf4j</span>
<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderConsumer</span> <span class="o">{</span>

    <span class="cm">/**
     * Consumer chính xử lý order events.
     *
     * Manual Ack pattern:
     * 1. Nhận message
     * 2. Xử lý business logic
     * 3. Ack nếu thành công, Nack nếu thất bại
     *
     * QUAN TRỌNG: Luôn ack/nack trong finally block để tránh message bị stuck
     */</span>
    <span class="nd">@RabbitListener</span><span class="o">(</span>
            <span class="n">queues</span> <span class="o">=</span> <span class="s">"order.processing.queue"</span><span class="o">,</span>
            <span class="n">containerFactory</span> <span class="o">=</span> <span class="s">"rabbitListenerContainerFactory"</span>
    <span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleOrderEvent</span><span class="o">(</span>
            <span class="nd">@Payload</span> <span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">,</span>
            <span class="nd">@Header</span><span class="o">(</span><span class="nc">AmqpHeaders</span><span class="o">.</span><span class="na">DELIVERY_TAG</span><span class="o">)</span> <span class="kt">long</span> <span class="n">deliveryTag</span><span class="o">,</span>
            <span class="nd">@Header</span><span class="o">(</span><span class="nc">AmqpHeaders</span><span class="o">.</span><span class="na">REDELIVERED</span><span class="o">)</span> <span class="kt">boolean</span> <span class="n">redelivered</span><span class="o">,</span>
            <span class="nc">Channel</span> <span class="n">channel</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>

        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Received order event: orderId={}, type={}, redelivered={}"</span><span class="o">,</span>
                <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">(),</span> <span class="n">redelivered</span><span class="o">);</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">// ========== Business Logic ==========</span>
            <span class="k">switch</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">())</span> <span class="o">{</span>
                <span class="k">case</span> <span class="s">"created"</span><span class="o">:</span>
                    <span class="n">processNewOrder</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="s">"paid"</span><span class="o">:</span>
                    <span class="n">processPayment</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="s">"shipped"</span><span class="o">:</span>
                    <span class="n">processShipment</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="s">"cancelled"</span><span class="o">:</span>
                    <span class="n">processCancellation</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">default</span><span class="o">:</span>
                    <span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Unknown event type: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">());</span>
            <span class="o">}</span>

            <span class="c1">// ========== ACK — Xử lý thành công ==========</span>
            <span class="n">channel</span><span class="o">.</span><span class="na">basicAck</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
            <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Order event processed successfully: orderId={}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>

        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">BusinessException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// Business error — không cần retry, gửi đến DLX</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Business error processing order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
            <span class="n">channel</span><span class="o">.</span><span class="na">basicReject</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="c1">// false = không requeue → đến DLX</span>

        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">TransientException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// Transient error (DB timeout, network issue) — có thể retry</span>
            <span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Transient error processing order: {}, redelivered: {}"</span><span class="o">,</span>
                    <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">redelivered</span><span class="o">);</span>

            <span class="k">if</span> <span class="o">(</span><span class="n">redelivered</span><span class="o">)</span> <span class="o">{</span>
                <span class="c1">// Đã retry rồi mà vẫn fail → gửi đến DLX</span>
                <span class="n">channel</span><span class="o">.</span><span class="na">basicReject</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
                <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Message sent to DLX after retry: orderId={}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="c1">// Retry lần đầu — requeue</span>
                <span class="n">channel</span><span class="o">.</span><span class="na">basicNack</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="c1">// true = requeue</span>
            <span class="o">}</span>

        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// Unexpected error</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Unexpected error processing order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
            <span class="n">channel</span><span class="o">.</span><span class="na">basicReject</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="c1">// Gửi đến DLX</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">processNewOrder</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Processing new order: {} - Amount: {} {}"</span><span class="o">,</span>
                <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getTotalAmount</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getCurrency</span><span class="o">());</span>
        <span class="c1">// Validate inventory, reserve stock, create order record...</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">processPayment</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Processing payment for order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>
        <span class="c1">// Update order status, trigger fulfillment...</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">processShipment</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Processing shipment for order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>
        <span class="c1">// Update tracking info, notify customer...</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">processCancellation</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Processing cancellation for order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>
        <span class="c1">// Release inventory, process refund...</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-notification-consumers"># notification consumers</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.example.order.consumer</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.example.order.model.OrderEvent</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.rabbitmq.client.Channel</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.rabbit.annotation.RabbitListener</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.amqp.support.AmqpHeaders</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.messaging.handler.annotation.Header</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.messaging.handler.annotation.Payload</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.stereotype.Component</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.io.IOException</span><span class="o">;</span>

<span class="nd">@Slf4j</span>
<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">NotificationConsumer</span> <span class="o">{</span>

    <span class="cm">/**
     * Email notification consumer.
     * Nhận từ Fanout Exchange — mọi order event đều trigger email.
     */</span>
    <span class="nd">@RabbitListener</span><span class="o">(</span><span class="n">queues</span> <span class="o">=</span> <span class="s">"notification.email.queue"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleEmailNotification</span><span class="o">(</span>
            <span class="nd">@Payload</span> <span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">,</span>
            <span class="nd">@Header</span><span class="o">(</span><span class="nc">AmqpHeaders</span><span class="o">.</span><span class="na">DELIVERY_TAG</span><span class="o">)</span> <span class="kt">long</span> <span class="n">deliveryTag</span><span class="o">,</span>
            <span class="nc">Channel</span> <span class="n">channel</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Sending email notification for order: {} to {}"</span><span class="o">,</span>
                    <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getCustomerEmail</span><span class="o">());</span>

            <span class="c1">// Gửi email logic...</span>
            <span class="c1">// emailService.sendOrderConfirmation(event);</span>

            <span class="n">channel</span><span class="o">.</span><span class="na">basicAck</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Failed to send email for order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
            <span class="n">channel</span><span class="o">.</span><span class="na">basicNack</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="c1">// Retry</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="cm">/**
     * SMS notification consumer.
     * Cũng nhận từ Fanout Exchange — cùng message, khác channel.
     */</span>
    <span class="nd">@RabbitListener</span><span class="o">(</span><span class="n">queues</span> <span class="o">=</span> <span class="s">"notification.sms.queue"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleSmsNotification</span><span class="o">(</span>
            <span class="nd">@Payload</span> <span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">,</span>
            <span class="nd">@Header</span><span class="o">(</span><span class="nc">AmqpHeaders</span><span class="o">.</span><span class="na">DELIVERY_TAG</span><span class="o">)</span> <span class="kt">long</span> <span class="n">deliveryTag</span><span class="o">,</span>
            <span class="nc">Channel</span> <span class="n">channel</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Sending SMS notification for order: {} to {}"</span><span class="o">,</span>
                    <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getCustomerPhone</span><span class="o">());</span>

            <span class="c1">// Gửi SMS logic...</span>
            <span class="c1">// smsService.sendOrderSms(event);</span>

            <span class="n">channel</span><span class="o">.</span><span class="na">basicAck</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Failed to send SMS for order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
            <span class="n">channel</span><span class="o">.</span><span class="na">basicNack</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-rest-controller--trigger-point"># rest controller — trigger point</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.example.order.controller</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">com.example.order.model.OrderEvent</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.example.order.producer.OrderProducer</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.RequiredArgsConstructor</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.http.ResponseEntity</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.web.bind.annotation.*</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.math.BigDecimal</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.LocalDateTime</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.UUID</span><span class="o">;</span>

<span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/api/orders"</span><span class="o">)</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderController</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderProducer</span> <span class="n">orderProducer</span><span class="o">;</span>

    <span class="nd">@PostMapping</span>
    <span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;&gt;</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nd">@RequestBody</span> <span class="nc">OrderEvent</span> <span class="n">orderRequest</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// Generate order ID</span>
        <span class="nc">String</span> <span class="n">orderId</span> <span class="o">=</span> <span class="s">"ORD-"</span> <span class="o">+</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">().</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">8</span><span class="o">).</span><span class="na">toUpperCase</span><span class="o">();</span>
        <span class="n">orderRequest</span><span class="o">.</span><span class="na">setOrderId</span><span class="o">(</span><span class="n">orderId</span><span class="o">);</span>
        <span class="n">orderRequest</span><span class="o">.</span><span class="na">setStatus</span><span class="o">(</span><span class="nc">OrderEvent</span><span class="o">.</span><span class="na">OrderStatus</span><span class="o">.</span><span class="na">PENDING</span><span class="o">);</span>
        <span class="n">orderRequest</span><span class="o">.</span><span class="na">setCreatedAt</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">());</span>
        <span class="n">orderRequest</span><span class="o">.</span><span class="na">setEventType</span><span class="o">(</span><span class="s">"created"</span><span class="o">);</span>

        <span class="c1">// Publish to order processing queue</span>
        <span class="n">orderProducer</span><span class="o">.</span><span class="na">publishOrderEvent</span><span class="o">(</span><span class="n">orderRequest</span><span class="o">);</span>

        <span class="c1">// Broadcast notification</span>
        <span class="n">orderProducer</span><span class="o">.</span><span class="na">broadcastNotification</span><span class="o">(</span><span class="n">orderRequest</span><span class="o">);</span>

        <span class="c1">// Response ngay lập tức — không chờ processing</span>
        <span class="k">return</span> <span class="nc">ResponseEntity</span><span class="o">.</span><span class="na">accepted</span><span class="o">().</span><span class="na">body</span><span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
                <span class="s">"orderId"</span><span class="o">,</span> <span class="n">orderId</span><span class="o">,</span>
                <span class="s">"status"</span><span class="o">,</span> <span class="s">"ACCEPTED"</span><span class="o">,</span>
                <span class="s">"message"</span><span class="o">,</span> <span class="s">"Order is being processed"</span>
        <span class="o">));</span>
    <span class="o">}</span>

    <span class="cm">/**
     * Demo endpoint để test các loại event
     */</span>
    <span class="nd">@PostMapping</span><span class="o">(</span><span class="s">"/{orderId}/events/{eventType}"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;&gt;</span> <span class="nf">publishEvent</span><span class="o">(</span>
            <span class="nd">@PathVariable</span> <span class="nc">String</span> <span class="n">orderId</span><span class="o">,</span>
            <span class="nd">@PathVariable</span> <span class="nc">String</span> <span class="n">eventType</span><span class="o">)</span> <span class="o">{</span>

        <span class="nc">OrderEvent</span> <span class="n">event</span> <span class="o">=</span> <span class="nc">OrderEvent</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
                <span class="o">.</span><span class="na">orderId</span><span class="o">(</span><span class="n">orderId</span><span class="o">)</span>
                <span class="o">.</span><span class="na">eventType</span><span class="o">(</span><span class="n">eventType</span><span class="o">)</span>
                <span class="o">.</span><span class="na">createdAt</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">())</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>

        <span class="n">orderProducer</span><span class="o">.</span><span class="na">publishOrderEvent</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>

        <span class="k">return</span> <span class="nc">ResponseEntity</span><span class="o">.</span><span class="na">accepted</span><span class="o">().</span><span class="na">body</span><span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
                <span class="s">"orderId"</span><span class="o">,</span> <span class="n">orderId</span><span class="o">,</span>
                <span class="s">"eventType"</span><span class="o">,</span> <span class="n">eventType</span><span class="o">,</span>
                <span class="s">"status"</span><span class="o">,</span> <span class="s">"EVENT_PUBLISHED"</span>
        <span class="o">));</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-chạy-thử"># chạy thử</h4>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. Start RabbitMQ bằng Docker</span>
docker run <span class="nt">-d</span> <span class="nt">--name</span> rabbitmq <span class="se">\</span>
  <span class="nt">-p</span> 5672:5672 <span class="se">\</span>
  <span class="nt">-p</span> 15672:15672 <span class="se">\</span>
  rabbitmq:3-management

<span class="c"># Management UI: http://localhost:15672 (guest/guest)</span>

<span class="c"># 2. Start Spring Boot app</span>
./mvnw spring-boot:run

<span class="c"># 3. Tạo order</span>
curl <span class="nt">-X</span> POST http://localhost:8080/api/orders <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "customerId": "CUST-001",
    "customerEmail": "user@example.com",
    "customerPhone": "+84123456789",
    "items": [
      {
        "productId": "PROD-001",
        "productName": "Mechanical Keyboard",
        "quantity": 1,
        "price": 150.00
      }
    ],
    "totalAmount": 150.00,
    "currency": "USD"
  }'</span>

<span class="c"># Response (ngay lập tức, &lt; 10ms):</span>
<span class="c"># {</span>
<span class="c">#   "orderId": "ORD-A1B2C3D4",</span>
<span class="c">#   "status": "ACCEPTED",</span>
<span class="c">#   "message": "Order is being processed"</span>
<span class="c"># }</span>

<span class="c"># 4. Publish event</span>
curl <span class="nt">-X</span> POST http://localhost:8080/api/orders/ORD-A1B2C3D4/events/paid

<span class="c"># 5. Xem logs — bạn sẽ thấy:</span>
<span class="c"># OrderConsumer: Received order event: orderId=ORD-A1B2C3D4, type=created</span>
<span class="c"># NotificationConsumer: Sending email notification for order: ORD-A1B2C3D4</span>
<span class="c"># NotificationConsumer: Sending SMS notification for order: ORD-A1B2C3D4</span>
</code></pre></div></div>

<h3 id="-reliability-patterns"># reliability patterns</h3>

<p>Đưa hệ thống lên Production thực tế không phải là sân chơi trải đầy hoa hồng. Tin nhắn bị mất mát đồng nghĩa với việc đơn hàng thất lạc, khách hàng phàn nàn và trực tiếp gây thiệt hại kinh doanh. Dưới đây là các kỹ thuật thực chiến giúp đảm bảo thông điệp của bạn luôn được truyền tải an toàn.</p>

<h4 id="-publisher-confirms-xác-nhận-từ-phía-người-gửi"># publisher confirms (xác nhận từ phía người gửi)</h4>

<p>Cơ chế này giúp Producer chắc chắn rằng message đã truyền tới được Exchange của RabbitMQ thành công.</p>

<pre><code class="language-mermaid">Producer ── publish ──▶ Exchange
         ◀──confirm──
</code></pre>

<p>Mặc dù trong file cấu hình Spring Boot ở trên chúng ta đã bật sẵn <code class="language-plaintext highlighter-rouge">publisher-confirm-type: correlated</code>, nhưng để tăng tính tin cậy tuyệt đối (đặc biệt khi mạng chập chờn hoặc Broker quá tải), chúng ta nên áp dụng <strong>Transactional Outbox Pattern</strong>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Slf4j</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ReliableProducer</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RabbitTemplate</span> <span class="n">rabbitTemplate</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderOutboxRepository</span> <span class="n">outboxRepository</span><span class="o">;</span> <span class="c1">// Outbox pattern</span>

    <span class="cm">/**
     * Transactional Outbox Pattern:
     * 1. Save message vào DB (cùng transaction với business logic)
     * 2. Publish message đến RabbitMQ
     * 3. Nếu publish thành công → mark as sent
     * 4. Nếu publish thất bại → scheduler retry
     */</span>
    <span class="nd">@Transactional</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">publishWithOutbox</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// Step 1: Save to outbox table</span>
        <span class="nc">OrderOutbox</span> <span class="n">outbox</span> <span class="o">=</span> <span class="nc">OrderOutbox</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
                <span class="o">.</span><span class="na">messageId</span><span class="o">(</span><span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">().</span><span class="na">toString</span><span class="o">())</span>
                <span class="o">.</span><span class="na">exchange</span><span class="o">(</span><span class="nc">RabbitMQConfig</span><span class="o">.</span><span class="na">ORDER_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">routingKey</span><span class="o">(</span><span class="s">"order."</span> <span class="o">+</span> <span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">())</span>
                <span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="n">objectMapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">event</span><span class="o">))</span>
                <span class="o">.</span><span class="na">status</span><span class="o">(</span><span class="nc">OutboxStatus</span><span class="o">.</span><span class="na">PENDING</span><span class="o">)</span>
                <span class="o">.</span><span class="na">createdAt</span><span class="o">(</span><span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">())</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
        <span class="n">outboxRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">outbox</span><span class="o">);</span>

        <span class="c1">// Step 2: Try to publish</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="nc">CorrelationData</span> <span class="n">correlationData</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CorrelationData</span><span class="o">(</span><span class="n">outbox</span><span class="o">.</span><span class="na">getMessageId</span><span class="o">());</span>
            <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span>
                    <span class="n">outbox</span><span class="o">.</span><span class="na">getExchange</span><span class="o">(),</span>
                    <span class="n">outbox</span><span class="o">.</span><span class="na">getRoutingKey</span><span class="o">(),</span>
                    <span class="n">event</span><span class="o">,</span>
                    <span class="n">correlationData</span>
            <span class="o">);</span>

            <span class="c1">// Step 3: Mark as sent (confirm callback sẽ verify)</span>
            <span class="n">outbox</span><span class="o">.</span><span class="na">setStatus</span><span class="o">(</span><span class="nc">OutboxStatus</span><span class="o">.</span><span class="na">SENT</span><span class="o">);</span>
            <span class="n">outboxRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">outbox</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Failed to publish message: {}"</span><span class="o">,</span> <span class="n">outbox</span><span class="o">.</span><span class="na">getMessageId</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
            <span class="c1">// Message vẫn ở PENDING → scheduler sẽ retry</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="cm">/**
     * Scheduler retry cho messages PENDING quá lâu.
     * Chạy mỗi 30 giây.
     */</span>
    <span class="nd">@Scheduled</span><span class="o">(</span><span class="n">fixedDelay</span> <span class="o">=</span> <span class="mi">30000</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">retryPendingMessages</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderOutbox</span><span class="o">&gt;</span> <span class="n">pendingMessages</span> <span class="o">=</span> <span class="n">outboxRepository</span>
                <span class="o">.</span><span class="na">findByStatusAndCreatedAtBefore</span><span class="o">(</span>
                        <span class="nc">OutboxStatus</span><span class="o">.</span><span class="na">PENDING</span><span class="o">,</span>
                        <span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">().</span><span class="na">minusMinutes</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>
                <span class="o">);</span>

        <span class="k">for</span> <span class="o">(</span><span class="nc">OrderOutbox</span> <span class="n">outbox</span> <span class="o">:</span> <span class="n">pendingMessages</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span>
                        <span class="n">outbox</span><span class="o">.</span><span class="na">getExchange</span><span class="o">(),</span>
                        <span class="n">outbox</span><span class="o">.</span><span class="na">getRoutingKey</span><span class="o">(),</span>
                        <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="n">outbox</span><span class="o">.</span><span class="na">getPayload</span><span class="o">(),</span> <span class="nc">OrderEvent</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
                <span class="o">);</span>
                <span class="n">outbox</span><span class="o">.</span><span class="na">setStatus</span><span class="o">(</span><span class="nc">OutboxStatus</span><span class="o">.</span><span class="na">SENT</span><span class="o">);</span>
                <span class="n">outbox</span><span class="o">.</span><span class="na">setRetryCount</span><span class="o">(</span><span class="n">outbox</span><span class="o">.</span><span class="na">getRetryCount</span><span class="o">()</span> <span class="o">+</span> <span class="mi">1</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">outbox</span><span class="o">.</span><span class="na">setRetryCount</span><span class="o">(</span><span class="n">outbox</span><span class="o">.</span><span class="na">getRetryCount</span><span class="o">()</span> <span class="o">+</span> <span class="mi">1</span><span class="o">);</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">outbox</span><span class="o">.</span><span class="na">getRetryCount</span><span class="o">()</span> <span class="o">&gt;=</span> <span class="mi">5</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">outbox</span><span class="o">.</span><span class="na">setStatus</span><span class="o">(</span><span class="nc">OutboxStatus</span><span class="o">.</span><span class="na">FAILED</span><span class="o">);</span>
                    <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Message permanently failed after 5 retries: {}"</span><span class="o">,</span>
                            <span class="n">outbox</span><span class="o">.</span><span class="na">getMessageId</span><span class="o">());</span>
                <span class="o">}</span>
            <span class="o">}</span>
            <span class="n">outboxRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">outbox</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-consumer-idempotency-tránh-xử-lý-trùng-lặp"># consumer idempotency (tránh xử lý trùng lặp)</h4>

<p>Trong hệ thống phân tán với cơ chế truyền tin ít nhất một lần (At-Least-Once Delivery), tin nhắn hoàn toàn có thể bị gửi trùng lặp do sự cố đường truyền (ví dụ Consumer đã xử lý xong nhưng mất kết nối nên không kịp gửi ack về). Do đó, Consumer bắt buộc phải được thiết kế theo cơ chế Idempotent (đảm bảo dù nhận tin nhiều lần thì kết quả cuối cùng vẫn không thay đổi).</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Slf4j</span>
<span class="nd">@Component</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">IdempotentOrderConsumer</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ProcessedMessageRepository</span> <span class="n">processedMessageRepo</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderService</span> <span class="n">orderService</span><span class="o">;</span>

    <span class="nd">@RabbitListener</span><span class="o">(</span><span class="n">queues</span> <span class="o">=</span> <span class="s">"order.processing.queue"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleOrderEvent</span><span class="o">(</span>
            <span class="nd">@Payload</span> <span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">,</span>
            <span class="nd">@Header</span><span class="o">(</span><span class="nc">AmqpHeaders</span><span class="o">.</span><span class="na">DELIVERY_TAG</span><span class="o">)</span> <span class="kt">long</span> <span class="n">deliveryTag</span><span class="o">,</span>
            <span class="nd">@Header</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="nc">AmqpHeaders</span><span class="o">.</span><span class="na">MESSAGE_ID</span><span class="o">,</span> <span class="n">required</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span> <span class="nc">String</span> <span class="n">messageId</span><span class="o">,</span>
            <span class="nc">Channel</span> <span class="n">channel</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>

        <span class="c1">// Idempotency check — đã xử lý message này chưa?</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">messageId</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">processedMessageRepo</span><span class="o">.</span><span class="na">existsByMessageId</span><span class="o">(</span><span class="n">messageId</span><span class="o">))</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Duplicate message detected, skipping: messageId={}"</span><span class="o">,</span> <span class="n">messageId</span><span class="o">);</span>
            <span class="n">channel</span><span class="o">.</span><span class="na">basicAck</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="c1">// Ack để không nhận lại</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">// Process order</span>
            <span class="n">orderService</span><span class="o">.</span><span class="na">processOrder</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>

            <span class="c1">// Mark as processed</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">messageId</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">processedMessageRepo</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="k">new</span> <span class="nc">ProcessedMessage</span><span class="o">(</span>
                        <span class="n">messageId</span><span class="o">,</span>
                        <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span>
                        <span class="nc">LocalDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">()</span>
                <span class="o">));</span>
            <span class="o">}</span>

            <span class="n">channel</span><span class="o">.</span><span class="na">basicAck</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Error processing order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span>
            <span class="n">channel</span><span class="o">.</span><span class="na">basicReject</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-tổng-hợp-reliability-flow"># tổng hợp reliability flow</h4>

<p><img src="/assets/img/blog/reliability-flow.png" alt="reliability-flow" /></p>

<h3 id="-dead-letter-exchange--xử-lý-message-thất-bại"># dead letter exchange — xử lý message thất bại</h3>

<p>Dead Letter Exchange (DLX) giống như “phòng cấp cứu” hoặc “nghĩa trang” cho message. Khi một tin nhắn không thể xử lý thành công vì lý do nào đó, thay vì để nó biến mất không dấu vết hoặc làm tắc nghẽn queue chính, chúng ta chuyển nó sang DLX để lưu trữ, phân tích hoặc xử lý thủ công sau.</p>

<h4 id="-khi-nào-message-trở-thành-dead-letter"># khi nào message trở thành “dead letter”?</h4>

<p>Có 3 trường hợp phổ biến khiến một message bị chuyển sang DLX:</p>

<ol>
  <li><strong>Consumer reject hoặc nack</strong> message với tham số <code class="language-plaintext highlighter-rouge">requeue=false</code>.</li>
  <li><strong>Hết hạn thời gian sống (TTL expired)</strong> — message nằm trong queue quá lâu mà không có ai consume.</li>
  <li><strong>Vượt quá dung lượng (Queue full)</strong> — hàng đợi đạt tới giới hạn độ dài tối đa (<code class="language-plaintext highlighter-rouge">x-max-length</code>).</li>
</ol>

<h4 id="-sơ-đồ-luồng-dlx"># sơ đồ luồng dlx</h4>

<pre><code class="language-mermaid">                    ┌─────────────┐
                    │ Main Queue  │
                    │ (order.q)   │
                    └──────┬──────┘
                           │
                    Consumer reject
                    hoặc TTL expired
                           │
                           ▼
                    ┌─────────────┐
                    │ DLX Exchange│
                    └──────┬──────┘
                           │
                           ▼
                    ┌─────────────┐
                    │  DLX Queue  │──▶ DLX Consumer
                    │  (parking)  │    (analyze, retry, alert)
                    └─────────────┘
</code></pre>

<h4 id="-dlx-consumer--phân-tích-và-xử-lý-sự-cố"># dlx consumer — phân tích và xử lý sự cố</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Slf4j</span>
<span class="nd">@Component</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DeadLetterConsumer</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RabbitTemplate</span> <span class="n">rabbitTemplate</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">AlertService</span> <span class="n">alertService</span><span class="o">;</span>

    <span class="cm">/**
     * Consumer cho Dead Letter Queue.
     * Phân tích nguyên nhân fail và quyết định:
     * 1. Retry (gửi lại main queue)
     * 2. Alert (thông báo team)
     * 3. Archive (lưu lại để debug)
     */</span>
    <span class="nd">@RabbitListener</span><span class="o">(</span><span class="n">queues</span> <span class="o">=</span> <span class="s">"dlx.queue"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleDeadLetter</span><span class="o">(</span>
            <span class="n">org</span><span class="o">.</span><span class="na">springframework</span><span class="o">.</span><span class="na">amqp</span><span class="o">.</span><span class="na">core</span><span class="o">.</span><span class="na">Message</span> <span class="n">message</span><span class="o">,</span>
            <span class="nd">@Header</span><span class="o">(</span><span class="nc">AmqpHeaders</span><span class="o">.</span><span class="na">DELIVERY_TAG</span><span class="o">)</span> <span class="kt">long</span> <span class="n">deliveryTag</span><span class="o">,</span>
            <span class="nc">Channel</span> <span class="n">channel</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>

        <span class="c1">// Lấy thông tin về nguyên nhân dead letter</span>
        <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">headers</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="na">getMessageProperties</span><span class="o">().</span><span class="na">getHeaders</span><span class="o">();</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;&gt;</span> <span class="n">xDeath</span> <span class="o">=</span> <span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;&gt;)</span> <span class="n">headers</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"x-death"</span><span class="o">);</span>

        <span class="k">if</span> <span class="o">(</span><span class="n">xDeath</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">xDeath</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
            <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">deathInfo</span> <span class="o">=</span> <span class="n">xDeath</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
            <span class="nc">String</span> <span class="n">reason</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">deathInfo</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"reason"</span><span class="o">);</span>      <span class="c1">// "rejected", "expired", "maxlen"</span>
            <span class="nc">String</span> <span class="n">queue</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">deathInfo</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"queue"</span><span class="o">);</span>         <span class="c1">// Queue gốc</span>
            <span class="nc">Long</span> <span class="n">count</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Long</span><span class="o">)</span> <span class="n">deathInfo</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"count"</span><span class="o">);</span>             <span class="c1">// Số lần dead letter</span>
            <span class="nc">String</span> <span class="n">exchange</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">deathInfo</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"exchange"</span><span class="o">);</span>   <span class="c1">// Exchange gốc</span>

            <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Dead letter received: reason={}, queue={}, count={}, exchange={}"</span><span class="o">,</span>
                    <span class="n">reason</span><span class="o">,</span> <span class="n">queue</span><span class="o">,</span> <span class="n">count</span><span class="o">,</span> <span class="n">exchange</span><span class="o">);</span>

            <span class="c1">// Retry logic dựa trên số lần fail</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">count</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">count</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="o">)</span> <span class="o">{</span>
                <span class="c1">// Retry — gửi lại queue gốc sau delay</span>
                <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Retrying message (attempt {}): queue={}"</span><span class="o">,</span> <span class="n">count</span> <span class="o">+</span> <span class="mi">1</span><span class="o">,</span> <span class="n">queue</span><span class="o">);</span>

                <span class="c1">// Delay trước khi retry (exponential backoff)</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="nc">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">min</span><span class="o">(</span><span class="mi">1000</span> <span class="o">*</span> <span class="o">(</span><span class="kt">long</span><span class="o">)</span> <span class="nc">Math</span><span class="o">.</span><span class="na">pow</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">count</span><span class="o">),</span> <span class="mi">30000</span><span class="o">));</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                    <span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">interrupt</span><span class="o">();</span>
                <span class="o">}</span>

                <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span><span class="n">exchange</span><span class="o">,</span>
                        <span class="n">message</span><span class="o">.</span><span class="na">getMessageProperties</span><span class="o">().</span><span class="na">getReceivedRoutingKey</span><span class="o">(),</span>
                        <span class="n">message</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="c1">// Đã retry quá nhiều — alert và archive</span>
                <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Message permanently failed after {} attempts. Alerting team."</span><span class="o">,</span> <span class="n">count</span><span class="o">);</span>
                <span class="n">alertService</span><span class="o">.</span><span class="na">sendAlert</span><span class="o">(</span>
                        <span class="s">"Dead Letter Alert"</span><span class="o">,</span>
                        <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"Message failed %d times in queue %s. Manual intervention needed."</span><span class="o">,</span>
                                <span class="n">count</span><span class="o">,</span> <span class="n">queue</span><span class="o">)</span>
                <span class="o">);</span>
                <span class="c1">// Archive to database for later analysis</span>
                <span class="c1">// deadLetterRepository.save(...)</span>
            <span class="o">}</span>
        <span class="o">}</span>

        <span class="n">channel</span><span class="o">.</span><span class="na">basicAck</span><span class="o">(</span><span class="n">deliveryTag</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-thử-lại-với-delay-queue-pattern-tránh-nghẽn-hàng-loạt"># thử lại với delay queue pattern (tránh nghẽn hàng loạt)</h4>

<p>Khi xảy ra lỗi tạm thời (ví dụ DB timeout), việc thử lại (retry) lập tức đôi khi chỉ làm hệ thống quá tải thêm (hiện tượng Thundering Herd). Giải pháp tốt nhất là sử dụng một Delay Queue trung gian để tạm giữ message một thời gian trước khi đẩy lại về Queue chính:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RetryQueueConfig</span> <span class="o">{</span>

    <span class="c1">// Retry queue với TTL — message tự động quay lại main queue sau delay</span>
    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Queue</span> <span class="nf">retryQueue</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">QueueBuilder</span>
                <span class="o">.</span><span class="na">durable</span><span class="o">(</span><span class="s">"order.retry.queue"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-exchange"</span><span class="o">,</span> <span class="nc">RabbitMQConfig</span><span class="o">.</span><span class="na">ORDER_EXCHANGE</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-dead-letter-routing-key"</span><span class="o">,</span> <span class="s">"order.created"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">withArgument</span><span class="o">(</span><span class="s">"x-message-ttl"</span><span class="o">,</span> <span class="mi">5000</span><span class="o">)</span> <span class="c1">// 5 giây delay</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">// Binding retry queue đến DLX exchange</span>
    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">Binding</span> <span class="nf">retryBinding</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">BindingBuilder</span>
                <span class="o">.</span><span class="na">bind</span><span class="o">(</span><span class="n">retryQueue</span><span class="o">())</span>
                <span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="k">new</span> <span class="nc">DirectExchange</span><span class="o">(</span><span class="nc">RabbitMQConfig</span><span class="o">.</span><span class="na">DLX_EXCHANGE</span><span class="o">))</span>
                <span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="s">"retry.order"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Flow: Main Queue → reject → DLX → Retry Queue (wait 5s) → TTL expire → Main Queue
                                                                          ↓
                                                                    Consumer retry
</code></pre></div></div>

<h3 id="-tối-ưu-hóa-hiệu-năng--best-practices"># tối ưu hóa hiệu năng &amp; best practices</h3>

<h4 id="-prefetch-count-sweet-spot"># prefetch count (sweet spot)</h4>

<p>Prefetch Count quyết định số lượng message tối đa mà RabbitMQ có thể đẩy xuống cho một Consumer khi chưa nhận được tín hiệu xác nhận (ack) từ Consumer đó.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prefetch = 1:  Chậm nhưng phân phối đều (fair distribution)
               Mỗi consumer chỉ nhận 1 message tại một thời điểm.
               Thích hợp khi thời gian xử lý giữa các message chênh lệch lớn.

prefetch = 10-50: Tối ưu cho hầu hết trường hợp (Balanced)
                  Đảm bảo throughput tốt và vẫn chia đều tải.
                  Nên dùng cho phần lớn ứng dụng Backend.

prefetch = 250+: High throughput
                 Consumer dễ bị ngợp nếu gặp message nặng.
                 Chỉ dùng khi thời gian xử lý siêu nhanh (&lt; 1ms).
</code></pre></div></div>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># application.yml</span>
<span class="na">spring</span><span class="pi">:</span>
  <span class="na">rabbitmq</span><span class="pi">:</span>
    <span class="na">listener</span><span class="pi">:</span>
      <span class="na">simple</span><span class="pi">:</span>
        <span class="na">prefetch</span><span class="pi">:</span> <span class="m">25</span> <span class="c1"># Sweet spot cho hầu hết use case</span>
</code></pre></div></div>

<h4 id="-quản-lý-connection-và-channel-hiệu-quả"># quản lý connection và channel hiệu quả</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SAI — Tạo connection mỗi lần publish</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">badPublish</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">ConnectionFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConnectionFactory</span><span class="o">();</span>
    <span class="nc">Connection</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">newConnection</span><span class="o">();</span>  <span class="c1">// Tốn kém!</span>
    <span class="nc">Channel</span> <span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">createChannel</span><span class="o">();</span>     <span class="c1">// Mỗi lần!</span>
    <span class="n">channel</span><span class="o">.</span><span class="na">basicPublish</span><span class="o">(...);</span>
    <span class="n">channel</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
    <span class="n">connection</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>

<span class="c1">// ĐÚNG — Spring Boot quản lý connection pool</span>
<span class="c1">// RabbitTemplate tự động reuse connection và channel</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">RabbitTemplate</span> <span class="n">rabbitTemplate</span><span class="o">;</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">goodPublish</span><span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span><span class="s">"exchange"</span><span class="o">,</span> <span class="s">"routing.key"</span><span class="o">,</span> <span class="n">event</span><span class="o">);</span>
    <span class="c1">// Connection và channel được pool và reuse</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-message-size-kích-thước-tin-nhắn"># message size (kích thước tin nhắn)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rule of thumb:
- &lt; 128KB: Lý tưởng
- 128KB - 1MB: OK nhưng cân nhắc
- &gt; 1MB: KHÔNG NÊN — dùng Claim Check pattern

Claim Check Pattern:
1. Upload large payload lên S3/MinIO
2. Gửi message chỉ chứa reference (URL/key)
3. Consumer download payload từ S3/MinIO
</code></pre></div></div>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Claim Check Pattern</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ClaimCheckProducer</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RabbitTemplate</span> <span class="n">rabbitTemplate</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">S3Client</span> <span class="n">s3Client</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">publishLargePayload</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">largePayload</span><span class="o">,</span> <span class="nc">String</span> <span class="n">orderId</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 1. Upload to S3</span>
        <span class="nc">String</span> <span class="n">s3Key</span> <span class="o">=</span> <span class="s">"messages/"</span> <span class="o">+</span> <span class="n">orderId</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">();</span>
        <span class="n">s3Client</span><span class="o">.</span><span class="na">putObject</span><span class="o">(</span><span class="nc">PutObjectRequest</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
                <span class="o">.</span><span class="na">bucket</span><span class="o">(</span><span class="s">"message-payloads"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">key</span><span class="o">(</span><span class="n">s3Key</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">(),</span> <span class="nc">RequestBody</span><span class="o">.</span><span class="na">fromBytes</span><span class="o">(</span><span class="n">largePayload</span><span class="o">));</span>

        <span class="c1">// 2. Publish reference only</span>
        <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">claimCheck</span> <span class="o">=</span> <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
                <span class="s">"orderId"</span><span class="o">,</span> <span class="n">orderId</span><span class="o">,</span>
                <span class="s">"payloadRef"</span><span class="o">,</span> <span class="n">s3Key</span><span class="o">,</span>
                <span class="s">"payloadSize"</span><span class="o">,</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">largePayload</span><span class="o">.</span><span class="na">length</span><span class="o">)</span>
        <span class="o">);</span>
        <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span><span class="s">"order.exchange"</span><span class="o">,</span> <span class="s">"order.created"</span><span class="o">,</span> <span class="n">claimCheck</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-batch-publishing-gửi-tin-nhắn-hàng-loạt"># batch publishing (gửi tin nhắn hàng loạt)</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Batch publish cho high-throughput scenarios</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">batchPublish</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderEvent</span><span class="o">&gt;</span> <span class="n">events</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">operations</span> <span class="o">-&gt;</span> <span class="o">{</span>
        <span class="k">for</span> <span class="o">(</span><span class="nc">OrderEvent</span> <span class="n">event</span> <span class="o">:</span> <span class="n">events</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">operations</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span>
                    <span class="nc">RabbitMQConfig</span><span class="o">.</span><span class="na">ORDER_EXCHANGE</span><span class="o">,</span>
                    <span class="s">"order."</span> <span class="o">+</span> <span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">(),</span>
                    <span class="n">event</span>
            <span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// Tất cả message được gửi trong cùng một channel operation</span>
        <span class="c1">// Giảm overhead so với gửi từng message</span>
        <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
    <span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-giám-sát-chỉ-số-monitoring-metrics"># giám sát chỉ số (monitoring metrics)</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># application.yml — Enable RabbitMQ metrics với Micrometer</span>
<span class="na">management</span><span class="pi">:</span>
  <span class="na">endpoints</span><span class="pi">:</span>
    <span class="na">web</span><span class="pi">:</span>
      <span class="na">exposure</span><span class="pi">:</span>
        <span class="na">include</span><span class="pi">:</span> <span class="s">health,metrics,prometheus</span>
  <span class="na">metrics</span><span class="pi">:</span>
    <span class="na">tags</span><span class="pi">:</span>
      <span class="na">application</span><span class="pi">:</span> <span class="s">order-service</span>

<span class="c1"># Metrics quan trọng cần monitor:</span>
<span class="c1"># - rabbitmq.consumed: Số message consumed</span>
<span class="c1"># - rabbitmq.published: Số message published</span>
<span class="c1"># - rabbitmq.acknowledged: Số message acked</span>
<span class="c1"># - rabbitmq.rejected: Số message rejected</span>
<span class="c1"># - rabbitmq.channels: Số channels active</span>
<span class="c1"># - spring.rabbitmq.listener: Listener container metrics</span>
</code></pre></div></div>

<h4 id="-tóm-tắt-best-practices-checklist"># tóm tắt best practices checklist</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Message Design:
✅ Dùng JSON serialization (Jackson2JsonMessageConverter)
✅ Luôn set messageId cho idempotency
✅ Luôn set correlationId cho tracing
✅ Giữ message size &lt; 128KB
✅ Version message schema (header "version": "1.0")

Queue Design:
✅ Durable queues cho production
✅ Persistent messages (delivery_mode = 2)
✅ Set TTL để tránh queue grow vô hạn
✅ Set max-length để protect memory
✅ Luôn configure DLX

Consumer Design:
✅ Manual acknowledgment
✅ Idempotent processing
✅ Proper error handling (business vs transient errors)
✅ Prefetch count phù hợp
✅ Graceful shutdown

Infrastructure:
✅ Cluster mode cho HA (ít nhất 3 nodes)
✅ Quorum queues thay vì classic mirrored queues (RabbitMQ 3.8+)
✅ Monitoring với Prometheus + Grafana
✅ Alerting cho queue depth, consumer count, memory usage
</code></pre></div></div>

<h3 id="-rabbitmq-vs-kafka--khi-nào-nên-dùng-gì"># rabbitmq vs kafka — khi nào nên dùng gì?</h3>

<p>Đây có lẽ là câu hỏi kinh điển nhất mà lập trình viên nào cũng tự đặt ra. Câu trả lời ngắn gọn: hai hệ thống này sinh ra để giải quyết các bài toán hoàn toàn khác nhau.</p>

<h4 id="-khác-biệt-về-mặt-kiến-trúc"># khác biệt về mặt kiến trúc</h4>

<p><img src="/assets/img/blog/rabbitmq-diff-kafka.png" alt="rabbitmq-diff-kafka" /></p>

<h4 id="-so-sánh-chi-tiết-các-tiêu-chí"># so sánh chi tiết các tiêu chí</h4>

<table>
  <thead>
    <tr>
      <th>Tiêu chí</th>
      <th>RabbitMQ</th>
      <th>Kafka</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Model</td>
      <td>Message Queue (push)</td>
      <td>Event Log (pull)</td>
    </tr>
    <tr>
      <td>Routing</td>
      <td>Flexible (exchange types)</td>
      <td>Topic + partition</td>
    </tr>
    <tr>
      <td>Message sau consume</td>
      <td>Xóa khỏi queue</td>
      <td>Giữ lại (retention)</td>
    </tr>
    <tr>
      <td>Replay</td>
      <td>Không (message đã xóa)</td>
      <td>Có (reset offset)</td>
    </tr>
    <tr>
      <td>Ordering</td>
      <td>Per-queue guarantee</td>
      <td>Per-partition guarantee</td>
    </tr>
    <tr>
      <td>Throughput</td>
      <td>~50K msg/s per queue</td>
      <td>~1M msg/s per topic</td>
    </tr>
    <tr>
      <td>Latency</td>
      <td>Rất thấp (&lt; 1ms)</td>
      <td>Thấp (&lt; 10ms)</td>
    </tr>
    <tr>
      <td>Consumer Groups</td>
      <td>Competing consumers</td>
      <td>Consumer groups native</td>
    </tr>
    <tr>
      <td>Protocol</td>
      <td>AMQP, MQTT, STOMP</td>
      <td>Custom binary protocol</td>
    </tr>
    <tr>
      <td>Use case chính</td>
      <td>Task queue, RPC, routing</td>
      <td>Event streaming, log aggregation</td>
    </tr>
  </tbody>
</table>

<h4 id="-khi-nào-nên-chọn-rabbitmq"># khi nào nên chọn rabbitmq?</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>✅ Task queue — distribute work giữa workers
✅ RPC pattern — request/reply
✅ Complex routing — route message dựa trên nhiều criteria
✅ Priority queue — xử lý message quan trọng trước
✅ Message TTL — message tự expire
✅ Khi cần flexible acknowledgment
✅ Khi message không cần replay
✅ Khi team quen với traditional messaging
</code></pre></div></div>

<h4 id="-khi-nào-nên-chọn-kafka"># khi nào nên chọn kafka?</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>✅ Event sourcing — lưu trữ toàn bộ event history
✅ Stream processing — xử lý data real-time
✅ Log aggregation — collect logs từ nhiều service
✅ Khi cần replay messages
✅ Khi throughput cực cao (&gt; 100K msg/s)
✅ Khi nhiều consumer cần đọc cùng data
✅ CDC (Change Data Capture)
✅ Khi cần long-term message retention
</code></pre></div></div>

<h4 id="-có-thể-kết-hợp-cả-hai-không"># có thể kết hợp cả hai không?</h4>

<p>Absolutely. Trong nhiều hệ thống lớn:</p>

<p><img src="/assets/img/blog/rabbitmq-with-kafka.png" alt="rabbitmq-with-kafka" /></p>

<h3 id="-production-readiness-checklist"># production readiness checklist</h3>

<p>Trước khi đưa hệ thống lên chạy thật (Production), hãy chắc chắn rằng bạn đã rà soát và cấu hình đầy đủ các yếu tố sau:</p>

<h4 id="-cấu-hình-hạ-tầng-infrastructure"># cấu hình hạ tầng (infrastructure)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>□ Cluster với ít nhất 3 nodes (odd number cho quorum)
□ Quorum queues thay vì classic mirrored queues
□ Separate disk cho RabbitMQ data directory
□ Memory alarm threshold: 0.4 (default) hoặc thấp hơn
□ Disk alarm threshold: ít nhất 2x RAM
□ TLS/SSL cho connections
□ Firewall rules: chỉ mở port cần thiết
  - 5672: AMQP
  - 5671: AMQPS (TLS)
  - 15672: Management UI (restrict access)
  - 25672: Inter-node communication
□ Separate vhost cho mỗi application/environment
□ Non-default credentials (KHÔNG dùng guest/guest)
</code></pre></div></div>

<h4 id="-cấu-hình-ứng-dụng-application"># cấu hình ứng dụng (application)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>□ Connection recovery enabled (Spring Boot default: true)
□ Publisher confirms enabled
□ Manual acknowledgment
□ Idempotent consumers
□ Dead Letter Exchange configured
□ Proper error handling (business vs transient)
□ Message TTL set
□ Queue max-length set
□ Graceful shutdown handling
□ Health check endpoint include RabbitMQ
</code></pre></div></div>

<h4 id="-giám-sát--cảnh-báo-monitoring--alerting"># giám sát &amp; cảnh báo (monitoring &amp; alerting)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>□ Queue depth monitoring (alert khi &gt; threshold)
□ Consumer count monitoring (alert khi = 0)
□ Memory usage monitoring
□ Disk usage monitoring
□ Connection count monitoring
□ Channel count monitoring
□ Message rate monitoring (publish/consume)
□ Unacked message count monitoring
□ Node health monitoring
□ Grafana dashboard cho RabbitMQ metrics
</code></pre></div></div>

<h4 id="-spring-boot-health-check"># spring boot health check</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># application.yml</span>
<span class="na">management</span><span class="pi">:</span>
  <span class="na">endpoint</span><span class="pi">:</span>
    <span class="na">health</span><span class="pi">:</span>
      <span class="na">show-details</span><span class="pi">:</span> <span class="s">always</span>
  <span class="na">health</span><span class="pi">:</span>
    <span class="na">rabbit</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span> <span class="c1"># Auto-configured khi spring-boot-starter-amqp có mặt</span>


<span class="c1"># Response:</span>
<span class="c1"># {</span>
<span class="c1">#   "status": "UP",</span>
<span class="c1">#   "components": {</span>
<span class="c1">#     "rabbit": {</span>
<span class="c1">#       "status": "UP",</span>
<span class="c1">#       "details": {</span>
<span class="c1">#         "version": "3.12.0"</span>
<span class="c1">#       }</span>
<span class="c1">#     }</span>
<span class="c1">#   }</span>
<span class="c1"># }</span>
</code></pre></div></div>

<h3 id="-lời-kết"># lời kết</h3>

<p>RabbitMQ chắc chắn không phải là một “viên đạn bạc” giải quyết được mọi vấn đề, nhưng nó luôn là một trong những công cụ đáng tin cậy nhất trong hộp đồ nghề của bất kỳ Backend Engineer nào khi bước vào thế giới hệ thống phân tán (Distributed Systems).</p>

<p>Một vài điểm cốt lõi mình hy vọng bạn sẽ nhớ sau bài viết này:</p>

<ul>
  <li><strong>Hiểu kiến trúc trước khi bắt tay vào code:</strong> Nắm vững Exchange, Queue, Binding và cơ chế hoạt động của Channel sẽ giúp bạn tự tin hơn rất nhiều khi debug hoặc thiết kế hệ thống.</li>
  <li><strong>Tính tin cậy (Reliability) không tự nhiên mà có:</strong> Publisher confirms, manual ack, idempotent consumers, DLX… mỗi thành phần là một lớp bảo vệ. Trên môi trường Production chạy thật, bạn sẽ cần tất cả chúng để đảm bảo dữ liệu không bị thất thoát.</li>
  <li><strong>Chọn đúng công cụ cho đúng bài toán:</strong> Dùng RabbitMQ khi cần định tuyến tin nhắn linh hoạt, phân phối tác vụ (task queue) và xử lý yêu cầu phản hồi nhanh bất đồng bộ. Dùng Kafka khi cần lưu lại vết lịch sử sự kiện (event sourcing), stream dữ liệu thời gian thực hoặc cần replay message.</li>
  <li><strong>Luôn luôn giám sát (Monitor):</strong> Đừng đợi đến khi hệ thống sập mới đi tìm nguyên nhân. Hãy thiết lập các cảnh báo về độ dài hàng đợi (queue depth), số lượng consumer online và mức độ sử dụng tài nguyên RAM/Disk của broker để phát hiện lỗi sớm nhất có thể.</li>
</ul>

<p><strong>Tài liệu tham khảo:</strong></p>

<ul>
  <li><a href="https://www.rabbitmq.com/docs">RabbitMQ Official Documentation</a></li>
  <li><a href="https://docs.spring.io/spring-amqp/reference/">Spring AMQP Reference</a></li>
  <li><a href="https://www.amqp.org/">AMQP 0-9-1 Protocol Specification</a></li>
  <li><a href="https://www.manning.com/books/rabbitmq-in-depth">RabbitMQ in Depth — Gavin M. Roy (Manning)</a></li>
  <li><a href="https://www.enterpriseintegrationpatterns.com/">Enterprise Integration Patterns — Hohpe &amp; Woolf</a></li>
</ul>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="rabbitmq" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[“Any sufficiently complex distributed system contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a message queue.” — Paraphrased from Greenspun’s Tenth Rule]]></summary></entry><entry><title type="html">java advanced performance techniques</title><link href="https://cristorvalds.netlify.app/java-advanced-performance-techniques/" rel="alternate" type="text/html" title="java advanced performance techniques" /><published>2026-05-29T19:29:39+07:00</published><updated>2026-05-29T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/java-advanced-performance-techniques</id><content type="html" xml:base="https://cristorvalds.netlify.app/java-advanced-performance-techniques/"><![CDATA[<p>Bài viết này đi sâu vào các kỹ thuật tối ưu <strong>cấp hệ thống</strong> mà senior engineers và framework authors sử dụng. Đây không phải kiến thức để dùng hàng ngày — mà là vũ khí để triển khai khi profiler chỉ ra bottleneck thực sự.</p>

<blockquote>
  <p>“Premature optimization is the root of all evil” — Donald Knuth</p>

  <p>Nhưng khi bạn đã đo, đã chứng minh, và cần tốc độ — đây là cách đạt được nó.</p>
</blockquote>

<h3 id="-branchless-programming--khi-cpu-đoán-sai"># branchless programming — khi cpu đoán sai</h3>

<h4 id="-tại-sao-branch-prediction-quan-trọng"># tại sao branch prediction quan trọng</h4>

<p>CPU hiện đại hoạt động như dây chuyền nhà máy (pipeline) với 14-20 stages. Để giữ pipeline đầy, CPU dùng <strong>Branch Predictor</strong> — đoán trước kết quả <code class="language-plaintext highlighter-rouge">if/else</code> và thực thi speculative. Khi đoán đúng: zero cost. Khi đoán sai (<strong>branch misprediction</strong>): CPU flush toàn bộ pipeline, mất <strong>12-25 clock cycles</strong> — tương đương hàng chục phép tính bị vứt.</p>

<p>Với data có pattern rõ ràng (sorted, mostly true/false), branch predictor đoán đúng &gt;95%. Nhưng với <strong>random data</strong> (hash values, network packets, game state), misprediction rate lên 30-50%.</p>

<h4 id="-branchless-trong-java--thực-tế"># branchless trong java — thực tế</h4>

<p>Java không cho phép inline assembly, nhưng JIT compiler có thể convert certain patterns thành conditional moves (CMOV) — instruction không tạo branch.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Branch-heavy: mỗi iteration = 1 branch prediction</span>
<span class="c1">// Với random data: ~50% misprediction</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">conditionalSum</span><span class="o">(</span><span class="kt">int</span><span class="o">[]</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
   <span class="kt">int</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">val</span> <span class="o">:</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">val</span> <span class="o">&gt;=</span> <span class="mi">128</span><span class="o">)</span> <span class="o">{</span>  <span class="c1">// Branch: predicted or mispredicted</span>
           <span class="n">sum</span> <span class="o">+=</span> <span class="n">val</span><span class="o">;</span>
       <span class="o">}</span>
   <span class="o">}</span>
   <span class="k">return</span> <span class="n">sum</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// ✅ Branchless: arithmetic thay thế branch</span>
<span class="c1">// JIT compiler thường convert sang CMOV instruction</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">branchlessSum</span><span class="o">(</span><span class="kt">int</span><span class="o">[]</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
   <span class="kt">int</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">val</span> <span class="o">:</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// (val - 128) &gt;&gt; 31 = 0 khi val &gt;= 128, = -1 (all 1s) khi val &lt; 128</span>
       <span class="c1">// ~((val - 128) &gt;&gt; 31) = -1 khi val &gt;= 128, = 0 khi val &lt; 128</span>
       <span class="c1">// val &amp; mask = val khi mask = -1, = 0 khi mask = 0</span>
       <span class="kt">int</span> <span class="n">mask</span> <span class="o">=</span> <span class="o">~((</span><span class="n">val</span> <span class="o">-</span> <span class="mi">128</span><span class="o">)</span> <span class="o">&gt;&gt;</span> <span class="mi">31</span><span class="o">);</span>
       <span class="n">sum</span> <span class="o">+=</span> <span class="n">val</span> <span class="o">&amp;</span> <span class="n">mask</span><span class="o">;</span>
   <span class="o">}</span>
   <span class="k">return</span> <span class="n">sum</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-math-based-branchless-patterns"># math-based branchless patterns</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Branching min/max</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">branchingMax</span><span class="o">(</span><span class="kt">int</span> <span class="n">a</span><span class="o">,</span> <span class="kt">int</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">a</span> <span class="o">&gt;</span> <span class="n">b</span> <span class="o">?</span> <span class="n">a</span> <span class="o">:</span> <span class="n">b</span><span class="o">;</span>  <span class="c1">// Branch — nhưng JIT thường optimize</span>
<span class="o">}</span>

<span class="c1">// ✅ Branchless max (khi JIT KHÔNG optimize, hoặc trong interpreted mode)</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">branchlessMax</span><span class="o">(</span><span class="kt">int</span> <span class="n">a</span><span class="o">,</span> <span class="kt">int</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
   <span class="c1">// Khi a &gt; b: a - b &gt; 0, sign bit = 0, &gt;&gt; 31 = 0</span>
   <span class="c1">// Khi a &lt;= b: a - b &lt; 0, sign bit = 1, &gt;&gt; 31 = -1 (all 1s)</span>
   <span class="kt">int</span> <span class="n">diff</span> <span class="o">=</span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="o">;</span>
   <span class="kt">int</span> <span class="n">sign</span> <span class="o">=</span> <span class="n">diff</span> <span class="o">&gt;&gt;</span> <span class="mi">31</span><span class="o">;</span>          <span class="c1">// 0 if a &gt;= b, -1 if a &lt; b</span>
   <span class="k">return</span> <span class="n">a</span> <span class="o">-</span> <span class="o">(</span><span class="n">diff</span> <span class="o">&amp;</span> <span class="n">sign</span><span class="o">);</span>       <span class="c1">// a - 0 = a, hoặc a - (a-b) = b</span>
   <span class="c1">// CẢNH BÁO: overflow khi |a - b| &gt; Integer.MAX_VALUE</span>
<span class="o">}</span>

<span class="c1">// ✅ Branchless absolute value</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">branchlessAbs</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
   <span class="kt">int</span> <span class="n">mask</span> <span class="o">=</span> <span class="n">n</span> <span class="o">&gt;&gt;</span> <span class="mi">31</span><span class="o">;</span>        <span class="c1">// 0 nếu positive, -1 nếu negative</span>
   <span class="k">return</span> <span class="o">(</span><span class="n">n</span> <span class="o">^</span> <span class="n">mask</span><span class="o">)</span> <span class="o">-</span> <span class="n">mask</span><span class="o">;</span>  <span class="c1">// flip bits + add 1 cho negative (two's complement)</span>
<span class="o">}</span>

<span class="c1">// ✅ Branchless clamp (giới hạn value trong [min, max])</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">branchlessClamp</span><span class="o">(</span><span class="kt">int</span> <span class="n">val</span><span class="o">,</span> <span class="kt">int</span> <span class="n">min</span><span class="o">,</span> <span class="kt">int</span> <span class="n">max</span><span class="o">)</span> <span class="o">{</span>
   <span class="c1">// Math.max(min, Math.min(val, max)) — nhưng branchless</span>
   <span class="kt">int</span> <span class="n">clamped</span> <span class="o">=</span> <span class="n">val</span> <span class="o">-</span> <span class="n">max</span><span class="o">;</span>
   <span class="n">clamped</span> <span class="o">&amp;=</span> <span class="n">clamped</span> <span class="o">&gt;&gt;</span> <span class="mi">31</span><span class="o">;</span>   <span class="c1">// 0 nếu val &gt; max, negative value nếu val &lt;= max</span>
   <span class="n">clamped</span> <span class="o">+=</span> <span class="n">max</span><span class="o">;</span>              <span class="c1">// clamp trên</span>

   <span class="kt">int</span> <span class="n">lower</span> <span class="o">=</span> <span class="n">clamped</span> <span class="o">-</span> <span class="n">min</span><span class="o">;</span>
   <span class="n">lower</span> <span class="o">&amp;=</span> <span class="o">~(</span><span class="n">lower</span> <span class="o">&gt;&gt;</span> <span class="mi">31</span><span class="o">);</span>    <span class="c1">// 0 nếu clamped &lt; min</span>
   <span class="k">return</span> <span class="n">lower</span> <span class="o">+</span> <span class="n">min</span><span class="o">;</span>          <span class="c1">// clamp dưới</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-branchless-lookup-boolean-to-int-conversion"># branchless lookup (boolean to int conversion)</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Java boolean comparison → thường tạo branch</span>
<span class="c1">// ✅ Convert boolean condition thành 0/1 integer arithmetic</span>

<span class="c1">// Tính phí giao dịch: VIP = 0%, Normal = 2%</span>
<span class="c1">// Don't</span>
<span class="kt">double</span> <span class="n">fee</span> <span class="o">=</span> <span class="n">isVip</span> <span class="o">?</span> <span class="mf">0.0</span> <span class="o">:</span> <span class="n">amount</span> <span class="o">*</span> <span class="mf">0.02</span><span class="o">;</span>

<span class="c1">// Do - Branchless (hữu ích khi gọi triệu lần với random isVip)</span>
<span class="c1">// Boolean.compare(isVip, false) = 1 khi true, 0 khi false</span>
<span class="kt">int</span> <span class="n">notVip</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="nc">Boolean</span><span class="o">.</span><span class="na">compare</span><span class="o">(</span><span class="n">isVip</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>  <span class="c1">// 0 khi VIP, 1 khi không</span>
<span class="kt">double</span> <span class="n">fee</span> <span class="o">=</span> <span class="n">amount</span> <span class="o">*</span> <span class="mf">0.02</span> <span class="o">*</span> <span class="n">notVip</span><span class="o">;</span>
</code></pre></div></div>

<h4 id="-khi-nào-branchless-thực-sự-giúp-ích-trong-java"># khi nào branchless thực sự giúp ích trong java?</h4>

<table>
  <thead>
    <tr>
      <th>Scenario</th>
      <th>Branch OK?</th>
      <th>Branchless?</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sorted/predictable data</td>
      <td>✅ Branch predictor đúng &gt;95%</td>
      <td>Không cần</td>
    </tr>
    <tr>
      <td>Random data, hot loop (&gt;1M iterations)</td>
      <td>❌ Misprediction penalty cao</td>
      <td>✅ Xem xét</td>
    </tr>
    <tr>
      <td>Business logic (if/else rõ ràng)</td>
      <td>✅ Readability quan trọng hơn</td>
      <td>Không bao giờ</td>
    </tr>
    <tr>
      <td>Inner loop của game engine/signal processing</td>
      <td>❌ Mỗi nanosecond count</td>
      <td>✅ Benchmark</td>
    </tr>
    <tr>
      <td>JIT-compiled code (production, warm JVM)</td>
      <td>✅ JIT thường tự optimize</td>
      <td>Profile trước</td>
    </tr>
  </tbody>
</table>

<p><strong>Cảnh báo</strong>: JIT compiler của HotSpot cực kỳ thông minh. Nó tự convert <code class="language-plaintext highlighter-rouge">Math.min/max</code>, ternary, và simple if/else thành CMOV khi detect pattern. Trước khi viết branchless thủ công, <strong>benchmark với JMH</strong> để chắc chắn JIT chưa optimize rồi.</p>

<h3 id="-autoboxing--thuế-ẩn-trong-java-enterprise"># autoboxing — thuế ẩn trong java enterprise</h3>

<h4 id="-chi-phí-thực-sự-của-autoboxing"># chi phí thực sự của autoboxing</h4>

<p>Mỗi lần Java auto-convert <code class="language-plaintext highlighter-rouge">int</code> → <code class="language-plaintext highlighter-rouge">Integer</code> (autoboxing), một object mới được tạo trên heap (trừ cached range -128..127). Với high-throughput systems, đây là “thuế vô hình” ăn mòn performance.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Memory layout comparison:</span>
<span class="c1">// int:     4 bytes (stack hoặc inline trong array)</span>
<span class="c1">// Integer: 16 bytes object header + 4 bytes value + 4 bytes padding = 24 bytes (heap)</span>
<span class="c1">// → 6x memory overhead cho MỖI element</span>

<span class="c1">// Autoboxing trap #1: Map với primitive keys</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">cache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">long</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">id</span> <span class="o">&lt;</span> <span class="mi">1_000_000</span><span class="o">;</span> <span class="n">id</span><span class="o">++)</span> <span class="o">{</span>
   <span class="n">cache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="s">"value"</span><span class="o">);</span>  <span class="c1">// id auto-boxed → 1M Long objects → ~24MB wasted</span>
<span class="o">}</span>

<span class="c1">// Autoboxing trap #2: Generic methods</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span> <span class="kd">extends</span> <span class="nc">Comparable</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;&gt;</span> <span class="no">T</span> <span class="nf">max</span><span class="o">(</span><span class="no">T</span> <span class="n">a</span><span class="o">,</span> <span class="no">T</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">a</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">b</span><span class="o">)</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">a</span> <span class="o">:</span> <span class="n">b</span><span class="o">;</span>
<span class="o">}</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">max</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">);</span>  <span class="c1">// x, y boxed → Integer, result unboxed → int</span>
<span class="c1">// 2 boxing + 1 unboxing PER CALL</span>

<span class="c1">// Autoboxing trap #3: Stream operations</span>
<span class="kt">long</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">longList</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">x</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="o">)</span>     <span class="c1">// x unboxed for comparison</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span><span class="o">)</span>          <span class="c1">// x unboxed, result RE-BOXED</span>
   <span class="o">.</span><span class="na">reduce</span><span class="o">(</span><span class="mi">0L</span><span class="o">,</span> <span class="nl">Long:</span><span class="o">:</span><span class="n">sum</span><span class="o">);</span>   <span class="c1">// unbox both, sum, RE-BOX accumulator</span>
<span class="c1">// MỖIMỖI pipeline stage = unbox + rebox</span>

<span class="c1">// Fix: dùng primitive streams</span>
<span class="kt">long</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">longList</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">mapToLong</span><span class="o">(</span><span class="nl">Long:</span><span class="o">:</span><span class="n">longValue</span><span class="o">)</span>  <span class="c1">// unbox ONCE</span>
   <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">x</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="o">)</span>        <span class="c1">// primitive operations from here</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span><span class="o">)</span>
   <span class="o">.</span><span class="na">sum</span><span class="o">();</span>                       <span class="c1">// primitive sum, no boxing</span>
</code></pre></div></div>

<h4 id="-phát-hiện-autoboxing-trong-production"># phát hiện autoboxing trong production</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// JFR (Java Flight Recorder) — monitor allocations</span>
<span class="c1">// jcmd &lt;pid&gt; JFR.start name=boxing duration=60s filename=boxing.jfr</span>

<span class="c1">// JMH benchmark with -prof gc</span>
<span class="c1">// @Benchmark reports gc.alloc.rate — nếu cao bất thường → boxing</span>

<span class="c1">// IntelliJ inspection: "Auto-boxing" → highlight tất cả boxing sites</span>
<span class="c1">// IDE settings: Inspections → Java → Performance → "Auto-boxing"</span>
</code></pre></div></div>

<h4 id="-giải-pháp-cấp-kiến-trúc"># giải pháp cấp kiến trúc</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ✅ Strategy 1: Primitive-specialized collections</span>
<span class="c1">// Eclipse Collections</span>
<span class="kn">import</span> <span class="nn">org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap</span><span class="o">;</span>
<span class="nc">LongObjectHashMap</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">cache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LongObjectHashMap</span><span class="o">&lt;&gt;();</span>
<span class="n">cache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="mi">42L</span><span class="o">,</span> <span class="s">"value"</span><span class="o">);</span>  <span class="c1">// NO boxing — stores raw long</span>

<span class="c1">// ✅ Strategy 2: Value-based classes + JIT optimization</span>
<span class="c1">// Java records thường được JIT inline (escape analysis)</span>
<span class="n">record</span> <span class="nf">PricePoint</span><span class="o">(</span><span class="kt">long</span> <span class="n">timestamp</span><span class="o">,</span> <span class="kt">double</span> <span class="n">price</span><span class="o">)</span> <span class="o">{}</span>
<span class="c1">// JIT có thể scalarize record fields → zero heap allocation</span>

<span class="c1">// ✅ Strategy 3: Redesign API to accept primitives</span>
<span class="c1">// ❌</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">processOrder</span><span class="o">(</span><span class="nc">Long</span> <span class="n">orderId</span><span class="o">,</span> <span class="nc">Integer</span> <span class="n">quantity</span><span class="o">,</span> <span class="nc">Double</span> <span class="n">price</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
<span class="c1">// ✅</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">processOrder</span><span class="o">(</span><span class="kt">long</span> <span class="n">orderId</span><span class="o">,</span> <span class="kt">int</span> <span class="n">quantity</span><span class="o">,</span> <span class="kt">double</span> <span class="n">price</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
<span class="c1">// Caller không bị boxing penalty</span>

<span class="c1">// ✅ Strategy 4: Primitive OptionalLong/OptionalInt thay vì Optional&lt;Long&gt;</span>
<span class="c1">// ❌</span>
<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">&gt;</span> <span class="nf">findPrice</span><span class="o">(</span><span class="nc">String</span> <span class="n">product</span><span class="o">);</span>  <span class="c1">// Long boxing</span>
<span class="c1">// ✅</span>
<span class="nc">OptionalLong</span> <span class="nf">findPrice</span><span class="o">(</span><span class="nc">String</span> <span class="n">product</span><span class="o">);</span>  <span class="c1">// No boxing</span>
</code></pre></div></div>

<h3 id="-enumset--bit-masking--power-tool-cho-feature-flags"># enumset &amp; bit masking — power tool cho feature flags</h3>

<h4 id="-enumset-internals--array-of-bits"># enumset internals — array of bits</h4>

<p><code class="language-plaintext highlighter-rouge">EnumSet</code> là Java’s built-in bitmask cho enums. Với ≤64 enum constants, internally nó chỉ là <strong>1 long</strong> (64 bits). Mọi operations (add, remove, contains, union, intersection) = bitwise operations = O(1).</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">enum</span> <span class="nc">Permission</span> <span class="o">{</span>
   <span class="no">VIEW_DASHBOARD</span><span class="o">,</span>    <span class="c1">// bit 0</span>
   <span class="no">EDIT_FORM</span><span class="o">,</span>         <span class="c1">// bit 1</span>
   <span class="no">DELETE_FORM</span><span class="o">,</span>       <span class="c1">// bit 2</span>
   <span class="no">MANAGE_USERS</span><span class="o">,</span>      <span class="c1">// bit 3</span>
   <span class="no">DEPLOY_PROCESS</span><span class="o">,</span>    <span class="c1">// bit 4</span>
   <span class="no">VIEW_AUDIT_LOG</span><span class="o">,</span>    <span class="c1">// bit 5</span>
   <span class="no">EXPORT_DATA</span><span class="o">,</span>       <span class="c1">// bit 6</span>
   <span class="no">MANAGE_ROLES</span><span class="o">,</span>      <span class="c1">// bit 7</span>
   <span class="no">SYSTEM_CONFIG</span><span class="o">,</span>     <span class="c1">// bit 8</span>
   <span class="no">VIEW_REPORTS</span>       <span class="c1">// bit 9</span>
<span class="o">}</span>

<span class="c1">// Role definitions — compile-time constant bit patterns</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="no">VIEWER</span> <span class="o">=</span> <span class="nc">EnumSet</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
   <span class="nc">Permission</span><span class="o">.</span><span class="na">VIEW_DASHBOARD</span><span class="o">,</span> <span class="nc">Permission</span><span class="o">.</span><span class="na">VIEW_REPORTS</span>
<span class="o">);</span>  <span class="c1">// Internal: 0000_0010_0000_0001</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="no">EDITOR</span> <span class="o">=</span> <span class="nc">EnumSet</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
   <span class="nc">Permission</span><span class="o">.</span><span class="na">VIEW_DASHBOARD</span><span class="o">,</span> <span class="nc">Permission</span><span class="o">.</span><span class="na">EDIT_FORM</span><span class="o">,</span>
   <span class="nc">Permission</span><span class="o">.</span><span class="na">VIEW_REPORTS</span><span class="o">,</span> <span class="nc">Permission</span><span class="o">.</span><span class="na">EXPORT_DATA</span>
<span class="o">);</span>  <span class="c1">// Internal: 0000_0010_0100_0011</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="no">ADMIN</span> <span class="o">=</span> <span class="nc">EnumSet</span><span class="o">.</span><span class="na">allOf</span><span class="o">(</span><span class="nc">Permission</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="c1">// Internal: 0000_0011_1111_1111</span>

<span class="c1">// Check permission — O(1) bitwise AND</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">hasPermission</span><span class="o">(</span><span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">userPerms</span><span class="o">,</span> <span class="nc">Permission</span> <span class="n">required</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">userPerms</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">required</span><span class="o">);</span>  <span class="c1">// Internally: (bits &amp; (1L &lt;&lt; ordinal)) != 0</span>
<span class="o">}</span>

<span class="c1">// Check ALL required permissions — O(1) containsAll</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">hasAllPermissions</span><span class="o">(</span><span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">userPerms</span><span class="o">,</span> <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">required</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">userPerms</span><span class="o">.</span><span class="na">containsAll</span><span class="o">(</span><span class="n">required</span><span class="o">);</span>  <span class="c1">// Internally: (bits &amp; required) == required</span>
<span class="o">}</span>

<span class="c1">// Combine permissions — O(1) OR</span>
<span class="kd">public</span> <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="nf">mergeRoles</span><span class="o">(</span><span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">role1</span><span class="o">,</span> <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">role2</span><span class="o">)</span> <span class="o">{</span>
   <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">combined</span> <span class="o">=</span> <span class="nc">EnumSet</span><span class="o">.</span><span class="na">copyOf</span><span class="o">(</span><span class="n">role1</span><span class="o">);</span>
   <span class="n">combined</span><span class="o">.</span><span class="na">addAll</span><span class="o">(</span><span class="n">role2</span><span class="o">);</span>  <span class="c1">// Internally: bits |= other.bits</span>
   <span class="k">return</span> <span class="n">combined</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-persist-enumset-as-bitmask-in-database"># persist enumset as bitmask in database</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Store permissions as BIGINT in PostgreSQL (single column, no join table)</span>
<span class="nd">@Entity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserRole</span> <span class="o">{</span>
   <span class="nd">@Id</span>
   <span class="kd">private</span> <span class="no">UUID</span> <span class="n">id</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>

   <span class="c1">// Store as long bitmask</span>
   <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"permissions_mask"</span><span class="o">)</span>
   <span class="kd">private</span> <span class="kt">long</span> <span class="n">permissionsMask</span><span class="o">;</span>

   <span class="c1">// Convert to/from EnumSet</span>
   <span class="nd">@Transient</span>
   <span class="kd">public</span> <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="nf">getPermissions</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">set</span> <span class="o">=</span> <span class="nc">EnumSet</span><span class="o">.</span><span class="na">noneOf</span><span class="o">(</span><span class="nc">Permission</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
       <span class="k">for</span> <span class="o">(</span><span class="nc">Permission</span> <span class="n">p</span> <span class="o">:</span> <span class="nc">Permission</span><span class="o">.</span><span class="na">values</span><span class="o">())</span> <span class="o">{</span>
           <span class="k">if</span> <span class="o">((</span><span class="n">permissionsMask</span> <span class="o">&amp;</span> <span class="o">(</span><span class="mi">1L</span> <span class="o">&lt;&lt;</span> <span class="n">p</span><span class="o">.</span><span class="na">ordinal</span><span class="o">()))</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
               <span class="n">set</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
           <span class="o">}</span>
       <span class="o">}</span>
       <span class="k">return</span> <span class="n">set</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setPermissions</span><span class="o">(</span><span class="nc">EnumSet</span><span class="o">&lt;</span><span class="nc">Permission</span><span class="o">&gt;</span> <span class="n">permissions</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">this</span><span class="o">.</span><span class="na">permissionsMask</span> <span class="o">=</span> <span class="mi">0L</span><span class="o">;</span>
       <span class="k">for</span> <span class="o">(</span><span class="nc">Permission</span> <span class="n">p</span> <span class="o">:</span> <span class="n">permissions</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">this</span><span class="o">.</span><span class="na">permissionsMask</span> <span class="o">|=</span> <span class="o">(</span><span class="mi">1L</span> <span class="o">&lt;&lt;</span> <span class="n">p</span><span class="o">.</span><span class="na">ordinal</span><span class="o">());</span>
       <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Query: find users with DEPLOY_PROCESS permission</span>
<span class="c1">// SQL: WHERE permissions_mask &amp; 16 != 0  (bit 4 = 2^4 = 16)</span>
<span class="nd">@Query</span><span class="o">(</span><span class="s">"SELECT u FROM UserRole u WHERE (u.permissionsMask &amp; :mask) = :mask"</span><span class="o">)</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">UserRole</span><span class="o">&gt;</span> <span class="nf">findByPermission</span><span class="o">(</span><span class="nd">@Param</span><span class="o">(</span><span class="s">"mask"</span><span class="o">)</span> <span class="kt">long</span> <span class="n">mask</span><span class="o">);</span>
</code></pre></div></div>

<h4 id="-so-sánh-performance-vs-alternatives"># so sánh performance vs alternatives</h4>

<table>
  <thead>
    <tr>
      <th>Approach</th>
      <th>Memory/User</th>
      <th>Contains check</th>
      <th>Add/Remove</th>
      <th>DB storage</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">EnumSet&lt;Permission&gt;</code> (10 perms)</td>
      <td>1 long = 8 bytes</td>
      <td>O(1) bitwise</td>
      <td>O(1) bitwise</td>
      <td>1 BIGINT column</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Set&lt;String&gt;</code> (10 perms)</td>
      <td>~400 bytes (HashSet + String objects)</td>
      <td>O(1) hash + equals</td>
      <td>O(1) amortized</td>
      <td>JOIN table hoặc JSON</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">List&lt;Permission&gt;</code> (10 perms)</td>
      <td>~200 bytes (ArrayList + enum refs)</td>
      <td>O(n) linear scan</td>
      <td>O(n) shift</td>
      <td>JOIN table</td>
    </tr>
    <tr>
      <td>Boolean fields (10 perms)</td>
      <td>10 bytes (padded)</td>
      <td>O(1) field access</td>
      <td>O(1) field set</td>
      <td>10 columns</td>
    </tr>
  </tbody>
</table>

<p>EnumSet wins on: memory, speed, và DB simplicity. Trade-off: ordinal-dependent persistence (thêm enum constant ở giữa = break data).</p>

<h3 id="-virtual-threads--giải-phóng-throughput-io-bound-java-21"># virtual threads — giải phóng throughput i/o-bound (java 21+)</h3>

<h4 id="-vấn-đề-với-platform-threads"># vấn đề với platform threads</h4>

<p>Mỗi platform thread = 1 OS thread ≈ 1MB stack. Server với 200 threads = 200MB stack alone. Khi thread blocked on I/O (DB call, HTTP call), OS thread bị giữ idle — lãng phí.</p>

<p>Hệ quả: Với 200 threads, server chỉ xử lý 200 concurrent requests. Request thứ 201 phải đợi.</p>

<h4 id="-virtual-threads--lightweight-millions-possible"># virtual threads — lightweight, millions possible</h4>

<p>Virtual threads (Java 21) là <strong>user-mode threads</strong> do JVM quản lý. Khi virtual thread blocked on I/O, JVM <strong>unmount</strong> nó khỏi carrier thread (platform thread) và mount virtual thread khác lên. Carrier thread KHÔNG BAO GIỜ bị idle.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Platform thread: 1 request = 1 OS thread (giới hạn ~200-500 threads)</span>
<span class="c1">// Virtual thread: 1 request = 1 virtual thread (có thể 1M+ concurrent)</span>

<span class="c1">// ✅ Spring Boot 3.2+ — bật virtual threads (1 dòng config)</span>
<span class="c1">// application.yml:</span>
<span class="c1">// spring:</span>
<span class="c1">//   threads:</span>
<span class="c1">//     virtual:</span>
<span class="c1">//       enabled: true</span>
<span class="c1">// → Tomcat dùng virtual threads cho mỗi request</span>
<span class="c1">// → @Async tasks chạy trên virtual threads</span>
<span class="c1">// → Scheduler tasks dùng virtual threads</span>

<span class="c1">// ✅ Tự tạo virtual thread executor</span>
<span class="nc">ExecutorService</span> <span class="n">executor</span> <span class="o">=</span> <span class="nc">Executors</span><span class="o">.</span><span class="na">newVirtualThreadPerTaskExecutor</span><span class="o">();</span>

<span class="c1">// Xử lý 10,000 concurrent HTTP calls — KHÔNG cần reactive/WebFlux</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Future</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;&gt;</span> <span class="n">futures</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">url</span> <span class="o">:</span> <span class="n">urls</span><span class="o">)</span> <span class="o">{</span>  <span class="c1">// 10,000 URLs</span>
   <span class="n">futures</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">executor</span><span class="o">.</span><span class="na">submit</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span>
       <span class="c1">// Mỗi call block 200ms trung bình</span>
       <span class="c1">// Platform threads: 200 threads × 200ms = 1000 req/s</span>
       <span class="c1">// Virtual threads: 10,000 concurrent = 50,000 req/s (cùng hardware)</span>
       <span class="k">return</span> <span class="n">httpClient</span><span class="o">.</span><span class="na">send</span><span class="o">(</span>
           <span class="nc">HttpRequest</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">().</span><span class="na">uri</span><span class="o">(</span><span class="no">URI</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">url</span><span class="o">)).</span><span class="na">build</span><span class="o">(),</span>
           <span class="nc">HttpResponse</span><span class="o">.</span><span class="na">BodyHandlers</span><span class="o">.</span><span class="na">ofString</span><span class="o">()</span>
       <span class="o">).</span><span class="na">body</span><span class="o">();</span>
   <span class="o">}));</span>
<span class="o">}</span>

<span class="c1">// Structured Concurrency (Java 21 preview) — clean error handling</span>
<span class="k">try</span> <span class="o">(</span><span class="kt">var</span> <span class="n">scope</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StructuredTaskScope</span><span class="o">.</span><span class="na">ShutdownOnFailure</span><span class="o">())</span> <span class="o">{</span>
   <span class="nc">Subtask</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">userTask</span> <span class="o">=</span> <span class="n">scope</span><span class="o">.</span><span class="na">fork</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">userService</span><span class="o">.</span><span class="na">getUser</span><span class="o">(</span><span class="n">id</span><span class="o">));</span>
   <span class="nc">Subtask</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;&gt;</span> <span class="n">ordersTask</span> <span class="o">=</span> <span class="n">scope</span><span class="o">.</span><span class="na">fork</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">orderService</span><span class="o">.</span><span class="na">getOrders</span><span class="o">(</span><span class="n">id</span><span class="o">));</span>
   <span class="nc">Subtask</span><span class="o">&lt;</span><span class="nc">Double</span><span class="o">&gt;</span> <span class="n">balanceTask</span> <span class="o">=</span> <span class="n">scope</span><span class="o">.</span><span class="na">fork</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">accountService</span><span class="o">.</span><span class="na">getBalance</span><span class="o">(</span><span class="n">id</span><span class="o">));</span>

   <span class="n">scope</span><span class="o">.</span><span class="na">join</span><span class="o">();</span>           <span class="c1">// Wait for all</span>
   <span class="n">scope</span><span class="o">.</span><span class="na">throwIfFailed</span><span class="o">();</span>  <span class="c1">// Propagate first failure</span>

   <span class="c1">// All completed successfully — combine results</span>
   <span class="k">return</span> <span class="k">new</span> <span class="nf">UserProfile</span><span class="o">(</span><span class="n">userTask</span><span class="o">.</span><span class="na">get</span><span class="o">(),</span> <span class="n">ordersTask</span><span class="o">.</span><span class="na">get</span><span class="o">(),</span> <span class="n">balanceTask</span><span class="o">.</span><span class="na">get</span><span class="o">());</span>
   <span class="c1">// 3 I/O calls chạy song song, tổng latency = max(3 calls) thay vì sum(3 calls)</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-virtual-threads--khi-nào-dùng-khi-nào-không"># virtual threads — khi nào dùng, khi nào không</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ✅ DÙNG cho I/O-bound workloads:</span>
<span class="c1">// - REST API calls to downstream services</span>
<span class="c1">// - Database queries (JDBC)</span>
<span class="c1">// - File I/O</span>
<span class="c1">// - Message queue operations (RabbitMQ consume/produce)</span>
<span class="c1">// - Anything that spends most time WAITING</span>

<span class="c1">// ❌ KHÔNG dùng cho CPU-bound workloads:</span>
<span class="c1">// - Heavy computation (encryption, compression, ML inference)</span>
<span class="c1">// - Image/video processing</span>
<span class="c1">// - Complex mathematical calculations</span>
<span class="c1">// Lý do: Virtual threads không tăng CPU throughput — vẫn limited by cores</span>
<span class="c1">// Dùng ForkJoinPool hoặc parallel streams cho CPU-bound</span>

<span class="c1">// ❌ KHÔNG dùng với synchronized blocks giữ lâu (pinning problem)</span>
<span class="c1">// synchronized block PIN virtual thread vào carrier — blocking carrier</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">lock</span><span class="o">)</span> <span class="o">{</span>
   <span class="n">database</span><span class="o">.</span><span class="na">query</span><span class="o">(...);</span>  <span class="c1">// ❌ Virtual thread PINNED → carrier thread blocked</span>
<span class="o">}</span>

<span class="c1">// ✅ Thay bằng ReentrantLock</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ReentrantLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ReentrantLock</span><span class="o">();</span>
<span class="n">lock</span><span class="o">.</span><span class="na">lock</span><span class="o">();</span>
<span class="k">try</span> <span class="o">{</span>
   <span class="n">database</span><span class="o">.</span><span class="na">query</span><span class="o">(...);</span>  <span class="c1">// ✅ Virtual thread can unmount during I/O</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
   <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-đo-lường-hiệu-quả-virtual-threads"># đo lường hiệu quả virtual threads</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// JFR events cho virtual threads:</span>
<span class="c1">// jdk.VirtualThreadStart, jdk.VirtualThreadEnd</span>
<span class="c1">// jdk.VirtualThreadPinned — QUAN TRỌNG: detect pinning issues</span>

<span class="c1">// System property để detect pinning at runtime:</span>
<span class="c1">// -Djdk.tracePinnedThreads=short</span>
<span class="c1">// Output warning khi virtual thread bị pinned</span>

<span class="c1">// Metric comparison (REST API, 1000 concurrent users, 100ms DB latency):</span>
<span class="c1">// Platform threads (200 pool): throughput = 2,000 req/s, p99 = 500ms (queuing)</span>
<span class="c1">// Virtual threads: throughput = 9,500 req/s, p99 = 110ms (no queuing)</span>
</code></pre></div></div>

<h3 id="-hibernatejpa-performance-traps--thuế-tiện-lợi"># hibernate/jpa performance traps — thuế “tiện lợi”</h3>

<h4 id="-n1-query-problem--kẻ-giết-performance-1"># n+1 query problem — kẻ giết performance #1</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ N+1: Load 100 orders → triggers 100 separate queries for items</span>
<span class="nd">@Entity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Order</span> <span class="o">{</span>
   <span class="nd">@OneToMany</span><span class="o">(</span><span class="n">mappedBy</span> <span class="o">=</span> <span class="s">"order"</span><span class="o">,</span> <span class="n">fetch</span> <span class="o">=</span> <span class="nc">FetchType</span><span class="o">.</span><span class="na">LAZY</span><span class="o">)</span>
   <span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderItem</span><span class="o">&gt;</span> <span class="n">items</span><span class="o">;</span>  <span class="c1">// LAZY = good default, BUT...</span>
<span class="o">}</span>

<span class="c1">// Khi access items trong loop:</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="n">orders</span> <span class="o">=</span> <span class="n">orderRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>  <span class="c1">// 1 query: SELECT * FROM orders</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Order</span> <span class="n">order</span> <span class="o">:</span> <span class="n">orders</span><span class="o">)</span> <span class="o">{</span>
   <span class="n">order</span><span class="o">.</span><span class="na">getItems</span><span class="o">().</span><span class="na">size</span><span class="o">();</span>  <span class="c1">// 100 queries: SELECT * FROM order_items WHERE order_id = ?</span>
<span class="o">}</span>
<span class="c1">// Tổng: 101 queries thay vì 1</span>

<span class="c1">// ✅ Fix 1: JOIN FETCH (eager load in JPQL)</span>
<span class="nd">@Query</span><span class="o">(</span><span class="s">"SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status"</span><span class="o">)</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="nf">findByStatusWithItems</span><span class="o">(</span><span class="nd">@Param</span><span class="o">(</span><span class="s">"status"</span><span class="o">)</span> <span class="nc">OrderStatus</span> <span class="n">status</span><span class="o">);</span>
<span class="c1">// 1 query: SELECT ... FROM orders JOIN order_items ...</span>

<span class="c1">// ✅ Fix 2: @EntityGraph (declarative fetch plan)</span>
<span class="nd">@EntityGraph</span><span class="o">(</span><span class="n">attributePaths</span> <span class="o">=</span> <span class="o">{</span><span class="s">"items"</span><span class="o">,</span> <span class="s">"items.product"</span><span class="o">})</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Order</span><span class="o">&gt;</span> <span class="nf">findByStatus</span><span class="o">(</span><span class="nc">OrderStatus</span> <span class="n">status</span><span class="o">);</span>
<span class="c1">// 1 query with LEFT JOIN</span>

<span class="c1">// ✅ Fix 3: @BatchSize (batch lazy loading — best for collections)</span>
<span class="nd">@Entity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Order</span> <span class="o">{</span>
   <span class="nd">@OneToMany</span><span class="o">(</span><span class="n">mappedBy</span> <span class="o">=</span> <span class="s">"order"</span><span class="o">)</span>
   <span class="nd">@BatchSize</span><span class="o">(</span><span class="n">size</span> <span class="o">=</span> <span class="mi">50</span><span class="o">)</span>  <span class="c1">// Load items for 50 orders at once</span>
   <span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">OrderItem</span><span class="o">&gt;</span> <span class="n">items</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 100 orders → 2 queries (batches of 50) thay vì 101 queries</span>
</code></pre></div></div>

<h4 id="-dirty-checking-overhead--hibernate-luôn-theo-dõi"># dirty checking overhead — hibernate luôn theo dõi</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Hibernate snapshot TOÀN BỘ entity khi load → so sánh khi flush</span>
<span class="c1">// 10,000 entities loaded = 10,000 snapshots in memory + O(n) comparison at flush</span>

<span class="c1">// ❌ Load nhiều entity chỉ để đọc</span>
<span class="nd">@Transactional</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="nf">getAllProducts</span><span class="o">()</span> <span class="o">{</span>
   <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">products</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>  <span class="c1">// 10K entities tracked</span>
   <span class="k">return</span> <span class="n">products</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">).</span><span class="na">toList</span><span class="o">();</span>
   <span class="c1">// Flush time: Hibernate so sánh 10K snapshots (dù không thay đổi gì)</span>
<span class="o">}</span>

<span class="c1">// ✅ Fix 1: Read-only transaction (skip dirty checking)</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="nf">getAllProducts</span><span class="o">()</span> <span class="o">{</span>
   <span class="c1">// Hibernate skip dirty checking for read-only TX</span>
   <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">products</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>
   <span class="k">return</span> <span class="n">products</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">).</span><span class="na">toList</span><span class="o">();</span>
<span class="o">}</span>

<span class="c1">// ✅ Fix 2: Projection/DTO query (bypass entity tracking entirely)</span>
<span class="nd">@Query</span><span class="o">(</span><span class="s">"SELECT new vn.com.vpbank.internal.csp.product.dto.ProductSummaryDTO("</span> <span class="o">+</span>
      <span class="s">"p.id, p.name, p.price) FROM Product p WHERE p.status = 'ACTIVE'"</span><span class="o">)</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">ProductSummaryDTO</span><span class="o">&gt;</span> <span class="nf">findActiveSummaries</span><span class="o">();</span>
<span class="c1">// KHÔNG tạo entity, KHÔNG snapshot, KHÔNG dirty check — pure DTO</span>

<span class="c1">// ✅ Fix 3: StatelessSession cho bulk reads</span>
<span class="nc">Session</span> <span class="n">session</span> <span class="o">=</span> <span class="n">entityManager</span><span class="o">.</span><span class="na">unwrap</span><span class="o">(</span><span class="nc">Session</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">StatelessSession</span> <span class="n">stateless</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="na">getSessionFactory</span><span class="o">().</span><span class="na">openStatelessSession</span><span class="o">();</span>
<span class="c1">// StatelessSession: no first-level cache, no dirty checking, no cascades</span>
<span class="c1">// Perfect for batch processing millions of rows</span>
</code></pre></div></div>

<h4 id="-open-session-in-view--anti-pattern-ẩn"># open session in view — anti-pattern ẩn</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// spring.jpa.open-in-view=true (DEFAULT in Spring Boot!)</span>
<span class="c1">// → Hibernate Session mở suốt HTTP request lifecycle</span>
<span class="c1">// → Lazy loading works in Controller/View layer (tiện)</span>
<span class="c1">// → Database connection held cho TOÀN BỘ request duration (NGUY HIỂM)</span>

<span class="c1">// Vấn đề: Request mất 2s (1.9s render JSON, 0.1s DB query)</span>
<span class="c1">// → DB connection bị giữ 2s thay vì 0.1s</span>
<span class="c1">// → Connection pool exhausted dưới load</span>

<span class="c1">// ✅ Fix: Tắt OSIV, eager fetch những gì cần</span>
<span class="err">#</span> <span class="n">application</span><span class="o">.</span><span class="na">yml</span><span class="o">:</span>
<span class="err">#</span> <span class="nl">spring:</span>
<span class="err">#</span>   <span class="nl">jpa:</span>
<span class="err">#</span>     <span class="n">open</span><span class="o">-</span><span class="n">in</span><span class="o">-</span><span class="nl">view:</span> <span class="kc">false</span>

<span class="c1">// Và fetch đầy đủ data trong Service layer (trước khi TX đóng)</span>
<span class="nd">@Service</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderService</span> <span class="o">{</span>
   <span class="kd">public</span> <span class="nc">OrderDetailDTO</span> <span class="nf">getOrderDetail</span><span class="o">(</span><span class="no">UUID</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="n">orderRepository</span><span class="o">.</span><span class="na">findByIdWithItemsAndCustomer</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
       <span class="c1">// JOIN FETCH tất cả cần thiết → không lazy load ngoài TX</span>
       <span class="k">return</span> <span class="n">mapper</span><span class="o">.</span><span class="na">toDetailDTO</span><span class="o">(</span><span class="n">order</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-json-serialization--thuế-serialization"># json serialization — thuế serialization</h3>

<h4 id="-jackson-performance-tuning"># jackson performance tuning</h4>

<p>Mỗi REST API request trong Spring Boot đều trải qua: <strong>Object → JSON (serialize)</strong> và <strong>JSON → Object (deserialize)</strong>. Với hàng ngàn requests/second, Jackson serialization trở thành CPU hotspot đáng kể.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ Default ObjectMapper — reflection-based, slow for first calls</span>
<span class="c1">// Jackson dùng reflection để inspect fields → tạo serializer/deserializer</span>
<span class="c1">// First serialize: ~500μs (reflection + cache build)</span>
<span class="c1">// Subsequent: ~5-50μs (cached serializers)</span>

<span class="c1">// ✅ Strategy 1: Reuse ObjectMapper (Spring Boot mặc định làm đúng)</span>
<span class="c1">// KHÔNG BAO GIỜ tạo new ObjectMapper() trong method — nó thread-safe, share được</span>
<span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JacksonConfig</span> <span class="o">{</span>
   <span class="nd">@Bean</span>
   <span class="kd">public</span> <span class="nc">ObjectMapper</span> <span class="nf">objectMapper</span><span class="o">()</span> <span class="o">{</span>
       <span class="k">return</span> <span class="nc">JsonMapper</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">addModule</span><span class="o">(</span><span class="k">new</span> <span class="nc">JavaTimeModule</span><span class="o">())</span>
           <span class="o">.</span><span class="na">disable</span><span class="o">(</span><span class="nc">SerializationFeature</span><span class="o">.</span><span class="na">WRITE_DATES_AS_TIMESTAMPS</span><span class="o">)</span>
           <span class="o">.</span><span class="na">disable</span><span class="o">(</span><span class="nc">DeserializationFeature</span><span class="o">.</span><span class="na">FAIL_ON_UNKNOWN_PROPERTIES</span><span class="o">)</span>
           <span class="c1">// ✅ Afterburner: bytecode generation thay vì reflection</span>
           <span class="o">.</span><span class="na">addModule</span><span class="o">(</span><span class="k">new</span> <span class="nc">AfterburnerModule</span><span class="o">())</span>  <span class="c1">// 20-30% faster serialization</span>
           <span class="o">.</span><span class="na">build</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
<span class="c1">// Dependency: com.fasterxml.jackson.module:jackson-module-afterburner</span>

<span class="c1">// ✅ Strategy 2: Blackbird (Java 9+ replacement for Afterburner)</span>
<span class="c1">// Dùng java.lang.invoke.LambdaMetafactory thay vì bytecode generation</span>
<span class="o">.</span><span class="na">addModule</span><span class="o">(</span><span class="k">new</span> <span class="nc">BlackbirdModule</span><span class="o">())</span>  <span class="c1">// Tương đương Afterburner, cleaner cho modern JVMs</span>
<span class="c1">// Dependency: com.fasterxml.jackson.module:jackson-module-blackbird</span>

<span class="c1">// ✅ Strategy 3: Avoid serializing unnecessary fields</span>
<span class="nd">@JsonInclude</span><span class="o">(</span><span class="nc">JsonInclude</span><span class="o">.</span><span class="na">Include</span><span class="o">.</span><span class="na">NON_NULL</span><span class="o">)</span>  <span class="c1">// Skip null fields → smaller JSON, less work</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductDTO</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="no">UUID</span> <span class="n">id</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">price</span><span class="o">;</span>

   <span class="nd">@JsonIgnore</span>  <span class="c1">// Never serialize — internal field</span>
   <span class="kd">private</span> <span class="nc">String</span> <span class="n">internalCode</span><span class="o">;</span>

   <span class="nd">@JsonProperty</span><span class="o">(</span><span class="n">access</span> <span class="o">=</span> <span class="nc">JsonProperty</span><span class="o">.</span><span class="na">Access</span><span class="o">.</span><span class="na">WRITE_ONLY</span><span class="o">)</span>  <span class="c1">// Deserialize only</span>
   <span class="kd">private</span> <span class="nc">String</span> <span class="n">secretToken</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// ✅ Strategy 4: Custom serializer cho hot DTOs</span>
<span class="c1">// Khi 1 DTO được serialize hàng triệu lần, custom serializer &gt; reflection</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductDTOSerializer</span> <span class="kd">extends</span> <span class="nc">JsonSerializer</span><span class="o">&lt;</span><span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="o">{</span>
   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">serialize</span><span class="o">(</span><span class="nc">ProductDTO</span> <span class="n">dto</span><span class="o">,</span> <span class="nc">JsonGenerator</span> <span class="n">gen</span><span class="o">,</span>
                         <span class="nc">SerializerProvider</span> <span class="n">provider</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
       <span class="n">gen</span><span class="o">.</span><span class="na">writeStartObject</span><span class="o">();</span>
       <span class="n">gen</span><span class="o">.</span><span class="na">writeStringField</span><span class="o">(</span><span class="s">"id"</span><span class="o">,</span> <span class="n">dto</span><span class="o">.</span><span class="na">getId</span><span class="o">().</span><span class="na">toString</span><span class="o">());</span>
       <span class="n">gen</span><span class="o">.</span><span class="na">writeStringField</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="n">dto</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
       <span class="n">gen</span><span class="o">.</span><span class="na">writeNumberField</span><span class="o">(</span><span class="s">"price"</span><span class="o">,</span> <span class="n">dto</span><span class="o">.</span><span class="na">getPrice</span><span class="o">().</span><span class="na">doubleValue</span><span class="o">());</span>
       <span class="n">gen</span><span class="o">.</span><span class="na">writeEndObject</span><span class="o">();</span>
       <span class="c1">// Direct field writes — no reflection, no introspection</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-streaming-json-cho-large-responses"># streaming json cho large responses</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ Build entire list in memory → serialize all at once</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/products/export"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="nf">exportAll</span><span class="o">()</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">productService</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>  <span class="c1">// 100K objects in memory simultaneously</span>
   <span class="c1">// Jackson serializes toàn bộ List → massive heap usage → GC pressure</span>
<span class="o">}</span>

<span class="c1">// ✅ Stream + Jackson Streaming API — constant memory</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/products/export"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">StreamingResponseBody</span><span class="o">&gt;</span> <span class="nf">exportAll</span><span class="o">()</span> <span class="o">{</span>
   <span class="k">return</span> <span class="nc">ResponseEntity</span><span class="o">.</span><span class="na">ok</span><span class="o">()</span>
       <span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
       <span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="n">outputStream</span> <span class="o">-&gt;</span> <span class="o">{</span>
           <span class="k">try</span> <span class="o">(</span><span class="nc">JsonGenerator</span> <span class="n">gen</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">getFactory</span><span class="o">()</span>
                   <span class="o">.</span><span class="na">createGenerator</span><span class="o">(</span><span class="n">outputStream</span><span class="o">,</span> <span class="nc">JsonEncoding</span><span class="o">.</span><span class="na">UTF8</span><span class="o">))</span> <span class="o">{</span>
               <span class="n">gen</span><span class="o">.</span><span class="na">writeStartArray</span><span class="o">();</span>

               <span class="n">productRepository</span><span class="o">.</span><span class="na">streamAll</span><span class="o">().</span><span class="na">forEach</span><span class="o">(</span><span class="n">product</span> <span class="o">-&gt;</span> <span class="o">{</span>
                   <span class="k">try</span> <span class="o">{</span>
                       <span class="n">gen</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="n">mapper</span><span class="o">.</span><span class="na">toDTO</span><span class="o">(</span><span class="n">product</span><span class="o">));</span>
                       <span class="n">gen</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>  <span class="c1">// Flush periodically — don't buffer everything</span>
                   <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                       <span class="k">throw</span> <span class="k">new</span> <span class="nf">UncheckedIOException</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
                   <span class="o">}</span>
               <span class="o">});</span>

               <span class="n">gen</span><span class="o">.</span><span class="na">writeEndArray</span><span class="o">();</span>
           <span class="o">}</span>
       <span class="o">});</span>
   <span class="c1">// Memory: O(1) — only 1 product in memory at a time</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-alternative-protocol-buffers-cho-internal-service-communication"># alternative: protocol buffers cho internal service communication</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Jackson JSON: human-readable, ~50μs serialize, ~100 bytes overhead per object</span>
<span class="c1">// Protobuf: binary, ~5μs serialize, ~10 bytes overhead per object</span>
<span class="c1">// → 10x faster, 10x smaller cho inter-service calls</span>

<span class="c1">// Khi CSP services gọi nhau (console → bpm-runtime → repo):</span>
<span class="c1">// REST + JSON: đơn giản, debug-friendly, standard</span>
<span class="c1">// gRPC + Protobuf: 10x throughput cho high-frequency internal calls</span>

<span class="c1">// Trade-off decision:</span>
<span class="c1">// External APIs (client-facing): JSON (compatibility, readability)</span>
<span class="c1">// Internal hot paths (service-to-service): consider Protobuf/gRPC</span>
</code></pre></div></div>

<h3 id="-graalvm-native-image--khởi-động-trong-milliseconds"># graalvm native image — khởi động trong milliseconds</h3>

<h4 id="-vấn-đề-jvm-startup-time"># vấn đề: jvm startup time</h4>

<p>Spring Boot app truyền thống: <strong>3-15 giây</strong> khởi động (class loading, annotation scanning, bean creation, JIT warmup). Trong serverless/container environments, mỗi cold start = user-visible latency.</p>

<h4 id="-native-image-giải-quyết-gì"># native image giải quyết gì</h4>

<p>GraalVM Native Image compile Java bytecode thành <strong>native binary</strong> tại build time (Ahead-of-Time compilation). Kết quả:</p>

<ul>
  <li>Startup: <strong>50-100ms</strong> (thay vì 3-15s)</li>
  <li>Memory: <strong>50-80MB</strong> (thay vì 200-500MB)</li>
  <li>No JIT warmup — peak performance from first request</li>
</ul>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- pom.xml: Spring Boot 3.x native support --&gt;</span>
<span class="nt">&lt;plugin&gt;</span>
   <span class="nt">&lt;groupId&gt;</span>org.graalvm.buildtools<span class="nt">&lt;/groupId&gt;</span>
   <span class="nt">&lt;artifactId&gt;</span>native-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/plugin&gt;</span>

<span class="c">&lt;!-- Build native image --&gt;</span>
<span class="c">&lt;!-- mvn -Pnative native:compile --&gt;</span>
<span class="c">&lt;!-- Output: target/my-service (single executable binary, ~80MB) --&gt;</span>
</code></pre></div></div>

<h4 id="-trade-offs-và-limitations"># trade-offs và limitations</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ✅ Hoạt động tốt với Native Image:</span>
<span class="c1">// - Standard Spring Boot controllers, services, repositories</span>
<span class="c1">// - Spring Data JPA (với proper hints)</span>
<span class="c1">// - Spring Security (basic configurations)</span>
<span class="c1">// - RestTemplate, WebClient</span>

<span class="c1">// ❌ Không hoạt động (hoặc cần config thủ công):</span>
<span class="c1">// - Runtime reflection (phải declare trước trong reflect-config.json)</span>
<span class="c1">// - Dynamic proxies (JDK proxy cần declare, CGLIB không support)</span>
<span class="c1">// - Resource loading từ classpath (cần resource-config.json)</span>
<span class="c1">// - Serialization (cần serialization-config.json)</span>

<span class="c1">// Spring Boot 3.x generates hints automatically cho most cases</span>
<span class="c1">// Nhưng third-party libraries có thể cần manual hints:</span>

<span class="c1">// reflect-config.json</span>
<span class="o">[</span>
 <span class="o">{</span>
   <span class="s">"name"</span><span class="o">:</span> <span class="s">"vn.com.vpbank.internal.csp.dto.ProductDTO"</span><span class="o">,</span>
   <span class="s">"allDeclaredConstructors"</span><span class="o">:</span> <span class="kc">true</span><span class="o">,</span>
   <span class="s">"allDeclaredFields"</span><span class="o">:</span> <span class="kc">true</span><span class="o">,</span>
   <span class="s">"allDeclaredMethods"</span><span class="o">:</span> <span class="kc">true</span>
 <span class="o">}</span>
<span class="o">]</span>

<span class="c1">// ❌ KHÔNG phù hợp cho:</span>
<span class="c1">// - Applications dùng heavy reflection (Activiti engine, complex Hibernate mappings)</span>
<span class="c1">// - Long-running services hưởng lợi từ JIT optimization (JIT &gt; AOT cho peak throughput)</span>
<span class="c1">// - Services cần dynamic class loading</span>

<span class="c1">// ✅ PHÙ HỢP cho:</span>
<span class="c1">// - Lightweight microservices (config-service, discovery, proxy)</span>
<span class="c1">// - Serverless functions (AWS Lambda, Azure Functions)</span>
<span class="c1">// - CLI tools</span>
<span class="c1">// - Services cần fast startup (auto-scaling, spot instances)</span>
</code></pre></div></div>

<h4 id="-so-sánh-thực-tế"># so sánh thực tế</h4>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>JVM (Hotspot)</th>
      <th>Native Image</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Startup time</td>
      <td>3-15s</td>
      <td>50-100ms</td>
    </tr>
    <tr>
      <td>Memory (idle)</td>
      <td>200-500MB</td>
      <td>50-80MB</td>
    </tr>
    <tr>
      <td>Peak throughput</td>
      <td>Higher (JIT optimized)</td>
      <td>10-20% lower</td>
    </tr>
    <tr>
      <td>Build time</td>
      <td>10-30s</td>
      <td>3-10 minutes</td>
    </tr>
    <tr>
      <td>Debug-ability</td>
      <td>Full (JFR, JMX, profilers)</td>
      <td>Limited</td>
    </tr>
    <tr>
      <td>Reflection</td>
      <td>Full support</td>
      <td>Requires hints</td>
    </tr>
  </tbody>
</table>

<h3 id="-caffeine-cache--local-cache-không-rác"># caffeine cache — local cache “không rác”</h3>

<h4 id="-tại-sao-không-chỉ-dùng-redis"># tại sao không chỉ dùng redis?</h4>

<p>Redis = network call (~0.5-2ms round-trip). Cho data accessed hàng nghìn lần/second, network overhead &gt; computation cost. <strong>Caffeine</strong> = in-process cache, <strong>O(nanoseconds)</strong> access, zero network.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Architecture: Request → Caffeine (L1, in-process) → Redis (L2, distributed) → DB (L3)</span>
<span class="c1">// Cache hit L1: ~50ns</span>
<span class="c1">// Cache hit L2: ~1-2ms (network)</span>
<span class="c1">// Cache miss (DB): ~5-50ms</span>

<span class="c1">// ✅ Spring Boot + Caffeine configuration</span>
<span class="c1">// Dependency: com.github.ben-manes.caffeine:caffeine</span>

<span class="nd">@Configuration</span>
<span class="nd">@EnableCaching</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CacheConfig</span> <span class="o">{</span>

   <span class="nd">@Bean</span>
   <span class="kd">public</span> <span class="nc">CacheManager</span> <span class="nf">cacheManager</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">CaffeineCacheManager</span> <span class="n">manager</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CaffeineCacheManager</span><span class="o">();</span>
       <span class="n">manager</span><span class="o">.</span><span class="na">setCaffeine</span><span class="o">(</span><span class="nc">Caffeine</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">maximumSize</span><span class="o">(</span><span class="mi">10_000</span><span class="o">)</span>              <span class="c1">// Max 10K entries</span>
           <span class="o">.</span><span class="na">expireAfterWrite</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">5</span><span class="o">))</span>  <span class="c1">// TTL 5 minutes</span>
           <span class="o">.</span><span class="na">recordStats</span><span class="o">()</span>                    <span class="c1">// Enable metrics</span>
       <span class="o">);</span>
       <span class="k">return</span> <span class="n">manager</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="c1">// Hoặc per-cache configuration</span>
   <span class="nd">@Bean</span>
   <span class="kd">public</span> <span class="nc">CacheManager</span> <span class="nf">cacheManager</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">SimpleCacheManager</span> <span class="n">manager</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleCacheManager</span><span class="o">();</span>
       <span class="n">manager</span><span class="o">.</span><span class="na">setCaches</span><span class="o">(</span><span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
           <span class="n">buildCache</span><span class="o">(</span><span class="s">"products"</span><span class="o">,</span> <span class="mi">5000</span><span class="o">,</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">10</span><span class="o">)),</span>
           <span class="n">buildCache</span><span class="o">(</span><span class="s">"users"</span><span class="o">,</span> <span class="mi">1000</span><span class="o">,</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">30</span><span class="o">)),</span>
           <span class="n">buildCache</span><span class="o">(</span><span class="s">"permissions"</span><span class="o">,</span> <span class="mi">2000</span><span class="o">,</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">5</span><span class="o">))</span>
       <span class="o">));</span>
       <span class="k">return</span> <span class="n">manager</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="kd">private</span> <span class="nc">CaffeineCache</span> <span class="nf">buildCache</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">maxSize</span><span class="o">,</span> <span class="nc">Duration</span> <span class="n">ttl</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="k">new</span> <span class="nf">CaffeineCache</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="nc">Caffeine</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">maximumSize</span><span class="o">(</span><span class="n">maxSize</span><span class="o">)</span>
           <span class="o">.</span><span class="na">expireAfterWrite</span><span class="o">(</span><span class="n">ttl</span><span class="o">)</span>
           <span class="o">.</span><span class="na">recordStats</span><span class="o">()</span>
           <span class="o">.</span><span class="na">build</span><span class="o">());</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-sử-dụng-với-spring-cacheable"># sử dụng với spring @cacheable</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ProductRepository</span> <span class="n">productRepository</span><span class="o">;</span>

   <span class="c1">// ✅ Cache kết quả — subsequent calls skip DB entirely</span>
   <span class="nd">@Cacheable</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"products"</span><span class="o">,</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"#id"</span><span class="o">)</span>
   <span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">getProduct</span><span class="o">(</span><span class="no">UUID</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">)</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orElseThrow</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="k">new</span> <span class="nc">EntityNotFoundException</span><span class="o">(</span><span class="s">"Product not found"</span><span class="o">));</span>
       <span class="c1">// First call: DB query + cache result</span>
       <span class="c1">// Subsequent calls: return from Caffeine (~50ns)</span>
   <span class="o">}</span>

   <span class="c1">// ✅ Invalidate khi data thay đổi</span>
   <span class="nd">@CacheEvict</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"products"</span><span class="o">,</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"#id"</span><span class="o">)</span>
   <span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">updateProduct</span><span class="o">(</span><span class="no">UUID</span> <span class="n">id</span><span class="o">,</span> <span class="nc">UpdateProductDTO</span> <span class="n">dto</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Product</span> <span class="n">product</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">).</span><span class="na">orElseThrow</span><span class="o">();</span>
       <span class="n">product</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="n">dto</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
       <span class="n">product</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="n">dto</span><span class="o">.</span><span class="na">getPrice</span><span class="o">());</span>
       <span class="k">return</span> <span class="nf">toDTO</span><span class="o">(</span><span class="n">productRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">product</span><span class="o">));</span>
   <span class="o">}</span>

   <span class="c1">// ✅ Invalidate toàn bộ cache</span>
   <span class="nd">@CacheEvict</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"products"</span><span class="o">,</span> <span class="n">allEntries</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">refreshCache</span><span class="o">()</span> <span class="o">{</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Products cache invalidated"</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="c1">// ✅ Conditional caching — chỉ cache khi result != null</span>
   <span class="nd">@Cacheable</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"products"</span><span class="o">,</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"#code"</span><span class="o">,</span> <span class="n">unless</span> <span class="o">=</span> <span class="s">"#result == null"</span><span class="o">)</span>
   <span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">findByCode</span><span class="o">(</span><span class="nc">String</span> <span class="n">code</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findByCode</span><span class="o">(</span><span class="n">code</span><span class="o">)</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orElse</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-caffeine-advanced-async-loading--multi-level-cache"># caffeine advanced: async loading + multi-level cache</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ✅ AsyncLoadingCache — non-blocking cache population</span>
<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductCacheService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">AsyncLoadingCache</span><span class="o">&lt;</span><span class="no">UUID</span><span class="o">,</span> <span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="n">cache</span><span class="o">;</span>

   <span class="kd">public</span> <span class="nf">ProductCacheService</span><span class="o">(</span><span class="nc">ProductRepository</span> <span class="n">repo</span><span class="o">,</span> <span class="nc">ProductMapper</span> <span class="n">mapper</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">this</span><span class="o">.</span><span class="na">cache</span> <span class="o">=</span> <span class="nc">Caffeine</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">maximumSize</span><span class="o">(</span><span class="mi">10_000</span><span class="o">)</span>
           <span class="o">.</span><span class="na">expireAfterWrite</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">5</span><span class="o">))</span>
           <span class="o">.</span><span class="na">refreshAfterWrite</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">1</span><span class="o">))</span>  <span class="c1">// Background refresh</span>
           <span class="o">.</span><span class="na">buildAsync</span><span class="o">((</span><span class="n">key</span><span class="o">,</span> <span class="n">executor</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="nc">CompletableFuture</span><span class="o">.</span><span class="na">supplyAsync</span><span class="o">(()</span> <span class="o">-&gt;</span>
               <span class="n">repo</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">key</span><span class="o">).</span><span class="na">map</span><span class="o">(</span><span class="nl">mapper:</span><span class="o">:</span><span class="n">toDTO</span><span class="o">).</span><span class="na">orElse</span><span class="o">(</span><span class="kc">null</span><span class="o">),</span> <span class="n">executor</span>
           <span class="o">));</span>
       <span class="c1">// refreshAfterWrite: sau 1 phút, lần access tiếp → return stale value</span>
       <span class="c1">// + trigger async refresh in background → next access gets fresh value</span>
       <span class="c1">// → User KHÔNG BAO GIỜ thấy latency spike từ cache miss</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="nf">getProduct</span><span class="o">(</span><span class="no">UUID</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="n">cache</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>  <span class="c1">// Non-blocking, returns CompletableFuture</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// ✅ Two-level cache: Caffeine (L1) + Redis (L2)</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TwoLevelCacheService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Cache</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="n">localCache</span> <span class="o">=</span> <span class="nc">Caffeine</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">()</span>
       <span class="o">.</span><span class="na">maximumSize</span><span class="o">(</span><span class="mi">5000</span><span class="o">)</span>
       <span class="o">.</span><span class="na">expireAfterWrite</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofSeconds</span><span class="o">(</span><span class="mi">30</span><span class="o">))</span>  <span class="c1">// Short TTL for consistency</span>
       <span class="o">.</span><span class="na">build</span><span class="o">();</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RedisTemplate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="n">redisTemplate</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ProductRepository</span> <span class="n">repository</span><span class="o">;</span>

   <span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">getProduct</span><span class="o">(</span><span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// L1: Local cache (nanoseconds)</span>
       <span class="nc">ProductDTO</span> <span class="n">cached</span> <span class="o">=</span> <span class="n">localCache</span><span class="o">.</span><span class="na">getIfPresent</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">cached</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="n">cached</span><span class="o">;</span>

       <span class="c1">// L2: Redis (milliseconds)</span>
       <span class="n">cached</span> <span class="o">=</span> <span class="n">redisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="s">"product:"</span> <span class="o">+</span> <span class="n">id</span><span class="o">);</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">cached</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
           <span class="n">localCache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">cached</span><span class="o">);</span>  <span class="c1">// Populate L1</span>
           <span class="k">return</span> <span class="n">cached</span><span class="o">;</span>
       <span class="o">}</span>

       <span class="c1">// L3: Database (tens of milliseconds)</span>
       <span class="nc">ProductDTO</span> <span class="n">fresh</span> <span class="o">=</span> <span class="n">repository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="no">UUID</span><span class="o">.</span><span class="na">fromString</span><span class="o">(</span><span class="n">id</span><span class="o">))</span>
           <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">toDTO</span><span class="o">)</span>
           <span class="o">.</span><span class="na">orElseThrow</span><span class="o">();</span>

       <span class="c1">// Populate both levels</span>
       <span class="n">redisTemplate</span><span class="o">.</span><span class="na">opsForValue</span><span class="o">().</span><span class="na">set</span><span class="o">(</span><span class="s">"product:"</span> <span class="o">+</span> <span class="n">id</span><span class="o">,</span> <span class="n">fresh</span><span class="o">,</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">ofMinutes</span><span class="o">(</span><span class="mi">10</span><span class="o">));</span>
       <span class="n">localCache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">fresh</span><span class="o">);</span>
       <span class="k">return</span> <span class="n">fresh</span><span class="o">;</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-caffeine-eviction-strategies"># caffeine eviction strategies</h4>

<table>
  <thead>
    <tr>
      <th>Strategy</th>
      <th>Config</th>
      <th>Use Case</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Size-based</td>
      <td><code class="language-plaintext highlighter-rouge">.maximumSize(N)</code></td>
      <td>Bounded memory, LRU-like eviction</td>
    </tr>
    <tr>
      <td>Weight-based</td>
      <td><code class="language-plaintext highlighter-rouge">.maximumWeight(N).weigher(...)</code></td>
      <td>Heterogeneous entry sizes</td>
    </tr>
    <tr>
      <td>Time-based (write)</td>
      <td><code class="language-plaintext highlighter-rouge">.expireAfterWrite(duration)</code></td>
      <td>Data có known TTL</td>
    </tr>
    <tr>
      <td>Time-based (access)</td>
      <td><code class="language-plaintext highlighter-rouge">.expireAfterAccess(duration)</code></td>
      <td>Session-like data</td>
    </tr>
    <tr>
      <td>Reference-based</td>
      <td><code class="language-plaintext highlighter-rouge">.weakValues()</code> / <code class="language-plaintext highlighter-rouge">.softValues()</code></td>
      <td>GC-friendly, memory-pressure eviction</td>
    </tr>
  </tbody>
</table>

<h3 id="-data-locality-trong-java--soa-pattern-thực-chiến"># data locality trong java — SoA pattern thực chiến</h3>

<h4 id="-vấn-đề-java-object-layout"># vấn đề: java object layout</h4>

<p>Mỗi Java object trên heap có <strong>16 bytes header</strong> (mark word + klass pointer, compressed oops). Một <code class="language-plaintext highlighter-rouge">Point { double x; double y; }</code> = 16 header + 8 + 8 = <strong>32 bytes</strong>. Array of 1M Points = 1M pointers (8 bytes each) + 1M objects (32 bytes each, scattered across heap) = <strong>~40MB</strong>, non-contiguous.</p>

<p>CPU cache line = 64 bytes. Khi iterate array of objects, mỗi <code class="language-plaintext highlighter-rouge">points[i].x</code> = <strong>pointer chase</strong> → random memory access → cache miss → <strong>~100 clock cycles</strong> penalty (vs 4 cycles cho cache hit).</p>

<h4 id="-struct-of-arrays-soa--cache-friendly-alternative"># struct of arrays (SoA) — cache-friendly alternative</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ Array of Structs (AoS) — default Java style</span>
<span class="c1">// Memory layout: [ptr0][ptr1][ptr2]... → [hdr|x0|y0|vx0|vy0] [hdr|x1|y1|vx1|vy1]...</span>
<span class="c1">// Khi chỉ cần x,y → load cả vx,vy vào cache line = waste 50% bandwidth</span>
<span class="kd">class</span> <span class="nc">Particle</span> <span class="o">{</span>
   <span class="kt">double</span> <span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">;</span>    <span class="c1">// position</span>
   <span class="kt">double</span> <span class="n">vx</span><span class="o">,</span> <span class="n">vy</span><span class="o">;</span>  <span class="c1">// velocity</span>
   <span class="kt">int</span> <span class="n">color</span><span class="o">;</span>      <span class="c1">// rendering only</span>
   <span class="kt">boolean</span> <span class="n">active</span><span class="o">;</span> <span class="c1">// logic only</span>
<span class="o">}</span>
<span class="nc">Particle</span><span class="o">[]</span> <span class="n">particles</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Particle</span><span class="o">[</span><span class="mi">1_000_000</span><span class="o">];</span>  <span class="c1">// 1M pointers + 1M scattered objects</span>

<span class="c1">// Tính toán physics (chỉ cần x, y, vx, vy — không cần color, active):</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Particle</span> <span class="n">p</span> <span class="o">:</span> <span class="n">particles</span><span class="o">)</span> <span class="o">{</span>
   <span class="n">p</span><span class="o">.</span><span class="na">x</span> <span class="o">+=</span> <span class="n">p</span><span class="o">.</span><span class="na">vx</span> <span class="o">*</span> <span class="n">dt</span><span class="o">;</span>  <span class="c1">// Cache miss: load toàn bộ object (48+ bytes) cho 2 fields</span>
   <span class="n">p</span><span class="o">.</span><span class="na">y</span> <span class="o">+=</span> <span class="n">p</span><span class="o">.</span><span class="na">vy</span> <span class="o">*</span> <span class="n">dt</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// ✅ Struct of Arrays (SoA) — group by access pattern</span>
<span class="c1">// Memory layout: [x0|x1|x2|x3|...] [y0|y1|y2|y3|...] [vx0|vx1|...] [vy0|vy1|...]</span>
<span class="c1">// Khi cần x → CPU prefetcher load sequential doubles → cache hit mọi access</span>
<span class="kd">class</span> <span class="nc">ParticleSystem</span> <span class="o">{</span>
   <span class="kt">int</span> <span class="n">count</span><span class="o">;</span>
   <span class="kt">double</span><span class="o">[]</span> <span class="n">xs</span><span class="o">;</span>     <span class="c1">// All X positions contiguous</span>
   <span class="kt">double</span><span class="o">[]</span> <span class="n">ys</span><span class="o">;</span>     <span class="c1">// All Y positions contiguous</span>
   <span class="kt">double</span><span class="o">[]</span> <span class="n">vxs</span><span class="o">;</span>    <span class="c1">// All X velocities contiguous</span>
   <span class="kt">double</span><span class="o">[]</span> <span class="n">vys</span><span class="o">;</span>    <span class="c1">// All Y velocities contiguous</span>
   <span class="kt">int</span><span class="o">[]</span> <span class="n">colors</span><span class="o">;</span>    <span class="c1">// Separate — not loaded during physics</span>
   <span class="kt">boolean</span><span class="o">[]</span> <span class="n">active</span><span class="o">;</span> <span class="c1">// Separate — not loaded during physics</span>

   <span class="kd">public</span> <span class="nf">ParticleSystem</span><span class="o">(</span><span class="kt">int</span> <span class="n">capacity</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">this</span><span class="o">.</span><span class="na">count</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
       <span class="k">this</span><span class="o">.</span><span class="na">xs</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="n">capacity</span><span class="o">];</span>
       <span class="k">this</span><span class="o">.</span><span class="na">ys</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="n">capacity</span><span class="o">];</span>
       <span class="k">this</span><span class="o">.</span><span class="na">vxs</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="n">capacity</span><span class="o">];</span>
       <span class="k">this</span><span class="o">.</span><span class="na">vys</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="n">capacity</span><span class="o">];</span>
       <span class="k">this</span><span class="o">.</span><span class="na">colors</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="n">capacity</span><span class="o">];</span>
       <span class="k">this</span><span class="o">.</span><span class="na">active</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">boolean</span><span class="o">[</span><span class="n">capacity</span><span class="o">];</span>
   <span class="o">}</span>

   <span class="c1">// Physics update: CPU prefetcher LOVES this pattern</span>
   <span class="c1">// Sequential access → every cache line fully utilized</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">updatePositions</span><span class="o">(</span><span class="kt">double</span> <span class="n">dt</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
           <span class="n">xs</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">+=</span> <span class="n">vxs</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">*</span> <span class="n">dt</span><span class="o">;</span>   <span class="c1">// xs[] contiguous → prefetch works perfectly</span>
           <span class="n">ys</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">+=</span> <span class="n">vys</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">*</span> <span class="n">dt</span><span class="o">;</span>   <span class="c1">// ys[] contiguous → same</span>
       <span class="o">}</span>
       <span class="c1">// JIT compiler có thể AUTO-VECTORIZE loop này (SIMD)</span>
       <span class="c1">// → Process 4 doubles per instruction (AVX2)</span>
   <span class="o">}</span>

   <span class="c1">// Rendering: chỉ load positions + colors (skip velocities)</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">render</span><span class="o">(</span><span class="nc">Graphics</span> <span class="n">g</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
           <span class="n">g</span><span class="o">.</span><span class="na">setColor</span><span class="o">(</span><span class="n">colors</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
           <span class="n">g</span><span class="o">.</span><span class="na">drawCircle</span><span class="o">(</span><span class="n">xs</span><span class="o">[</span><span class="n">i</span><span class="o">],</span> <span class="n">ys</span><span class="o">[</span><span class="n">i</span><span class="o">],</span> <span class="mi">3</span><span class="o">);</span>
           <span class="c1">// colors[], xs[], ys[] contiguous — 3 cache-friendly arrays</span>
           <span class="c1">// vxs[], vys[] KHÔNG bị load vào cache → more room for useful data</span>
       <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-soa-trong-spring-boot--batch-processing-thực-tế"># SoA trong spring boot — batch processing thực tế</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Scenario: Tính discount cho 100K products mỗi đêm</span>
<span class="c1">// Input: product prices + categories + stock levels</span>
<span class="c1">// Logic: discount phụ thuộc price + category (KHÔNG cần name, description, images...)</span>

<span class="c1">// ❌ AoS: Load full Product entities</span>
<span class="nd">@Scheduled</span><span class="o">(</span><span class="n">cron</span> <span class="o">=</span> <span class="s">"0 0 2 * * *"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">calculateNightlyDiscounts</span><span class="o">()</span> <span class="o">{</span>
   <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">products</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>  <span class="c1">// 100K full objects</span>
   <span class="c1">// Mỗi Product: id, name, description, price, category, stock, images, metadata...</span>
   <span class="c1">// Memory: ~500 bytes/product × 100K = 50MB (chỉ cần ~20 bytes/product)</span>
   <span class="k">for</span> <span class="o">(</span><span class="nc">Product</span> <span class="n">p</span> <span class="o">:</span> <span class="n">products</span><span class="o">)</span> <span class="o">{</span>
       <span class="kt">double</span> <span class="n">discount</span> <span class="o">=</span> <span class="n">computeDiscount</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getPrice</span><span class="o">(),</span> <span class="n">p</span><span class="o">.</span><span class="na">getCategory</span><span class="o">(),</span> <span class="n">p</span><span class="o">.</span><span class="na">getStock</span><span class="o">());</span>
       <span class="n">p</span><span class="o">.</span><span class="na">setDiscount</span><span class="o">(</span><span class="n">discount</span><span class="o">);</span>
   <span class="o">}</span>
   <span class="n">productRepository</span><span class="o">.</span><span class="na">saveAll</span><span class="o">(</span><span class="n">products</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// ✅ SoA: Query chỉ columns cần thiết, process in parallel arrays</span>
<span class="nd">@Scheduled</span><span class="o">(</span><span class="n">cron</span> <span class="o">=</span> <span class="s">"0 0 2 * * *"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">calculateNightlyDiscounts</span><span class="o">()</span> <span class="o">{</span>
   <span class="c1">// Projection query — chỉ lấy 4 fields</span>
   <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">[]&gt;</span> <span class="n">rows</span> <span class="o">=</span> <span class="n">entityManager</span><span class="o">.</span><span class="na">createNativeQuery</span><span class="o">(</span>
       <span class="s">"SELECT id, price, category, stock_level FROM products WHERE status = 'ACTIVE'"</span>
   <span class="o">).</span><span class="na">getResultList</span><span class="o">();</span>

   <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">rows</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
   <span class="no">UUID</span><span class="o">[]</span> <span class="n">ids</span> <span class="o">=</span> <span class="k">new</span> <span class="no">UUID</span><span class="o">[</span><span class="n">size</span><span class="o">];</span>
   <span class="kt">double</span><span class="o">[]</span> <span class="n">prices</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="n">size</span><span class="o">];</span>
   <span class="kt">int</span><span class="o">[]</span> <span class="n">categories</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="n">size</span><span class="o">];</span>
   <span class="kt">int</span><span class="o">[]</span> <span class="n">stocks</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="n">size</span><span class="o">];</span>

   <span class="c1">// Unpack into parallel arrays (SoA layout)</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
       <span class="nc">Object</span><span class="o">[]</span> <span class="n">row</span> <span class="o">=</span> <span class="n">rows</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
       <span class="n">ids</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="o">(</span><span class="no">UUID</span><span class="o">)</span> <span class="n">row</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span>
       <span class="n">prices</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="o">((</span><span class="nc">Number</span><span class="o">)</span> <span class="n">row</span><span class="o">[</span><span class="mi">1</span><span class="o">]).</span><span class="na">doubleValue</span><span class="o">();</span>
       <span class="n">categories</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="o">((</span><span class="nc">Number</span><span class="o">)</span> <span class="n">row</span><span class="o">[</span><span class="mi">2</span><span class="o">]).</span><span class="na">intValue</span><span class="o">();</span>
       <span class="n">stocks</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="o">((</span><span class="nc">Number</span><span class="o">)</span> <span class="n">row</span><span class="o">[</span><span class="mi">3</span><span class="o">]).</span><span class="na">intValue</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="c1">// Compute discounts — cache-friendly sequential access</span>
   <span class="kt">double</span><span class="o">[]</span> <span class="n">discounts</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="n">size</span><span class="o">];</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
       <span class="n">discounts</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">computeDiscount</span><span class="o">(</span><span class="n">prices</span><span class="o">[</span><span class="n">i</span><span class="o">],</span> <span class="n">categories</span><span class="o">[</span><span class="n">i</span><span class="o">],</span> <span class="n">stocks</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
   <span class="o">}</span>

   <span class="c1">// Batch update</span>
   <span class="n">jdbcTemplate</span><span class="o">.</span><span class="na">batchUpdate</span><span class="o">(</span>
       <span class="s">"UPDATE products SET discount = ? WHERE id = ?"</span><span class="o">,</span>
       <span class="k">new</span> <span class="nf">BatchPreparedStatementSetter</span><span class="o">()</span> <span class="o">{</span> <span class="cm">/* ... */</span> <span class="o">}</span>
   <span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-lookup-tables-luts--precomputation-strategy"># lookup tables (LUTs) — precomputation strategy</h3>

<h4 id="-nguyên-lý-đánh-đổi-memory-lấy-cpu-cycles"># nguyên lý: đánh đổi memory lấy cpu cycles</h4>

<p>Thay vì tính toán runtime, <strong>tính trước mọi kết quả</strong> và lưu trong array. Truy xuất = <code class="language-plaintext highlighter-rouge">array[index]</code> = O(1), 1 memory access.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ Runtime computation: sin() mỗi frame (floating point = expensive)</span>
<span class="c1">// Giả sử game loop 60fps, 10K particles mỗi frame = 600K sin() calls/second</span>
<span class="kd">public</span> <span class="kt">double</span> <span class="nf">getAngle</span><span class="o">(</span><span class="kt">int</span> <span class="n">degreeTenths</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="nc">Math</span><span class="o">.</span><span class="na">sin</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">toRadians</span><span class="o">(</span><span class="n">degreeTenths</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">));</span>
<span class="o">}</span>

<span class="c1">// ✅ Precomputed sine table — 1 memory read thay vì floating point computation</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SineLUT</span> <span class="o">{</span>
   <span class="c1">// 3600 entries = 0.0° to 359.9° (precision 0.1°)</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">double</span><span class="o">[]</span> <span class="no">SIN_TABLE</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="mi">3600</span><span class="o">];</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">double</span><span class="o">[]</span> <span class="no">COS_TABLE</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="mi">3600</span><span class="o">];</span>

   <span class="kd">static</span> <span class="o">{</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">3600</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
           <span class="kt">double</span> <span class="n">radians</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">toRadians</span><span class="o">(</span><span class="n">i</span> <span class="o">/</span> <span class="mf">10.0</span><span class="o">);</span>
           <span class="no">SIN_TABLE</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">sin</span><span class="o">(</span><span class="n">radians</span><span class="o">);</span>
           <span class="no">COS_TABLE</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">cos</span><span class="o">(</span><span class="n">radians</span><span class="o">);</span>
       <span class="o">}</span>
   <span class="o">}</span>

   <span class="c1">// O(1) lookup — ~4 clock cycles (cache hit) vs ~20-50 cycles (Math.sin)</span>
   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">double</span> <span class="nf">sin</span><span class="o">(</span><span class="kt">int</span> <span class="n">degreeTenths</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="no">SIN_TABLE</span><span class="o">[((</span><span class="n">degreeTenths</span> <span class="o">%</span> <span class="mi">3600</span><span class="o">)</span> <span class="o">+</span> <span class="mi">3600</span><span class="o">)</span> <span class="o">%</span> <span class="mi">3600</span><span class="o">];</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">double</span> <span class="nf">cos</span><span class="o">(</span><span class="kt">int</span> <span class="n">degreeTenths</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="no">COS_TABLE</span><span class="o">[((</span><span class="n">degreeTenths</span> <span class="o">%</span> <span class="mi">3600</span><span class="o">)</span> <span class="o">+</span> <span class="mi">3600</span><span class="o">)</span> <span class="o">%</span> <span class="mi">3600</span><span class="o">];</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-lut-cho-business-logic--statusfee-calculation"># LUT cho business logic — status/fee calculation</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Scenario: Tính phí giao dịch dựa trên (loại giao dịch × hạng khách hàng × kênh)</span>
<span class="c1">// 5 loại × 4 hạng × 3 kênh = 60 combinations</span>
<span class="c1">// ❌ Nested if/else hoặc switch: O(branches), hard to maintain, error-prone</span>

<span class="c1">// ✅ 3D Lookup Table — O(1) access, dễ cập nhật</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">FeeCalculator</span> <span class="o">{</span>
   <span class="c1">// Dimensions: [transactionType][customerTier][channel]</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">double</span><span class="o">[][][]</span> <span class="no">FEE_TABLE</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="mi">5</span><span class="o">][</span><span class="mi">4</span><span class="o">][</span><span class="mi">3</span><span class="o">];</span>

   <span class="kd">static</span> <span class="o">{</span>
       <span class="c1">// Load from config/database at startup</span>
       <span class="c1">// Hoặc hardcode:</span>
       <span class="c1">// FEE_TABLE[TRANSFER][VIP][MOBILE] = 0.001;</span>
       <span class="c1">// FEE_TABLE[TRANSFER][NORMAL][COUNTER] = 0.005;</span>
       <span class="n">initFromConfig</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="c1">// O(1) fee lookup — zero branching, zero computation</span>
   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">double</span> <span class="nf">getFeeRate</span><span class="o">(</span><span class="nc">TransactionType</span> <span class="n">type</span><span class="o">,</span> <span class="nc">CustomerTier</span> <span class="n">tier</span><span class="o">,</span> <span class="nc">Channel</span> <span class="n">channel</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="no">FEE_TABLE</span><span class="o">[</span><span class="n">type</span><span class="o">.</span><span class="na">ordinal</span><span class="o">()][</span><span class="n">tier</span><span class="o">.</span><span class="na">ordinal</span><span class="o">()][</span><span class="n">channel</span><span class="o">.</span><span class="na">ordinal</span><span class="o">()];</span>
   <span class="o">}</span>

   <span class="c1">// Hot-reload từ database (runtime config change)</span>
   <span class="nd">@Scheduled</span><span class="o">(</span><span class="n">fixedRate</span> <span class="o">=</span> <span class="mi">60_000</span><span class="o">)</span>  <span class="c1">// Refresh mỗi phút</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">refreshFeeTable</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">List</span><span class="o">&lt;</span><span class="nc">FeeConfig</span><span class="o">&gt;</span> <span class="n">configs</span> <span class="o">=</span> <span class="n">feeConfigRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>
       <span class="k">for</span> <span class="o">(</span><span class="nc">FeeConfig</span> <span class="n">config</span> <span class="o">:</span> <span class="n">configs</span><span class="o">)</span> <span class="o">{</span>
           <span class="no">FEE_TABLE</span><span class="o">[</span><span class="n">config</span><span class="o">.</span><span class="na">getType</span><span class="o">().</span><span class="na">ordinal</span><span class="o">()]</span>
                    <span class="o">[</span><span class="n">config</span><span class="o">.</span><span class="na">getTier</span><span class="o">().</span><span class="na">ordinal</span><span class="o">()]</span>
                    <span class="o">[</span><span class="n">config</span><span class="o">.</span><span class="na">getChannel</span><span class="o">().</span><span class="na">ordinal</span><span class="o">()]</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">getRate</span><span class="o">();</span>
       <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-lut-cho-validation--character-classification"># LUT cho validation — character classification</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ String-based validation (tạo strings, regex, multiple comparisons)</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isValidIdentifier</span><span class="o">(</span><span class="nc">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">s</span><span class="o">.</span><span class="na">matches</span><span class="o">(</span><span class="s">"[a-zA-Z_][a-zA-Z0-9_]*"</span><span class="o">);</span>  <span class="c1">// Regex = expensive per call</span>
<span class="o">}</span>

<span class="c1">// ✅ Lookup table: O(1) per character check</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CharClassifier</span> <span class="o">{</span>
   <span class="c1">// 128 ASCII entries — boolean array indexed by char value</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">boolean</span><span class="o">[]</span> <span class="no">IS_IDENTIFIER_START</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">boolean</span><span class="o">[</span><span class="mi">128</span><span class="o">];</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">boolean</span><span class="o">[]</span> <span class="no">IS_IDENTIFIER_PART</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">boolean</span><span class="o">[</span><span class="mi">128</span><span class="o">];</span>

   <span class="kd">static</span> <span class="o">{</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">char</span> <span class="n">c</span> <span class="o">=</span> <span class="sc">'a'</span><span class="o">;</span> <span class="n">c</span> <span class="o">&lt;=</span> <span class="sc">'z'</span><span class="o">;</span> <span class="n">c</span><span class="o">++)</span> <span class="o">{</span> <span class="no">IS_IDENTIFIER_START</span><span class="o">[</span><span class="n">c</span><span class="o">]</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="no">IS_IDENTIFIER_PART</span><span class="o">[</span><span class="n">c</span><span class="o">]</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">char</span> <span class="n">c</span> <span class="o">=</span> <span class="sc">'A'</span><span class="o">;</span> <span class="n">c</span> <span class="o">&lt;=</span> <span class="sc">'Z'</span><span class="o">;</span> <span class="n">c</span><span class="o">++)</span> <span class="o">{</span> <span class="no">IS_IDENTIFIER_START</span><span class="o">[</span><span class="n">c</span><span class="o">]</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="no">IS_IDENTIFIER_PART</span><span class="o">[</span><span class="n">c</span><span class="o">]</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">char</span> <span class="n">c</span> <span class="o">=</span> <span class="sc">'0'</span><span class="o">;</span> <span class="n">c</span> <span class="o">&lt;=</span> <span class="sc">'9'</span><span class="o">;</span> <span class="n">c</span><span class="o">++)</span> <span class="o">{</span> <span class="no">IS_IDENTIFIER_PART</span><span class="o">[</span><span class="n">c</span><span class="o">]</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span>
       <span class="no">IS_IDENTIFIER_START</span><span class="o">[</span><span class="sc">'_'</span><span class="o">]</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
       <span class="no">IS_IDENTIFIER_PART</span><span class="o">[</span><span class="sc">'_'</span><span class="o">]</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isValidIdentifier</span><span class="o">(</span><span class="nc">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">s</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">s</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
       <span class="kt">char</span> <span class="n">first</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">first</span> <span class="o">&gt;=</span> <span class="mi">128</span> <span class="o">||</span> <span class="o">!</span><span class="no">IS_IDENTIFIER_START</span><span class="o">[</span><span class="n">first</span><span class="o">])</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">s</span><span class="o">.</span><span class="na">length</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
           <span class="kt">char</span> <span class="n">c</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="na">charAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
           <span class="k">if</span> <span class="o">(</span><span class="n">c</span> <span class="o">&gt;=</span> <span class="mi">128</span> <span class="o">||</span> <span class="o">!</span><span class="no">IS_IDENTIFIER_PART</span><span class="o">[</span><span class="n">c</span><span class="o">])</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
       <span class="o">}</span>
       <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
       <span class="c1">// O(n) scan nhưng mỗi check = 1 array access (vs regex backtracking)</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-memory-pooling-nâng-cao--arena-allocation-java-22"># memory pooling nâng cao — arena allocation (java 22+)</h3>

<h4 id="-vấn-đề-sâu-hơn-object-pooling"># vấn đề sâu hơn object pooling</h4>

<p>Object pooling (đã cover ở high-performance-java-techniques) giải quyết reuse. Nhưng với <strong>off-heap memory</strong> (native memory, direct ByteBuffers), vấn đề phức tạp hơn:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ByteBuffer.allocateDirect()</code> = system call, expensive (~microseconds)</li>
  <li>Native memory leaks nếu quên <code class="language-plaintext highlighter-rouge">Cleaner</code>/<code class="language-plaintext highlighter-rouge">free()</code></li>
  <li>GC không quản lý off-heap → manual lifecycle</li>
</ul>

<h4 id="-memorysegment-arena-java-22-foreign-function--memory-api"># MemorySegment arena (java 22+ foreign function &amp; memory api)</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ✅ Arena-based allocation — deterministic deallocation, no GC pressure</span>
<span class="kn">import</span> <span class="nn">java.lang.foreign.*</span><span class="o">;</span>

<span class="c1">// Confined arena: memory freed khi close(), scoped to 1 thread</span>
<span class="k">try</span> <span class="o">(</span><span class="nc">Arena</span> <span class="n">arena</span> <span class="o">=</span> <span class="nc">Arena</span><span class="o">.</span><span class="na">ofConfined</span><span class="o">())</span> <span class="o">{</span>
   <span class="c1">// Allocate 1MB off-heap buffer — no GC involvement</span>
   <span class="nc">MemorySegment</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">arena</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="o">);</span>

   <span class="c1">// Allocate structured data (like C struct)</span>
   <span class="nc">MemorySegment</span> <span class="n">point</span> <span class="o">=</span> <span class="n">arena</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="nc">ValueLayout</span><span class="o">.</span><span class="na">JAVA_DOUBLE</span><span class="o">,</span> <span class="mi">2</span><span class="o">);</span>
   <span class="n">point</span><span class="o">.</span><span class="na">setAtIndex</span><span class="o">(</span><span class="nc">ValueLayout</span><span class="o">.</span><span class="na">JAVA_DOUBLE</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mf">3.14</span><span class="o">);</span>  <span class="c1">// x</span>
   <span class="n">point</span><span class="o">.</span><span class="na">setAtIndex</span><span class="o">(</span><span class="nc">ValueLayout</span><span class="o">.</span><span class="na">JAVA_DOUBLE</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mf">2.71</span><span class="o">);</span>  <span class="c1">// y</span>

   <span class="c1">// Allocate array of 10,000 doubles (contiguous, off-heap)</span>
   <span class="nc">MemorySegment</span> <span class="n">prices</span> <span class="o">=</span> <span class="n">arena</span><span class="o">.</span><span class="na">allocateArray</span><span class="o">(</span><span class="nc">ValueLayout</span><span class="o">.</span><span class="na">JAVA_DOUBLE</span><span class="o">,</span> <span class="mi">10_000</span><span class="o">);</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">10_000</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
       <span class="n">prices</span><span class="o">.</span><span class="na">setAtIndex</span><span class="o">(</span><span class="nc">ValueLayout</span><span class="o">.</span><span class="na">JAVA_DOUBLE</span><span class="o">,</span> <span class="n">i</span><span class="o">,</span> <span class="n">computePrice</span><span class="o">(</span><span class="n">i</span><span class="o">));</span>
   <span class="o">}</span>
   <span class="c1">// Process prices — data is contiguous, cache-friendly</span>
   <span class="n">processContiguous</span><span class="o">(</span><span class="n">prices</span><span class="o">,</span> <span class="mi">10_000</span><span class="o">);</span>

<span class="o">}</span>  <span class="c1">// ALL memory freed instantly here — no GC, no finalizers, deterministic</span>

<span class="c1">// Shared arena: accessible from multiple threads</span>
<span class="nc">Arena</span> <span class="n">shared</span> <span class="o">=</span> <span class="nc">Arena</span><span class="o">.</span><span class="na">ofShared</span><span class="o">();</span>
<span class="nc">MemorySegment</span> <span class="n">sharedBuffer</span> <span class="o">=</span> <span class="n">shared</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">4096</span><span class="o">);</span>
<span class="c1">// ... pass to multiple threads ...</span>
<span class="n">shared</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>  <span class="c1">// Free when done — thread-safe close</span>

<span class="c1">// Auto arena: memory freed by GC (like DirectByteBuffer, but safer API)</span>
<span class="nc">Arena</span> <span class="n">auto</span> <span class="o">=</span> <span class="nc">Arena</span><span class="o">.</span><span class="na">ofAuto</span><span class="o">();</span>
<span class="nc">MemorySegment</span> <span class="n">managed</span> <span class="o">=</span> <span class="n">auto</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">1024</span><span class="o">);</span>
<span class="c1">// Freed when arena is garbage collected — no explicit close needed</span>
</code></pre></div></div>

<h4 id="-practical-use-high-throughput-network-buffer-pool"># practical use: high-throughput network buffer pool</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Scenario: Server xử lý 100K connections, mỗi connection cần read/write buffer</span>
<span class="c1">// Allocate/free ByteBuffer liên tục = expensive, fragmented</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DirectBufferPool</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">bufferSize</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Queue</span><span class="o">&lt;</span><span class="nc">MemorySegment</span><span class="o">&gt;</span> <span class="n">pool</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Arena</span> <span class="n">arena</span><span class="o">;</span>  <span class="c1">// Long-lived arena for pool buffers</span>

   <span class="kd">public</span> <span class="nf">DirectBufferPool</span><span class="o">(</span><span class="kt">int</span> <span class="n">bufferSize</span><span class="o">,</span> <span class="kt">int</span> <span class="n">poolSize</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">this</span><span class="o">.</span><span class="na">bufferSize</span> <span class="o">=</span> <span class="n">bufferSize</span><span class="o">;</span>
       <span class="k">this</span><span class="o">.</span><span class="na">pool</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentLinkedQueue</span><span class="o">&lt;&gt;();</span>
       <span class="k">this</span><span class="o">.</span><span class="na">arena</span> <span class="o">=</span> <span class="nc">Arena</span><span class="o">.</span><span class="na">ofShared</span><span class="o">();</span>

       <span class="c1">// Pre-allocate all buffers in one large allocation (contiguous)</span>
       <span class="nc">MemorySegment</span> <span class="n">bulk</span> <span class="o">=</span> <span class="n">arena</span><span class="o">.</span><span class="na">allocate</span><span class="o">((</span><span class="kt">long</span><span class="o">)</span> <span class="n">bufferSize</span> <span class="o">*</span> <span class="n">poolSize</span><span class="o">);</span>
       <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">poolSize</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
           <span class="n">pool</span><span class="o">.</span><span class="na">offer</span><span class="o">(</span><span class="n">bulk</span><span class="o">.</span><span class="na">asSlice</span><span class="o">((</span><span class="kt">long</span><span class="o">)</span> <span class="n">i</span> <span class="o">*</span> <span class="n">bufferSize</span><span class="o">,</span> <span class="n">bufferSize</span><span class="o">));</span>
       <span class="o">}</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="nc">MemorySegment</span> <span class="nf">acquire</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">MemorySegment</span> <span class="n">seg</span> <span class="o">=</span> <span class="n">pool</span><span class="o">.</span><span class="na">poll</span><span class="o">();</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">seg</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="n">seg</span><span class="o">;</span>
       <span class="c1">// Pool exhausted — allocate new (should be rare if pool sized correctly)</span>
       <span class="k">return</span> <span class="n">arena</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="n">bufferSize</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">release</span><span class="o">(</span><span class="nc">MemorySegment</span> <span class="n">seg</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// Zero-out for security, then return to pool</span>
       <span class="n">seg</span><span class="o">.</span><span class="na">fill</span><span class="o">((</span><span class="kt">byte</span><span class="o">)</span> <span class="mi">0</span><span class="o">);</span>
       <span class="n">pool</span><span class="o">.</span><span class="na">offer</span><span class="o">(</span><span class="n">seg</span><span class="o">);</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">shutdown</span><span class="o">()</span> <span class="o">{</span>
       <span class="n">arena</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>  <span class="c1">// Free ALL pooled memory at once</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-lock-free-nâng-cao--beyond-atomicinteger"># lock-free nâng cao — beyond AtomicInteger</h3>

<h4 id="-compare-and-swap-cas--cơ-chế-phần-cứng"># compare-and-swap (cas) — cơ chế phần cứng</h4>

<p>CAS là <strong>single CPU instruction</strong> (x86: <code class="language-plaintext highlighter-rouge">CMPXCHG</code>). Nó atomically: đọc value → so sánh với expected → nếu bằng, ghi new value. Không cần OS lock, không context switch.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// CAS pseudo-code (thực tế là 1 CPU instruction):</span>
<span class="c1">// boolean CAS(address, expectedValue, newValue) {</span>
<span class="c1">//     if (*address == expectedValue) {</span>
<span class="c1">//         *address = newValue;</span>
<span class="c1">//         return true;   // Success</span>
<span class="c1">//     }</span>
<span class="c1">//     return false;      // Someone else changed it — retry</span>
<span class="c1">// }</span>

<span class="c1">// ✅ CAS-based stack (lock-free, obstruction-free)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LockFreeStack</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">AtomicReference</span><span class="o">&lt;</span><span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;&gt;</span> <span class="n">top</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AtomicReference</span><span class="o">&lt;&gt;(</span><span class="kc">null</span><span class="o">);</span>

   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="o">{</span>
       <span class="kd">final</span> <span class="no">E</span> <span class="n">item</span><span class="o">;</span>
       <span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="n">next</span><span class="o">;</span>
       <span class="nc">Node</span><span class="o">(</span><span class="no">E</span> <span class="n">item</span><span class="o">,</span> <span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="n">next</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">item</span> <span class="o">=</span> <span class="n">item</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">next</span> <span class="o">=</span> <span class="n">next</span><span class="o">;</span> <span class="o">}</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">push</span><span class="o">(</span><span class="no">E</span> <span class="n">item</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="n">newNode</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Node</span><span class="o">&lt;&gt;(</span><span class="n">item</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
       <span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="n">currentTop</span><span class="o">;</span>
       <span class="k">do</span> <span class="o">{</span>
           <span class="n">currentTop</span> <span class="o">=</span> <span class="n">top</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
           <span class="n">newNode</span><span class="o">.</span><span class="na">next</span> <span class="o">=</span> <span class="n">currentTop</span><span class="o">;</span>
       <span class="o">}</span> <span class="k">while</span> <span class="o">(!</span><span class="n">top</span><span class="o">.</span><span class="na">compareAndSet</span><span class="o">(</span><span class="n">currentTop</span><span class="o">,</span> <span class="n">newNode</span><span class="o">));</span>
       <span class="c1">// CAS loop: retry nếu another thread modified top giữa get() và compareAndSet()</span>
       <span class="c1">// Trong practice: retry rất ít lần (1-3) trừ extreme contention</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="no">E</span> <span class="nf">pop</span><span class="o">()</span> <span class="o">{</span>
       <span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="n">currentTop</span><span class="o">;</span>
       <span class="nc">Node</span><span class="o">&lt;</span><span class="no">E</span><span class="o">&gt;</span> <span class="n">newTop</span><span class="o">;</span>
       <span class="k">do</span> <span class="o">{</span>
           <span class="n">currentTop</span> <span class="o">=</span> <span class="n">top</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
           <span class="k">if</span> <span class="o">(</span><span class="n">currentTop</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>  <span class="c1">// Empty stack</span>
           <span class="n">newTop</span> <span class="o">=</span> <span class="n">currentTop</span><span class="o">.</span><span class="na">next</span><span class="o">;</span>
       <span class="o">}</span> <span class="k">while</span> <span class="o">(!</span><span class="n">top</span><span class="o">.</span><span class="na">compareAndSet</span><span class="o">(</span><span class="n">currentTop</span><span class="o">,</span> <span class="n">newTop</span><span class="o">));</span>
       <span class="k">return</span> <span class="n">currentTop</span><span class="o">.</span><span class="na">item</span><span class="o">;</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-varhandle--modern-cas-api-java-9"># varhandle — modern cas api (java 9+)</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// VarHandle: type-safe, performance-equivalent to Unsafe, legal API</span>
<span class="c1">// Thay thế sun.misc.Unsafe cho CAS operations</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LockFreeCounter</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">volatile</span> <span class="kt">long</span> <span class="n">value</span><span class="o">;</span>

   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">VarHandle</span> <span class="no">VALUE_HANDLE</span><span class="o">;</span>
   <span class="kd">static</span> <span class="o">{</span>
       <span class="k">try</span> <span class="o">{</span>
           <span class="no">VALUE_HANDLE</span> <span class="o">=</span> <span class="nc">MethodHandles</span><span class="o">.</span><span class="na">lookup</span><span class="o">()</span>
               <span class="o">.</span><span class="na">findVarHandle</span><span class="o">(</span><span class="nc">LockFreeCounter</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="s">"value"</span><span class="o">,</span> <span class="kt">long</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
       <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">ExceptionInInitializerError</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
       <span class="o">}</span>
   <span class="o">}</span>

   <span class="kd">public</span> <span class="kt">long</span> <span class="nf">incrementAndGet</span><span class="o">()</span> <span class="o">{</span>
       <span class="kt">long</span> <span class="n">current</span><span class="o">;</span>
       <span class="kt">long</span> <span class="n">next</span><span class="o">;</span>
       <span class="k">do</span> <span class="o">{</span>
           <span class="n">current</span> <span class="o">=</span> <span class="o">(</span><span class="kt">long</span><span class="o">)</span> <span class="no">VALUE_HANDLE</span><span class="o">.</span><span class="na">getVolatile</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
           <span class="n">next</span> <span class="o">=</span> <span class="n">current</span> <span class="o">+</span> <span class="mi">1</span><span class="o">;</span>
       <span class="o">}</span> <span class="k">while</span> <span class="o">(!</span><span class="no">VALUE_HANDLE</span><span class="o">.</span><span class="na">compareAndSet</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">current</span><span class="o">,</span> <span class="n">next</span><span class="o">));</span>
       <span class="k">return</span> <span class="n">next</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="c1">// Memory ordering options (từ weak → strong):</span>
   <span class="c1">// getOpaque()    — no ordering guarantees (fastest)</span>
   <span class="c1">// getAcquire()   — acquire semantics (reads after this see updates)</span>
   <span class="c1">// getVolatile()  — full volatile (sequential consistency)</span>

   <span class="c1">// setOpaque()    — no ordering</span>
   <span class="c1">// setRelease()   — release semantics (writes before this visible)</span>
   <span class="c1">// setVolatile()  — full volatile</span>

   <span class="c1">// weakCompareAndSet() — may spuriously fail (dùng trong loops anyway)</span>
   <span class="c1">// compareAndSet()     — strong, never spuriously fails</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-false-sharing--cache-line-contention-ẩn"># false sharing — cache line contention ẩn</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Vấn đề: 2 threads write vào 2 biến khác nhau</span>
<span class="c1">// NHƯNG 2 biến nằm trên CÙNG cache line (64 bytes)</span>
<span class="c1">// → CPU phải invalidate cache line liên tục giữa cores = performance collapse</span>

<span class="c1">// ❌ False sharing: counters cạnh nhau trong memory</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Counters</span> <span class="o">{</span>
   <span class="kd">volatile</span> <span class="kt">long</span> <span class="n">counter1</span><span class="o">;</span>  <span class="c1">// Core 0 writes</span>
   <span class="kd">volatile</span> <span class="kt">long</span> <span class="n">counter2</span><span class="o">;</span>  <span class="c1">// Core 1 writes</span>
   <span class="c1">// Cả 2 fit trong 1 cache line (16 bytes &lt;&lt; 64 bytes)</span>
   <span class="c1">// → Core 0 write invalidates Core 1's cache line (and vice versa)</span>
   <span class="c1">// → Performance giảm 10-50x so với single-threaded!</span>
<span class="o">}</span>

<span class="c1">// ✅ @Contended: JVM chèn padding để tách cache lines</span>
<span class="c1">// Cần: -XX:-RestrictContended (hoặc trong java.base module)</span>
<span class="nd">@jdk</span><span class="o">.</span><span class="na">internal</span><span class="o">.</span><span class="na">vm</span><span class="o">.</span><span class="na">annotation</span><span class="o">.</span><span class="na">Contended</span>  <span class="c1">// hoặc dùng manual padding:</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PaddedCounters</span> <span class="o">{</span>
   <span class="kd">volatile</span> <span class="kt">long</span> <span class="n">counter1</span><span class="o">;</span>
   <span class="c1">// Manual padding: 7 longs = 56 bytes → đẩy counter2 sang cache line khác</span>
   <span class="kt">long</span> <span class="n">p1</span><span class="o">,</span> <span class="n">p2</span><span class="o">,</span> <span class="n">p3</span><span class="o">,</span> <span class="n">p4</span><span class="o">,</span> <span class="n">p5</span><span class="o">,</span> <span class="n">p6</span><span class="o">,</span> <span class="n">p7</span><span class="o">;</span>
   <span class="kd">volatile</span> <span class="kt">long</span> <span class="n">counter2</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// LongAdder đã solve false sharing internally — dùng nó thay vì tự padding</span>
<span class="c1">// Internally: Cell[] array với @Contended annotation trên mỗi Cell</span>
</code></pre></div></div>

<h3 id="-simd-vectorization--khi-jit-tự-tối-ưu"># SIMD vectorization — khi JIT tự tối ưu</h3>

<h4 id="-auto-vectorization-trong-hotspot-jit"># auto-vectorization trong hotspot jit</h4>

<p>JIT compiler của HotSpot có thể tự động convert simple loops thành <strong>SIMD instructions</strong> (SSE/AVX trên x86). Một instruction xử lý 4 doubles (AVX2) hoặc 8 floats cùng lúc.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ✅ Loop mà JIT CÓ THỂ auto-vectorize:</span>
<span class="c1">// - Simple array operations</span>
<span class="c1">// - No method calls trong loop body</span>
<span class="c1">// - No branches (hoặc branch rất đơn giản)</span>
<span class="c1">// - Predictable loop bounds</span>

<span class="c1">// ✅ Vectorizable: element-wise addition</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">addArrays</span><span class="o">(</span><span class="kt">double</span><span class="o">[]</span> <span class="n">a</span><span class="o">,</span> <span class="kt">double</span><span class="o">[]</span> <span class="n">b</span><span class="o">,</span> <span class="kt">double</span><span class="o">[]</span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">a</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
       <span class="n">result</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">a</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">+</span> <span class="n">b</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>  <span class="c1">// JIT: 4 additions per AVX2 instruction</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// ✅ Vectorizable: scalar multiplication</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">scale</span><span class="o">(</span><span class="kt">double</span><span class="o">[]</span> <span class="n">arr</span><span class="o">,</span> <span class="kt">double</span> <span class="n">factor</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">arr</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
       <span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">*=</span> <span class="n">factor</span><span class="o">;</span>  <span class="c1">// JIT: 4 multiplications per instruction</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// ❌ NOT vectorizable: method call in loop</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">transform</span><span class="o">(</span><span class="kt">double</span><span class="o">[]</span> <span class="n">arr</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">arr</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
       <span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">customTransform</span><span class="o">(</span><span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>  <span class="c1">// Method call blocks vectorization</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// ❌ NOT vectorizable: data-dependent branch</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">conditional</span><span class="o">(</span><span class="kt">double</span><span class="o">[]</span> <span class="n">arr</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">arr</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">sqrt</span><span class="o">(</span><span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>  <span class="c1">// Branch blocks SIMD</span>
       <span class="k">else</span> <span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-vector-api-java-22-incubator--explicit-simd-control"># vector api (java 22+ incubator) — explicit SIMD control</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Khi auto-vectorization không đủ: Vector API cho explicit SIMD programming</span>
<span class="kn">import</span> <span class="nn">jdk.incubator.vector.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VectorMath</span> <span class="o">{</span>
   <span class="c1">// Process 4 doubles at once (AVX2: 256-bit = 4 × 64-bit double)</span>
   <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">VectorSpecies</span><span class="o">&lt;</span><span class="nc">Double</span><span class="o">&gt;</span> <span class="no">SPECIES</span> <span class="o">=</span> <span class="nc">DoubleVector</span><span class="o">.</span><span class="na">SPECIES_256</span><span class="o">;</span>

   <span class="c1">// ✅ Explicit vectorized dot product</span>
   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">double</span> <span class="nf">dotProduct</span><span class="o">(</span><span class="kt">double</span><span class="o">[]</span> <span class="n">a</span><span class="o">,</span> <span class="kt">double</span><span class="o">[]</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
       <span class="kt">int</span> <span class="n">upperBound</span> <span class="o">=</span> <span class="no">SPECIES</span><span class="o">.</span><span class="na">loopBound</span><span class="o">(</span><span class="n">a</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
       <span class="kt">double</span><span class="o">[]</span> <span class="n">sum</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">double</span><span class="o">[</span><span class="mi">1</span><span class="o">];</span>
       <span class="nc">DoubleVector</span> <span class="n">vsum</span> <span class="o">=</span> <span class="nc">DoubleVector</span><span class="o">.</span><span class="na">zero</span><span class="o">(</span><span class="no">SPECIES</span><span class="o">);</span>

       <span class="c1">// Main loop: process 4 elements per iteration</span>
       <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
       <span class="k">for</span> <span class="o">(;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">upperBound</span><span class="o">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="no">SPECIES</span><span class="o">.</span><span class="na">length</span><span class="o">())</span> <span class="o">{</span>
           <span class="nc">DoubleVector</span> <span class="n">va</span> <span class="o">=</span> <span class="nc">DoubleVector</span><span class="o">.</span><span class="na">fromArray</span><span class="o">(</span><span class="no">SPECIES</span><span class="o">,</span> <span class="n">a</span><span class="o">,</span> <span class="n">i</span><span class="o">);</span>
           <span class="nc">DoubleVector</span> <span class="n">vb</span> <span class="o">=</span> <span class="nc">DoubleVector</span><span class="o">.</span><span class="na">fromArray</span><span class="o">(</span><span class="no">SPECIES</span><span class="o">,</span> <span class="n">b</span><span class="o">,</span> <span class="n">i</span><span class="o">);</span>
           <span class="n">vsum</span> <span class="o">=</span> <span class="n">va</span><span class="o">.</span><span class="na">fma</span><span class="o">(</span><span class="n">vb</span><span class="o">,</span> <span class="n">vsum</span><span class="o">);</span>  <span class="c1">// Fused Multiply-Add: vsum += va * vb</span>
           <span class="c1">// 1 instruction = 4 multiplications + 4 additions</span>
       <span class="o">}</span>

       <span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">vsum</span><span class="o">.</span><span class="na">reduceLanes</span><span class="o">(</span><span class="nc">VectorOperators</span><span class="o">.</span><span class="na">ADD</span><span class="o">);</span>

       <span class="c1">// Tail loop: remaining elements (&lt; 4)</span>
       <span class="k">for</span> <span class="o">(;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">a</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
           <span class="n">result</span> <span class="o">+=</span> <span class="n">a</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">*</span> <span class="n">b</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>
       <span class="o">}</span>
       <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
   <span class="o">}</span>

   <span class="c1">// Speedup: 3-4x vs scalar loop (4 operations per instruction)</span>
   <span class="c1">// Với AVX-512 (SPECIES_512): 7-8x (8 doubles per instruction)</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-khi-nào-simd-matters-trong-enterprise-java"># khi nào simd matters trong enterprise java?</h4>

<table>
  <thead>
    <tr>
      <th>Use Case</th>
      <th>Frequency</th>
      <th>SIMD benefit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>JSON parsing (character scanning)</td>
      <td>Every request</td>
      <td>Moderate (JIT auto-vectorizes)</td>
    </tr>
    <tr>
      <td>Batch price calculations</td>
      <td>Nightly batch</td>
      <td>High (explicit Vector API)</td>
    </tr>
    <tr>
      <td>Image/PDF processing</td>
      <td>On-demand</td>
      <td>Very high</td>
    </tr>
    <tr>
      <td>Encryption/hashing</td>
      <td>Every request</td>
      <td>Built-in (JDK uses intrinsics)</td>
    </tr>
    <tr>
      <td>String operations (equals, indexOf)</td>
      <td>Frequent</td>
      <td>Already optimized by JDK</td>
    </tr>
    <tr>
      <td>ML inference</td>
      <td>Rare in enterprise</td>
      <td>Extreme (use native libs)</td>
    </tr>
  </tbody>
</table>

<p>Trong enterprise Java, bạn <strong>hiếm khi</strong> cần explicit SIMD. JIT compiler và JDK intrinsics đã handle hầu hết cases. Vector API relevant cho: custom signal processing, scientific computing, hoặc khi JIT fails to vectorize.</p>

<h3 id="-decision-matrix"># decision matrix</h3>

<table>
  <thead>
    <tr>
      <th>Technique</th>
      <th>Complexity</th>
      <th>Risk</th>
      <th>Reward</th>
      <th>Dùng khi</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Tránh Autoboxing</td>
      <td>Low</td>
      <td>None</td>
      <td>Medium</td>
      <td>Luôn luôn (habit tốt)</td>
    </tr>
    <tr>
      <td>EnumSet bitmask</td>
      <td>Low</td>
      <td>None</td>
      <td>Medium</td>
      <td>Permission/flag systems</td>
    </tr>
    <tr>
      <td>Virtual Threads</td>
      <td>Low</td>
      <td>Low</td>
      <td>High</td>
      <td>I/O-bound services (Java 21+)</td>
    </tr>
    <tr>
      <td>Caffeine L1 cache</td>
      <td>Medium</td>
      <td>Low</td>
      <td>High</td>
      <td>Hot data, &gt;1000 reads/sec</td>
    </tr>
    <tr>
      <td>Hibernate tuning (OSIV, projections)</td>
      <td>Medium</td>
      <td>Low</td>
      <td>High</td>
      <td>Mọi JPA project</td>
    </tr>
    <tr>
      <td>Jackson optimization</td>
      <td>Medium</td>
      <td>None</td>
      <td>Medium</td>
      <td>High-throughput APIs</td>
    </tr>
    <tr>
      <td>Branchless programming</td>
      <td>High</td>
      <td>Medium</td>
      <td>Medium</td>
      <td>Inner loops + random data (profile first!)</td>
    </tr>
    <tr>
      <td>SoA data layout</td>
      <td>High</td>
      <td>Medium</td>
      <td>High</td>
      <td>Batch processing &gt;100K items</td>
    </tr>
    <tr>
      <td>Lookup Tables</td>
      <td>Low</td>
      <td>None</td>
      <td>High</td>
      <td>Repeated computation, bounded input</td>
    </tr>
    <tr>
      <td>Lock-free (CAS)</td>
      <td>Very High</td>
      <td>High</td>
      <td>High</td>
      <td>Extreme contention, framework-level</td>
    </tr>
    <tr>
      <td>Arena allocation</td>
      <td>High</td>
      <td>Medium</td>
      <td>High</td>
      <td>Off-heap, native interop</td>
    </tr>
    <tr>
      <td>GraalVM Native</td>
      <td>High</td>
      <td>High</td>
      <td>High</td>
      <td>Serverless, startup-critical</td>
    </tr>
    <tr>
      <td>Explicit SIMD (Vector API)</td>
      <td>Very High</td>
      <td>Medium</td>
      <td>Very High</td>
      <td>Scientific/batch computation</td>
    </tr>
  </tbody>
</table>

<h4 id="-nguyên-tắc-vàng"># nguyên tắc vàng</h4>

<ol>
  <li>
    <p><strong>Profile trước</strong> — Dùng JFR/async-profiler xác định bottleneck thực sự. 90% thời gian, bottleneck không phải ở chỗ bạn nghĩ.</p>
  </li>
  <li>
    <p><strong>Low-hanging fruit trước</strong> — Autoboxing, Hibernate N+1, missing cache, wrong collection type. Những thứ này fix trong 5 phút, gain 10x.</p>
  </li>
  <li>
    <p><strong>Readability mặc định</strong> — Clean code + correct algorithm &gt; micro-optimization. Team phải đọc và maintain code này 5 năm tới.</p>
  </li>
  <li>
    <p><strong>Benchmark, không đoán</strong> — JMH cho micro-benchmarks, Gatling/k6 cho load tests. Số liệu thực &gt; trực giác.</p>
  </li>
  <li>
    <p><strong>JIT compiler thông minh hơn bạn nghĩ</strong> — HotSpot JIT tự inline, escape analyze, vectorize, và eliminate dead code. Đừng “optimize” thứ JIT đã handle.</p>
  </li>
  <li>
    <p><strong>Tối ưu đúng level</strong> — Architecture fix (caching layer, async processing, batching) thường gain 100x. Micro-optimization (branchless, SIMD) gain 2-5x. Đầu tư đúng chỗ.</p>
  </li>
</ol>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="optimization" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[Bài viết này đi sâu vào các kỹ thuật tối ưu cấp hệ thống mà senior engineers và framework authors sử dụng. Đây không phải kiến thức để dùng hàng ngày — mà là vũ khí để triển khai khi profiler chỉ ra bottleneck thực sự.]]></summary></entry><entry><title type="html">java comparison</title><link href="https://cristorvalds.netlify.app/java-comparison/" rel="alternate" type="text/html" title="java comparison" /><published>2026-05-25T19:29:39+07:00</published><updated>2026-05-25T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/java-comparison</id><content type="html" xml:base="https://cristorvalds.netlify.app/java-comparison/"><![CDATA[<p>Tổng hợp các cặp concepts trong Java/Spring thường bị hỏi trong phỏng vấn Senior và dễ dùng sai trong production.</p>

<h2 id="1--vs-equals">1. == vs equals()</h2>

<p><strong>Khác biệt cốt lõi</strong>: <code class="language-plaintext highlighter-rouge">==</code> so sánh reference (cùng object trong memory?), <code class="language-plaintext highlighter-rouge">equals()</code> so sánh value (nội dung giống nhau?).</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"hello"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"hello"</span><span class="o">);</span>

<span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="o">;</span>       <span class="c1">// false — 2 objects khác nhau trong heap</span>
<span class="n">a</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>  <span class="c1">// true — nội dung giống nhau</span>

<span class="c1">// String pool exception</span>
<span class="nc">String</span> <span class="n">c</span> <span class="o">=</span> <span class="s">"hello"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">d</span> <span class="o">=</span> <span class="s">"hello"</span><span class="o">;</span>
<span class="n">c</span> <span class="o">==</span> <span class="n">d</span><span class="o">;</span>       <span class="c1">// true — cùng reference trong String pool (compile-time optimization)</span>

<span class="c1">// Integer caching (-128 to 127)</span>
<span class="nc">Integer</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">127</span><span class="o">;</span>
<span class="nc">Integer</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">127</span><span class="o">;</span>
<span class="n">x</span> <span class="o">==</span> <span class="n">y</span><span class="o">;</span>       <span class="c1">// true — cached</span>

<span class="nc">Integer</span> <span class="n">m</span> <span class="o">=</span> <span class="mi">128</span><span class="o">;</span>
<span class="nc">Integer</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">128</span><span class="o">;</span>
<span class="n">m</span> <span class="o">==</span> <span class="n">n</span><span class="o">;</span>       <span class="c1">// false — ngoài cache range, different objects</span>
<span class="n">m</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">n</span><span class="o">);</span>  <span class="c1">// true</span>

<span class="c1">// Best practice: LUÔN dùng equals() cho objects, == chỉ cho primitives và null check</span>
<span class="nc">Objects</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">);</span>  <span class="c1">// Null-safe equals</span>
</code></pre></div></div>

<hr />

<h2 id="2-final-vs-finally-vs-finalize">2. final vs finally vs finalize</h2>

<p>Ba keywords bắt đầu bằng “final” nhưng hoàn toàn khác nhau.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// final — constant/immutable</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="no">MAX</span> <span class="o">=</span> <span class="mi">100</span><span class="o">;</span>              <span class="c1">// Variable: không reassign được</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Utility</span> <span class="o">{</span> <span class="o">}</span>           <span class="c1">// Class: không extend được</span>
<span class="kd">final</span> <span class="kt">void</span> <span class="nf">process</span><span class="o">()</span> <span class="o">{</span> <span class="o">}</span>          <span class="c1">// Method: không override được</span>

<span class="c1">// final trong modern Java: effectively final cho lambdas</span>
<span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="s">"John"</span><span class="o">;</span>  <span class="c1">// Effectively final (không reassign)</span>
<span class="nc">Runnable</span> <span class="n">r</span> <span class="o">=</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>  <span class="c1">// OK</span>

<span class="c1">// finally — cleanup code (luôn chạy sau try/catch)</span>
<span class="k">try</span> <span class="o">{</span>
   <span class="n">connection</span> <span class="o">=</span> <span class="n">dataSource</span><span class="o">.</span><span class="na">getConnection</span><span class="o">();</span>
   <span class="c1">// ... do work</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SQLException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
   <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"DB error"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
   <span class="k">if</span> <span class="o">(</span><span class="n">connection</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="n">connection</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>  <span class="c1">// LUÔN chạy</span>
<span class="o">}</span>
<span class="c1">// Modern: try-with-resources thay thế finally cho AutoCloseable</span>

<span class="c1">// finalize — DEPRECATED (Java 9+), DO NOT USE</span>
<span class="c1">// Chạy trước GC collect object — unpredictable timing, performance killer</span>
<span class="c1">// Thay thế: Cleaner API hoặc try-with-resources</span>
</code></pre></div></div>

<hr />

<h2 id="3-abstract-class-vs-interface">3. abstract class vs interface</h2>

<p>Trước Java 8, ranh giới rõ ràng. Sau Java 8+ (default methods), ranh giới mờ hơn nhưng vẫn có semantic khác nhau.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Interface — contract, "can do" relationship (HAS-A behavior)</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Auditable</span> <span class="o">{</span>
   <span class="nc">LocalDateTime</span> <span class="nf">getCreatedAt</span><span class="o">();</span>
   <span class="nc">String</span> <span class="nf">getCreatedBy</span><span class="o">();</span>

   <span class="c1">// Default method (Java 8+): behavior chung, override được</span>
   <span class="k">default</span> <span class="nc">String</span> <span class="nf">getAuditInfo</span><span class="o">()</span> <span class="o">{</span>
       <span class="k">return</span> <span class="nf">getCreatedBy</span><span class="o">()</span> <span class="o">+</span> <span class="s">" at "</span> <span class="o">+</span> <span class="n">getCreatedAt</span><span class="o">();</span>
   <span class="o">}</span>

   <span class="c1">// Static method</span>
   <span class="kd">static</span> <span class="nc">Auditable</span> <span class="nf">empty</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">EmptyAuditable</span><span class="o">();</span> <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Abstract class — shared implementation, "is-a" relationship</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">BaseEntity</span> <span class="o">{</span>
   <span class="nd">@Id</span>
   <span class="kd">private</span> <span class="no">UUID</span> <span class="n">id</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">LocalDateTime</span> <span class="n">createdAt</span><span class="o">;</span>

   <span class="c1">// Concrete method: shared behavior</span>
   <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isNew</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">id</span> <span class="o">==</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span>

   <span class="c1">// Abstract method: subclass must implement</span>
   <span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">String</span> <span class="nf">getEntityType</span><span class="o">();</span>

   <span class="c1">// Constructor (interface KHÔNG có)</span>
   <span class="kd">protected</span> <span class="nf">BaseEntity</span><span class="o">()</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">();</span> <span class="o">}</span>

   <span class="c1">// State (fields) — interface chỉ có constants</span>
   <span class="kd">private</span> <span class="kt">int</span> <span class="n">version</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// Class implement nhiều interfaces, extend chỉ 1 abstract class</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Product</span> <span class="kd">extends</span> <span class="nc">BaseEntity</span> <span class="kd">implements</span> <span class="nc">Auditable</span><span class="o">,</span> <span class="nc">Serializable</span> <span class="o">{</span> <span class="o">}</span>
</code></pre></div></div>

<p><strong>Khi nào dùng gì:</strong>
| Criteria | Interface | Abstract Class |
|———-|———–|—————-|
| Multiple inheritance | ✓ (implements nhiều) | ✗ (extends 1) |
| Has state (fields) | ✗ (chỉ constants) | ✓ |
| Has constructor | ✗ | ✓ |
| Forces “is-a” relationship | ✗ | ✓ |
| Evolving API | ✓ (default methods) | ✓ |
| Template Method pattern | ✗ | ✓ (preferred) |
| Strategy/Capability | ✓ (preferred) | ✗ |</p>

<hr />

<h2 id="4-checked-vs-unchecked-exceptions">4. Checked vs Unchecked Exceptions</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Checked Exception — compiler FORCE bạn handle (extends Exception)</span>
<span class="c1">// "Recoverable errors" — caller có thể xử lý</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">InsufficientFundsException</span> <span class="kd">extends</span> <span class="nc">Exception</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">BigDecimal</span> <span class="n">balance</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">BigDecimal</span> <span class="n">amount</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// Phải try-catch hoặc throws</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">transfer</span><span class="o">(</span><span class="nc">BigDecimal</span> <span class="n">amount</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InsufficientFundsException</span> <span class="o">{</span>
   <span class="k">if</span> <span class="o">(</span><span class="n">balance</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">amount</span><span class="o">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">InsufficientFundsException</span><span class="o">(</span><span class="n">balance</span><span class="o">,</span> <span class="n">amount</span><span class="o">);</span>
<span class="o">}</span>

<span class="c1">// Unchecked Exception — compiler KHÔNG force handle (extends RuntimeException)</span>
<span class="c1">// "Programming errors" — caller thường không thể recover</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ProductNotFoundException</span> <span class="kd">extends</span> <span class="nc">RuntimeException</span> <span class="o">{</span>
   <span class="kd">public</span> <span class="nf">ProductNotFoundException</span><span class="o">(</span><span class="no">UUID</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
       <span class="kd">super</span><span class="o">(</span><span class="s">"Product not found: "</span> <span class="o">+</span> <span class="n">id</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Không cần try-catch</span>
<span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">getById</span><span class="o">(</span><span class="no">UUID</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
   <span class="k">return</span> <span class="n">repo</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">).</span><span class="na">orElseThrow</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="k">new</span> <span class="nc">ProductNotFoundException</span><span class="o">(</span><span class="n">id</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>Spring convention</strong>: Dùng UNCHECKED exceptions cho mọi business/application errors. Lý do:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">@Transactional</code> mặc định chỉ rollback RuntimeException</li>
  <li>Không pollute method signatures với throws</li>
  <li>Controller advice catch tất cả centrally</li>
</ul>

<hr />

<h2 id="5-hashmap-vs-concurrenthashmap-vs-linkedhashmap-vs-treemap">5. HashMap vs ConcurrentHashMap vs LinkedHashMap vs TreeMap</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// HashMap — fastest, unordered, NOT thread-safe</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Product</span><span class="o">&gt;</span> <span class="n">cache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>
<span class="c1">// O(1) get/put, null keys/values allowed</span>
<span class="c1">// ❌ KHÔNG dùng trong multi-threaded context</span>

<span class="c1">// ConcurrentHashMap — thread-safe, NO locks on reads</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Product</span><span class="o">&gt;</span> <span class="n">concurrentCache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentHashMap</span><span class="o">&lt;&gt;();</span>
<span class="c1">// Thread-safe, null keys/values NOT allowed</span>
<span class="c1">// Segment-level locking (Java 8: CAS + synchronized on node)</span>
<span class="c1">// ✅ Dùng cho shared state giữa threads</span>

<span class="c1">// LinkedHashMap — insertion order preserved</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Product</span><span class="o">&gt;</span> <span class="n">orderedMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedHashMap</span><span class="o">&lt;&gt;();</span>
<span class="c1">// Access order mode → LRU cache implementation</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Product</span><span class="o">&gt;</span> <span class="n">lruCache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedHashMap</span><span class="o">&lt;&gt;(</span><span class="mi">16</span><span class="o">,</span> <span class="mf">0.75f</span><span class="o">,</span> <span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
   <span class="nd">@Override</span>
   <span class="kd">protected</span> <span class="kt">boolean</span> <span class="nf">removeEldestEntry</span><span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">Entry</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Product</span><span class="o">&gt;</span> <span class="n">eldest</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="nf">size</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="o">;</span>  <span class="c1">// Max 100 entries</span>
   <span class="o">}</span>
<span class="o">};</span>

<span class="c1">// TreeMap — sorted by key (Red-Black tree)</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Product</span><span class="o">&gt;</span> <span class="n">sortedMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TreeMap</span><span class="o">&lt;&gt;();</span>
<span class="c1">// O(log n) get/put, keys must be Comparable</span>
<span class="c1">// Use case: range queries, "give me all products between A-M"</span>
<span class="nc">NavigableMap</span><span class="o">&lt;</span><span class="nc">LocalDate</span><span class="o">,</span> <span class="nc">BigDecimal</span><span class="o">&gt;</span> <span class="n">dailyRevenue</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TreeMap</span><span class="o">&lt;&gt;();</span>
<span class="n">dailyRevenue</span><span class="o">.</span><span class="na">subMap</span><span class="o">(</span><span class="n">startDate</span><span class="o">,</span> <span class="n">endDate</span><span class="o">);</span>  <span class="c1">// Range query</span>
</code></pre></div></div>

<hr />

<h2 id="6-arraylist-vs-linkedlist-vs-copyonwritearraylist">6. ArrayList vs LinkedList vs CopyOnWriteArrayList</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ArrayList — dynamic array, default choice 99% of the time</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">products</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
<span class="c1">// O(1) random access, O(1) amortized add, O(n) insert/remove middle</span>
<span class="c1">// Cache-friendly (contiguous memory)</span>

<span class="c1">// LinkedList — doubly-linked list, RARELY better choice</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">linkedProducts</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedList</span><span class="o">&lt;&gt;();</span>
<span class="c1">// O(n) random access, O(1) add/remove at head/tail</span>
<span class="c1">// ❌ Almost NEVER faster than ArrayList in practice (cache misses)</span>
<span class="c1">// Only use case: frequent add/remove at both ends (Deque interface)</span>

<span class="c1">// CopyOnWriteArrayList — thread-safe, optimized for reads</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">EventListener</span><span class="o">&gt;</span> <span class="n">listeners</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CopyOnWriteArrayList</span><span class="o">&lt;&gt;();</span>
<span class="c1">// Every write creates new internal array copy → expensive writes</span>
<span class="c1">// Reads never block, never throw ConcurrentModificationException</span>
<span class="c1">// ✅ Use for: listener lists, rarely-modified collections read by many threads</span>
</code></pre></div></div>

<hr />

<h2 id="7-synchronized-vs-reentrantlock-vs-volatile-vs-atomic">7. synchronized vs ReentrantLock vs volatile vs Atomic</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// synchronized — implicit lock, simple</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Counter</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

   <span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">increment</span><span class="o">()</span> <span class="o">{</span> <span class="n">count</span><span class="o">++;</span> <span class="o">}</span>
   <span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">int</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">count</span><span class="o">;</span> <span class="o">}</span>
   <span class="c1">// Pros: Simple, auto-release</span>
   <span class="c1">// Cons: No tryLock, no timeout, no fairness control</span>
<span class="o">}</span>

<span class="c1">// ReentrantLock — explicit lock, more control</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AdvancedCounter</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ReentrantLock</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ReentrantLock</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="c1">// fair</span>
   <span class="kd">private</span> <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">increment</span><span class="o">()</span> <span class="o">{</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">lock</span><span class="o">.</span><span class="na">tryLock</span><span class="o">(</span><span class="mi">5</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">))</span> <span class="o">{</span>  <span class="c1">// Timeout!</span>
           <span class="k">try</span> <span class="o">{</span> <span class="n">count</span><span class="o">++;</span> <span class="o">}</span>
           <span class="k">finally</span> <span class="o">{</span> <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span> <span class="o">}</span>  <span class="c1">// Manual unlock</span>
       <span class="o">}</span>
   <span class="o">}</span>
   <span class="c1">// Pros: tryLock, timeout, fairness, multiple conditions</span>
   <span class="c1">// Cons: Verbose, must remember unlock in finally</span>
<span class="o">}</span>

<span class="c1">// volatile — visibility guarantee, NO atomicity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Flag</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">volatile</span> <span class="kt">boolean</span> <span class="n">running</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>  <span class="c1">// Visible across threads immediately</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">stop</span><span class="o">()</span> <span class="o">{</span> <span class="n">running</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span>  <span class="c1">// Write visible to all readers</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="k">while</span> <span class="o">(</span><span class="n">running</span><span class="o">)</span> <span class="o">{</span> <span class="cm">/* work */</span> <span class="o">}</span> <span class="o">}</span>
   <span class="c1">// Pros: Lightweight, no locking</span>
   <span class="c1">// Cons: Only works for single write/read — NOT for compound operations (count++)</span>
<span class="o">}</span>

<span class="c1">// AtomicInteger — lock-free atomic operations (CAS)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AtomicCounter</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">AtomicInteger</span> <span class="n">count</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AtomicInteger</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>

   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">increment</span><span class="o">()</span> <span class="o">{</span> <span class="n">count</span><span class="o">.</span><span class="na">incrementAndGet</span><span class="o">();</span> <span class="o">}</span>  <span class="c1">// Atomic!</span>
   <span class="kd">public</span> <span class="kt">int</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">count</span><span class="o">.</span><span class="na">get</span><span class="o">();</span> <span class="o">}</span>
   <span class="c1">// Pros: Lock-free, high performance under contention</span>
   <span class="c1">// Cons: Only single variable, no compound atomicity</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>Decision tree:</strong></p>

<ul>
  <li>Chỉ cần visibility (flag, config) → <code class="language-plaintext highlighter-rouge">volatile</code></li>
  <li>Single variable atomic ops → <code class="language-plaintext highlighter-rouge">AtomicInteger/AtomicReference</code></li>
  <li>Simple mutual exclusion → <code class="language-plaintext highlighter-rouge">synchronized</code></li>
  <li>Cần tryLock/timeout/fairness → <code class="language-plaintext highlighter-rouge">ReentrantLock</code></li>
</ul>

<hr />

<h2 id="8-component-vs-service-vs-repository-vs-controller">8. @Component vs @Service vs @Repository vs @Controller</h2>

<p>Technically tất cả đều là <code class="language-plaintext highlighter-rouge">@Component</code> — Spring đều scan và đăng ký bean. Khác biệt nằm ở <strong>semantic</strong> và <strong>special behavior</strong>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>          <span class="c1">// Generic Spring bean (utility, helper)</span>
<span class="nd">@Service</span>            <span class="c1">// Business logic layer — KHÔNG có behavior đặc biệt, chỉ semantic</span>
<span class="nd">@Repository</span>         <span class="c1">// Data access layer — Exception translation (DataAccessException)</span>
<span class="nd">@Controller</span>         <span class="c1">// Web layer — view resolution</span>
<span class="nd">@RestController</span>     <span class="c1">// Web layer — = @Controller + @ResponseBody</span>
<span class="nd">@Configuration</span>      <span class="c1">// Config class — CGLIB proxy, @Bean method interception</span>
</code></pre></div></div>

<p><strong>@Repository đặc biệt</strong>: Spring tự động translate database-specific exceptions (SQLIntegrityConstraintViolation) thành Spring’s <code class="language-plaintext highlighter-rouge">DataAccessException</code> hierarchy — portable error handling.</p>

<hr />

<h2 id="9-put-vs-patch--http-semantics">9. PUT vs PATCH — HTTP semantics</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// PUT — replace TOÀN BỘ resource (idempotent)</span>
<span class="c1">// Client gửi complete representation</span>
<span class="nd">@PutMapping</span><span class="o">(</span><span class="s">"/{id}"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">update</span><span class="o">(</span><span class="nd">@PathVariable</span> <span class="no">UUID</span> <span class="n">id</span><span class="o">,</span> <span class="nd">@Valid</span> <span class="nd">@RequestBody</span> <span class="nc">UpdateProductRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
   <span class="c1">// request PHẢI chứa TẤT CẢ fields</span>
   <span class="c1">// Fields không gửi = set null/default</span>
   <span class="nc">Product</span> <span class="n">product</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">).</span><span class="na">orElseThrow</span><span class="o">();</span>
   <span class="n">product</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>       <span class="c1">// Bắt buộc</span>
   <span class="n">product</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getPrice</span><span class="o">());</span>     <span class="c1">// Bắt buộc</span>
   <span class="n">product</span><span class="o">.</span><span class="na">setDescription</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getDescription</span><span class="o">());</span> <span class="c1">// null nếu không gửi → xóa description</span>
   <span class="k">return</span> <span class="nf">toDTO</span><span class="o">(</span><span class="n">productRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">product</span><span class="o">));</span>
<span class="o">}</span>

<span class="c1">// PATCH — update MỘT PHẦN resource (chỉ fields gửi lên)</span>
<span class="c1">// Client gửi partial representation</span>
<span class="nd">@PatchMapping</span><span class="o">(</span><span class="s">"/{id}"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">patch</span><span class="o">(</span><span class="nd">@PathVariable</span> <span class="no">UUID</span> <span class="n">id</span><span class="o">,</span> <span class="nd">@RequestBody</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">updates</span><span class="o">)</span> <span class="o">{</span>
   <span class="nc">Product</span> <span class="n">product</span> <span class="o">=</span> <span class="n">productRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">).</span><span class="na">orElseThrow</span><span class="o">();</span>

   <span class="c1">// Chỉ update fields có trong request</span>
   <span class="n">updates</span><span class="o">.</span><span class="na">forEach</span><span class="o">((</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span>
       <span class="k">switch</span> <span class="o">(</span><span class="n">key</span><span class="o">)</span> <span class="o">{</span>
           <span class="k">case</span> <span class="s">"name"</span> <span class="o">-&gt;</span> <span class="n">product</span><span class="o">.</span><span class="na">setName</span><span class="o">((</span><span class="nc">String</span><span class="o">)</span> <span class="n">value</span><span class="o">);</span>
           <span class="k">case</span> <span class="s">"price"</span> <span class="o">-&gt;</span> <span class="n">product</span><span class="o">.</span><span class="na">setPrice</span><span class="o">(</span><span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="n">value</span><span class="o">.</span><span class="na">toString</span><span class="o">()));</span>
           <span class="k">case</span> <span class="s">"description"</span> <span class="o">-&gt;</span> <span class="n">product</span><span class="o">.</span><span class="na">setDescription</span><span class="o">((</span><span class="nc">String</span><span class="o">)</span> <span class="n">value</span><span class="o">);</span>
       <span class="o">}</span>
   <span class="o">});</span>

   <span class="k">return</span> <span class="nf">toDTO</span><span class="o">(</span><span class="n">productRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">product</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>PUT</th>
      <th>PATCH</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Semantics</td>
      <td>Replace full resource</td>
      <td>Partial update</td>
    </tr>
    <tr>
      <td>Body</td>
      <td>Complete object</td>
      <td>Only changed fields</td>
    </tr>
    <tr>
      <td>Missing fields</td>
      <td>Set to null/default</td>
      <td>Leave unchanged</td>
    </tr>
    <tr>
      <td>Idempotent</td>
      <td>✓</td>
      <td>Not guaranteed</td>
    </tr>
    <tr>
      <td>Validation</td>
      <td>Validate all required fields</td>
      <td>Validate only sent fields</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="10-requestparam-vs-pathvariable-vs-requestbody">10. @RequestParam vs @PathVariable vs @RequestBody</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// @PathVariable — resource identifier, part of URL path</span>
<span class="c1">// GET /api/products/550e8400-e29b-41d4-a716-446655440000</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/{id}"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">get</span><span class="o">(</span><span class="nd">@PathVariable</span> <span class="no">UUID</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="c1">// Khi nào: identifying specific resource</span>

<span class="c1">// @RequestParam — filtering, pagination, search criteria</span>
<span class="c1">// GET /api/products?status=ACTIVE&amp;page=0&amp;size=20&amp;keyword=laptop</span>
<span class="nd">@GetMapping</span>
<span class="kd">public</span> <span class="nc">Page</span><span class="o">&lt;</span><span class="nc">ProductDTO</span><span class="o">&gt;</span> <span class="nf">list</span><span class="o">(</span>
   <span class="nd">@RequestParam</span><span class="o">(</span><span class="n">required</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span> <span class="nc">ProductStatus</span> <span class="n">status</span><span class="o">,</span>
   <span class="nd">@RequestParam</span><span class="o">(</span><span class="n">defaultValue</span> <span class="o">=</span> <span class="s">"0"</span><span class="o">)</span> <span class="kt">int</span> <span class="n">page</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="c1">// Khi nào: optional filters, query parameters</span>

<span class="c1">// @RequestBody — JSON payload (POST/PUT/PATCH body)</span>
<span class="c1">// POST /api/products  with JSON body</span>
<span class="nd">@PostMapping</span>
<span class="kd">public</span> <span class="nc">ProductDTO</span> <span class="nf">create</span><span class="o">(</span><span class="nd">@Valid</span> <span class="nd">@RequestBody</span> <span class="nc">CreateProductRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="c1">// Khi nào: sending complex object, create/update operations</span>
</code></pre></div></div>

<hr />

<h2 id="11-comparator-vs-comparable">11. Comparator vs Comparable</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Comparable — "natural ordering" (defined IN the class)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Product</span> <span class="kd">implements</span> <span class="nc">Comparable</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="o">{</span>
   <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
   <span class="kd">private</span> <span class="nc">BigDecimal</span> <span class="n">price</span><span class="o">;</span>

   <span class="nd">@Override</span>
   <span class="kd">public</span> <span class="kt">int</span> <span class="nf">compareTo</span><span class="o">(</span><span class="nc">Product</span> <span class="n">other</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">other</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>  <span class="c1">// Natural order: by name</span>
   <span class="o">}</span>
<span class="o">}</span>
<span class="nc">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">products</span><span class="o">);</span>  <span class="c1">// Uses compareTo</span>

<span class="c1">// Comparator — "custom ordering" (defined OUTSIDE the class)</span>
<span class="c1">// Nhiều cách sort khác nhau cho cùng 1 class</span>
<span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">byPrice</span> <span class="o">=</span> <span class="nc">Comparator</span><span class="o">.</span><span class="na">comparing</span><span class="o">(</span><span class="nl">Product:</span><span class="o">:</span><span class="n">getPrice</span><span class="o">);</span>
<span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">byPriceDesc</span> <span class="o">=</span> <span class="nc">Comparator</span><span class="o">.</span><span class="na">comparing</span><span class="o">(</span><span class="nl">Product:</span><span class="o">:</span><span class="n">getPrice</span><span class="o">).</span><span class="na">reversed</span><span class="o">();</span>
<span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">byNameThenPrice</span> <span class="o">=</span> <span class="nc">Comparator</span><span class="o">.</span><span class="na">comparing</span><span class="o">(</span><span class="nl">Product:</span><span class="o">:</span><span class="n">getName</span><span class="o">)</span>
   <span class="o">.</span><span class="na">thenComparing</span><span class="o">(</span><span class="nl">Product:</span><span class="o">:</span><span class="n">getPrice</span><span class="o">);</span>

<span class="c1">// Null-safe</span>
<span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">Product</span><span class="o">&gt;</span> <span class="n">nullSafe</span> <span class="o">=</span> <span class="nc">Comparator</span><span class="o">.</span><span class="na">comparing</span><span class="o">(</span>
   <span class="nl">Product:</span><span class="o">:</span><span class="n">getCategory</span><span class="o">,</span> <span class="nc">Comparator</span><span class="o">.</span><span class="na">nullsLast</span><span class="o">(</span><span class="nc">Comparator</span><span class="o">.</span><span class="na">naturalOrder</span><span class="o">()));</span>

<span class="n">products</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">byPrice</span><span class="o">);</span>
<span class="n">products</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">sorted</span><span class="o">(</span><span class="n">byNameThenPrice</span><span class="o">).</span><span class="na">toList</span><span class="o">();</span>
</code></pre></div></div>

<p><strong>Rule</strong>: Implement <code class="language-plaintext highlighter-rouge">Comparable</code> cho 1 obvious natural order (String → alphabetical, Date → chronological). Dùng <code class="language-plaintext highlighter-rouge">Comparator</code> cho alternative/custom orderings.</p>

<hr />

<h2 id="12-stringbuilder-vs-stringbuffer-vs-string-concatenation">12. StringBuilder vs StringBuffer vs String concatenation</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// String concatenation (+) — compiler optimizes simple cases</span>
<span class="nc">String</span> <span class="n">result</span> <span class="o">=</span> <span class="s">"Hello "</span> <span class="o">+</span> <span class="n">name</span> <span class="o">+</span> <span class="s">"!"</span><span class="o">;</span>
<span class="c1">// Java 9+: compiler uses invokedynamic → efficient cho simple cases</span>
<span class="c1">// ❌ KHÔNG dùng trong loops</span>

<span class="c1">// ❌ Anti-pattern: concatenation in loop</span>
<span class="nc">String</span> <span class="n">csv</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Product</span> <span class="n">p</span> <span class="o">:</span> <span class="n">products</span><span class="o">)</span> <span class="o">{</span>
   <span class="n">csv</span> <span class="o">+=</span> <span class="n">p</span><span class="o">.</span><span class="na">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">","</span><span class="o">;</span>  <span class="c1">// Tạo new String object mỗi iteration → O(n²)</span>
<span class="o">}</span>

<span class="c1">// ✅ StringBuilder — NOT thread-safe, fast (single-threaded)</span>
<span class="nc">StringBuilder</span> <span class="n">sb</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Product</span> <span class="n">p</span> <span class="o">:</span> <span class="n">products</span><span class="o">)</span> <span class="o">{</span>
   <span class="n">sb</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">p</span><span class="o">.</span><span class="na">getName</span><span class="o">()).</span><span class="na">append</span><span class="o">(</span><span class="s">","</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">String</span> <span class="n">csv</span> <span class="o">=</span> <span class="n">sb</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>

<span class="c1">// StringBuffer — thread-safe (synchronized), slower</span>
<span class="nc">StringBuffer</span> <span class="n">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuffer</span><span class="o">();</span>  <span class="c1">// Legacy, HIẾM KHI cần</span>
<span class="c1">// Chỉ khi nhiều threads cùng build 1 string (rất rare)</span>

<span class="c1">// ✅ Modern approach: String.join / Collectors</span>
<span class="nc">String</span> <span class="n">csv</span> <span class="o">=</span> <span class="n">products</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
   <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Product:</span><span class="o">:</span><span class="n">getName</span><span class="o">)</span>
   <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">joining</span><span class="o">(</span><span class="s">","</span><span class="o">));</span>

<span class="nc">String</span> <span class="n">csv</span> <span class="o">=</span> <span class="nc">String</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="s">","</span><span class="o">,</span> <span class="n">names</span><span class="o">);</span>
</code></pre></div></div>

<p><strong>Decision</strong>: Dùng <code class="language-plaintext highlighter-rouge">+</code> cho simple expressions. <code class="language-plaintext highlighter-rouge">StringBuilder</code> cho loops/complex building. <code class="language-plaintext highlighter-rouge">String.join</code>/<code class="language-plaintext highlighter-rouge">Collectors.joining</code> cho collections. <code class="language-plaintext highlighter-rouge">StringBuffer</code> → almost never.</p>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[Tổng hợp các cặp concepts trong Java/Spring thường bị hỏi trong phỏng vấn Senior và dễ dùng sai trong production.]]></summary></entry><entry><title type="html">saga pattern — distributed transactions trong microservices</title><link href="https://cristorvalds.netlify.app/saga-pattern/" rel="alternate" type="text/html" title="saga pattern — distributed transactions trong microservices" /><published>2026-05-25T19:29:39+07:00</published><updated>2026-05-25T19:29:39+07:00</updated><id>https://cristorvalds.netlify.app/saga-pattern</id><content type="html" xml:base="https://cristorvalds.netlify.app/saga-pattern/"><![CDATA[<h3 id="-vấn-đề-tại-sao-không-dùng-được-acid-transaction-trong-microservices"># vấn đề: tại sao không dùng được acid transaction trong microservices?</h3>

<p>Trong monolith, bạn wrap tất cả trong 1 <code class="language-plaintext highlighter-rouge">@Transactional</code>: tạo order, trừ tiền, giảm inventory — hoặc tất cả thành công, hoặc tất cả rollback. Database đảm bảo ACID. Cuộc sống đẹp.</p>

<p>Nhưng khi tách thành microservices — Order Service dùng PostgreSQL riêng, Payment Service dùng DB riêng, Inventory Service dùng DB riêng — không còn 1 transaction manager nào kiểm soát cả 3 databases. Two-Phase Commit (2PC) tồn tại nhưng trong thực tế:</p>

<ul>
  <li>Chậm (lock resources cross-service)</li>
  <li>Fragile (coordinator crash = stuck locks)</li>
  <li>Không scale (tight coupling giữa services)</li>
  <li>Nhiều databases/message brokers không support</li>
</ul>

<p><strong>Saga Pattern</strong> giải quyết: thay vì 1 big transaction, chia thành chuỗi local transactions. Mỗi service commit local transaction của mình.
Nếu 1 step fail → chạy compensating transactions để undo các steps trước.</p>

<h3 id="-saga-là-gì"># saga là gì?</h3>

<p>Saga = sequence of local transactions, mỗi transaction update 1 service/database. Sau mỗi local transaction thành công,
publish event/message trigger step tiếp theo. Nếu 1 step fail, chạy compensating transactions theo thứ tự ngược.</p>

<p><img src="/assets/img/blog/saga.png" alt="saga" /></p>

<p><strong>Key insight</strong>: Saga KHÔNG đảm bảo Isolation (chữ I trong ACID). Giữa T2 và T3, inventory đã reserved nhưng payment chưa
charge — state “tạm thời inconsistent” visible cho other transactions. Đây là trade-off chấp nhận được trong distributed systems (BASE &gt; ACID).</p>

<h3 id="-hai-mô-hình-choreography-vs-orchestration"># hai mô hình: choreography vs orchestration</h3>

<h4 id="-choreography--event-driven-no-central-coordinator"># choreography — event-driven, no central coordinator</h4>

<p>Mỗi service lắng nghe events và quyết định phản ứng. Không có ai “chỉ huy.” Giống nhóm nhạc jazz — mỗi người nghe nhau và improvise.</p>

<p><img src="/assets/img/blog/saga-models.png" alt="saga-models" /></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Order Service — publishes event after creating order</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrderService</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderRepository</span> <span class="n">orderRepository</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ApplicationEventPublisher</span> <span class="n">eventPublisher</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RabbitTemplate</span> <span class="n">rabbitTemplate</span><span class="o">;</span>

   <span class="nd">@Transactional</span>
   <span class="kd">public</span> <span class="nc">OrderDTO</span> <span class="nf">createOrder</span><span class="o">(</span><span class="nc">CreateOrderRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
       <span class="nc">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="nc">Order</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
           <span class="o">.</span><span class="na">customerId</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getCustomerId</span><span class="o">())</span>
           <span class="o">.</span><span class="na">items</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">getItems</span><span class="o">())</span>
           <span class="o">.</span><span class="na">status</span><span class="o">(</span><span class="nc">OrderStatus</span><span class="o">.</span><span class="na">PENDING</span><span class="o">)</span>
           <span class="o">.</span><span class="na">build</span><span class="o">();</span>
       <span class="n">order</span> <span class="o">=</span> <span class="n">orderRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">order</span><span class="o">);</span>

       <span class="c1">// Publish event → Inventory Service listens</span>
       <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span><span class="s">"order.exchange"</span><span class="o">,</span> <span class="s">"order.created"</span><span class="o">,</span>
           <span class="k">new</span> <span class="nf">OrderCreatedEvent</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">order</span><span class="o">.</span><span class="na">getItems</span><span class="o">()));</span>

       <span class="k">return</span> <span class="nf">toDTO</span><span class="o">(</span><span class="n">order</span><span class="o">);</span>
   <span class="o">}</span>
<span class="o">}</span>

<span class="c1">// Inventory Service — listens and reacts</span>
<span class="nd">@Component</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">InventoryEventListener</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">InventoryService</span> <span class="n">inventoryService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RabbitTemplate</span> <span class="n">rabbitTemplate</span><span class="o">;</span>

   <span class="nd">@RabbitListener</span><span class="o">(</span><span class="n">queues</span> <span class="o">=</span> <span class="s">"inventory.order-created"</span><span class="o">)</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onOrderCreated</span><span class="o">(</span><span class="nc">OrderCreatedEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
       <span class="k">try</span> <span class="o">{</span>
           <span class="n">inventoryService</span><span class="o">.</span><span class="na">reserve</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getItems</span><span class="o">());</span>

           <span class="c1">// Success → notify Payment Service</span>
           <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span><span class="s">"order.exchange"</span><span class="o">,</span> <span class="s">"inventory.reserved"</span><span class="o">,</span>
               <span class="k">new</span> <span class="nf">InventoryReservedEvent</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">event</span><span class="o">.</span><span class="na">getTotalAmount</span><span class="o">()));</span>

       <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InsufficientStockException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
           <span class="c1">// Fail → compensate: cancel order</span>
           <span class="n">rabbitTemplate</span><span class="o">.</span><span class="na">convertAndSend</span><span class="o">(</span><span class="s">"order.exchange"</span><span class="o">,</span> <span class="s">"inventory.failed"</span><span class="o">,</span>
               <span class="k">new</span> <span class="nf">InventoryFailedEvent</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">(),</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">()));</span>
       <span class="o">}</span>
   <span class="o">}</span>

   <span class="c1">// Compensation: release inventory khi payment fails</span>
   <span class="nd">@RabbitListener</span><span class="o">(</span><span class="n">queues</span> <span class="o">=</span> <span class="s">"inventory.payment-failed"</span><span class="o">)</span>
   <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onPaymentFailed</span><span class="o">(</span><span class="nc">PaymentFailedEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
       <span class="n">inventoryService</span><span class="o">.</span><span class="na">release</span><span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Inventory released for order: {}"</span><span class="o">,</span> <span class="n">event</span><span class="o">.</span><span class="na">getOrderId</span><span class="o">());</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>Ưu điểm Choreography:</strong></p>

<ul>
  <li>Simple: không cần central component</li>
  <li>Loose coupling: services chỉ biết events, không biết nhau</li>
  <li>Easy to add participants: thêm service mới chỉ cần subscribe event</li>
</ul>

<p><strong>Nhược điểm:</strong></p>

<ul>
  <li>Hard to understand flow: logic phân tán across services</li>
  <li>Hard to debug: “tại sao order stuck?” → phải trace events qua 5 services</li>
  <li>Cyclic dependencies: service A listen B, B listen A</li>
  <li>Difficult to add cross-cutting logic (timeout, retry cho toàn saga)</li>
</ul>

<h4 id="-orchestration--central-coordinator"># orchestration — central coordinator</h4>

<p>Một Saga Orchestrator biết toàn bộ flow, gọi từng service theo thứ tự, và handle failures. Giống conductor trong dàn nhạc — 1 người chỉ huy, mọi người follow.</p>

<p><img src="/assets/img/blog/saga-orchestration.png" alt="saga-orchestration" /></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Saga Orchestrator — biết toàn bộ flow, điều phối step-by-step.
*
* Implement dưới dạng state machine hoặc sequential steps.
* Mỗi step: execute → success → next step / fail → compensate previous steps.
*/</span>
<span class="nd">@Service</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CreateOrderSagaOrchestrator</span> <span class="o">{</span>

   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">OrderService</span> <span class="n">orderService</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">InventoryClient</span> <span class="n">inventoryClient</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">PaymentClient</span> <span class="n">paymentClient</span><span class="o">;</span>
   <span class="kd">private</span> <span class="kd">final</span> <span class="nc">NotificationClient</span> <span class="n">notificationClient</span><span class="o">;</span>

   <span class="cm">/**
    * Execute saga: từng step, rollback nếu fail.
    *
    * Flow: Create Order → Reserve Inventory → Charge Payment → Confirm
    * Compensate: Cancel Order ← Release Inventory ← Refund Payment
    */</span>
   <span class="kd">public</span> <span class="nc">OrderDTO</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">CreateOrderRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// Step 1: Create order (PENDING status)</span>
       <span class="nc">OrderDTO</span> <span class="n">order</span> <span class="o">=</span> <span class="n">orderService</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
       <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Saga step 1/4: Order created | orderId={}"</span><span class="o">,</span> <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>

       <span class="k">try</span> <span class="o">{</span>
           <span class="c1">// Step 2: Reserve inventory</span>
           <span class="n">inventoryClient</span><span class="o">.</span><span class="na">reserve</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">request</span><span class="o">.</span><span class="na">getItems</span><span class="o">());</span>
           <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Saga step 2/4: Inventory reserved | orderId={}"</span><span class="o">,</span> <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>

           <span class="k">try</span> <span class="o">{</span>
               <span class="c1">// Step 3: Charge payment</span>
               <span class="n">paymentClient</span><span class="o">.</span><span class="na">charge</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="n">order</span><span class="o">.</span><span class="na">getTotalAmount</span><span class="o">(),</span> <span class="n">request</span><span class="o">.</span><span class="na">getPaymentMethod</span><span class="o">());</span>
               <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Saga step 3/4: Payment charged | orderId={}"</span><span class="o">,</span> <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>

               <span class="c1">// Step 4: Confirm order</span>
               <span class="n">orderService</span><span class="o">.</span><span class="na">confirm</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
               <span class="n">notificationClient</span><span class="o">.</span><span class="na">sendOrderConfirmation</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getCustomerId</span><span class="o">(),</span> <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
               <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Saga step 4/4: Order confirmed | orderId={}"</span><span class="o">,</span> <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>

               <span class="k">return</span> <span class="n">orderService</span><span class="o">.</span><span class="na">getById</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>

           <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">PaymentException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
               <span class="c1">// Compensate step 2: release inventory</span>
               <span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Saga compensation: payment failed, releasing inventory | orderId={}"</span><span class="o">,</span>
                   <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
               <span class="n">inventoryClient</span><span class="o">.</span><span class="na">release</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
               <span class="n">orderService</span><span class="o">.</span><span class="na">cancel</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="s">"Payment failed: "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
               <span class="k">throw</span> <span class="k">new</span> <span class="nf">OrderCreationFailedException</span><span class="o">(</span><span class="s">"Payment failed"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
           <span class="o">}</span>

       <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InsufficientStockException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
           <span class="c1">// Compensate step 1: cancel order</span>
           <span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Saga compensation: insufficient stock, cancelling order | orderId={}"</span><span class="o">,</span>
               <span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
           <span class="n">orderService</span><span class="o">.</span><span class="na">cancel</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">(),</span> <span class="s">"Insufficient stock: "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
           <span class="k">throw</span> <span class="k">new</span> <span class="nf">OrderCreationFailedException</span><span class="o">(</span><span class="s">"Insufficient stock"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
       <span class="o">}</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>Ưu điểm Orchestration:</strong></p>

<ul>
  <li>Easy to understand: toàn bộ flow visible trong 1 class/diagram</li>
  <li>Easy to debug: orchestrator logs every step</li>
  <li>Central error handling: timeout, retry, compensation logic tập trung</li>
  <li>No cyclic dependencies</li>
</ul>

<p><strong>Nhược điểm:</strong></p>

<ul>
  <li>Single point of failure (orchestrator service)</li>
  <li>Coupling: orchestrator biết tất cả participants</li>
  <li>Risk of “god class”: orchestrator quá lớn</li>
</ul>

<h3 id="-khi-nào-dùng-cái-nào"># khi nào dùng cái nào?</h3>

<table>
  <thead>
    <tr>
      <th>Tiêu chí</th>
      <th>Choreography</th>
      <th>Orchestration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Số services</td>
      <td>2-3 (simple)</td>
      <td>4+ (complex)</td>
    </tr>
    <tr>
      <td>Flow complexity</td>
      <td>Linear, ít branches</td>
      <td>Nhiều branches, conditions</td>
    </tr>
    <tr>
      <td>Team structure</td>
      <td>Mỗi team own 1 service</td>
      <td>1 team own toàn flow</td>
    </tr>
    <tr>
      <td>Debugging needs</td>
      <td>Low (simple flows)</td>
      <td>High (complex flows)</td>
    </tr>
    <tr>
      <td>Business visibility</td>
      <td>Low</td>
      <td>High (flow visible)</td>
    </tr>
    <tr>
      <td>Flexibility to change</td>
      <td>High (add listener)</td>
      <td>Medium (change orchestrator)</td>
    </tr>
  </tbody>
</table>

<p><strong>Rule of thumb</strong>: Bắt đầu Choreography cho simple flows (2-3 steps). Chuyển sang Orchestration khi flow &gt; 4 steps hoặc có complex branching/compensation logic.</p>

<h3 id="-handling-challenges"># handling challenges</h3>

<h4 id="-idempotency--charged-2-lần-vì-retry"># idempotency — “charged 2 lần vì retry”</h4>

<p>Message broker có thể deliver message nhiều lần (at-least-once). Compensating transaction cũng có thể trigger nhiều lần. Mỗi step PHẢI idempotent.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PaymentService</span> <span class="o">{</span>

   <span class="nd">@Transactional</span>
   <span class="kd">public</span> <span class="nc">PaymentResult</span> <span class="nf">charge</span><span class="o">(</span><span class="no">UUID</span> <span class="n">orderId</span><span class="o">,</span> <span class="nc">BigDecimal</span> <span class="n">amount</span><span class="o">)</span> <span class="o">{</span>
       <span class="c1">// Check idempotency: đã charge cho order này chưa?</span>
       <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Payment</span><span class="o">&gt;</span> <span class="n">existing</span> <span class="o">=</span> <span class="n">paymentRepository</span><span class="o">.</span><span class="na">findByOrderId</span><span class="o">(</span><span class="n">orderId</span><span class="o">);</span>
       <span class="k">if</span> <span class="o">(</span><span class="n">existing</span><span class="o">.</span><span class="na">isPresent</span><span class="o">())</span> <span class="o">{</span>
           <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Payment already processed for order: {}"</span><span class="o">,</span> <span class="n">orderId</span><span class="o">);</span>
           <span class="k">return</span> <span class="n">existing</span><span class="o">.</span><span class="na">get</span><span class="o">().</span><span class="na">toResult</span><span class="o">();</span>  <span class="c1">// Return existing result, don't charge again</span>
       <span class="o">}</span>

       <span class="c1">// First time → execute charge</span>
       <span class="nc">Payment</span> <span class="n">payment</span> <span class="o">=</span> <span class="n">processCharge</span><span class="o">(</span><span class="n">orderId</span><span class="o">,</span> <span class="n">amount</span><span class="o">);</span>
       <span class="k">return</span> <span class="n">payment</span><span class="o">.</span><span class="na">toResult</span><span class="o">();</span>
   <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-timeout--saga-stuck-forever"># timeout — “saga stuck forever”</h4>

<p>Mỗi step cần timeout. Nếu service không respond trong X seconds → consider failed → compensate.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Orchestrator với timeout per step</span>
<span class="nc">CompletableFuture</span><span class="o">&lt;</span><span class="nc">InventoryResult</span><span class="o">&gt;</span> <span class="n">inventoryFuture</span> <span class="o">=</span> <span class="nc">CompletableFuture</span>
   <span class="o">.</span><span class="na">supplyAsync</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">inventoryClient</span><span class="o">.</span><span class="na">reserve</span><span class="o">(</span><span class="n">orderId</span><span class="o">,</span> <span class="n">items</span><span class="o">))</span>
   <span class="o">.</span><span class="na">orTimeout</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">);</span>  <span class="c1">// 10s timeout</span>

<span class="k">try</span> <span class="o">{</span>
   <span class="nc">InventoryResult</span> <span class="n">result</span> <span class="o">=</span> <span class="n">inventoryFuture</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">TimeoutException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
   <span class="c1">// Timeout = treat as failure → compensate</span>
   <span class="n">orderService</span><span class="o">.</span><span class="na">cancel</span><span class="o">(</span><span class="n">orderId</span><span class="o">,</span> <span class="s">"Inventory service timeout"</span><span class="o">);</span>
   <span class="k">throw</span> <span class="k">new</span> <span class="nf">SagaTimeoutException</span><span class="o">(</span><span class="s">"Inventory reservation timed out"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="-semantic-lock--tránh-dirty-reads-giữa-saga-steps"># semantic lock — tránh “dirty reads” giữa saga steps</h4>

<p>Khi order đang PENDING (giữa steps), user khác không nên thấy nó ở trạng thái final. Dùng status field: <code class="language-plaintext highlighter-rouge">PENDING → CONFIRMED</code> hoặc <code class="language-plaintext highlighter-rouge">PENDING → CANCELLED</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">enum</span> <span class="nc">OrderStatus</span> <span class="o">{</span>
   <span class="no">PENDING</span><span class="o">,</span>              <span class="c1">// Saga đang chạy — chưa final</span>
   <span class="no">INVENTORY_RESERVED</span><span class="o">,</span>   <span class="c1">// Step 2 done</span>
   <span class="no">PAYMENT_CHARGED</span><span class="o">,</span>      <span class="c1">// Step 3 done</span>
   <span class="no">CONFIRMED</span><span class="o">,</span>            <span class="c1">// Saga complete — happy path</span>
   <span class="no">CANCELLED</span><span class="o">,</span>            <span class="c1">// Saga compensated — sad path</span>
   <span class="no">COMPENSATION_FAILED</span>   <span class="c1">// Compensation cũng fail — cần manual intervention</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="-saga--bpmn--process-engine-as-orchestrator"># saga + bpmn — process engine as orchestrator</h3>

<p>BPMN engines (Activiti, Camunda) là natural Saga Orchestrators. Mỗi service task = 1 saga step. Error boundary events = compensation triggers. Compensation handlers = undo logic.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- BPMN as Saga Orchestrator --&gt;</span>
<span class="nt">&lt;process</span> <span class="na">id=</span><span class="s">"createOrderSaga"</span><span class="nt">&gt;</span>
   <span class="nt">&lt;startEvent</span> <span class="na">id=</span><span class="s">"start"</span><span class="nt">/&gt;</span>

   <span class="c">&lt;!-- Step 1: Create Order --&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"createOrder"</span> <span class="na">activiti:delegateExpression=</span><span class="s">"${createOrderDelegate}"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;boundaryEvent</span> <span class="na">id=</span><span class="s">"createOrderCompensation"</span> <span class="na">attachedToRef=</span><span class="s">"createOrder"</span><span class="nt">&gt;</span>
       <span class="nt">&lt;compensateEventDefinition/&gt;</span>
   <span class="nt">&lt;/boundaryEvent&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"cancelOrder"</span> <span class="na">isForCompensation=</span><span class="s">"true"</span>
                <span class="na">activiti:delegateExpression=</span><span class="s">"${cancelOrderDelegate}"</span><span class="nt">/&gt;</span>

   <span class="c">&lt;!-- Step 2: Reserve Inventory --&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"reserveInventory"</span> <span class="na">activiti:delegateExpression=</span><span class="s">"${reserveInventoryDelegate}"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;boundaryEvent</span> <span class="na">id=</span><span class="s">"reserveCompensation"</span> <span class="na">attachedToRef=</span><span class="s">"reserveInventory"</span><span class="nt">&gt;</span>
       <span class="nt">&lt;compensateEventDefinition/&gt;</span>
   <span class="nt">&lt;/boundaryEvent&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"releaseInventory"</span> <span class="na">isForCompensation=</span><span class="s">"true"</span>
                <span class="na">activiti:delegateExpression=</span><span class="s">"${releaseInventoryDelegate}"</span><span class="nt">/&gt;</span>

   <span class="c">&lt;!-- Step 3: Charge Payment (may fail) --&gt;</span>
   <span class="nt">&lt;serviceTask</span> <span class="na">id=</span><span class="s">"chargePayment"</span> <span class="na">activiti:delegateExpression=</span><span class="s">"${chargePaymentDelegate}"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;boundaryEvent</span> <span class="na">id=</span><span class="s">"paymentError"</span> <span class="na">attachedToRef=</span><span class="s">"chargePayment"</span><span class="nt">&gt;</span>
       <span class="nt">&lt;errorEventDefinition</span> <span class="na">errorRef=</span><span class="s">"PAYMENT_FAILED"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;/boundaryEvent&gt;</span>

   <span class="c">&lt;!-- Happy path end --&gt;</span>
   <span class="nt">&lt;endEvent</span> <span class="na">id=</span><span class="s">"success"</span><span class="nt">/&gt;</span>

   <span class="c">&lt;!-- Error path: trigger compensation for all completed steps --&gt;</span>
   <span class="nt">&lt;sequenceFlow</span> <span class="na">sourceRef=</span><span class="s">"paymentError"</span> <span class="na">targetRef=</span><span class="s">"compensateAll"</span><span class="nt">/&gt;</span>
   <span class="nt">&lt;intermediateThrowEvent</span> <span class="na">id=</span><span class="s">"compensateAll"</span><span class="nt">&gt;</span>
       <span class="nt">&lt;compensateEventDefinition/&gt;</span>
   <span class="nt">&lt;/intermediateThrowEvent&gt;</span>
   <span class="nt">&lt;endEvent</span> <span class="na">id=</span><span class="s">"sagaFailed"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/process&gt;</span>
</code></pre></div></div>

<p>Lợi ích: flow visualizable trên diagram, version control, monitoring built-in, retry/timeout native. Đây là cách nhiều enterprise systems implement Saga mà không cần build orchestrator framework từ đầu.</p>

<h3 id="-tổng-kết"># tổng kết</h3>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>Saga</th>
      <th>Distributed Transaction (2PC)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Consistency</td>
      <td>Eventual (BASE)</td>
      <td>Strong (ACID)</td>
    </tr>
    <tr>
      <td>Availability</td>
      <td>High</td>
      <td>Low (blocking)</td>
    </tr>
    <tr>
      <td>Performance</td>
      <td>Good (async)</td>
      <td>Poor (locks)</td>
    </tr>
    <tr>
      <td>Complexity</td>
      <td>Higher (compensation logic)</td>
      <td>Lower (framework handles)</td>
    </tr>
    <tr>
      <td>Scalability</td>
      <td>High</td>
      <td>Limited</td>
    </tr>
    <tr>
      <td>Use case</td>
      <td>Microservices</td>
      <td>Monolith/tightly-coupled</td>
    </tr>
  </tbody>
</table>

<p><strong>Saga là trade-off</strong>: bạn đổi strong consistency lấy availability và scalability. Compensation logic phức tạp hơn rollback, nhưng system resilient hơn nhiều. Trong microservices world, đây là accepted standard — không phải workaround.</p>

<blockquote>
  <p>Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè &amp; đồng nghiệp nhé!</p>
</blockquote>

<p>Happy coding <Twemoji emoji="clinking-beer-mugs"></Twemoji> 😎 👍🏻 🚀 🔥.</p>]]></content><author><name>CrisDaDev</name></author><category term="Software Development" /><category term="java" /><category term="software-development" /><category term="saga-pattern" /><category term="system-design" /><category term="best-practices" /><category term="vietnamese" /><summary type="html"><![CDATA[# vấn đề: tại sao không dùng được acid transaction trong microservices?]]></summary></entry></feed>