- vừa được xem lúc

[Declarative Programming + Elm] Bài 18 - Simplicity SPA Elm

0 0 15

Người đăng: Semi Dev

Theo Viblo Asia

Như vậy là chúng ta lại bắt đầu thêm một mini project trong hành trình tự học code để có thể tự xây dựng nên những phần mềm thiết yếu và cũng là để tự tìm hiểu dạng thức tư duy của chính mình và rèn luyện khả năng sắp xếp logic.

Về việc trì hoãn đăng bài viết mới trong thời gian dài khi Sub-Series chưa kết thúc thì mình thực sự rất xin lỗi nếu bạn là người đang theo dõi các Series bài viết của mình tại mạng blog Viblo này. Lý do là vì mình muốn dành thời gian hoàn thiện cái mini project này trước và định hình một vài giới hạn về mặt triển khai code trước khi chia sẻ tại các bài viết ở đây.

Câu chuyện là nếu như bạn đã đồng hành cùng chặng đường tự học bắt đầu từ Series Tự Học Lập Trình Web đầu tiên thì đây đã là mini project thứ ba rồi; Và mình chắc chắn là chúng ta đều đã quen thuộc với thao tác sử dụng Google Search và Translate để tìm kiếm những câu trả lời cho các vấn đề liên quan khi đọc tham khảo code ví dụ.

Chính vì vậy nên kể từ Sub-Series này trở đi thì mình sẽ không đăng tải code chi tiết trong từng bài viết, mà thay vào đó thì toàn bộ source code đã hoàn thiện sẽ được gắn ở bài viết mở đầu của mỗi mini project. Sau đó thì các bài viết sẽ nhằm mục đích chia sẻ lại góc nhìn thiết kế tổng quan và một số thao tác hay dạng thức đáng lưu ý trong quá trình viết code triển khai. Như vậy bạn sẽ có thể dễ dàng đọc lướt qua và tham khảo những điểm mà bạn có thể sẽ cảm thấy cần thiết để ghi chú và không phải tốn thời gian tự Google Search và áp dụng vào kiến trúc code riêng của bạn.

Và đây là liên kết tới trang blog cá nhân tại GitHub được viết bằng Elm, một ứng dụng trang đơn SPA đơn giản sử dụng GitHub Page làm hậu phần back-end xử lý yêu cầu truy vấn dữ liệu thuần túy.

Data Service

Mục tiêu xây dựng của chúng ta là một ứng dụng trang đơn SPA và như vậy hiển nhiên logic hoạt động chính của trang web sẽ được gom cả vào code JavaScript ở mặt tiền của trang web. Code này sẽ được vận hành bởi trình duyệt web trên máy tính của người dùng để điều khiển toàn bộ logic điều hướng xử lý sau mỗi thao tác sự kiện tương tác của người dùng.

Ví dụ: khi người dùng chọn mở xem một liên kết bất kỳ trong trang web, code này cần phải tạm thời chặn cách xử lý mặc định của trình duyệt web để ngăn việc tải lại toàn bộ trang web. Ngay sau đó thì SPA cần phải thực hiện phân tích đường dẫn của liên kết mới mà người dùng vừa chọn để ra quyết định điều hướng hiển thị bố cục trang đơn phù hợp. Nếu đó là một yêu cầu xem một bài viết thì SPA sẽ cần phải xác định các yếu tố định vị dữ liệu của bài viết đó trong thư viện đang lưu tại GitHub, bao gồm:

  • topic-id - bài viết thuộc chủ đề nào?
  • series-id - bài viết thuộc Series nào trong chủ đề trên?
  • post-slug - đoạn mô tả tên định danh của Post đó trong thư mục của Series trên?

Và đó là cách mà mình đã định hình cơ sở dữ liệu đơn giản cho trang blog cá nhân tại GitHub. Sơi đồ cây thư mục dưới đây là cấu trúc của thư mục /data chứa dữ liệu của blog mà mình đang sử dụng với nội dung của các bài viết được lưu ở dạng code markdown trong các tệp .txt, còn các dữ liệu metadata được đặt trong các tệp .json.

/data
├── topic-list.json
|
├── origin [topic]
│ ├── overview.txt
│ ├── series-list.json
| |
│ ├── hrdaya-sutra [series]
│ | ├── post-list.json
│ | ├── 00-the-first-preface.txt
│ | ├── 01-the-second-preface.txt
│ | ├── ...
│ | └── 12-no-five-skandhas-no-object-until-we-come-to-no-realm.txt
│ └── yoga-sutra [series]
│ ├── post-list.json
│ └── ...
|
├── linux [topic]
│ ├── overview.txt
│ ├── series-list.json
| |
│ ├── gnome [series]
│ | ├── post-list.json
│ | └── ...
│ └── libreoffice [series]
│ ├── post-list.json
│ └── ...
|
└── image

Như vậy dạng liên kết có thể được sử dụng cho logic điều hướng của SPA sẽ có dạng thức đơn giản nhất là:

https://username.github.io/ topic-id / series-id / post-slug

Và như vậy, sau khi tách lấy các yếu tố định vị tệp dữ liệu bài viết nói trên thì SPA sẽ có thể gửi yêu cầu tới GitHub để truy vấn nội dung tệp nguyên gốc raw với liên kết có dạng như sau:

https://raw.githubusercontent.com/username/username.github.io/main/data/ topic-id / series-id / post-slug.txt

Ví dụ:

https://raw.githubusercontent.com/thinhtranhnvn/thinhtranhnvn.github.io/main/data/linux/gnome/00-gioi-thieu.txt

Ở đây, khi người dùng chọn dừng lại ở trang đơn giới thiệu tổng quan Topic thì SPA sẽ hiện nội dung của một bài viết overview.txt của Topic đó và như vậy bố cục sẽ giống với trang bài viết. Còn nếu người dùng chọn dừng lại ở trang đơn xem tổng quan của một Series thì SPA sẽ hiển thị bố cục hơi khác một chút với phần nội dung chính được thay bằng một khối liên kết đề mục bao gồm danh sách liên kết trỏ tới các bài viết bên trong Topic đó.

Các tệp khởi chạy

Toàn bộ source code được đặt trong thư mục /src ở cùng cấp đầu tiên với thư mục /data và khởi đầu với tệp src/App.elm. Khi chạy lệnh biên dịch elm make src/App.elm, chúng ta sẽ nhận được một tệp /index.html đặt ở bên ngoài thư mục /src và sẽ được GitHub Page nhận diện ngay để hiển thị khi người dùng mở liên kết tới trang chủ của blog lần đầu.

Thêm vào đó thì tệp này cũng được tạo bản sao ngay trong cùng thư mục để làm tệp /404.html và như vậy khi người dùng mở một liên kết được chia sẻ ở đâu đó và trỏ tới một trang bài viết bất kỳ thì GitHub Page sẽ trả về tệp dự phòng có chứa code với logic hoạt động tương tự.

Ví dụ:

https://thinhtranhnvn.github.io/linux/gnome/00-gioi-thieu

Khi người dùng mở liên kết trên được chia sẻ ở nguồn nào đó thì trình duyệt sẽ nhận được tệp /404.html thay vì tệp /index.html, và lúc này chúng ta vẫn có code logic xử lý tương tự với tệp ban đầu để phân tích đường dẫn hiện tại và gửi truy vấn dữ liệu để điều chỉnh giao diện cho phù hợp. Trong trường hợp có tìm thấy dữ liệu phù hợp để hiển thị thì hiển nhiên nội dung sẽ không phải là một bố cục trang đơn thông báo lỗi mà vẫn sẽ là trang đơn có chứa nội dung.

Kiến trúc code

Mình đã bắt đầu kiến trúc code một cách tự nhiên xuất phát từ giai đoạn tạo ra thư mục /mockup để soạn các trang đơn HTML tĩnh mô phỏng thiết kế trước khi bắt đầu code logic. Ở đây, chúng ta có các thành phần là: thanh điều hướng chính Navigator, thanh điều hướng phụ Overview, phần hiển thị nội dung bài viết Reader, khối liên kết đề mục Indexer. Tất cả đều được nhóm vào một thư mục /mockup/Element, thực ra người ta thường sử dụng từ Component, nhưng có lẽ cũng không quá quan trọng. 😄

Sau đó các thành phần này được sử dụng cho các bố cục trang đơn là: trang chủ HomePage.html, trang tổng quan chủ đề TopicPage.html, trang tổng quan loạt bài viết SeriesPage.html, và trang bài viết PostPage.html. Tất cả số này được nhóm vào một thư mục /mockup/Layout. Do chỉ có duy nhất bố cục SeriesPage.html là khác biệt so với số còn lại ở phần hiển thị nội dung chính nên mình chỉ soạn có hai tệp trong thư mục đó.

Ở đoạn này thì có một chút lưu ý đó là mình sử dụng một convention ngầm định nho nhỏ đó là trang chủ được xem là trang tổng quan của một Topic mặc định có tên là Origin.

Như vậy tới giai đoạn viết triển khai code logic thì mình đã định hình là cần một module tổng quan cho toàn bộ SPA đó là module App. Sau đó thì App sẽ sử dụng các thành phần được cung cấp bởi các module Element là : Element.Navigator, Element.Overview, Element.Reader, Element.Indexer.

Tới điểm này thì là nơi mà mình băn khoăn nhiều nhất để chọn cấp độ thiết kế đóng gói và chia sẻ trong bài viết ở đây. Cụ thể là thiết kế kỳ vọng nhất là các Element đều được thiết kế độc lập với bối cảnh context của thiết kế và có thể sử dụng cho các trang web khác nếu muốn.

Để triển khai thiết kế ở cấp độ này thì yêu cầu của code đó là mỗi một Element sẽ là một chương trình biệt lập và cần định nghĩa kiểu dữ liệu nội tại riêng, chứ không biết tới các yếu tố liên quan tới logic điều hướng của ứng dụng. Sau đó code sử dụng bên ngoài của một project bất kỳ ví dụ như chúng ta đang có module App ở đây sẽ phải import cả kiểu dữ liệu mà Element đó sử dụng nội tại. Sau đó khi truy vấn được dữ liệu của một Post thì sẽ phải chuyển đổi bản ghi Post thành dạng bản ghi dữ liệu mà Element kia yêu cầu để hoạt động được.

Tuy nhiên, mình đã chọn dừng lại ở cấp độ thiết kế module đóng gói không hoàn chỉnh với các Element có hiểu biết về logic điều hướng toàn cục của ứng dụng, và thậm chí tự phát động các yêu cầu tới GitHub Page để truy vấn dữ liệu cần thiết để hiển thị. Như vậy module App chỉ làm nhiệm vụ điều chỉnh bố cục tổng quan của trang đơn cần hiển thị dựa trên các yếu tố định tuyến và không cần xử lý gì nhiều tới logic hoạt động chi tiết của các Element.

Lý do cho lựa chọn này là bởi vì thứ kiến trúc này đơn giản hơn để triển khai nếu không có nhu cầu sử dụng lại các Element cho các project khác. Thêm vào đó là từ góc nhìn của bất kỳ ai khác cũng mới học code và Elm thì cũng sẽ dễ theo dõi logic hoạt động của code qua các bài viết mà mình chia sẻ tại đây. Nếu bạn vẫn chưa code xong mini project nào bằng Elm và rất tự tin vào khả năng của bản thân thì có thể tái cấu trúc refactor lại source code mà mình đã đăng tải theo kiến trúc phía trên. Đó chắc chắn là một công luyện tập rất nghiêm túc và kết quả chắc chắn cũng sẽ rất đáng giá.

Nhân tiện thì thứ kiến trúc nửa vời mà mình sử dụng có phần không tối ưu về mặt hiệu năng do các module Element tự gửi yêu cầu truy vấn dữ liệu để hiển thị chứ không sử dụng dữ liệu chia sẻ chung. Vì vậy nên sẽ có trường hợp khi người dùng yêu cầu xem một trang đơn mà các tệp metadata.json được gửi truy vấn nhiều lần từ các Element khác nhau.

Với phần giới thiệu tổng quan sơ lược về mini project này thì mình hy vọng là bạn sẽ có thể nghiệm thu quá trình học Declarative + Elm với một bộ source code mà cá nhân bạn cảm thấy hài lòng. Và ở thời điểm hiện tại, sau khi đã sử dụng Elm để xây dựng một trang blog đơn giản thì mình đã thêm được dữ kiện để ra quyết định rằng sẽ không có thêm một project elm-fullstack.

Đúng là Elm rất tuyệt, rất đơn giản để tiếp cận khi đã có nền tảng JavaScript căn bản và tư duy Imperative chưa quá nặng nề. Tuy nhiên thì mình cảm thấy là rất cần thiết có một Sub-Series khác dành riêng cho câu chuyện Functional Programming nghiêm túc với ngôn ngữ Haskell. Đây sẽ là một dự định chắc chắn sau khi chúng ta đã ưu tiên xử lý xong Sub-Series Object-Oriented + Java đã được định vị trước đó.

(chưa đăng tải) [Functional Programming + Haskell] Bài 1 - ...

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 51

- vừa được xem lúc

Var vs let vs const? Các cách khai báo biến và hằng trong Javascript

Dạo này mình tập tành học Javascript, thấy có 2 cách khai báo biến khác nhau nên đã tìm tòi sự khác biệt. Nay xin đăng lên đây để mọi người đọc xong hy vọng phân biệt được giữa let và var, và sau đó là khai báo hằng bằng const.

0 0 31

- vừa được xem lúc

VueJS: Tính năng Mixins

Chào mọi người, hôm nay mình sẽ viết về Mixins và 1 số vấn đề trong sử dụng Mixins hay ho mà mình gặp trong dự án thực. Trích dẫn từ trang chủ của VueJS:.

0 0 27

- vừa được xem lúc

Asset Pipeline là cái chi chi?

Asset Pipeline. Asset pipeline là cái chi chi. . Giải thích:.

0 0 47

- vừa được xem lúc

Tạo data table web app lấy dữ liệu từ Google Sheets sử dụng Apps Script

Google Sheets là công cụ tuyệt vời để lưu trữ bảng tính trực tuyến, bạn có thể truy cập bảng tính bất kỳ lúc nào ở bất kỳ đâu và luôn sẵn sàng để chia sẻ với người khác. Bài này gồm 2 phần.

0 0 266

- vừa được xem lúc

Học Deep Learning trên Coursera miễn phí

Bạn muốn bắt đầu với Deep Learning nhưng không biết bắt đầu từ đâu? Bạn muốn có một công việc ở mức fresher về Deep Learning? Bạn muốn khoe bạn bè về kiến thức Deep Learning của mình. Bắt đầu từ đâu.

0 0 35