Quần Cam Blog

[Web nhà nghèo] Tui đã viết tính năng “chém gió” như thế nào?

Như các bạn đã biết, như các bạn chưa biết, như các bạn đã biết mà giả bộ chưa biết, tuần vừa rồi tui đã làm khuynh đảo làng blog nước nhà bằng cách tung chức năng “chém gió” để thay cho Disqus như trước đây, mà không tốn một đồng server nào cả.

binh-luan

Đúng là quảng cáo hơi bỉ ổi vô liêm sỉ chút, nhưng mà tui đã làm điều đó như thế nào?

Mục tiêu và Yêu cầu

Tất nhiên như mọi phần mềm trong thế giới Midgard của vũ trụ Marvel, bạn phải đặt ra yêu cầu trước khi tiến hành thiết kế và phát triển, dù cho đó chỉ là một chức năng nhỏ. Làm việc không có mục tiêu thì y như tay không đánh giặc vậy.

Tui đã tự biên, tự diễn ra mục tiêu và đặt ra 3 câu hỏi cho mục tiêu đó: Thay thế chức năng comment bằng Disqus trên blog bằng chức năng tự xây dựng.

1. Có cho phép bình luận ẩn danh hay không?

Không. Lý do là vì tui thích thế. Bạn muốn tranh luận/khen ngợi/đánh giá thì bạn vui lòng tiết lộ danh tính.

Điều này đòi hỏi người dùng phải đăng nhập, nhờ đó ta có câu hỏi thứ hai.

2. Đăng nhập như thế nào?

Không có form đăng ký. Cái tui ghét nhất trên đời này là form đăng ký 😖.

Tui phân vân giữa Facebook và Twitter. Sau khi cân nhắc kĩ lưỡng cuối cùng tui chọn Github. Vì Quần Cam là blog lập trình, nên việc người dùng có tài khoản Github gần như là điều tất nhiên.

3. Bình luận lưu ở đâu?

Tiêu chí của tui là không tốn tiền server (bạn có thể xem cách tui xây dựng blog qua bài viết Tôi đã tiết kiệm $5 mỗi tháng với Heroku như thế nào?). Nhà nghèo thì ta chơi cách của nhà nghèo.

Vì blog tui chạy trên Heroku, tui không thể sử dụng Postgres hay một DBMS nào (mà không tốn tiền) cả. Heroku Postgres cho miễn phí 10,000 dòng đầu tiên, cơ mà tui nghĩ lỡ sau này tui nổi tiếng, 10,000 dòng là không đủ 🙊. Với lại phải lo backup/restore này nọ, phiền phức lắm.

Tui sử dụng Github Gist. Hẳn bạn rất ngạc nhiên vì sao lại là Github Gist đúng không? Đọc tiếp nhé 😉.

Hiện thực chức năng

Như đã nói ở trên, có hai phần cần được hiện thực cho chức năng “chém gió”: đăng nhập và bình luận.

1. Đăng nhập

Flow đăng nhập rất đơn giản theo chuẩn OAuth của Github (như hình).

login-flow

Người dùng chọn đăng nhập, website redirect họ sang Github và đòi quyền truy cập một số thông tin theo scope (như email, read/write repo, v.v). Quần Cam không đòi scope gì cả, vì tui chỉ cần định danh, tui lấy các thông tin khác của bạn … để làm gì.

Sau đó server sẽ trao đổi để lấy access token của người dùng từ Github API, và trả token về cho web browser. Vì token không có scope, nó chỉ truy cập được chính những gì public trên tài khoản Github của bạn, nên tui cũng không mã hóa hay mã toán nó làm gì cả.

Web browser nhận được access token và thông tin người dùng (tên và avatar) và tiến hành lưu lại trong localStorage. Lần sau khi website được load, nó sẽ kiểm tra từ localStorage để biết bạn đã đăng nhập chưa.

Thế là xong phần đăng nhập.

Vì sao cần quan tâm “scope” khi auth?

Khi bạn chứng thực tài khoản trên website X với OAuth provider Y, thường X sẽ đính kèm các quyền truy cập đọc/ghi trên Y mà họ cần. Việc nhắm mắt bấm “chấp nhận” là rất nguy hiểm, dễ khiến bạn để lọt những thông tin nhạy cảm. Cứ tưởng tượng bạn đi tập gym mà thằng chủ phòng gym đòi truy cập số điện thoại của gấu bạn. Thay vì “chấp nhận”, hãy tán ngay tên đó một bạt tai. Mà nếu nó “đô” quá thì thôi, tha cho nó một lần đi.

2.1 Tạo comment

comment-flow

Khi bạn bình luận, browser sẽ submit nội dung lên server cùng với access token của bạn.

Server sẽ kiểm tra access token của bạn với Github API. Nếu hợp lệ, nó sẽ lưu comment của bạn vào một ETS table.

ETS (Erlang built-in Term Storage) là một in-memory key-value storage ship cùng với Erlang/OTP. Bạn có thể hiểu nôm na là với Erlang/OTP, bạn được cung cấp sẵn một hệ thống Redis.

Tất nhiên khi một user khác truy cập vào bài viết, comment cũng được đọc ra từ ETS.

ETS còn cho phép bạn cấu hình read concurrency và write concurrency. Ví dụ như tui cấu hình non-concurrent write và concurrent read, cho phép tốc độ truy cập đọc các bình luận nhanh hơn.

2.2 Lưu comment

Như nói ở trên, comment được lưu trong một in-memory storage, có nghĩa là nếu Heroku hứng lên tắt server, mọi công sức comment của bạn sẽ hoàn toàn lạc trôi. Vì vậy tui phải lưu chúng nó ở một nơi nào đó.

Như giới thiệu, nơi ấy tên là Github Gist. Cứ mỗi 2 phút, một Erlang process sẽ tiến trích xuất và đồng bộ toàn bộ comment lên một file trên Gist. Bạn có thể xem file đó ở đây.

Thao tác đồng bộ này hoàn toàn không ảnh hưởng đến tốc độ truy cập của người dùng. Hệ thống vẫn cho phép họ đọc comment bình thường mà không cần phải lock hay block storage (như tui đã nói ở trên, non-concurrent write và concurrent read).

Chắc bạn cũng đã đoán ra, mỗi lần restart (khi tui viết bài mới chẳng hạn, hay Heroku trở chứng bật tắt dyno), hệ thống sẽ tiến hành đọc file Gist, và import lại toàn bộ comment vào ETS.

Lưu dữ liệu trên Gist còn có một điểm cộng: nó là một Git repository, mọi revision đều được lưu lại. Tức là dữ liệu tự nhiên được backup mà không tốn một đồng cắc bạc nào cả. Cám ơn Github!

Góc nhỏ privacy: Vì sao tui lại public bình luận?

Bởi vì tui không tìm thấy lý do nào để giấu cả. Tất cả comment của bạn đều được định danh và đều được hiển thị, trừ một số comment khó nhìn quá thì tui xóa. Cơ mà blog chạy gần 2 năm rồi, chưa có comment (xấu) nào cả.

Góc tăng lương

Như thường lệ, bài viết này không giúp bạn tăng lương, cơ mà tui hy vọng nó có thể:

1. Lôi kéo được bạn học Elixir

Như bạn thấy, với Elixir/Erlang xây dựng một robust concurrent system trở nên dễ dàng hơn bao giờ hết. Với một ngôn ngữ khác, có thể tui đã phải tự implement in-memory storage (tốn công) hoặc sử dụng Redis (tốn tiền), hoặc tìm một giải pháp khác.

Bạn có thể xem thêm các bài viết về Elixir với tag #elixir trên blog.

2. Lôi kéo được bạn comment

Trời ơi làm ơn comment đi mà.

3. Trở thành một better developer

Nghe có vẻ đao to búa lớn, cơ mà cái tui định nói là cái ai cũng biết: một developer tốt phải hiểu vấn đề và tool của mình.

Có nhiều bạn mới vào nghề hay đặt câu hỏi như “Mình có một vấn đề X, vậy thì mình nên dùng gem/framework Y gì?”. Đó là một câu hỏi vô dụng và là một suy nghĩ lối mòn. Y là một phần mềm do anh/chị/bạn Z viết ra để giải quyết vấn đề λ nào đó của họ. Việc phụ thuộc vào giải pháp ăn liền có thể sẽ khiến bạn sa vào cảnh “ép vấn đề vào khuôn khổ của giải pháp”.

Nôm na như ví dụ thô thiển dưới đây, đi 💩 là giải pháp giúp bài trừ chất thải sau khi ăn ra khỏi cơ thể. Bây giờ vì cây của bạn bị héo, bạn bắt bản thân phải ăn thật nhiều để có 💩 bón cho cây, nghe hoàn toàn vô lý đúng không?

Trong khi bạn nên tự hỏi bản thân: Giải pháp cho vấn đề X lần lượt là A, B, C gì? Ưu nhược điểm của A, B, C là gì? Với tool Y mà tui đang có, tui sẽ hiện thực chúng lần lượt như thế nào?

Vậy làm sao để hiểu vấn đề và hiểu tool? Chỉ có một cách thôi là học đi đôi với hành. Bạn muốn giỏi toán thì bạn phải đọc thật nhiều công thức và giải thật nhiều bài toán. Bạn muốn giỏi Java thì bạn phải đọc thật nhiều Java documentation và làm thật nhiều Java.

Vkl ông Quần Cam, nói như ông thì tui cũng nói được.

Ờ thì ở đời, nói thì dễ, làm mới khó.

Tái bút

Nếu bạn thắc mắc vì sao không xài Disqus cho nhanh thì tham khảo bài viết này nhé.


NGUY HIỂM! KHU VỰC NHIỀU GIÓ!
Khuyến cáo giữ chặt bàn phím và lướt thật nhanh khi đi qua khu vực này.
Chức năng này hỗ trợ markdown và các thứ liên quan.
huytd chém vào lúc

Đọc cái 1 thấy giống như đá xoáy blog mình

hisiter97 chém vào lúc

hay a

hisiter97 chém vào lúc

hay a

qcam chém vào lúc

@huytd: trả lời ờ có làm bạn đau lòng không? 😢

thanhngvpt chém vào lúc

kaka

teoanhss113 chém vào lúc

Bài viết hay và assmin có dọng văn dí dỏm quá :satisfied:

duykhoa chém vào lúc

broswer viet sai kia` ong quan cam oi

qcam chém vào lúc

@duykhoa: ờ, cơ mà YOLO đi.

haconglinh1990 chém vào lúc

Sao assmin lại sài cái quần màu cam ???

qcam chém vào lúc

@haconglinh1990 bạn vừa đặt câu hỏi bí mật quốc gia đó.

huydx chém vào lúc

Test một cái comment thật dài

>>>>>>>>>>>>>>>>

EDITED: Admin đã xóa bớt text vì comment chiếm quá nhiều chỗ.

duongnv1996 chém vào lúc

Ủa vậy bạn lưu gist lại vào đâu để khi heroku resart thì dùng gist để load lại ?

qcam chém vào lúc

@duongnv1996 Mình lưu comment trên memory. Sau một khoảng thời gian nhất định (như 5 phút) thì mình upload file này lên Github Gist. Khi Heroku restart, mình sẽ load lại từ file gist vào memory.

jinhduong chém vào lúc

Ủa lỡ trước 5p server tự dưng restart, là mất comment của mình chăng? 😨

qcam chém vào lúc

@jinhduong mình (chính xác là Erlang VM) có trap exit signal từ Heroku, khi nhận được tín hiệu, hệ thống sẽ sync lại với Heroku lần cuối.

Nếu việc sync này fail, một con bot chạy bằng cơm sẽ nhập lại comment của bạn vào file gist. 😁

Bài viết cùng chủ đề

My engineering life

Hôm nay tui muốn kế cho các bạn nghe một câu chuyện phiếm về đời engineering, và cách học mà tui cho là hiệu quả.

Chuyện uống trà

Có lẽ bạn chưa biết: Uống trà thay cho cà phê đã trở thành một thói quen hằng ngày của tui trong suốt một năm qua.

[XML DoS] Những nụ cười rực rỡ

Làm thế nào để chỉ với một đoạn text vài trăm ký tự, bạn có thể làm ngốn vài gigabyte bộ nhớ và từ chối dịch vụ của một hệ thống dùng XML?