Xem mẫu
- TÁI CẤU TRÚC CONTROLLER VÀ ACTIONS TRONG THỰC TẾ
Nguyễn Hữu Cầm
Trường Đại học Hà Nội
Abstract: Khi phát triển một ứng dụng web application theo mô hình MVC, các nhà phát
triển trong một tổ chức thường tự mình đặt tên cho controller và actions. Điều này sẽ dẫn đến
khó có thể hiểu được action trong controller đó làm việc gì nếu như đặt tên không chính xác.
Thực chất, việc đặt tên các controllers và các actions trong controllers đó là một bài toán không
đơn giản do làm thế nào để đặt tên actions ngắn gọn, dễ nhớ và dễ tìm kiếm khi ứng dụng có
xu hướng mở rộng. Trong ứng dụng nhỏ có thể không cần theo cách áp dụng này; tuy nhiên
khi dự án có xu hướng mở rộng và phát triển lâu dài thì vấn đề đặt tên controller và actions theo
chuẩn sẽ giúp quá trình phát triển nhanh hơn và dễ bảo trì hơn.
Từ khoá: Action, Controller, Laravel, MVC architecture, Naming convention.
A.Giới thiệu
Trong một ứng dụng MVC, việc đặt tên cho controller và action là một bài toán
đau đầu đối với các nhà lập trình viên. Sẽ có những lập trình viên khi lập trình một ứng
dụng Laravel thường tạo controller rồi viết rất nhiều actions trong đó với chiều dài có
thể lên tới hàng trăm dòng code. Điều này sẽ gây khó k [23]hăn không chỉ với bản thân
người lập trình mà với những người duy trì dự án sau này khi đọc lại chính code của
mình viết ra mà không hiểu tại sao mình lại viết như thế này cũng như mất thời gian để
tìm hiểu lại xem phương thức này có ý nghĩa là gì. Vì thế, việc tách controller cũng như
actions trong controller đó thành các actions nhỏ hơn sẽ giúp quản lí hiệu quả khi dự án
ngày một phức tạp hơn.
B. Lý thuyết và ứng dụng
Bản chất chính của method này là hãy tách nhỏ controller thành các method quy
chuẩn sau
Tên function Method type Chức năng
Index() GET Xem danh sách resources tồn tại trong hệ thống: Ví
dụ như xem danh sách users và danh sách roles, etc.
Create() GET Xem trang create resource. Ví dụ như xem trang
create user, create role
Store() POST Tiến hành thêm mới resource vào hệ thống
Edit() GET Xem trang edit resource
Update() PUT|PATCH Tiến hành update resource vào hệ thống
Delete() DELETE Tiến hành xoá resource trong hệ thống
Show() GET Xem thông tin chi tiết resource nhất định trong hệ
thống
200
- Bài toán được ứng dụng như sau. Người dùng có xem danh sách podcasts, trong
mỗi podcasts có nhiều episodes. Người dùng có thể xem danh sách episodes trong hệ
thống, subscribe/unsubscribe podcast nhất định, publish/unpublish podcast và thay đổi
cover image của podcasts trong hệ thống.
Quy tắc 1: Tách nested resource thành controller riêng
Ví dụ với vấn đề người dùng xem danh sách episodes trong podcast với URI theo
chuẩn RESTful như sau
Figure 9: URI trước khi refactor route cho nested resource
- Lựa chọn thứ nhất: Nếu để action ví dụ như listEpisodes() trong
PodcastController thì sẽ bị vi phạm nguyên tắc 7 methods như đã đề cập ở trên
sai nguyên tắc
- Lựa chọn thứ 2: Nếu để method index() trong EpisodeController thì
bản thân function này có ý nghĩa xem tất cả các episodes trong hệ thống không chính
xác.
- Lựa chọn thứ 3: Tách thành 1 controller riêng có tên là
PodcastEpisodesController với method là index(). Route sẽ được biểu
diễn như sau
Figure 10: URI sau khi đã refactor nested resource trong route/web.php
Trong code
Figure 11: Code behind trong nested resource
với $id là id của podcasts.
201
- Việc làm này còn có một ưu điểm khác, trong trường hợp nếu muốn tạo mới 1 episode
trong 1 podcasts nhất định thì URI sẽ như sau
Figure 12: Different routing trong route/web.php
Với create() là xem trang thêm mới episode trong podcasts nhất định và store()
dùng để tiến hành thêm mới episode trong podcasts.
Với các lí do trên, lựa chọn 3 là lựa chọn tốt nhất để quản lí episodes trong podcasts.
Quy tắc 2: Tách edited property thành 1 controller riêng
Bài toán tiếp theo cho phép người dùng thay đổi cover image của podcast nhất định
Figure 13: Trước khi refactor route “Update cover image” trong route/web.php
Code
Figure 14: Code behind edited property
Bản thân hàm updateCoverImage($id) là một hàm cập nhật resource, nếu dể
hàm update() này vào trong PodcastController thì không đúng vì hàm đó là
hàm cập nhật thông tin podcast, đặt vào EpisodeController cũng không đúng do
hàm này không cập nhật thông tin episode
Vì thế hãy tách ra thành 1 controller riêng có tên là
PodcastCoverImageController với method update(),với method là PUT.
URI sẽ được hình thành như sau
Figure 15: Sau khi refactor URI cho edited property
202
- Tuy nhiên có vấn đề cần lưu ý: Trong trường hợp nếu cover image cần update
nằm trên cùng một UI với những thông tin cơ bản của podcast thì có thể sử dụng hàm
update() để thực hiện hành động này, tuy nhiên trong trường hợp
updateCoverImage() nằm trên một UI khác với UI cập nhật cơ bản của podcasts
thì nên tách ra thành 1 controller mới, với method update() đã được đề cập ở trên,
tránh trường hợp sử dụng chung logic trong controller action làm code phức tạp.
Quy tắc 3: Để pivot model là resource riêng biệt
Bài toán tiếp theo cho phép người dùng đã đăng nhập có thể subscribe hoặc
unsubscribe podcasts.
Figure 16: URI trước khi refactor cho vấn đề subscribe/unsubscribe
Trong case này thì mối quan hệ sẽ là many-many relationship: Một user
subscribes nhiều Podcasts và một podcasts có thể được
subscribe bởi nhiều users.
User 1 – M Subscriptions M – 1 Podcasts
Chúng ta sẽ tiến hành tạo ra SubscriptionController để model trường hợp M-N đó
Figure 17: URI cho vấn đề subscribe
Vậy thì vấn đề được đặt ra: podcast_id này giờ lấy ở đâu, thay vì trên URL thì
podcast_id bây giờ có thể được truyền qua request body - thông qua hidden field
hoặc query string parameter.
Figure 18: Method store()
203
- Figure 19: Podcast model
Ở đây, subscription sẽ là 1 model riêng biệt, được map vào bảng
podcast_user với nhiệm vụ xử li pivot table.
Figure 20: Subscription model
Đối với trường hợp unsubscribe, thay vì creating resource như hàm store() thì sẽ là
deleting resource với hàm delete(), sử dụng method DELETE.
Với trường hợp này, {id} cũng lấy từ request body tương tự như ở trên, {id} ở đây là
subscription_id.
Figure 21: Delete subscription
204
- Quy tắc 4: Think of different states as different resources
Bài toán tiếp theo cho phép người dùng publish hoặc unpublish podcast, thông
qua việc chuyển trạng thái cột published_at từ NULL sang current datetime hoặc ngược
lại
Figure 22: URI trước khi refactor
Trong trường hợp này, việc sử dụng hàm update() trong podcast đã có tác
dụng cập nhật thông tin podcasts, nên không thể sử dụng update() để cập nhật trạng
thái podcasts
Khi publish 1 podcast mới, điều đó cũng giống như việc sử dụng hàm store()
trong PublishedPodcastsController. URI sau khi refactor sẽ như sau
Figure 23: URI sau khi refactor
Để xử lí việc đó, hãy tạo ra controller mới có tên là
PublishedPodcastsController
Tương tự, {id} sẽ được truyền qua request body, thông qua hidden field hoặc
query string parameter
Với trường hợp unpublish sẽ là
Figure 24: URI cho unpublished podcasts
Với {id} là id của podcast được published
Code sẽ được thực thi như sau
205
- C.Kết luận
Mỗi tổ chức đều có quy định cấu trúc dự án riêng, nhằm giúp dự án dễ tiếp cận,
bảo trì và mở rộng nếu cần thiết. Việc theo các practice bằng cách quy các actions trong
controller thành 7 methods như đã định nghĩa ở trên là một trong những cách giúp cho
project gặt hái thành công, giúp tiết kiệm thời gian và công sức cho bản thân chính nhà
phát triển cũng như những người tiếp tục duy trì dự án sau này.
Link Github tham khảo: https://gitlab.com/nguyenhuucam91/laravel-coding-convention
206
nguon tai.lieu . vn