<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Timothy Leung</title>
    <description>The latest articles on Forem by Timothy Leung (@timleunghk).</description>
    <link>https://forem.com/timleunghk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3660808%2Ff2ecb456-eada-4640-942e-daae6c7161dc.png</url>
      <title>Forem: Timothy Leung</title>
      <link>https://forem.com/timleunghk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/timleunghk"/>
    <language>en</language>
    <item>
      <title>Discovering n8n — Wish I Found It Sooner (Part 2)</title>
      <dc:creator>Timothy Leung</dc:creator>
      <pubDate>Mon, 26 Jan 2026 09:05:07 +0000</pubDate>
      <link>https://forem.com/timleunghk/discovering-n8n-wish-i-found-it-sooner-part-2-4npk</link>
      <guid>https://forem.com/timleunghk/discovering-n8n-wish-i-found-it-sooner-part-2-4npk</guid>
      <description>&lt;p&gt;This is workflow diagram in n8n&lt;/p&gt;

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

&lt;p&gt;Hello everyone, I studied n8n for four days and then took its certification exam (Part 1), and I passed. I wouldn’t have known otherwise that it’s very easy to get started with and can handle many different types of projects. One example I’ve seen is a workflow for scheduling YouTube video uploads. You can really see how much potential it has! But no matter how complex things get, you should always start simple. Now I’d like to share a simple workflow with everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step: Understanding this n8n workflow
&lt;/h2&gt;

&lt;p&gt;This workflow runs on a schedule, fetches order data via an API, checks a condition, then splits into two paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path A:&lt;/strong&gt; store/update orders and generate a &lt;strong&gt;detailed CSV&lt;/strong&gt; for &lt;em&gt;processing&lt;/em&gt; orders
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path B:&lt;/strong&gt; calculate the &lt;strong&gt;total blocked orders&lt;/strong&gt; and display a completion message&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1) Schedule Trigger (start automatically)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;Schedule Trigger&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Starts the workflow at a defined interval (for example, every hour or every day).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose a schedule type (interval or Cron)&lt;/li&gt;
&lt;li&gt;Set time zone (optional)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2) HTTP Request (fetch orders from an API)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;HTTP Request&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Calls an external endpoint and returns order data (usually JSON).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Method: &lt;code&gt;GET&lt;/code&gt; (common)&lt;/li&gt;
&lt;li&gt;URL: your orders endpoint&lt;/li&gt;
&lt;li&gt;Authentication: API key/Bearer token (if required)&lt;/li&gt;
&lt;li&gt;Response format: JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt; order items/records to be processed downstream.&lt;/p&gt;




&lt;h3&gt;
  
  
  3) IF (branch based on a condition)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;IF&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Routes the execution depending on a rule (for example, whether orders exist, or whether status matches a value).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup ideas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Do we have any orders?”&lt;/li&gt;
&lt;li&gt;“Is order status = processing?”&lt;/li&gt;
&lt;li&gt;“Is blocked count &amp;gt; 0?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Two branches: &lt;strong&gt;true&lt;/strong&gt; and &lt;strong&gt;false&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Path A — Processing orders → Upsert → CSV export
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4) Upsert row(s) (insert or update records)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;Upsert row(s)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Writes orders into a destination (database/spreadsheet) and updates existing rows based on a unique key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the destination (e.g., DB table / sheet)&lt;/li&gt;
&lt;li&gt;Choose a unique match key (e.g., &lt;code&gt;order_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Map incoming fields to columns (status, amount, timestamps, etc.)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5) Convert to CSV — Processing Orders
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;Convert to CSV — Processing Orders&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Converts the processing-orders dataset into CSV.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose included fields/columns&lt;/li&gt;
&lt;li&gt;Enable headers (recommended)&lt;/li&gt;
&lt;li&gt;Ensure only &lt;em&gt;processing&lt;/em&gt; orders reach this step (via IF/filter)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt; CSV content (text/binary depending on node).&lt;/p&gt;




&lt;h3&gt;
  
  
  6) Detailed Processing Orders CSV (save/send the CSV)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;Detailed Processing Orders CSV&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Final action for the CSV, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upload to cloud storage (Drive/S3)&lt;/li&gt;
&lt;li&gt;email attachment&lt;/li&gt;
&lt;li&gt;save file for reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Use a timestamped filename, e.g. &lt;code&gt;processing_orders_YYYY-MM-DD.csv&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Path B — Blocked orders → totals → message
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7) Total Blocked Orders (aggregate or summarize)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;Total Blocked Orders&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Computes metrics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;number of blocked orders&lt;/li&gt;
&lt;li&gt;total blocked amount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure only blocked orders reach this node&lt;/li&gt;
&lt;li&gt;Use an aggregate/function step to compute totals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const items = $input.all();

let totalRecord=items.length;


let bookedSum = 0;

for (const item of items) {
  const v = Number(item.json.orderPrice ?? 0);
  if (!Number.isNaN(v)) bookedSum += v;
}



return [{ json: { totalRecord, bookedSum } }];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  8) Show Message for Completed Orders (notify/log)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Node:&lt;/strong&gt; &lt;code&gt;Show Message for Completed Orders&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Provides a completion notice (execution log, Slack, email, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example message:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Workflow completed. Blocked orders: X. Processing orders CSV generated.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;{{ $json.count }} orders are booked&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Then, it becomes:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bxdtc9z8omdyfbv34xm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bxdtc9z8omdyfbv34xm.png" alt=" " width="598" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you can click Executive Workflow to check how does it go.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to verify it in the Execution Window
&lt;/h2&gt;

&lt;p&gt;In the execution view, confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the HTTP request returns items successfully&lt;/li&gt;
&lt;li&gt;the IF node routes to the correct branch&lt;/li&gt;
&lt;li&gt;the CSV step produces the expected row/column set&lt;/li&gt;
&lt;li&gt;totals and notifications match the data&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I am preparing for Part 2 and hope I can pass next week. When the time comes, I will share my exam tips and strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect with Me
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/timleunghk" rel="noopener noreferrer"&gt;https://github.com/timleunghk&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/in/timothy-leung-48261b8b" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/timothy-leung-48261b8b&lt;/a&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
    </item>
    <item>
      <title>Discovering n8n — Wish I Found It Sooner (Part 1)</title>
      <dc:creator>Timothy Leung</dc:creator>
      <pubDate>Mon, 12 Jan 2026 06:15:49 +0000</pubDate>
      <link>https://forem.com/timleunghk/discovering-n8n-wish-i-found-it-sooner-part-1--171j</link>
      <guid>https://forem.com/timleunghk/discovering-n8n-wish-i-found-it-sooner-part-1--171j</guid>
      <description>&lt;p&gt;I can't believe I didn't find n8n earlier. If I met it earlier, I could make many many wonderful Apps. This is my first image when I know n8n. &lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I recently discovered &lt;strong&gt;n8n&lt;/strong&gt;, and honestly, I can’t believe I didn’t find it earlier. If I had discovered it sooner, I could have built so many useful (and fun) automations.&lt;/p&gt;

&lt;p&gt;n8n is a workflow automation tool that lets you connect apps and services with a visual, node-based editor. You can run it in the cloud, but you can also self-host it—so you fully control your data and environment.&lt;/p&gt;

&lt;p&gt;In this article, we’ll do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set up n8n locally with Docker&lt;/strong&gt; (safe, isolated, and easy to reset).&lt;/li&gt;
&lt;li&gt;Create a simple &lt;strong&gt;&lt;em&gt;Webhook&lt;/em&gt;&lt;/strong&gt; workflow and send a test request to trigger it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end, you’ll have n8n running at &lt;code&gt;http://localhost:5678&lt;/code&gt; and you’ll be able to trigger your first workflow from PowerShell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Locally
&lt;/h2&gt;

&lt;p&gt;You may start programming in n8n cloud editon. So, you can skip this step. However, if you setup your local environment, you will get a full picture of the &lt;em&gt;universe of n8n&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Before you start this setup, I recommend using Docker because it isolates everything from your host machine. Even if something goes wrong, it won’t impact your main environment.&lt;/p&gt;

&lt;p&gt;I’ll show you how to install n8n locally with Docker, step by step. On Windows, install Docker Desktop. On macOS or Linux, install Docker.&lt;/p&gt;

&lt;p&gt;Step 1: Create Folder / Directory&lt;/p&gt;

&lt;p&gt;You can type following commands to create these folders&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir C:\n8n-demo
cd C:\n8n-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can copy this docker compose script first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      # Basic
      - N8N_HOST=localhost
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - WEBHOOK_URL=http://localhost:5678/
      - N8N_EDITOR_BASE_URL=http://localhost:5678/

      # Timezone
      - TZ=Asia/Hong_Kong
      - GENERIC_TIMEZONE=Asia/Hong_Kong

      # Security / hygiene (recommended)
      - N8N_ENCRYPTION_KEY=change_me_to_a_long_random_string

      # Optional: disable diagnostics
      - N8N_DIAGNOSTICS_ENABLED=false
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Start n8b&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd C:\n8n-demo
docker compose up -d
docker compose ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker will start download images and process setup automatically based on your setup in above script. Please wait few seconds to finish it. After that, you can access your n8n website &lt;code&gt;http://localhost:5678&lt;/code&gt;. If you want to access your n8n outside your PC, please modify the code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- WEBHOOK_URL=http://localhost:5678/
- N8N_EDITOR_BASE_URL=http://localhost:5678/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- WEBHOOK_URL=http://&amp;lt;YOUR IP ADDRESS&amp;gt;:&amp;lt;&amp;lt;YOUR PORT NUMBER&amp;gt;
- N8N_EDITOR_BASE_URL=http://&amp;lt;YOUR IP ADDRESS&amp;gt;:&amp;lt;YOUR PORT NUMBER&amp;gt;
- N8N_SECURE_COOKIE=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR use dynamic DNS service&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- WEBHOOK_URL=https://&amp;lt;YOUR DYNAMIC DNS URL&amp;gt;
- N8N_EDITOR_BASE_URL=https://&amp;lt;YOUR DYNAMIC DNS URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hello n8n, Hello World!
&lt;/h2&gt;

&lt;p&gt;If you see this screen, Conguratulation! It means you've entered an universe of n8n! &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6sikvqcvfdctrvrnx0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6sikvqcvfdctrvrnx0k.png" alt=" " width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now provide your name, e-mail and password and then submit. You'll get a activation key in your mailbox. It makes you access it for lifetime!! That's why I suggest you use local environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Say "Hello World" with &lt;em&gt;Webhook&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Webhook&lt;/em&gt; node in n8n lets your workflow receive incoming HTTP requests (such as GET, POST, or PUT). When a request hits the webhook URL, it triggers the workflow so you can process the data and respond. In this article, we’ll use it to return a simple “Hello, World!” response from n8n.&lt;/p&gt;

&lt;p&gt;Here is your first screen of creating your first workflow&lt;/p&gt;

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

&lt;p&gt;Step 1: Click on the "+" icon.&lt;br&gt;
Step 2: Type "Webhook" as shown below&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie60s5bzq1cgqh6boafu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie60s5bzq1cgqh6boafu.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 3: Select "Webhook" (The first one)&lt;/p&gt;

&lt;p&gt;Then, you can see the properties screen of webhook as below:&lt;/p&gt;

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

&lt;p&gt;Then, modify the following values:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HTTP Method&lt;/code&gt;: &lt;code&gt;POST&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Path&lt;/code&gt;:&lt;code&gt;hellopath&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Authentication&lt;/code&gt;:&lt;code&gt;None&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Response&lt;/code&gt;:&lt;code&gt;Immediately&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Step 4: Ping It!&lt;br&gt;
Before starting ping, click &lt;em&gt;Listen for test event&lt;/em&gt; button. After clicking this button, it is start listening now.&lt;/p&gt;

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

&lt;p&gt;While it is listening, open your Power Shell Command Windows and then type the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invoke-RestMethod -Method Post `
  -Uri "http://localhost:5678/webhook-test/hellopath" `
  -ContentType "application/json" `
  -Body '{"ping":"pong"}'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see message returns Workflow has started, congratulations! You've already create your own automation successfully!&lt;/p&gt;

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

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

&lt;p&gt;That’s it! You’ve now installed n8n locally with Docker and triggered your first workflow using a webhook. In the next article, we can build on this by adding more nodes (for example, transforming the incoming JSON, sending a reply, or connecting to external services) and turning this simple test into a real automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect with Me
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/timleunghk" rel="noopener noreferrer"&gt;https://github.com/timleunghk&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/in/timothy-leung-48261b8b" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/timothy-leung-48261b8b&lt;/a&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>workflow</category>
      <category>docker</category>
    </item>
    <item>
      <title>The Deadline That Made Me Rethink My Architecture: Building Timesheetflow with Domain Thinking</title>
      <dc:creator>Timothy Leung</dc:creator>
      <pubDate>Sun, 04 Jan 2026 13:58:54 +0000</pubDate>
      <link>https://forem.com/timleunghk/the-deadline-that-made-me-rethink-my-architecture-building-timesheetflow-with-domain-thinking-53g2</link>
      <guid>https://forem.com/timleunghk/the-deadline-that-made-me-rethink-my-architecture-building-timesheetflow-with-domain-thinking-53g2</guid>
      <description>&lt;p&gt;“Can your system enforce a month-end deadline, handle late submissions fairly, and still produce a clean payroll summary?”&lt;/p&gt;

&lt;p&gt;That one question changed how I approached what I initially thought was “just Excel processing”.&lt;/p&gt;

&lt;p&gt;I was building &lt;strong&gt;Timesheetflow&lt;/strong&gt;—a small system to automate monthly employee timesheets that are still submitted as Excel files (because, realistically, many teams still live in spreadsheets).&lt;/p&gt;

&lt;p&gt;At first, I designed it like a typical file-processing app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read Excel files&lt;/li&gt;
&lt;li&gt;validate rows&lt;/li&gt;
&lt;li&gt;merge results&lt;/li&gt;
&lt;li&gt;export a summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;But it didn’t explain the business. And it didn’t protect the business rules that actually matter at month-end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Workflow (What the Business Actually Does)
&lt;/h2&gt;

&lt;p&gt;Timesheetflow’s workflow is simple—and very real:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Admin sets a processing deadline&lt;/strong&gt; (usually month-end)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Employees upload their own monthly timesheet&lt;/strong&gt; to a Google Drive folder before the deadline&lt;/li&gt;
&lt;li&gt;submissions are for &lt;strong&gt;the current month only&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;At the &lt;strong&gt;deadline&lt;/strong&gt;, the system processes timesheets &lt;strong&gt;one by one&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approvers review and approve&lt;/strong&gt; one, many, or all timesheets&lt;/li&gt;
&lt;li&gt;late submissions are allowed but clearly marked Late&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin downloads an Excel salary summary&lt;/strong&gt; for that month&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When I wrote it down like this, I realized something:&lt;/p&gt;

&lt;p&gt;This isn’t an “Excel automation” problem.&lt;/p&gt;

&lt;p&gt;This is a monthly payroll close problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Thought the Problem Was
&lt;/h2&gt;

&lt;p&gt;My first approach was very implementation-driven:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“There’s a Drive folder”&lt;/li&gt;
&lt;li&gt;“There are Excel files”&lt;/li&gt;
&lt;li&gt;“I need to parse and export”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So my architecture naturally became a pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drive client downloads files&lt;/li&gt;
&lt;li&gt;a parser extracts rows&lt;/li&gt;
&lt;li&gt;a validator checks columns&lt;/li&gt;
&lt;li&gt;an exporter builds a consolidated report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looked clean… but the code kept reading like:&lt;/p&gt;

&lt;p&gt;“update tables, move files, generate output”&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;“close the month, enforce the deadline, approve submissions, finalize payroll”&lt;/p&gt;

&lt;p&gt;That difference sounds subtle, but it changes everything—especially when requirements evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Realized Afterward
&lt;/h2&gt;

&lt;p&gt;Good architecture isn’t just about layers and patterns.&lt;/p&gt;

&lt;p&gt;It’s about &lt;strong&gt;meaning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The moment I treated the month-end workflow as a _domain _(instead of a batch job), the design got clearer:&lt;/p&gt;

&lt;p&gt;A deadline isn’t a timestamp field—it’s a rule that changes system behavior.&lt;br&gt;
“Late” isn’t a warning label—it affects approval visibility and auditability.&lt;br&gt;
“Latest submission wins” isn’t a query trick—it’s a policy the business depends on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ubiquitous Language I Started Using
&lt;/h2&gt;

&lt;p&gt;Once I stopped thinking in “files” and started thinking in “business”, the vocabulary became obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monthly Run / Payroll Period (the month being closed)&lt;/li&gt;
&lt;li&gt;Deadline (the cutoff time)&lt;/li&gt;
&lt;li&gt;Timesheet Submission (one employee’s monthly entry)&lt;/li&gt;
&lt;li&gt;Late Submission (allowed, but flagged)&lt;/li&gt;
&lt;li&gt;Latest Submission Wins (policy when employees upload multiple versions)&lt;/li&gt;
&lt;li&gt;Approval (approve/reject, bulk approve valid)&lt;/li&gt;
&lt;li&gt;Salary Summary Export (the admin deliverable)&lt;/li&gt;
&lt;li&gt;When your code uses this language, you don’t need long comments to explain what it does.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before vs After (Pipeline Thinking vs Domain Thinking)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before: “Process files”
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Pipeline mindset: process whatever is in the folder
var files = drive.ListXlsxFiles(folderId);

foreach (var file in files)
{
    var rows = ParseExcel(file);
    Validate(rows);
    Save(rows);
}

ExportSalarySummary();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works—but it hides the real rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which month is this?&lt;/li&gt;
&lt;li&gt;Is the deadline enforced?&lt;/li&gt;
&lt;li&gt;Which file wins if an employee uploads twice?&lt;/li&gt;
&lt;li&gt;What happens after approval?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After: “Close a monthly run”
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Domain mindset: close a payroll period with explicit rules
run.LockAtDeadline(now);

run.RefreshSubmissionsFromDrive();   // latest submission wins
run.FlagLateSubmissions();           // uploaded after deadline

foreach (var submission in run.ActiveSubmissions())
{
    submission.ValidateCurrentMonthOnly();
}

approvals.ApproveAllValid(run);      // late is a badge, invalid blocks approval

var report = payroll.ExportSalarySummary(run); // Excel for admin

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the code reads like the business process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lock the period&lt;/li&gt;
&lt;li&gt;evaluate submissions under known rules&lt;/li&gt;
&lt;li&gt;approve what’s valid&lt;/li&gt;
&lt;li&gt;export the payroll summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That shift made everything easier to maintain and explain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Domain Rules (That Matter More Than Excel)
&lt;/h2&gt;

&lt;p&gt;Here are the policies I implemented in Timesheetflow’s MVP:&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Current month only
&lt;/h3&gt;

&lt;p&gt;Employees can submit timesheets for the active month only.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Deadline locks the run
&lt;/h3&gt;

&lt;p&gt;At the deadline, the run transitions into a “locked” phase for processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Late submissions are allowed, but flagged
&lt;/h3&gt;

&lt;p&gt;After the deadline, submissions are marked Late and stay visible in approval/export.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) Latest submission wins
&lt;/h3&gt;

&lt;p&gt;If an employee uploads multiple timesheets for the same month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the system uses the most recent file (by Drive timestamps)&lt;/li&gt;
&lt;li&gt;older ones are marked superseded&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5) Invalid beats late
&lt;/h3&gt;

&lt;p&gt;If a timesheet is invalid, it’s invalid—whether it’s late or not.&lt;br&gt;
Late is a badge. Invalid is a blocker.&lt;/p&gt;

&lt;h2&gt;
  
  
  6) Salary export is the admin deliverable
&lt;/h2&gt;

&lt;p&gt;The end result is not “processed rows”—it’s a monthly Excel summary that payroll/admin can use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Notes (Tech Stack)
&lt;/h2&gt;

&lt;p&gt;Timesheetflow is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ASP.NET Core (.NET 8) for Web + API&lt;/li&gt;
&lt;li&gt;Hangfire for deadline scheduling and background processing&lt;/li&gt;
&lt;li&gt;ClosedXML for Excel parsing/export&lt;/li&gt;
&lt;li&gt;Google Drive as the submission channel (Workspace folder + service account access)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with a simple stack, modeling the domain clearly made the system feel “product-like” instead of “script-like”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;This wasn’t really an Excel problem—it was a month-end payroll close problem.&lt;/li&gt;
&lt;li&gt;Deadlines, late flags, and “latest submission wins” are domain rules, not utility logic.&lt;/li&gt;
&lt;li&gt;When code mirrors the business language, it becomes easier to read, test, and extend.&lt;/li&gt;
&lt;li&gt;The best architecture doesn’t just run—it communicates intent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Closing Thoughts
&lt;/h3&gt;

&lt;p&gt;I started Timesheetflow thinking I was building an Excel automation tool.&lt;/p&gt;

&lt;p&gt;But the real value came when I stopped designing around files and started designing around the workflow the business lives by every month.&lt;/p&gt;

&lt;p&gt;If you’ve ever had a moment where a “simple automation” turned out to be a domain problem in disguise, I’d love to hear it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect with Me
&lt;/h3&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/timleunghk" rel="noopener noreferrer"&gt;https://github.com/timleunghk&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/in/timothy-leung-48261b8b" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/timothy-leung-48261b8b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’d like, I can also share:&lt;/p&gt;

&lt;p&gt;a minimal domain model (&lt;code&gt;MonthlyRun&lt;/code&gt;, &lt;code&gt;TimesheetSubmission&lt;/code&gt;, &lt;code&gt;Approval&lt;/code&gt;) for .NET 8&lt;br&gt;
a Hangfire job layout for the deadline + processing pipeline&lt;br&gt;
an example &lt;code&gt;Salary_Summary.xlsx&lt;/code&gt; structure (&lt;code&gt;ByEmployee / Details / Exceptions&lt;/code&gt;)&lt;br&gt;
Just tell me whether you want the article to include code that compiles (full sample classes), or keep it at conceptual snippets like this post.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>ddd</category>
      <category>automation</category>
    </item>
    <item>
      <title>The Interview Question That Made Me Rethink My Architecture: Understanding Domain‑Driven Design</title>
      <dc:creator>Timothy Leung</dc:creator>
      <pubDate>Sun, 21 Dec 2025 04:20:22 +0000</pubDate>
      <link>https://forem.com/timleunghk/the-interview-question-that-made-me-rethink-my-architecture-understanding-domain-driven-design-3ajh</link>
      <guid>https://forem.com/timleunghk/the-interview-question-that-made-me-rethink-my-architecture-understanding-domain-driven-design-3ajh</guid>
      <description>&lt;p&gt;“&lt;em&gt;Can you explain how you’ve used Domain‑Driven Design in your past projects?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;That one simple question stopped me cold.&lt;/p&gt;

&lt;p&gt;🌱 The Interview Question&lt;br&gt;
It was my second interview — and also my last one for that position.&lt;br&gt;
After the first round, the interviewer had mentioned that a technical test would be required.&lt;br&gt;
So, when I got the invitation to the second interview, I spent almost a week preparing — mostly focusing on LINQ and Entity Framework in .NET Core.&lt;/p&gt;

&lt;p&gt;My programming journey started with Visual Basic 6, then moved to VB.NET, and later to C# and Python.&lt;br&gt;
In my C# years, I mostly worked with ADO.NET, writing raw SQL queries and stored procedures.&lt;br&gt;
That’s why I wasn’t familiar with these newer methodologies — and I knew that was a weakness I had to fix.&lt;/p&gt;

&lt;p&gt;So I spent a week catching up. It only took a couple of days to get the big picture (thanks to my previous experience), and the rest of the week went into polishing and preparing for the test.&lt;/p&gt;

&lt;p&gt;🤔 What I Thought DDD Was&lt;br&gt;
During the interview, I explained my architecture like this:&lt;/p&gt;

&lt;p&gt;Repositories communicated with the database through Entity Framework&lt;br&gt;
Services used these repositories to handle business logic&lt;br&gt;
Controllers acted as endpoints, exchanging data with frontend or mobile apps&lt;br&gt;
After I finished, the interviewer smiled and said:&lt;/p&gt;

&lt;p&gt;“Tim, you’re smart, honestly.”&lt;/p&gt;

&lt;p&gt;When the interview ended, I asked an AI what that architecture style was called.&lt;br&gt;
The answer came back: Domain‑Driven Design.&lt;/p&gt;

&lt;p&gt;That moment surprised me — I had been applying some parts of DDD all along, without even realizing it.&lt;/p&gt;

&lt;p&gt;💡 What I Realized Afterward&lt;br&gt;
At first, I thought DDD was just about extra layers: entities, repositories, services… like a neatly organized folder structure.&lt;/p&gt;

&lt;p&gt;But I later realized DDD is not about structure — it’s about meaning.&lt;/p&gt;

&lt;p&gt;It’s about how the structure represents the real business domain.&lt;br&gt;
The key idea is the ubiquitous language — code that mirrors how the business actually talks about its work.&lt;/p&gt;

&lt;p&gt;Instead of thinking:&lt;/p&gt;

&lt;p&gt;“This function updates a table.”&lt;/p&gt;

&lt;p&gt;DDD makes you think:&lt;/p&gt;

&lt;p&gt;“This function completes an order.”&lt;/p&gt;

&lt;p&gt;The difference sounds small, but it completely changes how you design, read, and discuss your code.&lt;/p&gt;

&lt;p&gt;The interviewer hadn’t been testing my knowledge of frameworks — he’d been testing whether I could connect technical implementation with business intent.&lt;br&gt;
And that is what Domain‑Driven Design is all about.&lt;/p&gt;

&lt;p&gt;🧭 What DDD Really Means (in Simple Terms)&lt;br&gt;
Here’s how I now understand DDD’s main ideas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain&lt;/strong&gt; – The real-world problem space you’re modeling (orders, payments, customers…).&lt;br&gt;
&lt;strong&gt;Ubiquitous Language&lt;/strong&gt; – A shared language between developers and domain experts.&lt;br&gt;
&lt;strong&gt;Entities&lt;/strong&gt; – Objects that have identity and lifecycle (like Order, Customer).&lt;br&gt;
&lt;strong&gt;Value Objects&lt;/strong&gt; – Define characteristics without identity (like Money, Address).&lt;br&gt;
&lt;strong&gt;Aggregates&lt;/strong&gt; – Groups of entities that change together as one unit.&lt;br&gt;
&lt;strong&gt;Repositories&lt;/strong&gt; – Abstract persistence, letting you save or load aggregates without leaking database logic.&lt;/p&gt;

&lt;p&gt;🧱 Applying That Insight&lt;br&gt;
Before that interview, my OrderService looked like this:&lt;br&gt;
csharp&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void CompleteOrder(int orderId)
{
    var order = _repository.GetById(orderId);
    order.Status = "Completed";
    _repository.Update(order);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works — but it’s mainly data‑driven.&lt;/p&gt;

&lt;p&gt;After learning about the domain perspective, I rewrote it to express intent, not CRUD actions:&lt;/p&gt;

&lt;p&gt;csharp&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void CompleteOrder(Order order)
{
    order.MarkAsCompleted();
    _repository.Save(order);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the focus is on what the business is doing — not how the database changes.&lt;br&gt;
That one small shift made my architecture feel cleaner, more readable, and more meaningful.&lt;/p&gt;

&lt;p&gt;✍️ Key Takeaways&lt;br&gt;
DDD isn’t about fancy patterns or layers — it’s about representing the business domain in code.&lt;br&gt;
Even if you’re not applying full DDD, thinking in domain terms makes your code clearer and more maintainable.&lt;br&gt;
The best architecture doesn’t just run — it communicates.&lt;/p&gt;

&lt;p&gt;🔚 Closing Thoughts&lt;br&gt;
That interview taught me something I didn’t expect —&lt;br&gt;
sometimes, you already understand good architectural principles — you just haven’t learnt the name for them yet.&lt;/p&gt;

&lt;p&gt;And that’s okay.&lt;br&gt;
Understanding comes in layers — just like good architecture.&lt;/p&gt;

&lt;p&gt;🧩 Thanks for reading! If this story resonated with you or helped clarify what DDD really means, consider leaving a comment — I’d love to hear your own “aha!” moments too.&lt;/p&gt;

&lt;p&gt;🌐 Connect with Me&lt;br&gt;
💻 GitHub: &lt;a href="https://github.com/timleunghk" rel="noopener noreferrer"&gt;https://github.com/timleunghk&lt;/a&gt;&lt;br&gt;
💼 LinkedIn: &lt;a href="https://www.linkedin.com/in/timothy-leung-48261b8b/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/timothy-leung-48261b8b/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⭐ If you enjoyed this article, feel free to follow me on dev.to or star my GitHub projects — I’m always exploring new ways to make code tell better stories.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>ddd</category>
      <category>cleancode</category>
    </item>
  </channel>
</rss>
