Middleware cho ứng dụng di động — nền tảng, kiến trúc và ứng dụng

Tác giả: IT Sectr Đã đăng: 2026-03-09 Thời gian đọc: 8 phút

Middleware là một lớp phần mềm trung gian xử lý dữ liệu trước hoặc sau logic chính của ứng dụng, cô lập các mối quan tâm xuyên suốt khỏi mã nghiệp vụ. Theo Redux (2026), middleware giảm sự trùng lặp mã ghi log và xác thực xuống 40% nhờ xử lý tập trung. Redux middleware là một ví dụ cổ điển, nhưng mẫu này được sử dụng rộng rãi hơn: Ktor Client, Bloc, Express.js và Dio.

Những điểm chính

  • Middleware là lớp giữa nguồn dữ liệu và logic nghiệp vụ, cô lập các mối quan tâm xuyên suốt.
  • Redux middleware chặn dispatch và sửa đổi action trước hoặc sau reducer.
  • Bloc sử dụng middleware qua BlocObserver để ghi log và phân tích.
  • Ktor Client xây dựng HTTP-middleware dựa trên pipeline với các plugin Logging và Auth.
  • Dio Interceptor là middleware cho các yêu cầu HTTP trong Flutter với chuỗi bộ chặn.

Middleware là gì?

Middleware là một lớp phần mềm nằm giữa hai thành phần hệ thống, chặn và xử lý dữ liệu trước khi chuyển đến thành phần đích. Trong phát triển di động, middleware được sử dụng trong ba ngữ cảnh chính: quản lý trạng thái (Redux, Bloc), giao tiếp HTTP (Ktor Client, Dio) và xử lý sự kiện (EventBus, NotificationCenter). Giá trị cốt lõi là sự cô lập các mối quan tâm xuyên suốt (ghi log, xác thực, phân tích) khỏi logic miền của ứng dụng. Thay vì thêm lệnh gọi phân tích vào mỗi màn hình, middleware thực hiện điều đó một cách tập trung.

Kiến trúc Pipe and Filter

Middleware triển khai mẫu Pipe and Filter: mỗi thành phần middleware nhận dữ liệu, xử lý và chuyển cho mắt xích tiếp theo trong chuỗi. Thứ tự kết nối middleware xác định trình tự xử lý — middleware đầu tiên nhận dữ liệu thô, middleware cuối cùng chuyển cho trình xử lý đích. Theo JetBrains (2026), kiến trúc này cho phép thêm hoặc xóa middleware mà không thay đổi mã hiện có, đơn giản hóa việc kiểm thử và kiểm thử A/B các mô-đun thử nghiệm.

Sự khác biệt với Interceptor

Middleware là một mẫu chung, Interceptor là trường hợp đặc biệt của nó cho HTTP. Middleware hoạt động với mọi luồng dữ liệu: actions trong Redux, sự kiện trong Bloc, yêu cầu HTTP trong Ktor. Interceptor luôn gắn với lớp mạng và chỉ làm việc với Request/Response. Hiểu sự khác biệt này giúp chọn đúng sự trừu tượng: để ghi log hành động người dùng — middleware, để thêm tiêu đề — Interceptor. Trong các dự án lớn, cả hai mẫu thường cùng tồn tại: middleware quản lý trạng thái, Interceptor xử lý giao tiếp HTTP.

Middleware trong quản lý trạng thái

Redux middleware chặn mọi action dispatch trước khi nó đến reducer. Điều này cho phép ghi log các action, thực hiện các yêu cầu bất đồng bộ qua Redux Thunk hoặc Redux Saga, sửa đổi action hoặc hủy nó có điều kiện. Mỗi middleware nhận store (quyền truy cập trạng thái), next (tham chiếu đến middleware hoặc reducer tiếp theo) và action, quyết định làm gì: chuyển action, sửa đổi hoặc chặn.

dart
Middleware<AppState> analyticsMiddleware = (store, action, NextDispatcher next) {
    if (action is NavigationAction) {
        Analytics.logEvent(action.screenName);
    }
    return next(action);
};

final store = Store<AppState>>(
    reducer,
    initialState,
    middleware: [analyticsMiddleware]
);

Ví dụ về analyticsMiddleware trong Dart cho Flutter Redux. Middleware chặn tất cả NavigationAction, ghi tên màn hình vào hệ thống phân tích và gọi next(action) để tiếp tục chuỗi. Nếu next không được gọi, action sẽ không đến được reducer — bằng cách này có thể triển khai điều hướng có điều kiện hoặc chặn các action không mong muốn. Thứ tự middleware trong mảng xác định trình tự xử lý.

Middleware bất đồng bộ: Thunk và Saga

Redux Thunk là middleware cho phép dispatch không chỉ các đối tượng action mà còn cả hàm. Hàm nhận dispatch và getState, có thể thực hiện các thao tác bất đồng bộ (yêu cầu API qua máy khách HTTP, đọc từ DB) và dispatch các action thông thường khi hoàn thành. Đây là cách tiếp cận tiêu chuẩn cho các yêu cầu mạng trong ứng dụng Redux. Redux Saga sử dụng trình tạo (yield) cho các kịch bản phức tạp hơn: hủy yêu cầu, điều kiện đua, thao tác song song và debounce đầu vào người dùng. Theo Redux Saga (2026), các coroutine Saga dễ kiểm thử và gỡ lỗi hơn so với callback lồng nhau của Thunk.

Middleware trong máy khách HTTP

Ktor Client của JetBrains xây dựng xử lý HTTP dựa trên pipeline middleware. Mỗi giai đoạn yêu cầu — thiết lập kết nối, gửi tiêu đề, đọc phản hồi — được đại diện bởi một pha riêng biệt trong pipeline. Nhà phát triển cài đặt plugin (middleware) qua client.install { }, nhận được một chuỗi xử lý. Thứ tự cài đặt xác định middleware nào xử lý dữ liệu đầu tiên: Logging, Auth, ContentNegotiation, Caching.

kotlin
val client = HttpClient {
    install(Logging) {
        level = LogLevel.BODY
    }
    install(Auth) {
        bearer {
            loadTokens { BearerTokens("access", "refresh") }
        }
    }
    install(ContentNegotiation) {
        json(Json { ignoreUnknownKeys = true })
    }
    install(HttpTimeout) {
        requestTimeoutMillis = 15000
    }
}

Cấu hình Ktor Client với các plugin middleware đã cài đặt. Logging — ghi nội dung yêu cầu và phản hồi. Auth — tự động thêm token Bearer với hỗ trợ refresh. ContentNegotiation — tuần tự hóa/giải tuần tự hóa JSON. HttpTimeout — đặt thời gian chờ. Mỗi plugin độc lập: trong môi trường kiểm thử có thể vô hiệu hóa Auth bằng cách thay thế cấu hình máy khách mà không thay đổi mã yêu cầu.

Dio Interceptor trong Flutter

Dio là máy khách HTTP phổ biến cho Flutter, sử dụng Interceptor làm middleware. Interceptor chặn RequestOptions trước khi gửi và Response sau khi nhận, hỗ trợ chuỗi nhiều bộ chặn. Dio Interceptor tương tự như OkHttp Interceptor cho Dart/Flutter. Theo Dio (2026), RetryInterceptor và LogInterceptor là các middleware được sử dụng nhiều nhất trong các dự án Flutter.

Middleware trong kiến trúc Bloc

Bloc không có middleware tích hợp như một thành phần riêng biệt, nhưng mẫu được triển khai qua BlocObserver — một quan sát viên toàn cục nhận sự kiện từ mọi bloc trong ứng dụng. BlocObserver.onEvent được gọi trước khi xử lý mỗi sự kiện, onTransition tại mỗi chuyển đổi trạng thái, onError tại mỗi ngoại lệ. Đây là một middleware hoàn chỉnh cho phân tích, ghi log, báo cáo sự cố và giám sát hiệu suất.

dart
class AppBlocObserver extends BlocObserver {
    @override
    void onEvent(Bloc bloc, Object? event) {
        Crashlytics.log("${bloc.runtimeType}: $event");
        super.onEvent(bloc, event);
    }

    @override
    void onTransition(Bloc bloc, Transition transition) {
        Analytics.log(transition.eventName());
        super.onTransition(bloc, transition);
    }

    @override
    void onError(Bloc bloc, Object error, StackTrace stackTrace) {
        Crashlytics.recordError(error, stackTrace);
        super.onError(bloc, error, stackTrace);
    }
}

BlocOverrides.runZoned(() {
    runApp(MyApp());
}, blocObserver: AppBlocObserver());

Ví dụ về AppBlocObserver — middleware cho Bloc trong Dart. onEvent ghi mỗi sự kiện vào Crashlytics, onTransition gửi sự kiện đến phân tích, onError ghi ngoại lệ vào báo cáo sự cố. Kết nối qua BlocOverrides.runZoned làm cho quan sát viên trở nên toàn cục cho tất cả các bloc mà không thay đổi mã của chúng. Để vô hiệu hóa trong kiểm thử, chỉ cần truyền một quan sát viên trống hoặc không ghi đè BlocOverrides.

Khi nào và cách sử dụng Middleware

Middleware có hiệu quả cho các tác vụ ảnh hưởng đến nhiều thành phần: ghi log, xác thực, phân tích, lưu cache, giám sát hiệu suất. Sử dụng middleware khi cùng một logic lặp lại ở các phần khác nhau của ứng dụng — thêm token vào mỗi yêu cầu, ghi log mỗi hành động người dùng, phân tích mỗi chuyển đổi màn hình. Theo Dio (2026), xử lý tập trung qua middleware giảm lỗi 25% so với việc sao chép mã trong mỗi thành phần riêng lẻ.

  • Đừng lạm dụng — quá nhiều middleware làm phức tạp việc gỡ lỗi và giảm hiệu suất do các lệnh gọi bổ sung
  • Thứ tự quan trọng — middleware đầu tiên nhận dữ liệu ở dạng gốc, middleware cuối cùng sau mọi sửa đổi
  • Thao tác bất đồng bộ — chuyển các lệnh gọi nặng sang middleware bất đồng bộ mà không chặn luồng UI chính
  • Khả năng kiểm thử — mỗi middleware nên được kiểm thử riêng lẻ qua môi trường mock
  • Ghi lại tài liệu chuỗi — mô tả rõ ràng middleware nào được kết nối và theo thứ tự nào trong dự án

Các lỗi thường gặp khi sử dụng middleware

Lỗi phổ biến nhất là thứ tự middleware không đúng, khi bộ chặn đầu tiên mong đợi dữ liệu mà bộ chặn thứ hai thêm vào. Lỗi phổ biến thứ hai là các thao tác chặn trong middleware trên luồng chính: ghi tệp, lệnh gọi HTTP đồng bộ, mã hóa. Thứ ba là thiếu xử lý ngoại lệ: nếu middleware ném ngoại lệ, toàn bộ chuỗi bị đứt và action sẽ không đến được reducer hoặc yêu cầu sẽ không được gửi. Luôn bọc logic middleware trong try-catch và ghi lỗi vào Crashlytics hoặc Sentry mà không làm đứt chuỗi. Thường xuyên kiểm tra chuỗi middleware trong các buổi đánh giá mã — điều này ngăn chặn sự suy thoái kiến trúc.

Các câu hỏi thường gặp

Middleware khác Interceptor như thế nào?

Interceptor là trường hợp đặc biệt của middleware cho giao tiếp HTTP. Middleware là mẫu rộng hơn: nó có thể xử lý actions (Redux), sự kiện (Bloc), HTTP (Ktor) và mọi luồng dữ liệu. Interceptor luôn gắn với lớp mạng và chỉ làm việc với Request/Response.

Làm thế nào để vô hiệu hóa middleware trong môi trường kiểm thử?

Sử dụng phương thức nhà máy hoặc vùng chứa DI (Dagger, Koin, GetIt) trả về một bộ middleware khác nhau cho dev và prod. Trong Redux, truyền một mảng trống trong kiểm thử. Trong Ktor, sử dụng HttpClient kiểm thử không có plugin. Nguyên tắc chính — middleware không nên được mã hóa cứng trong code.

Middleware có thể sửa đổi action sau dispatch không?

Có — middleware sửa đổi action trước khi chuyển đến reducer hoặc middleware tiếp theo. Ví dụ, Redux middleware có thể thêm siêu dữ liệu (userId, timestamp, deviceId) vào mỗi action mà không thay đổi mã của bộ điều phối. Nguyên tắc chính là không làm thay đổi đối tượng gốc mà tạo một đối tượng mới qua toán tử spread.

Sự khác biệt giữa middleware và Interceptor trong Ktor là gì?

Trong Ktor, các thuật ngữ có thể thay thế cho nhau — Ktor Client middlewareplugin có cùng ý nghĩa. Mỗi plugin triển khai HttpClientPlugin và được cài đặt qua client.install { }. Tất cả các plugin được nhúng vào pipeline yêu cầu, tạo thành một chuỗi xử lý.

Middleware trong Bloc xử lý lỗi như thế nào?

Bằng cách ghi đè BlocObserver.onError — một trình xử lý toàn cục được gọi tại mỗi ngoại lệ trong bất kỳ bloc nào. Đây là một giải pháp thay thế cho try-catch trong mỗi bloc: một middleware tập trung xử lý lỗi, ghi chúng vào Crashlytics và hiển thị snackbar cho người dùng.

Tổng kết

  • Middleware là mẫu phổ quát để cô lập các mối quan tâm xuyên suốt giữa các thành phần ứng dụng, rộng hơn Interceptor.
  • Redux middleware chặn dispatch để ghi log, yêu cầu bất đồng bộ (Thunk) và kịch bản phức tạp (Saga).
  • Ktor Client triển khai middleware qua pipeline với các plugin độc lập Logging, Auth, ContentNegotiation.
  • BlocObserver là middleware cho Flutter Bloc: onEvent, onTransition và onError xử lý tất cả các bloc trên toàn cục.
  • Dio Interceptor là HTTP-middleware cho Flutter với chuỗi tương tự OkHttp Interceptor.
  • Thứ tự kết nối của middleware xác định trình tự xử lý dữ liệu — hãy ghi lại tài liệu rõ ràng.
  • Sử dụng middleware đúng cách giảm sự trùng lặp mã 25-40% và đơn giản hóa kiểm thử đơn vị các mối quan tâm xuyên suốt.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm