大家好!今天我們要討論的是 API 設計中的一個關鍵步驟——界定 API 邊界。這個過程就像是在建造一個圖書館之前,先確定每個區域的用途:哪些地方是放書的、哪些地方是供人閱讀的,以及哪些地方需要與其他部門協作。通過合理界定 API 的邊界,我們可以確保 API 的功能既精確又高效,避免陷入過度設計或欠缺設計的陷阱。
API 的邊界決定了它的功能範圍,就像圖書館的功能區劃一樣。清晰地界定 API 的邊界,不僅能夠幫助你專注於 API 的核心功能,還能避免捲入不必要的複雜性和業務耦合,從而提高系統的靈活性和可維護性。
我們先來反向思考一下:如果 API 沒有明確的邊界,會變成什麼樣子?
沒有邊界的 API:多合一反模式
假設今天你要為公司開發一個客戶管理系統,這個系統需要對公司客戶進行 CRUD 操作(Create, Read, Update, Delete),具體需求包括:
如果我們不清楚地界定 API 的邊界,而是選擇一個「多合一」的 API 來處理這些需求,那麼 API 可能會設計成這樣:
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class ApiResult
{
public bool Success { get; set; }
public string Message { get; set; }
public List<Customer> Customers { get; set; }
public Customer SingleCustomer { get; set; }
}
public ApiResult CustomerAction(List<Customer> customers, string actionType)
{
switch (actionType)
{
case "GetSingle":
// 查詢單筆客戶邏輯
break;
case "GetMultiple":
// 查詢多筆客戶邏輯
break;
case "Create":
// 新增客戶邏輯
break;
case "UpdateSingle":
// 修改單筆客戶邏輯
break;
case "UpdateBatch":
// 批次修改客戶邏輯
break;
case "Delete":
// 刪除客戶邏輯
break;
default:
return new ApiResult { Success = false, Message = "無效的操作類型" };
}
return new ApiResult { Success = true, Message = "操作成功" };
}
或許部分初學者會覺得這不就是switch case 的用法,根據Type來分類並執行他的動作,但若是把詳細的各動作邏輯都寫出來,程式碼的閱讀性會變的非常糟糕。不妨看看下面這個將邏輯部分完整呈現的範例:
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class ApiResult
{
public bool Success { get; set; }
public string Message { get; set; }
public List<Customer> Customers { get; set; }
public Customer SingleCustomer { get; set; }
}
private List<Customer> _customers = new List<Customer>();
public ApiResult CustomerAction(List<Customer> customers, string actionType, string customerId = null)
{
switch (actionType)
{
case "GetSingle":
var customer = _customers.FirstOrDefault(c => c.Id == customerId);
if (customer == null)
{
return new ApiResult { Success = false, Message = "客戶不存在" };
}
return new ApiResult { Success = true, Message = "查詢成功", SingleCustomer = customer };
case "GetMultiple":
return new ApiResult { Success = true, Message = "查詢成功", Customers = _customers };
case "Create":
foreach (var newCustomer in customers)
{
_customers.Add(newCustomer);
}
return new ApiResult { Success = true, Message = "新增成功" };
case "UpdateSingle":
var existingCustomer = _customers.FirstOrDefault(c => c.Id == customerId);
if (existingCustomer == null)
{
return new ApiResult { Success = false, Message = "客戶不存在" };
}
existingCustomer.Name = customers[0].Name;
existingCustomer.Email = customers[0].Email;
return new ApiResult { Success = true, Message = "更新成功" };
case "UpdateBatch":
foreach (var updatedCustomer in customers)
{
var customerToUpdate = _customers.FirstOrDefault(c => c.Id == updatedCustomer.Id);
if (customerToUpdate != null)
{
customerToUpdate.Name = updatedCustomer.Name;
customerToUpdate.Email = updatedCustomer.Email;
}
}
return new ApiResult { Success = true, Message = "批次更新成功" };
case "Delete":
var customerToDelete = _customers.FirstOrDefault(c => c.Id == customerId);
if (customerToDelete == null)
{
return new ApiResult { Success = false, Message = "客戶不存在" };
}
_customers.Remove(customerToDelete);
return new ApiResult { Success = true, Message = "刪除成功" };
default:
return new ApiResult { Success = false, Message = "無效的操作類型" };
}
}
看到這一大陀的程式碼,如果要針對特定的功能進行測試與修改,你要先能一眼找出要修改的點變的非常困難,更何況上述還不是很完整的處理商業邏輯,還是非常省略過後的程式。因此我們能看到
沒有邊界的 API 帶來的問題
這種多合一的 API 設計看起來很方便,所有的操作都集中在一個方法中。然而,這種設計會帶來以下問題:
為了解決上述問題,我們需要明確 API 的邊界,將不同的操作分散到不同的 API 方法中。這樣,每個 API 只負責一項具體的功能,既清晰又易於維護。
public ApiResult GetCustomerById(string id)
{
// 查詢單筆客戶邏輯
}
public ApiResult GetCustomers()
{
// 查詢多筆客戶邏輯
}
public ApiResult CreateCustomer(Customer customer)
{
// 新增客戶邏輯
}
public ApiResult UpdateCustomer(Customer customer)
{
// 修改單筆客戶邏輯
}
public ApiResult UpdateCustomers(List<Customer> customers)
{
// 批次修改客戶邏輯
}
public ApiResult DeleteCustomer(string id)
{
// 刪除單筆客戶邏輯
}
有邊界的 API 帶來的好處
上面的程式範例,還不是主流的架構設計下的程式碼範例,帶篇幅慢慢帶到,會一步一步將程式碼修正到較好的架構與設計。
界定 API 邊界涉及多個步驟,從理解業務需求到識別核心資源,再到劃分功能責任。這些步驟幫助我們構建一個精確且靈活的 API,能夠滿足業務需求而不會過度膨脹。
假設你正在設計一個圖書館管理系統的 API,我們可以通過以下方式來界定它的邊界:
這樣的劃分可以幫助 API 專注於圖書館的核心業務,而不會因為捲入過多的非核心功能而變得複雜難維護。
總結:
界定 API 邊界是設計高效且可維護 API 的第一步。通過明確 API 的職責範圍,我們可以確保它專注於核心業務,並且能夠靈活應對未來的需求變更。這種精確的劃分可以避免過度設計和欠缺設計,從而提高整個系統的穩定性和可擴展性。明天,我們將在此基礎上進一步討論如何建立 API 模型,這將為 API 的實際開發奠定堅實的基礎。