Triển khai Base Flutter - Phần 3: Chi tiết - Tầng Data

Firebase

I. Nhiệm vụ

Nhiệm vụ của tầng này chính là việc quản lý và tương tác giữa các nguồn dữ liệu (remote và local).

II. Các công việc cần xử lý

Đi từ dưới lên trên chính là các công việc mà chúng ta phải xử lý được ở tầng này: 

  1. Triển khai được trên nhiều môi trường khác nhau Dev -> SIT -> UAT -> Prod
  2. Triển khai được base network để call API
  3. Tạo models có sử dụng auto generate code
  4. Triển khai repositories

III. Chi tiết xử lý

Bắt đầu xử lý từng việc 1 thôi nhỉ:

1. Triển khai các môi trường khác nhau, đi từ DEV -> SIT -> UAT -> PROD.

Các ứng dụng chúng ta triển khai đều đi từ các bước phát triển xong rồi đẩy cho Tester kiểm thử rồi mới thực hiện Golive, tương ứng chúng ta cũng sẽ có các môi trường khác nhau. Chính vì vậy, việc chuyển đổi giữa các môi trường linh hoạt là điều cần thiết và trong base của dự án mình xác định cần phải có.

Trên pub.dev có rất nhiều thư viện có thể hỗ trợ được việc này. Mình lựa thư viện flutter_dotenv dựa theo lượt like và cách sử dụng nó cũng rất đơn giản.

Khai báo các môi trường: Mình tạo thư mục assets trong root project -> tạo thư mục env để quản lý các môi trường. Trông sẽ như thế này:

Trong .env chỉ bao gồm 1 dòng ENV=prod. Giá trị ENV được set theo các môi trường mà bạn muốn build: dev, sit, uat, prod

Còn trong các file .env.dev .env.ida .env.prod thì chứa những gì?

Đó chính là các đầu domain mà ứng dụng của bạn cần kết nối với BE, ở đây mình có API_AUTH, nếu có thêm nhiều BE cần kết nối thì bạn có thể thêm các domain khác tương ứng ở đây.

Lấy dữ liệu: Khai báo các môi trường xong rồi giờ là tới việc lấy dữ liệu các môi trường tương ứng. Mình tạo 1 class Env nằm trong utils để tiến hành việc khởi tạo trong main và truy xuất các đầu domain tương ứng như sau:

Và thực hiện call await Env.init() trong main là hoàn tất việc setup.

2. Triển khai base network để gọi API và tương tác với Backend

Để hoàn thiện được base network chúng ta cần phải:

Ban đầu mình sử dụng thư viện http của dart, nhưng sau đó thì chuyển đổi sang dio để dùng vì tiện lợi trong việc truyền tải binary data. Việc phân tách đầy đủ các lớp khiến cho việc chuyển đổi vô cùng đơn giản, mình chỉ tiến hành sửa đổi 1 chút do sự khác biệt giữa 2 thư viện ở trong base service mà không ảnh hưởng tới bất kỳ các tầng/lớp khác.

Việc quan trọng nhất ở đây chính là triển khai được BaseService và xử lý hết các exception và trả dữ liệu lên các lớp phía trên. Mình có mô hình sơ bộ cho phần này như sau:

Như vậy, nhìn vào sơ đồ mình sẽ tạo ra class BaseService, nó sẽ chứa base các method get, put, post, delete và trả về dữ liệu được mô tả trong class ServiceResponse.

Dữ liệu được trả về là ServiceResponse bao gồm: Trạng thái thành công/thất bại, dữ liệu khi thành công và lỗi nếu thất bại

Trong đó thì có chứa BaseException là các exception mà mình tự định nghĩa khi call API thất bại, bao gồm: BadRequestException, ForbiddenException, FetchDataException, ApiNotRespondingException, UnAuthorizedException, … và có thể thêm nhiều case exception mà các bạn muốn check thêm.

Công tác mô tả đối tượng ServiceResponse mà BaseService trả về đã xong, giờ ta tiến hành việc hoàn thiện nốt các method GET, POST, PUT, DELETE và xử lý tất cả các trường hợp lỗi xảy ra nếu có thể khi gọi API thôi.

Không biết các bạn có thắc mắc trong _handleErrorDio kia là chứa những gì không? Hay là mọi người đã hình dung được đó chính là nơi mà các Exception còn lại sẽ được chúng ta xử lý. Ví dụ như nếu call api bị timeout thì ta sẽ trả về lỗi ApiNotRespondingException, mất kết nối internet thì trả về FetchDataException, …

Triển khai xong BaseService rồi, giờ trong folder datasource_remote sẽ có các service tương ứng và chỉ call api rồi trả response cho repositories. Ví dụ luồng login mình có AuthService có chứa 2 api là login và registerFcm:

3. Triển khai models có sử dụng auto generate code

Dart là ngôn ngữ hướng đối tượng, chính vì vậy mà chúng ta cần có models để mô tả cho các đối tượng request và response. Và để quá trình cast dữ liệu từ API về được tự động hơn mình có sử dụng freezed để generate code tương ứng cho models. Các class request và response của mình sẽ tương tự như sau:

4. Triển khai repositories implement

Bài này đã rất dài rồi nên phần này mình để lại và xử chúng kèm với interface repositories trong bài sau nha. Tại đấy mình sẽ triển khai và giải thích thêm làm sao để có thể tự động cast dữ liệu về Object mà mình mong muốn trong repositories