iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Software Development

程式設計沒有告訴你的事:30 天破解每一個 Why系列 第 25

Framework 的核心零件都有了,最後到底該怎麼把它們組起來?

  • 分享至 

  • xImage
  •  

走到今天,前面幾天研究過的東西已經很多。

我們從最底層開始:

TCP

Socket

NetworkStream

HTTP Request

接著慢慢加入:

HttpRequest

Routing

Headers

Query

Body

JSON

Reflection

Parameter Binding

Handler

Result Conversion

HttpResponse

Middleware

單獨看,每一個概念都開始有一點理解了。

但真正把它們全部攤開後,我突然發現一個新的問題。

這些東西到底應該放在哪裡?

如果最後全部還是塞在 Program.cs:

讀 Request

解析 Request

找 Route

Binding

執行 Handler

處理 Result

組 Response

跑 Middleware

那即使每一個功能都做出來了,整個程式還是會很亂。

所以今天想理解的問題是:

一個 Framework 到底該怎麼把這些核心元件組織起來?

💭 我原本以為……

一開始做這個 Mini Web Framework 時,幾乎所有程式都可以放在 Program.cs。

因為那時候只有:

TcpListener

AcceptTcpClient()

NetworkStream

Read()

幾十行程式就能理解整個流程。

但隨著功能越來越多,Program.cs 開始同時知道太多事情。

它知道 TCP。

它知道 HTTP 格式。

它知道 Request Line 怎麼切。

它知道 Routing。

它知道 Response 怎麼組。

如果再把 Headers、Query、Body、Reflection、Binding、Middleware 全部加進去,Program.cs 很快就會變成一個什麼都知道的巨大檔案。

功能可能可以執行。

但它已經不像 Framework。

反而更像:

所有功能全部黏在一起的一支大型程式。

所以 Framework 除了「有什麼功能」之外,好像還有另一個很重要的問題:

誰應該負責什麼?

為什麼一定要拆?

最直接的理由是:

複雜度會越來越高。

假設今天只有一個功能:

讀取 Request

那放在 Program.cs 完全沒問題。

但如果同時有:

讀取 TCP

Parse HTTP

Route Matching

Parameter Binding

JSON Serialization

Middleware

Error Handling

Response Sending

全部混在一起後,只要其中一個地方要修改,就很容易影響其他功能。

所以拆分並不是單純為了:

檔案比較多,看起來比較專業。

真正的目的是:

讓每一個部分只負責自己的事情。

這其實和前幾天一直看到的 Responsibility 有關。

HttpRequest 應該負責什麼?

HttpRequest 不應該負責:

找 Route

執行 Handler

送 Response

它比較適合負責:

描述這次 Request。

例如:

Method

Path

Version

Headers

Query

Body

甚至可以包含 Request Parsing 的相關邏輯。

所以:

Raw HTTP

HttpRequest

提供 Request 資料

它的責任就很清楚。

HttpResponse 又負責什麼?

Day15 已經建立過 HttpResponse。

它適合保存:

StatusCode

ContentType

Body

Headers

以及最後:

Send()

所以:

Handler Result

HttpResponse

真正送回 Client

這樣 Response 相關細節不需要散落到其他元件。

Router 應該負責什麼?

Router 的核心問題很單純:

這個 Request 應該交給誰?

例如:

GET /hello

Hello Handler

GET /products

Products Handler

所以 Router 不應該同時:

解析 JSON

寫 NetworkStream

處理 Logging

它只要專心:

Route Registration

Route Matching

找到 Handler

即可。

這就是把 Routing 從整個 Server 流程中獨立出來。

Parameter Binder 又放哪裡?

前面 Day22 已經知道,Parameter Binding 的工作是:

查看 Handler Parameter

決定資料來源

從 Request 取得資料

Type Conversion

建立 Arguments

所以這件事情也可以獨立成:

ParameterBinder

概念上:

Handler Metadata
+
HttpRequest

ParameterBinder

object[] arguments

然後 Handler Invoker 再拿這些 arguments 去執行 Handler。

這樣 Router 就不需要知道:

JSON 怎麼 Deserialize。

HttpRequest 也不需要知道:

Handler 需要幾個參數。

大家的責任開始真正分開。

Handler Invoker 是什麼?

這是目前可以想像再拆出來的一層。

Router 的工作:

找到 Handler。

Parameter Binder:

準備 Handler 需要的參數。

那最後誰真的執行 Handler?

可以有一個:

HandlerInvoker

負責:

找到 Method

拿到 Arguments

Invoke

取得 Return Value

所以可以拆成:

Router

找到 Handler

ParameterBinder

準備 Arguments

HandlerInvoker

執行 Handler

這樣每一層的問題都比較單純。

Result Converter 呢?

Day23 又知道:

Handler 執行完之後,可能回:

string

Object

HttpResponse

null

不同結果需要先統一處理。

所以可以再想像一個:

ResultConverter

它負責:

Handler Result

判斷類型

轉成 HttpResponse

例如:

string

Text Response

Object

JSON Response

HttpResponse

直接使用

最後全部變成:

HttpResponse

這樣 Handler 和真正的 HTTP Response 就被隔開了。

Middleware 又放在哪裡?

Middleware 比較特別。

它不是一個單純的資料處理工具。

它控制的是:

整條 Request Pipeline。

例如:

Request

Exception Middleware

Logging Middleware

Routing

Binding

Handler

Result Conversion

Response

所以 Framework 最後還需要一個地方:

把這些 Middleware 組成 Pipeline。

也就是:

Pipeline Builder

或某個 Application / Server 類別。

這開始有一點像我們平常看到的:

app.Use(...)

app.MapGet(...)

這類 API。

Program.cs 應該剩什麼?

這是今天我覺得最重要的一個問題。

如果 Framework 做得夠好,使用 Framework 的人理論上不應該每天都看到:

TcpListener

NetworkStream

Encoding

Split

Reflection

JsonSerializer

這些底層細節。

Program.cs 應該開始往:

描述「我要怎麼使用 Framework」

而不是:

描述「Framework 底層怎麼運作」

的方向前進。

例如概念上可以慢慢變成:

建立 App

註冊 Middleware

註冊 Route

Run

也就是:

var app = new MiniWebApplication();

app.Use(...);

app.MapGet("/hello", ...);

app.Run();

這種感覺。

真正的實作之後可以再決定。

但這個方向本身非常重要。

因為如果最後使用 Framework 還要自己寫:

AcceptTcpClient()

stream.Read()

Parse()

那我們其實只是建立一些 Helper Class。

還沒有真的做出一個方便使用的 Framework。

框架和 Library 有什麼不一樣?

做到這裡也開始碰到一個很有趣的差別。

Library 通常是:

我的程式

主動呼叫 Library

例如:

JsonSerializer.Serialize(...)

是我決定什麼時候呼叫它。

Framework 則常常更像:

我先註冊:

Route

Handler

Middleware

接著 Framework 自己控制整個執行流程。

也就是:

Framework

收到 Request

決定何時呼叫我的 Handler

這種概念常常會被稱為:

Inversion of Control

也就是控制反轉。

以前:

我的程式是主角。

我想呼叫誰就呼叫誰。

Framework 的情況:

Framework 掌握主流程。

在適當時機回頭呼叫我提供的程式。

例如:

app.MapGet("/hello", HelloHandler)

我只是告訴 Framework:

如果遇到 /hello,請執行這個 Handler。

真正什麼時候執行,是 Framework 決定。

這就是 Framework 開始真正和普通工具函式不同的地方。

Mini Web Framework 最後的核心應該長什麼樣?

目前可以先把我們前面學過的元件整理成:

MiniWebFramework

Server

負責 TCP Connection

HttpRequest

描述 Request

Router

找到 Handler

ParameterBinder

準備 Handler Parameters

HandlerInvoker

執行 Handler

ResultConverter

將結果轉成 HttpResponse

HttpResponse

回傳 Client

Middleware Pipeline

控制整個 Request Flow

如果畫成完整流程:

Browser

TCP Server

HttpRequest

Middleware

Router

ParameterBinder

HandlerInvoker

ResultConverter

HttpResponse

TCP

Browser

到這裡,一個 Mini Web Framework 的主要骨架其實已經很明顯了。

是不是每一個都一定要做成 Class?

不一定。

這裡也要避免:

為了拆而拆。

我們現在是在學 Framework Architecture,所以把責任分開思考很重要。

但實際實作時,不代表:

每一個概念一定都要建立一個 Class。

有些很小的功能可以先:

Method

Delegate

Static Helper

甚至放在同一個 Class 裡。

重點不是檔案數量。

而是:

責任有沒有清楚。

例如 Mini Web Framework 第一版可能沒有專門的 HandlerInvoker Class。

也沒有完整的 ResultConverter 系統。

都沒有關係。

只要最終能清楚看到:

Request

Routing

Handler

Response

Pipeline

各自的角色,就已經達到這次專案的目的。

先做最小版本,而不是做 ASP.NET Core

這點對最後五天非常重要。

做到今天,會很容易開始想:

ASP.NET Core 有 Dependency Injection。

那我要不要做?

有完整 Middleware。

那我要不要做?

有 Endpoint Metadata。

要不要做?

有 Model Validation。

要不要做?

如果一直這樣加,30 天根本做不完。

所以最後成品的目標不是:

自己重做 ASP.NET Core。

而是:

做出一個足以證明自己真的理解 Web Framework 核心流程的 Mini Framework。

也就是:

可以啟動 Server

可以 Parse Request

可以 Routing

可以執行 Handler

可以產生 Response

最好可以有簡單 Middleware

這樣就已經是一個很完整的學習成果。

官方怎麼說?

真正的 ASP.NET Core 本身就把不同 HTTP 功能拆成很多抽象。

例如:

HttpContext

HttpRequest

HttpResponse

Endpoint

Routing

Middleware

RequestDelegate

等等。

這些類型背後的實作當然比我們目前想像複雜非常多。

但它們反映了一個很重要的 Framework Architecture 想法:

不同責任由不同元件處理。

最後再透過 Request Pipeline 把它們組合起來。

所以我們 Mini Web Framework 的目的,不是複製官方架構。

而是透過最小化版本理解:

這些抽象為什麼會存在。


以前的我:

Framework 就是一大堆很厲害的功能。

現在的我:

Framework

其實是很多責任不同的元件

透過 Pipeline 組合

最後提供簡單 API 給開發者使用

所以 Framework 的難點不只是:

功能能不能做。

還包括:

複雜度應該被放在哪裡?

使用者應該看到什麼?

底層細節又應該藏在哪裡?

這些其實也是 Framework Design 的一部分。


今天最大的發現是:

真正的 Framework 不只是把程式碼「拆成很多 Class」。

而是:

把複雜度藏到適合的地方。

例如:

開發者只想:

MapGet("/hello", handler)

Framework 底下卻可能處理:

TCP

HTTP Parsing

Routing

Reflection

Binding

Result Conversion

Serialization

Response

Middleware

但這些東西不應該全部暴露給 Framework 的使用者。

所以 Framework 的價值可以再重新理解成:

複雜度沒有消失。

只是 Framework 把它集中管理,再提供更容易使用的介面。


做到今天,我們終於可以先畫出最後成品的方向:

MiniWebFramework
├── Program.cs
├── HttpRequest.cs
├── HttpResponse.cs
├── Router.cs
├── Middleware
└── 其他必要元件

實際最後會有幾個檔案,可以在實作過程中再調整。

但最重要的是:

Program.cs 要開始變簡單。

理想方向是:

建立 Framework

註冊 Route

註冊 Middleware

啟動

而真正:

TCP 怎麼 Listen

Request 怎麼 Parse

Route 怎麼 Match

Response 怎麼送

應該逐步移到 Framework 內部。

今天暫時不實作

Day25 是正式進入最後成品前的整理日。

今天先把這 20 多天學過的核心概念重新排列好。

因為如果沒有先整理,就直接開始把所有程式碼塞進專案,很容易最後又變成一個巨大 Program.cs。

接下來五天的目標也不再是:

繼續增加更多新概念。

而是:

把我們已經理解過的東西真正接起來。

也就是從明天開始:

Day26~Day30

正式完成 Mini Web Framework。

結尾

從 Day1 開始,我一直在追問:

為什麼?

一開始是:

CPU 為什麼看不懂 C#?

Compiler 到底做了什麼?

Build 到底產生什麼?

後來一路走到:

Server 為什麼可以一直等?

Browser 到底傳什麼?

HTTP Request 長什麼樣?

Routing 為什麼可以找到 Handler?

Response 怎麼回到 Browser?

再走到:

Request Object

Response Object

Query

Body

JSON

Reflection

Parameter Binding

Middleware

做到今天,突然發現每一個 Why 最後都變成了 Framework 裡的一個小零件。

所以 Day25 對我來說不是再增加一個新功能。

而是第一次把前面的答案全部放回同一張圖裡。

Browser

Request

Framework

Handler

Framework

Response

Browser

真正的 Framework,就是把中間那些大量細節組織起來。

而接下來最後五天,就是把這張圖真正變成一個可以執行的程式。

理論已經差不多了。

從明天開始,不再只是問:

Framework 為什麼需要這些東西?

而是要開始面對:

我真的能不能把這些東西組成自己的 Framework?


上一篇
每個 Request 都要做 Logging 和驗證嗎?Middleware 到底是什麼?
下一篇
開始組裝 Mini Web Framework:第一步,先把巨大 Program.cs 拆掉
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言