Ứng dụng thiết kế Clean Architecture trong ứng dụng Frontend.
Image for post
Minh họa quy tắc phụ thuộc cho ứng dụng FE

Nội dung:

1. Giới thiệu.

2. Phân tích các yêu cầu.

3. TDD (Test first).

4. Cải thiện cho các kiểm thử.

5. Mã nguồn sản phẩm.

6. Kết luận.


1. Giới thiệu:

Qua bài viết trước, chúng ta đã xác định được một vài khái niệm cơ bản, trong đó có các Entity là trung tâm của ứng dụng. Chúng sở hữu và định hình chính các quy tắc trong nghiệp vụ (business). Chúng được chia thành các domain trong ứng dụng. Nhưng trong thực tế thì nó có ý nghĩa gì ?
Không thể trả lời một cách chính xác cho câu hỏi dạng thế này, bởi mỗi một ứng dụng lại sinh ra để phục vụ những bộ quy tắc cũng như logic của riêng nó. Nhưng chúng ta có thể định nghĩa được các tập quy tắc cho ứng dụng của mình.
Phát triển một trang Blog, thì theo lẽ thường một Article chính là trung tâm của ứng dụng Blog. Đó được coi là một Entity, một đối tượng chứa các trường cụ thể và có các quy tắc cụ thể (ví dụ: tiêu đề, nội dung...)
Hãy cùng nhau tìm hiểu cách định nghĩa Entity này, Trong repo này hãy switch về nhánh "init". Trong chương này, chúng ta sẽ tập trung hoàn toàn vào thư mục Entities.

2. Phân tích các yêu cầu:

Image for post
Photo by João Silas on Unsplash
Từ quan điểm kỹ thuật, một Article trong sẽ như thế nào ? Nó nên sở hữu những đặc tính nào ? Nó nên có hành vi gì ?
Để trả lời những câu hỏi này, chúng ta có thể dùng bản demo cũng như phần API vận hành các Article ở phía backend. Để cho đơn giản, chúng ta sẽ không thực sự dùng một API thật mà chỉ đơn giản là một file mock "data.json" để có thể nhanh chóng sử dụng. Chúng ta có thể tìm thấy nó trong thư mục "services".

Article Entity

Rõ ràng một Article sẽ sở hữu một id duy nhất, (tiêu đề - title), (nội dung - content), (trích dẫn cho nội dung - "short"), (các chỉ mục nếu có một item active - indicator if an item is active) , (ảnh - picture), (ngày tháng bài được khởi tạo - date of creation), (các tag - tags), và cuối cùng là một mảng chứa các comment. Trong khi hầu hết các trường này đều đơn giản và rõ ràng là các giá trị cơ bản thì các comment lại có phần phức tạp hơn một chút. Chúng có các thuộc tính con và thậm trí là hành vi: người dùng có thể xác định các comment mới, Vì vậy, thật tự nhiên khi tạo ra một Entity riêng cho chúng và đóng gói các quy tắc cũng như logic nghiệp vụ.

Comment Entity

Thật tuyệt, đối tượng Comment này sẽ có các trường sau (theo "data.json"): id duy nhất, tên người comment, tiêu đề, nội dung, chỉ mục nếu có và ngày được tạo. Ứng dụng cho phép khởi tạo comment, nên chúng ta cần phải có các quy tắc và quy ước cho một comment mang tính hợp lệ để tránh trường hợp người dùng cố gắng tạo ra các dữ liệu không hợp lệ:
* (Tiêu đề - title) không được phép để trống và có độ dài dưới 10 kí tự.
* Tương tự tiêu đề chúng ta cũng sẽ áp dụng quy ước đó cho (tên người comment - the author name).
* Cuối cùng là (nội dung - content) comment sẽ không được phép để rộng hay có độ dài vượt quá 255 kí tự.
Những quy ước đã dần hình thành, thật tuyệt vời khi có cảm giác chúng ta đã có đủ nguyên liệu cho việc triển khai thực hành :))

3. TDD (Test first):

Image for post
Photo by Thomas Kelley on Unsplash
Áp dụng phương pháp TDD, trước tiên chúng ta hãy viết các kiểm thử đơn vị cho các Entity. Ta sẽ khởi tạo hai thư mục ứng với tên 2 Entity: "Article" và "Comment". Mỗi thư mục sẽ tuân theo một cấu trúc tương tự: nó sẽ chứa một file có tên là tên của Entity, file cho các định kiểu của nó, và các file spec và barrel index:
src/entities/article/article.ts
src/entities/article/article.types.ts
src/entities/article/article.spec.ts
src/entities/article/index.ts
Khởi tạo các file cho Article Entity:


Và chúng ta làm tương tự với Comment Entity:
src/entities/comment/comment.ts
src/entities/comment/comment.types.ts
src/entities/comment/comment.spec.ts
src/entities/comment/index.ts
và chúng ta cần update lại barrel file
/src/entity/index.ts
và export Artical và Comment
Giờ thì chúng ta có thể kiểm thử những gì ? Việc kiểm thử các trường không mang lại quá nhiều giá trị và bên cạnh đó TypeScript đã phần nào hỗ trợ việc đó. Nhưng những gì chúng ta cần kiểm thử đó chính là các quy định cho một Comment hợp lệ.

Kiểm thử các quy tắc hợp lệ

Giả sử Entity Comment có một phương thức "validate" để kiểm tra xem Entity này có hợp lệ hay không và trả về dạng true/false. Cần xem xét những quy tắc chúng ta nhận từ phía nghiệp vụ: tối đa 10 kí tự cho tiêu đề cũng như phần tên người comment và 255 kí tự tối đa cho nội dung của comment. Hãy thêm phương thức này vào interface. Đối với "số ký tự tối đa", chúng ta không thể đặt chúng dưới dạng các trường khai báo trong Entity bởi chúng không phụ thuộc vào một instance cụ thể của Entity. Chúng ta có thể sử dụng các trường tĩnh (static fields) hoặc chỉ cần tạo ra các hằng số đứng độc lập:
Giờ chúng ta sẽ bắt tay vào viết các kiểm thử đơn vị:
Tất nhiên bạn có thể thấy TS tiếp tục cảnh báo. Rốt cuộc, IComment và Comment không cung cấp thông tin về "tiêu đề - title", "tên tác giả - author" hoặc "nội dung - content". Nhưng trước khi bắt tay vào sửa lại mã nguồn, mình muốn thảo luận thêm một điều nữa.

Data vs. Entity

Dữ liệu chúng ta nhận được từ API (hoặc trong trường hợp của chúng ta là data.json) hoàn toàn không phải là các entity như Comment và Articale. Ít nhất là chưa. Đó là dữ liệu thuần túy, bản thân chúng không có các khái niệm về kiểm tra tính "hợp lệ - validation" hay bất kỳ chức năng nào khác. Đó là dữ liệu thuần, không phải là một phiên bản của class instance. Vì vậy, chỉ có IComment và IArticle là chưa đủ. Chúng ta cần một interface chuyên về dữ liệu như ICommentData và IArticaleData. Chúng đại diện cho dữ liệu thuần, chúng ta nhận chúng từ bên ngoài và chúng ta có thể sử dụng chúng để khởi tạo một Entity. Ta sẽ đi trước một bước và dự đoán rằng chúng ta cũng sẽ sử dụng các interface này để đại diện cho dữ liệu thuần mà chúng ta lưu trữ trong store của VueX (bởi chúng ta được khuyên rằng không nên lữu trữ các đối tượng bằng cách thông qua các phương thức trong VueX).
Vì vậy, hãy xác định các interface này và cũng xác định các trường mà chúng phải có tương ứng với dữ liệu trong file data.json:
Có một số thứ chúng ta cần chú ý:
1. IArticleData là một phần mở rộng của IArticle, cũng tương tự với ICommentData và IComment. Nó có ý nghĩa vì các đối tượng chúng ta sẽ sử dụng thông qua các Entity nhận vào các dữ liệu từ API trả về.
2. IArticleData phụ thuộc vào ICommentData, nhưng IArticle phụ thuộc vào IComment. Và điều đó là tự nhiên vì trước khi chúng ta chuyển dữ liệu thành đối tượng thì các dữ liệu phụ thuộc vào nhau. Nhưng sau khi khởi tạo, các đối tượng trở thành phụ thuộc vào đối tượng.
3. "Nhưng chúng ta từng nói , các Entity không nên phụ thuộc vào bất cứ thứ gì !" Nhưng chúng vẫn có thể phụ thuộc vào các thực thể khác. Và điều đó diễn ra khá thường xuyên.
4. Ngoài ra, bạn có thể lưu ý rằng chúng ta đã đánh dấu các trường "id" và "createAt" là tùy chọn. Không có gì đảm bảo rằng chúng sẽ luôn tồn tại. Một ví dụ đơn giản: Người dùng viết một comment mới và cung cấp cho chúng ta một số dữ liệu. Chúng ta có tiều đề - title, tên người comment - author's comment và nooijg dung - content (người dùng được yêu cầu cung cấp những dữ liệu này). Chúng ta có thể dặt thời gian comment được rạo ra tại trường "createAt". Nhưng không có cách nào chúng ta có thể tìm thấy giá trị cho "id" tại thời điểm đó. Giá trị sẽ được cài đặt bởi API (hoặc chính xác hơn là bởi cơ sở dữ liệu của API). Chúng ta không thể cho phép việc tăng tự động trường "id" bởi không có gì đảm bảo chúng ta sở hữu mọi dữ liệu ở cơ sở dữ liệu dưới local. Tương tự, chúng ta không nên đặt "createAt" ở frontend: chỉ cần tưởng tượng các vấn đề mà chúng ta có thể gặp phải nếu ứng dụng phục vụ những user ở các nước khác nhau chênh lệch nhau về múi giờ. Vì vậy, việc cài đặt "id" và "createAt" cho việc theo dõi vòng đời dữ liệu nên là một dạng lựa chọn bỏ ngỏ (có thể có hoặc không tùy thuộc vào yêu cầu của ứng dụng).

 4. Cải thiện cho các kiểm thử:

Image for post
Photo by Jungwoo Hong on Unsplash
Cuối cùng thì chúng ta cũng đã có những bước đầu để biết được những phần cần thiết để có thể viết kiểm thử. Chúng ta nên kiểm thử việc một Entity đã được khởi tạo mà không bao gồm trường dữ liệu "id" hoặc "createAt". Và cuối cùng, chúng ta nên kiểm tra xem một Article có thể khởi tạo chính xác những thành phần con nó bao hàm ví dụ như comment hay không.
Hãy cùng cập nhật những phần kiểm thử:

Mocks - Mô phỏng dữ kiện, dữ liệu kiểm thử:

Có một cải tiến nhỏ mà chúng ta muốn thực hiện cho các kiểm thử. Vì các Entity là trung tâm của ứng dụng, chúng ta chắc chắn sẽ tham chiếu chúng trong các kiểm thử khác như: Services, Store, các UI Component. Chúng ta có thể chỉ cần gọi tới một constructor và cung cấp các dữ liệu cần thiết, nhưng điều đó có thể tạo ra dự phụ thuộc về cấu trúc giữa những kiểm thử này và các Entity. Tệ hơn nữa, chúng ta sẽ buộc phải cập nhật chúng mỗi khi có sự thay đổi ở một Entity nào đó.
Để giảm thiểu vấn đề này, chúng ta có thể định nghĩa và sử dụng mock hoặc mock factory và giữ chúng gần lại với Entity. Mỗi khi Entity thay đổi, chúng ta cập nhật mock, chứ không phải là bản kiểm thử. Các phần mã khác của chúng ta có thể không bị ảnh hưởng trong trường hợp này.
Hãy cùng khởi tạo một số mock cho các Entity và đẩy chúng vào factory cho các đối tượng mock theo như bên dưới đây:
src/entities/article/article.mock.ts
src/entities/comment/comment.mock.ts
Cùng cập nhật lại các file barrel:
File entities/article/index.ts
File entities/comment/index.ts
Giờ chúng ta có thể dùng các mock trong các phần kiểm thử của mình:

5. Mã nguồn sản phẩm:

Image for post
Photo by Mathew Schwartz on Unsplash
Đó là một hành trình dài, nhưng cuối cùng chúng ta đã sẵn sàng để viết mã cho production. Và tại thời điểm này, thật đơn giản vì các kiểm thử của chúng ta đã định hình các luồng của hệ thống.
Chúng ta chỉ cần khởi tạo hai Entity, cung cấp dữ liệu cho một constructor, thiết lập các validation cho Comment và đảm bảo rằng tất cả: tiêu đề - title, nội dung - content và tên người tạo comment - author's comment đều tồn tại và không được chứa nội dung quá dài:
Tại thời điểm này, mọi thứ cuối cùng cũng đã sẵn sàng để compile và các kiểm thử sẽ đảm bảo được tính hợp lệ của hệ thống 100%. Toàn bộ mã nguồn trong bài viết này sẽ được tìm thấy khi switch tới branch "entities".

6. Kết luận:

Chúng ta nên tự hào về bản thân: chúng ta đã đặt nền tảng vững chắc cho kiến trúc ứng dụng của mình theo thiên hướng ứng dụng enterprise. Chúng ta đã xác định được các Entity và quy tắc nghiệp vụ - Business rules. Chúng ta đã có kiểm thử chúng và đảm bảo mã nguồn trở nên ổn định, đáng tin cậy và có thể dễ dàng bảo trì. Lần tới, chúng ta sẽ làm việc với các Service và áp dụng chúng thông qua Entity và các quy tắc nghiệp vụ đã được đặt ra.