ESI Press

Đã hoàn thiện ESI Press 1.0

Phần tiện ích quan trọng của EP là biên tập đã đạt đến 1.0...
Dù giao diện front web dành cho khách hàng đã xong và chạy khá ổn, nhưng phần biên tập còn dang dở... nay đã hoàn thiện đủ mức để dùng rộng rãi. Hãy đọc để hiểu về quá trình xây dựng ESI Press của chúng tôi.

## Tiếp theo câu chuyện giai đoạn 1

Cách đây hơn 1 năm, tháng 7/2017, mình đã viết về [[ESI-Press]] ở bài [[25]] và lý do hình thành ra nó. Ở thời điểm đó, thi phần biên tập - đặt tên là **EP-Master** - đang trong kế hoạch thực hiện và được người của ESI viết xong một số phần. Tuy thế nó vẫn chưa hoàn thiện đạt được đến chất lượng cần thiết để triển khai. Sau thời gian thực hiện tới 3 tháng, nhân viên dev phát triển chính phần này chính thức bỏ cuộc, người hỗ trợ nội dung và kiểm thử kèm theo sau đó cũng thôi việc. EP Master dù sao cũng có thể chạy được, dù lỗi nhiều nhưng tôi quyết định vẫn cho lên. Phần này ẩn sau, dành cho biên tập và nó cần tài khoản đăng nhập để sử dụng.

![Hình chụp này là mình đang soạn bài này bằng EP Master](ep-master-edit1.png)

## EPFS - hệ thống lưu web file cho EP

Phần trước, mình có nói về EPFS, viết bằng Java dạng servlet và dựa lưu trữ backend trên GridFS của MongoDB, và module này đã chạy tốt với phiên bản đầu của EP lên sóng lúc đó.

Tuy nhiên sau đó, mình đã thay đổi thiết kế, EPFS được viết lại chỉ là 1 lớp web servlet, để dành triển khai phần lưu trữ object & metadata được độc lập dạng module (interface implementation) và module có thể gắn vào web app tuỳ chọn theo cấu hình. Backend có thể là MongoDB, file system truyền thống hay in-memory, nội dung file nén... tuỳ ý. Điều này khiến EPFS đa năng hơn, mạnh mẽ hơn với nhiều môi trường khác nhau.

> Bạn có thể bỏ qua phần này, bởi tôi có hơi dài dòng giới thiệu tính năng kỹ thuật của EPFS, hơi mất thì giờ đọc.

Phiên bản thứ 2 của EPFS này, phần web servlet có các thêm khả năng kỹ thuật sau:

* Hỗ trợ đầy đủ RESTful API và nhiều tính năng sẵn có của HTTP/1.1 trong việc mô tả metadata của nội dung. Có nhiều chức năng làm nhắm để tương thích với Amazon S3.
* Cho phép liệt kê dạng thư mục ảo(có index file), có lưu mime type và có khả năng lưu nhiều phiên bản, nó cũng đảm bảo các hành động thay đổi nội dung là atomic (transactional: đúng tất cả hoặc không có gì, không có chuyện upload nửa file dở dang).
* Hỗ trợ lưu và tính toán file integrity check với MD5 theo chuẩn HTTP (với header có tên Content-MD5). Tương tự như cách [Amazon S3 áp dụng data integrity](https://aws.amazon.com/vi/premiumsupport/knowledge-center/data-integrity-s3/). Nếu file object upload lên có md5 hash ở header, thì EPFS sẽ tính toán hash và so sánh, object chỉ được cập nhật nếu hash tính ra và so với header là đúng đắn. Giá trị này sẽ được trả về nếu như backend có hỗ trợ lưu.
* Sử dụng Java NIO nếu backend cho phép, tiết kiệm CPU cho thao tác stream nội dung file lên và xuống. Thậm chí mình có hỗ trợ tính năng send-file có trong tomcat, cho phép tiết kiệm hơn nữa CPU với NIO của kernel.
* Hỗ trợ phân chia không gian theo bucket được xử lý bởi filter (cắm vào tùy ý được), cho phép theo website hoặc người đăng nhập có không gian riêng mà không cần thay đổi URI path.
* Hỗ trợ tốt hơn cho HTTP proxy cache và cả client cache thông qua việc cung cấp thông tin Last-Modified và ETag trong header. Web servlet cũng xử lý so sánh thời gian nếu client gửi header If-Modified-Since và nếu không thay đổi, một 304 status sẽ được trả về. ETag sẽ căn cứ vào MD5 nếu có, cũng trợ giúp tốt hơn cho client cache so sánh không sử dụng tham số thời gian.

Hiện tại, các backend module sau đã được phát triển, kiểm thử:

1. **TestBackEnd**: module đơn giản này lưu object trên memory, dưới dạng các byte array với tên file và metadata tìm kiếm qua HashMap. Nó được dùng để test các chức năng của EPFS, nhưng nếu cần vẫn có thể sử dụng để lưu tốc độ cao, in-memory.
2. **Simple FileDirectory** backend: backend này dùng một cây thư mục để lưu cây file ảo của EPFS, với khả năng đầy đủ của EPFS. File metadata được lưu 1 file riêng nếu có. Backend này thiết kế rất an toàn, nên dù upload file thực thi được như .jsp, cũng không khiến nó thực thi giống như DefaultServlet của tomcat hay jetty. Việc upload và truy cập cũng được kiểm soát chặt chẽ, chỉ file nằm dưới cây thư mục khai báo mới được truy cập, bất kể link hoặc kiểu tấn công dùng đường dẫn ../../.. cũng không làm khó nó.
3. **DbHybrid** backend: như cái tên, thì việc lưu trữ metadata của file sẽ nằm trên một SQL DB (kết nối JDBC), còn nội dung file thì lưu trên đĩa với cấu trúc thư mục tối ưu cho số lượng lớn file (tên file là id không còn là tên URI path). Tôi phát triển backend này để thay thế cho GridFS của MongoDB với quy mô 1 node lưu trữ đủ (tức có thể đạt hàng triệu file và hàng chục TiB). Tương lai thì các file nhỏ có thể lưu luôn trên SQL DB, vì lưu row trên SQL DB có hiệu quả tốt hơn file system với các file nhỏ hơn 32KiB. Module này đã test tương thích với Apache Derby (hay còn gọi là Java DB), cho phép chạy một instance EPFS mà không cần thành phần ngoài. MySQL với ndbcluster engine cũng được test nên xét về khả năng scale, thì không có vấn đề gì cả.

Tôi đã chuyển hết file từ GridFS sang DbHybrid, theo đánh giá và các benchmark, lưu file trên single node, file system có hiệu năng tốt hơn nhiều so với GridFS. Tôi chưa có dịch vụ chạy thật đạt tới mức vượt giới hạn 1 node nên để đơn giản vận hành, thì lưu file system sẽ tốt hơn. 

Các ảnh và nội dung tải về như css, js của mcard.vn và esi.vn hiện tại là được xử lý trên EPFS, bạn có thấy hiệu năng nó tốt không? Dù vậy, tôi dự định rằng EPFS sẽ vẫn tiếp tục được phát triển thêm, mục tiêu hỗ trợ WebDav và khả năng sharding theo bucket header và/hoặc path, cách đơn giản để EPFS biến thành một dịch vụ lưu trữ web storage mạnh mẽ cho backup, share content và có scale không giới hạn. Các backend module lưu phân tán, hoặc dùng cloud storage cũng cho phép nó mạnh mẽ hơn.

## Nhân vật chính của bài: **EP Master**

Công cụ biên tập tuy không cần đa dạng giao diện và theme như phần front, tuy nhiên do nội dung (site nào) tuỳ thuộc vào người đăng nhập nên buộc phải xử lý ánh xạ tên đăng nhập vào mã site ID (gọi là acctid) - khác front-app, dùng host hoặc địa chỉ IP. Mọi câu lệnh áp dụng vào Db đều có gắn điều kiện này. Việc upload lên EPFS cũng sẽ áp dụng mã acctid này, và nó là bucket của EPFS (để mỗi site có không gian lưu trữ riêng, độc lập nhau).

EP Master này phần web app cũng dựa trên ESI-Apps tương tự phần front app. Nhưng phần front của EP Master thật là khó chọn. ESI Apps đã cung cấp toàn bộ các REST API cần thiết, do đó phần web client phải là rich client.

AngularJS đã được chọn, nhất trí giữa cả tôi và nhân viên. Chúng tôi đã từng có công cụ với web client dựa trên cái này và nó cũng khá tiện cho phát triển. Thời điểm chọn thì Angular đã có bản 1.4 và beta của Angular 2. Nhưng tối quyết định chọn 1.4 bởi nhân viên của tôi biết js chứ không học thêm, mặc dù bạn này chưa làm Angular thật sự nhưng có thể nó tiện hơn jquery.

Bắt tay vào làm một thời gian, tôi mới nhận ra đó là sai lầm. Phần REST API tôi hoàn thiện rất nhanh nhưng web client là thử thách, tốn thời gian và nếu bạn đi xa hơn tính năng vốn có của AngularJS thì rất mệt. Cậu dev thiếu kinh nghiệm xử lý chainning sự kiện, quen jquery hơn single page app (và js thật sự cũng khó xơi nữa), nên cứ gặp exception là loay hoay và để chạy được thì dùng lệnh $location replace/reload lại trang, gây mất state và unsaved data. Tôi đã từng mất bài đang soạn, khá dài với trang này, khi xong bấm save vào gặp lỗi phiên đăng nhập bị hết hạn, trang load lại luôn, mất bài soạn nản quá không viết nữa.

Các thứ AngularJS chưa có, chưa ai làm module, mà EP Master cần, phải chế thêm, rất vất vả:

1. EP Master cần soạn bài, hiện **markdown** là định dạng khuyên dùng và có thể dùng 1 textarea được. Nhưng không chỉ nó, một HTML wysiwyg editor quả thật thử thách, nhất là phải render ra phù hợp nhiều template. Câu dev chọn Angular tinymce nhưng có vẻ không tích hợp tốt lắm với nhau. Bản thân các markdown editor cũng ít cái tích hợp với angular, toàn là chạy độc lập hoặc với jquery.
2. File upload: EPFS REST API dùng PUT hoặc POST thẳng nội dung lên, chứ không cần qua form multipart/data upload. Nếu dùng form upload cũng gặp khó do angular hỗ trợ hạn chế, dùng module ng-file-upload gắn thê, thì việc hỗ trợ EPFS không tốt (ví dụ content-type cần phải đúng). May mắn web api mới có FileReader, cho phép upload file với $http và hơn nữa, cho xem trước file khi upload. Xem hình chụp bạn sẽ thấy có thể upload với sự hồi đáp tiến trình upload file lớn, và thực sự công cụ file manager đã này tiêu tốn nhiều thời gian làm việc của chúng tôi.
3. Xử lý giao diện bootstrap, kết hợp angularjs: bootstrap (3.x) không hỗ trợ angular. Một module là angular-ui-bootstrap làm việc này giúp chúng tôi. Tuy nhiên không phải mọi component đều ổn, nhất là chức năng chọn ngày tháng giờ không tốt với mobile, html5 input type hỗ trợ tốt hơn nhưng nó có thể bất tương thích các component của UI.
4.  Không muốn xé lẻ chức năng nhiều menu, nên chúng tôi đặt 3 phân cấp đường dẫn từ chức năng chính => chọn bài viết => chức năng của riêng bài viết. Chức năng chính là menu, ra danh sách chọn bài rồi chức năng phụ là tab. Việc xử lý tab chức năng bên dưới mỗi path liên quan đến object cần thao tác thì rất có vấn đề. Chúng tôi đặt path khi route dạng /object/id/sub-function, trong đó object là category, post (bài viết), file... còn id là mã định danh của object, sub-function có thể là list, edit, view hay media, content edit... cấu trúc này cho phép tạo mỗi sub function là 1 controller của angular. Vấn đề dở nhất gặp phải khi ta phải đi đổi tab, hoặc lên cấp cao hơn, ví dụ đang view sang edit, thì phải lấy lại data từ server, state trong history không được bảo lưu, filter trong query phải điền lại... cách giải quyết có thể là dùng query string để lưu state này với thao tác duyệt chọn.

Hình sau là hình chụp tôi upload file lên với progress bar, thông tin metadata có content type.

![](ep-master-file-upload.png)

Tóm lại, ở thời điểm này, sau hơn 1 năm dở dang với EP Master - làm việc khác quan trọng hơn - nay rảnh, trong 1 tháng nay tôi đã sửa hầu hết lỗi và chỉ thay đổi thêm 1 chút tính năng, bỏ plugin cũ mà thôi. Tôi cũng nâng cấp lên angularjs 1.7.5 và thấy nó ổn. Các chức năng của EP Master nó đã đủ để dùng chính thức rồi. Bài blog này chính là để thử thành quả làm việc suốt tháng qua, được viết nhiều khúc ở các thiết bị khác nhau.

![Hình chụp này là tôi sửa bài này bằng EP Master trên Firefox - Windows 10](ep-master-edit2.png)

Kết thúc bài này, tôi sẽ trở lại với một số điểm mới sau này. Nhưng có 1 điều chắc chắn rút ra qua kinh nghiệm dùng angularjs, thì đây sẽ là công cụ cuối cùng dùng nó. AngularJS khá tiện và làm nhanh đó, nhưng nếu bị gặp các yêu cầu phải tuỳ biến nó, bạn sẽ tốn công hơn, khó hơn vì lỗi, vênh và chiết lý viết app khá là khó hiểu đối với nhiều dev. Chaining event tôi cũng sẽ viết ở 1 bài sau.

@EP team lead

Add new comment



Comments

1. Toi

Tôi comment nơi đây. reply

2. TEST

RestCall again reply

3. RESTCall

Test rest call reply

4. Vo Thi

Nhận tiền sau vay reply

5. Vo Thi

https://mcard.vn/mcard/forgetpass?S2DATA=U2FsdGVkX188Ko-hlmvqjckYOos4ZZVKn0FHFG5o4Skr8THJGX-8rs1RJS9iGAeWudjjxXaawRs7-5J22LTyf6FhgrOXgOdS7dacbYsxdxFcv_rDlIXttJKSfKqOzNoY reply

6. soa

Đủ đk làm boss ngon hơn làm con trâu reply

7. Tùng

đủ điều kiện vô FIS làm rồi nhé. reply