Như đã nói ở phần một, Frontend Du Ký S1E2 | EGANY Apps sẽ kể về những lỗ hổng trong quy trình làm việc cũng như những vướng mắc về kỹ thuật mà team gặp phải trong quá trình phát triển EGANY Apps.
Nếu bạn chưa đọc phần một, bạn có thể đọc trước tại đây.
Giới thiệu
Sau khi thống nhất được quy trình làm việc cũng như công nghệ để thực hiện dự án, team bắt đầu quá trình phát triển.
Kế hoạch lúc bấy giờ bao gồm:
- Tập trung phát triển sườn dự án: authentication (sign-in/sign-out), layout, …
- Phát triển component để sử dụng chung
- Chuyển đổi các dashboard ở hệ thống cũ
Đây là lúc các vấn đề bắt đầu xuất hiện. Để dễ dàng hơn cho các bạn thì mình sẽ chia các vấn đề thành hai nhóm chính, cụ thể:
- Kỹ thuật
- Quy trình
Cùng bắt đầu tìm hiểu nha!
Trong bài viết mình sẽ gọi bên đối tác e-commerce (bao gồm Haravan và Sapo) là platform để tránh việc lặp lại tên của các nền tảng đó.
Kỹ thuật
1. Xác thực người dùng
EGANY Apps không có một hệ thống xác thực hoàn chỉnh. EGANY chỉ đóng vai trò là trung gian cho phía platform. Flow xác thực sẽ như hình sau:
- EGANY Apps gửi thông tin người dùng cũng như thông tin ứng dụng về phía backend
- Backend nhận thông tin và truy xuất thêm một vài thuộc tính khác và gửi cho phía platform
- Phía platform tiến hành xác thực và trả API token lại cho phía backend. Quá trình này thường chỉ làm một lần khi cài đặt hoặc khi người dùng revoke (thu hồi) token
- Backend tạo một mã OTP để xác thực với EGANY Apps
- EGANY Apps nhận mã xác thực và gửi ngược lại phía backend
- Backend tạo token xác thực và gửi lại cho EGANY Apps
Một số bạn có thể thắc mắc là vì sao phía backend không gửi trực tiếp token về phía EGANY Apps luôn mà phải tạo ra một token trung gian. Lý do như sau:
- Hạn chế rủi ro lộ token. Do API phía platform đều là public API, nếu để lộ token của khách thì thiệt hại sẽ tương đối lớn. Hơn nữa, nếu token do EGANY quản lý thì khi gặp sự cố sẽ xử lý dễ dàng và nhanh chóng hơn, không bị phụ thuộc vào phía platform
- Phục vụ business. Đồng ý là EGANY Apps có thể tự làm được nhiều thứ với API token trả về trực tiếp. Tuy nhiên, các tác vụ liên quan đến business (nghiệp vụ) buộc phải xử lý ở backend. Các nghiệp vụ bao gồm quản lý hạn mức, quản lý hạn sử dụng cũng như giới hạn các tính năng của ứng dụng tùy vào gói cước (pricing plan) mà khách đang sử dụng
Xong vấn đề về flow thì team gặp tiếp vấn đề về lưu trữ token. Mỗi ứng dụng sẽ có một token riêng và khách có thể đăng nhập một hoặc nhiều ứng dụng cùng một lúc. Hơn nữa, do EGANY Apps hỗ trợ nhiều platform nên khách có thể đăng nhập một ứng dụng trên nhiều platform khác nhau. Phức tạp phải không nào? Sau khi thảo luận và bàn bạc thì team chốt đơn như sau:
- Cho phép đăng nhập nhiều ứng dụng
- Một ứng dụng chỉ được đăng nhập trên một platform. Nếu khách hàng đăng nhập một ứng dụng trên nhiều platform khác nhau thì chỉ platform cuối cùng mới được tính
Team lựa chọn lưu trữ token dưới dạng Array
. Đây không phải là một sự lựa chọn hợp lý vì token thường được truy cập dưới dạng key
nên hiệu năng sẽ không tốt. Ví dụ:
// Array
const token = tokenArr.find(isCurrentAuthSession)
// => Về lý thuyết là O(n)
// Map
const tokenKey = getCurrentAuthSessionKey()
const token = tokenMap.get(tokenKey)
// => Về lý thuyết là O(1)
Tất nhiên, về lý thuyết là như vậy nhưng trên thực tế thì sẽ không nhận ra được ảnh hưởng rõ rệt do số lượng phần tử (ở đây là phiên đăng nhập của người dùng) khá là thấp. Dù vậy thì ở những phiên bản sau này, team vẫn quyết định thay thế Array
bằng Map
để tiện cho việc truy xuất.
2. Global state management
Sau khi xác thực người dùng thành công, hầu hết các ứng dụng đều lưu trữ lại thông tin xác thực để sử dụng trong suốt thời gian ứng dụng hoạt động. EGANY Apps cũng không phải là ngoại lệ.
Để giải quyết bài toán này thì team có hai lựa chọn lúc bấy giờ:
Sau khi tham khảo một số nguồn trên mạng và trải nghiệm thử thì team quyết định chọn MobX. Lý do:
- Nhu cầu của team không quá phức tạp. Cả 2 đều đáp ứng đủ
- Syntax đơn giản và đỡ rườm rà hơn Redux (tính theo thời điểm triển khai, sau khi React hooks ra đời thì Redux đã gọn gàng hơn rất nhiều)
- Dễ hiểu, dễ sử dụng
Dù vậy MobX vẫn còn một số hạn chế sau:
- Buộc phải sử dụng Class component
- Muốn debug ở console buộc team phải sử dụng hàm
toJS()
để in dữ liệu do dữ liệu được lưu ở dạngObservable
- Cần cài đặt và thiết lập dự án một chút thì mới dùng được các syntax của MobX
Ngoài những khó khăn trên thì team khá hài lòng với giải pháp ở thời điểm hiện tại.
3. Dynamic route (url) với Next.js
Sau khi có khung sườn xác thực người dùng cũng như móc nối thông tin vào global state thì team bắt tay vào phát triển giao diện dashboard.
Nếu các bạn chưa biết thì Next.js sử dụng file-based routing, nghĩa là đường dẫn file sẽ tương ứng với đường dẫn trang. Ví dụ:
- Đường dẫn file:
pages/products/index.jsx
- Đường dẫn trang
/products
hoặc
- Đường dẫn file:
pages/products/productId.jsx
- Đường dẫn trang
/products/productId
Có lẽ các bạn cũng nhận ra vấn đề ở đây rồi phải không? Trong thực tế sẽ xuất hiện vô số productId
và nếu phải tạo từng file cho mỗi trang chi tiết sản phẩm theo id
thì.. gần như không thể, đúng không nào?
Đó là vấn đề mà dynamic route (hay còn gọi là dynamic url) giải quyết. Nó sẽ tự động truyền productId
vào một trang (template) phù hợp và trả về HTML tương ứng.
Tiếc thay, ở thời điểm này, Next.js v7 chưa hỗ trợ sẵn mà buộc lòng team phải custom để xử lý. Cụ thể là sử dụng express
để quản lý Server-side Rendering cho Next.js. Một đoạn code mẫu cho dynamic route sẽ như sau:
server.get('/products/:id', (req, res) => {
const pageTemplate = '/products/id'
const productId = req.params.id
app.render(req, res, pageTemplate, { id: productId })
})
Tuy nhiên, khi chạy với custom server như trên thì sẽ mất đi tính năng hot-reload
(một dạng auto reload nhưng giữ lại state
hiện tại của ứng dụng), khiến cho trải nghiệm khi code giảm đi đáng kể, đặc biệt là khi làm việc với form hoặc modal.
4. Custom component
Như đã đề cập ở phần 1 thì team đã quyết định dựng bộ component riêng dựa trên nền tảng CSS đã có từ các sản phẩm trước đó.
Do chưa có kinh nghiệm nhiều nên việc phân tách component cũng gặp tương đối khó khăn. Đáng lưu ý nhất có lẽ là việc tích hợp các thư viện bên ngoài, đặc biệt là thư viện rich-text editor.
Các thư viện rich-text editor đa số đều sử dụng object window
trong source code của mình. Ngặt nỗi khi render lần đầu tiên thì trang lại được chạy ở server, làm gì có object window
mà dùng. Ấy vậy là team lại phải làm đủ trò để page có thể được render thành công trên server.
Chưa hết, ngoài rich-text editor thì team còn phải optimize color-picker. Khi người dùng chọn màu, event onChange
sẽ được gọi rất nhiều lần. Nếu quản lý không khéo sẽ tạo ra hiện tượng giật/khựng UI rất khó chịu. Team chủ động áp dụng các thủ thuật debounce (một dạng delay function) để cải thiện hiệu năng cho component này
5. Docker
Khó khăn cuối cùng có lẽ là tạo dockerfile để triển khai ứng dụng.
Do cũng chưa có nhiều kinh nghiệm về mảng này nên lỗi cũng tương đối nhiều. Nổi bật nhất có lẽ là lệnh npm install --production
Nếu bạn chưa biết, --production
flag (cờ) sẽ loại bỏ toàn bộ package nằm trong devDependencies
và chỉ cài đặt các package có trong dependencies
. Về lý thuyết thì nó sẽ giảm thời gian deploy vì số lượng package sẽ giảm đi đáng kể. Trong thực tế, một số package ở devDependencies
lại cần thiết để build được project, đơn cử như node-sass
để compile file .scss
Nếu nhớ không nhầm thì team mất khoảng một buổi chiều chỉ để tạo và triển khai thành công ứng dụng bằng docker.
Quy trình
Nguồn ảnh: https://dribbble.com/shots/9707966-Web-Site-Developing-Process
1. Bỏ bước review code
Tuy trong quy trình đã chốt có bước tạo pull request cũng như review code nhưng khi bắt đầu phát triển thì khối lượng công việc phát sinh khá nhiều, không đủ thời gian để thực hiện review. Team quyết định bỏ bước review code để tiết kiệm thời gian.
Đây là quyết định không tốt và dẫn tới nhiều hậu quả xấu như:
- Chất lượng code không cao. Thường xuyên xuất hiện các đoạn code không được format hoặc xử lý theo hướng không hợp lý, gây giảm hiệu năng hoặc thậm chí gây lỗi cho ứng dụng
- Không nhất quán khi cùng xử lý một vấn đề
- Các thành viên không có cơ hội học hỏi
Hiện tại team đã có một số giải pháp tạm thời để khắc phục tình trạng trên. Chắc để bài sau mình chia sẻ nhé!
2. Single point of failure
Thường ở những bước quan trọng như merge code hoặc deploy thì chỉ có một người đảm nhận. Điều này là hợp lý vì họ là những người nắm rõ dự án nhất và khi làm việc với các team khác thì một người đại diện sẽ dễ trao đổi hơn rất nhiều.
Thế nhưng, nếu không có người backup thì sẽ là một tai họa. Đã xảy ra trường hợp EGANY Apps gặp lỗi lớn trên production
và không may hôm đấy bạn phụ trách deploy lại có việc cá nhân không thể lên công ty. Ấy vậy là anh em phải xin lỗi và hẹn khách lại vào ngày mai.
Đây là một thiếu sót lớn trong team và hiện đang dần được khắc phục bằng các buổi workshop training cũng như tài liệu đi kèm dự án.
3. Giao tiếp trong team
Giao tiếp trong giai đoạn này có lẽ là nguyên nhân lớn nhất dẫn tới các khó khăn bên trên.
Tuy các thành viên chủ động liên hệ lẫn nhau khi cần merge code hoặc deploy nhưng ngoài những việc đó thì lại ít trao đổi những việc khác với nhau. Điều đó dẫn tới các vấn đề như không đồng nhất trong xử lý hoặc đôi khi là việc lặp code không đáng có.
Rất may là giao tiếp giữa các team với nhau khá ổn, làm việc rất chủ động và ăn ý nên dự án vẫn phát triển đều, không bị khựng lại nhiều.
Tạm kết
Có công mài sắt, có ngày nên kim. Dù còn nhiều vấn đề trong quy trình cũng như các vấn đề liên quan tới kỹ thuật nhưng team vẫn hoàn thành được mục tiêu ra mắt phiên bản đầu tiên vào tháng 4 năm 2019 với thông số:
- 5 ứng dụng
- 1 platform
Chưa hài lòng với kết quả trên, team tiếp tục quá trình phát triển EGANY Apps với các mục tiêu sau:
- Hỗ trợ thêm ít nhất một platform
- Phát triển các ứng dụng dạng subscription thay vì one-time payment
- Cải thiện quy trình cũng như source code để có nền tảng vững chắc hơn
Chặng đường còn dài, chắc hẹn các bạn ở bài viết tiếp theo nha!
Cám ơn các bạn đã đọc. Happy hacking!