微服務之道:8個原則,打造高效的微服務體系

hi,我是熵減,見字如面。

現在,在大型的軟件工程系統中,微服務化的系統設計,成爲了大部分時候的必然之選。

而如何將微服務做有效的設計,則是需要每一個團隊和工程師都需要考慮的一個問題。在保持系統的一致性、可理解性、可維護性和可擴展性上,需要有一些基本的指導原則。

下面分享微服務設計和實踐中的8個基礎原則,具體如下:

基本原則概覽

在微服務中,設計API時可以遵循以下原則:

  • 單一職責原則(Single Responsibility Principle):每個微服務應該只關注一個特定的業務領域或功能,因此其API應該具有明確的職責,只提供與該領域或功能相關的接口。

  • 顯式接口原則(Explicit Interface Principle):API應該明確和清晰地定義其接口,包括輸入參數、輸出結果和可能的錯誤碼。這樣可以提高接口的可理解性和可維護性,減少誤用和不必要的溝通。

  • 界限上下文(Bounded Context):根據微服務的邊界和業務需求,劃分出不同的界限上下文。API的設計應該與界限上下文相匹配,以確保接口的一致性和內聚性。

  • 服務契約(Service Contract):API應該建立明確的服務契約,包括接口的語義、協議和版本管理。這有助於不同團隊之間的協作,確保服務之間的兼容性和互操作性。

  • 信息隱藏(Information Hiding):API應該隱藏內部實現細節,只暴露必要的接口。這樣可以減少對內部實現的依賴性,提高服務的獨立性和可替代性。

  • 無狀態性(Statelessness):儘量設計無狀態的API,即不保存客戶端的狀態信息,每個請求都應該是獨立的。這樣可以提高服務的可伸縮性和容錯性。

  • 適應性與演進性(Adaptability and Evolution):API應該具有適應性和演進性,能夠容易地適應不同的需求和變化。通過版本控制和向後兼容性,可以使API在不破壞現有客戶端的情況下進行演進和升級。

  • 安全性和身份驗證(Security and Authentication):API應該提供適當的安全機制和身份驗證,以保護服務和數據的安全性。這可能包括使用加密傳輸、身份驗證令牌和訪問控制等措施。

接下來,我們將通過日常的一些具體demo,來逐個展開這8個原則。

原則具體實例

單一職責原則

以下是一個違反單一職責原則的反例,使用Java代碼進行演示:

public class UserAPI {
    
    public void createUser(String username, String password) {
        // 創建用戶的邏輯
    }
    
    public void getUserInfo(String username) {
        // 獲取用戶信息的邏輯
    }
    
    public void updateUser(String username, String newPassword) {
        // 更新用戶密碼的邏輯
    }
    
    public void deleteUser(String username) {
        // 刪除用戶的邏輯
    }
    
    public void sendEmail(String username, String subject, String content) {
        // 發送郵件的邏輯
    }
    
}

在上述代碼中,UserAPI 類違反了單一職責原則。它既包含了用戶管理的功能(創建、獲取、更新、刪除用戶),又包含了郵件發送的功能。這使得這個類承擔了多個不同的職責,導致了耦合度增加、代碼複雜度提高和可維護性下降的問題。

根據單一職責原則,應該將不同的職責拆分爲獨立的類或模塊。

對於上述例子,可以將用戶管理和郵件發送拆分爲兩個單獨的類,分別負責各自的功能。這樣可以提高代碼的可讀性、可維護性和擴展性。

public class UserManagementAPI {
    
    public void createUser(String username, String password) {
        // 創建用戶的邏輯
    }
    
    public void getUserInfo(String username) {
        // 獲取用戶信息的邏輯
    }
    
    public void updateUser(String username, String newPassword) {
        // 更新用戶密碼的邏輯
    }
    
    public void deleteUser(String username) {
        // 刪除用戶的邏輯
    }
    
}

public class EmailService {
    
    public void sendEmail(String username, String subject, String content) {
        // 發送郵件的邏輯
    }
    
}

通過將功能進行拆分,每個類只關注一個特定的職責,代碼變得更加清晰、可維護,並符合單一職責原則。

顯式接口原則

以下是一個違反顯式接口原則的反例,使用Java代碼進行演示:

public class Calculator {
    
    public int calculate(int a, int b, String operation) {
        if (operation.equals("add")) {
            return a + b;
        } else if (operation.equals("subtract")) {
            return a - b;
        } else if (operation.equals("multiply")) {
            return a * b;
        } else if (operation.equals("divide")) {
            return a / b;
        }
        return 0;
    }
    
}

在上述代碼中,Calculator 類違反了顯式接口原則。它使用了一個字符串參數 operation 來決定進行何種計算操作。這種設計方式不明確和不清晰,沒有明確定義接口和輸入輸出的結構,使用方在調用時無法準確理解和使用該接口。

根據顯式接口原則,應該明確和清晰地定義接口,包括輸入參數、輸出結果和可能的錯誤碼。以下是一個改進的示例:

public interface Calculator {
    
    int add(int a, int b);
    
    int subtract(int a, int b);
    
    int multiply(int a, int b);
    
    int divide(int a, int b);
    
}

通過定義明確的接口,使用方可以清晰地知道接口的輸入和輸出,而不需要傳遞一個字符串參數來確定操作類型。這樣可以提高接口的可理解性和可維護性,遵循了顯式接口原則。

public class SimpleCalculator implements Calculator {
    
    @Override
    public int add(int a, int b) {
        return a + b;
    }
    
    @Override
    public int subtract(int a, int b) {
        return a - b;
    }
    
    @Override
    public int multiply(int a, int b) {
        return a * b;
    }
    
    @Override
    public int divide(int a, int b) {
        return a / b;
    }
    
}

通過實現明確的接口,使用方可以根據接口定義來調用相應的方法,而無需傳遞一個字符串參數來指定操作。這樣可以提高代碼的可讀性、可維護性和擴展性,並符合顯式接口原則。

界限上下文

界限上下文原則(Bounded Context)的主要目標是將大型系統拆分爲不同的上下文,每個上下文專注於一個特定的業務領域,並且在該上下文內保持一致性。以下是一個違反界限上下文原則的反例:

假設有一個電子商務系統,其中包含訂單管理和庫存管理兩個領域。下面是一個違反界限上下文原則的實現示例:

public class OrderService {
    
    public void createOrder(Order order) {
        // 創建訂單的邏輯
    }
    
    public void updateOrderStatus(Order order, String status) {
        // 更新訂單狀態的邏輯
    }
    
    public void reserveStock(Order order) {
        // 預留庫存的邏輯
    }
    
    public void releaseStock(Order order) {
        // 釋放庫存的邏輯
    }
    
    public void updateStock(Order order) {
        // 更新庫存的邏輯
    }
    
}

在上述代碼中,OrderService 類同時包含了訂單管理和庫存管理的邏輯,違反了界限上下文原則。應該將這兩個業務領域拆分爲獨立的上下文,分別負責各自的功能。

public class OrderService {
    
    public void createOrder(Order order) {
        // 創建訂單的邏輯
    }
    
    public void updateOrderStatus(Order order, String status) {
        // 更新訂單狀態的邏輯
    }
    
}

public class InventoryService {
    
    public void reserveStock(Order order) {
        // 預留庫存的邏輯
    }
    
    public void releaseStock(Order order) {
        // 釋放庫存的邏輯
    }
    
    public void updateStock(Order order) {
        // 更新庫存的邏輯
    }
    
}

通過將訂單管理和庫存管理拆分爲獨立的服務,每個服務只關注自己領域的邏輯,代碼變得更加清晰、可維護,並符合界限上下文原則。這樣可以減少不同領域之間的耦合度,提高代碼的模塊化和可擴展性。

服務契約

服務契約(Service Contracts)旨在定義服務之間的明確契約,包括輸入參數、輸出結果和可能的錯誤碼。

假設我們有一個用戶管理服務,其中有一個方法用於更新用戶信息。下面是一個違反服務契約原則的示例:

public class UserService {
    
    public void updateUser(String userId, String newEmail) {
        // 更新用戶信息的邏輯
    }
    
}

在上述代碼中,updateUser 方法只接受用戶ID和新的電子郵件地址作爲參數,但是它沒有返回任何結果或處理任何錯誤情況。這違反了服務契約原則,因爲它沒有明確定義輸入參數、輸出結果和錯誤處理。

改進的做法是明確定義服務的契約,包括輸入參數、輸出結果和錯誤處理。以下是一個改進的示例:

public class UserService {
    
    public UpdateUserResponse updateUser(UpdateUserRequest request) {
        // 更新用戶信息的邏輯
        // ...
        UpdateUserResponse response = new UpdateUserResponse();
        // 設置更新結果
        return response;
    }
    
}

public class UpdateUserRequest {
    private String userId;
    private String newEmail;
    // 其他相關屬性和方法
    
    // Getters and setters
}

public class UpdateUserResponse {
    private boolean success;
    private String errorMessage;
    // 其他相關屬性和方法
    
    // Getters and setters
}

通過引入UpdateUserRequest和UpdateUserResponse對象來明確定義輸入參數和輸出結果的結構,我們可以更好地遵循服務契約原則。使用方可以清楚地瞭解服務的輸入參數類型、輸出結果類型以及如何處理錯誤情況。這樣可以提高代碼的可讀性、可維護性和擴展性,並確保服務之間的契約明確。

信息隱藏

信息隱藏原則(Information Hiding Principle)是面向對象設計中的一個原則,它強調將類的內部細節隱藏起來,只暴露必要的接口給外部使用。

以下是一個違反信息隱藏原則的反例:

public class BankAccount {
    
    public String accountNumber;
    public String accountHolder;
    public double balance;
    
    public void deposit(double amount) {
        balance += amount;
    }
    
    public void withdraw(double amount) {
        balance -= amount;
    }
    
    public void displayAccountInfo() {
        System.out.println("Account Number: " + accountNumber);
        System.out.println("Account Holder: " + accountHolder);
        System.out.println("Balance: " + balance);
    }
}

在上述代碼中,BankAccount 類違反了信息隱藏原則。它將賬戶號碼、賬戶持有人和餘額都聲明爲公共的屬性,可以被外部直接訪問和修改。同時,displayAccountInfo 方法也直接打印賬戶信息到控制檯。

改進的做法是將類的內部狀態和實現細節封裝起來,只提供必要的接口供外部訪問和操作。以下是一個改進的示例:

public class BankAccount {
    
    private String accountNumber;
    private String accountHolder;
    private double balance;
    
    public BankAccount(String accountNumber, String accountHolder) {
        this.accountNumber = accountNumber;
        this.accountHolder = accountHolder;
        this.balance = 0;
    }
    
    public void deposit(double amount) {
        balance += amount;
    }
    
    public void withdraw(double amount) {
        balance -= amount;
    }
    
    public void displayAccountInfo() {
        System.out.println("Account Number: " + accountNumber);
        System.out.println("Account Holder: " + accountHolder);
        System.out.println("Balance: " + balance);
    }
}

在改進後的代碼中,將賬戶的屬性聲明爲私有,並提供了公共的方法來進行存款、取款和顯示賬戶信息的操作。外部代碼無法直接訪問和修改賬戶的內部狀態,只能通過提供的接口來與賬戶對象進行交互,保護了類的內部實現細節並提高了封裝性。

無狀態性

無狀態性原則(Statelessness Principle)強調在設計中避免保存客戶端的狀態信息,每個請求應該是獨立且自包含的。

以下是一個違反無狀態性原則的反例:

public class ShoppingCart {
    
    private List<Item> items;
    private double totalPrice;
    
    public void addItem(Item item) {
        items.add(item);
        totalPrice += item.getPrice();
    }
    
    public void removeItem(Item item) {
        items.remove(item);
        totalPrice -= item.getPrice();
    }
    
    public double getTotalPrice() {
        return totalPrice;
    }
    
    // 其他方法
}

在上述代碼中,ShoppingCart 類保存了客戶端的狀態信息,包括購物車中的商品列表和總價格。每次添加或移除商品時,都會更新購物車中的商品列表和總價格。這違反了無狀態性原則,因爲購物車的狀態信息依賴於之前的操作,並且隨着時間的推移會發生變化。

改進的做法是使購物車變得無狀態,每個請求都是獨立的。以下是一個改進的示例:

public class ShoppingCart {
    
    public double calculateTotalPrice(List<Item> items) {
        double totalPrice = 0;
        for (Item item : items) {
            totalPrice += item.getPrice();
        }
        return totalPrice;
    }
    
    // 其他方法
}

在改進後的代碼中,我們將購物車改爲一個無狀態的類,不保存任何狀態信息。相反,我們提供了一個 calculateTotalPrice 方法,接收商品列表作爲參數,並根據傳入的列表計算總價格。每次調用該方法時,都是獨立的,不依賴於之前的操作,保持了無狀態性。

這樣設計可以更好地遵循無狀態性原則,使每個請求都是獨立的,無需保存客戶端的狀態信息,提高了可伸縮性和可靠性。

適應性與演進性

適應性與演進性原則(Adaptability and Evolvability Principle)強調系統的設計應具備適應變化和演進的能力,能夠靈活應對新需求和技術的引入。

以下是一個違反適應性與演進性原則的反例:

public class PaymentService {
    
    public void processPayment(String paymentMethod, double amount) {
        if (paymentMethod.equals("creditCard")) {
            // 處理信用卡支付邏輯
            System.out.println("Processing credit card payment...");
        } else if (paymentMethod.equals("paypal")) {
            // 處理PayPal支付邏輯
            System.out.println("Processing PayPal payment...");
        } else if (paymentMethod.equals("applePay")) {
            // 處理Apple Pay支付邏輯
            System.out.println("Processing Apple Pay payment...");
        } else {
            throw new IllegalArgumentException("Unsupported payment method");
        }
    }
    
    // 其他方法
}

在上述代碼中,PaymentService 類中的 processPayment 方法根據傳入的支付方式進行相應的支付處理。這種實現方式違反了適應性與演進性原則,因爲當引入新的支付方式時,需要修改 processPayment 方法的代碼來添加新的邏輯分支。

改進的做法是通過接口和策略模式來實現支付方式的適應性和演進性。以下是一個改進的示例:

public interface PaymentMethod {
    void processPayment(double amount);
}

public class CreditCardPayment implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        // 處理信用卡支付邏輯
        System.out.println("Processing credit card payment...");
    }
}

public class PayPalPayment implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        // 處理PayPal支付邏輯
        System.out.println("Processing PayPal payment...");
    }
}

public class ApplePayPayment implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        // 處理Apple Pay支付邏輯
        System.out.println("Processing Apple Pay payment...");
    }
}

public class PaymentService {
    
    public void processPayment(PaymentMethod paymentMethod, double amount) {
        paymentMethod.processPayment(amount);
    }
    
    // 其他方法
}

在改進後的代碼中,我們定義了一個 PaymentMethod 接口,併爲每種支付方式實現了具體的實現類。

PaymentService 類的 processPayment 方法接收一個 PaymentMethod 對象作爲參數,並調用相應的支付方式的 processPayment 方法進行支付處理。

通過接口和策略模式的使用,我們使得支付方式的添加和修改變得更加靈活和可擴展,符合適應性與演進性原則。

安全性和身份驗證

安全性和身份驗證原則(Security and Authentication Principle)強調在系統設計中應該考慮安全性需求,並採取適當的身份驗證措施來保護系統和用戶的安全。

以下是一個違反安全性和身份驗證原則的反例:

public class UserController {
    
    public UserDTO getUser(String userId) {
        // 根據用戶ID查詢用戶信息
        return userRepository.findUserById(userId);
    }
    
    public void updateUser(String userId, UserDTO updatedUser) {
        // 更新用戶信息
        userRepository.updateUser(userId, updatedUser);
    }
    
    // 其他方法
}

在上述代碼中,UserController 類提供了獲取用戶和更新用戶信息的方法。然而,該實現沒有進行任何身份驗證或安全性檢查,任何人只要知道用戶ID就可以獲取和更新用戶信息。

改進的做法是引入身份驗證和安全性措施來保護用戶信息。以下是一個改進的示例:

public class UserController {
    
    private AuthenticationService authenticationService;
    private UserRepository userRepository;
    
    public UserDTO getUser(String userId, String authToken) {
        if (!authenticationService.isAuthenticated(authToken)) {
            throw new SecurityException("Unauthorized access");
        }
        // 根據用戶ID查詢用戶信息
        return userRepository.findUserById(userId);
    }
    
    public void updateUser(String userId, UserDTO updatedUser, String authToken) {
        if (!authenticationService.isAuthenticated(authToken)) {
            throw new SecurityException("Unauthorized access");
        }
        // 更新用戶信息
        userRepository.updateUser(userId, updatedUser);
    }
    
    // 其他方法
}

在改進後的代碼中,我們引入了 AuthenticationService 來進行身份驗證,並在獲取用戶和更新用戶信息的方法中進行驗證。

如果身份驗證失敗,將拋出一個安全異常,阻止未經授權的訪問。

這樣,用戶信息的訪問和修改將受到身份驗證和安全性保護,符合安全性和身份驗證原則。

最後

以上的8個基本原則,可以作爲我們日常設計微服務API的指導,來幫助團隊確保服務的一致性、可理解性、可維護性和可擴展性。

然而,具體的微服務的API設計,還應根據具體的項目的特定需求、團隊的技術棧和業務場景做出適當的調整和決策。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章