Dockerize Development với Javascript MERN Stack

Thiết kế scalable React App từ đầu

Tui viết

Ngày nay khi mà MVC dần trở thành một thứ nhà quê “lạc hậu”, APIs được thiết kế chung cho nhiều client app khác nhau (web app, mobile app or desktop app) và khá nhiều business logic được chuyển đến client app. Front-end development giờ không chỉ đơn thuần là tạo ra giao diện nữa mà là cả một nghệ thuật và bí kíp lập trình emoji-sunglasses.

Note nhỏ: Bài này được viết dựa trên giả định rằng bạn đã có một ít kinh nghiệm về coding với React và đang tìm kiếm hoặc xây dựng một kiến trúc mới để tạo ra một web app lớn, scalable.

Một ngày đẹp trời, công ty của bạn quyết định đập bỏ hết, build lại toàn bộ hệ thống để tạo ra một thứ gì đó lớn hơn để theo kịp với business grow của công ty emoji-smirk, maintainable & scalable hơn đống sh!t code cũ và cuối cùng quyết định chọn React. Bạn chắc hẳn đã vọc React rất nhiều, chứ nếu không thì đã méo chọn React emoji-smirk, tuy vậy vẫn còn khá là nhiều thứ cần quyết định trong front-end stack. React về cơ bản chỉ là một view library và có quá nhiều thứ không đi kèm (nếu không muốn chọn nhiều thì xài framework như Ạngular hoặc Vue cho dễ đi emoji-smirk tuy nhiên độ customableoptimizable thì tất nhiên không bằng rồi). State management, routing, localization, styling và UI, deployment và build script… Có cần PWA (Progressive Web App) hay SSR (Server-side Rendering) không?

Thật không may, có rất nhiều sự tự do cho bạn khi quyết định các thành phần trong React stack. Nếu bạn biết rõ mình đang làm gì (requirements), thậm chỉ có thể setup hết tất cả trong vòng một ngày hoặc ít hơn. Hoặc không thì có thể mất tới một tuần hoặc vài tháng tuần cho tới khi mọi thứ chạy suôn sẻ. Ở bài viết này tui chỉ đề cấp tới việc architecting trong một bối cảnh tương đối lí tưởng, bởi vì nếu không lí tưởng thì có quá nhiều thứ cần phải viết emoji-joy.

Trước khi bắt đầu, hãy phân tích kĩ và code đơn giản thôi

Phân tích requirements và nhu cầu nên là điều đầu tiên bạn cần làm bất cứ khi nào bạn muốn bắt đầu làm một cái mới. Dù là project nhỏ hay lớn, hãy viết rõ ra các yêu cầu và suy nghĩ trong bối cảnh bạn đang không có nó emoji-joy. Dĩ nhiên, requirements có thể thay đổi thường xuyên, có khi còn thay đổi luôn hướng đi, mục đích của project emoji-joy, tuy nhiên đó là điều trong tương lai và méo ai có thể dự đoán được. Mà tốt nhất là dự đoán ít thôi cho đỡ mệt emoji-joy.

Để release ổn thoả phiên bản đầu tiên của sản phẩm (MVP: Minimum Viable Product hay Sản phẩm khả dụng tối thiểu), tốt nhất nên code đơn giản thôi, chỉ xài tools và libraries cần thiết, đừng phức tạp hoá quá hoặc làm màu emoji-joy. Developer nào cũng thích thử & xài cái mới, nhưng không phải vào thời điểm này (bắt đầu project) emoji-joy.

Về team và con người

Tui đã setup khá nhiều project rồi, và có một sự thật được rút ra là: Không có gì nát hơn là bắt đầu project khi để teamate của bạn phải research quá nhiều công nghệ mới emoji-joy. Hãy tưởng tượng có thêm người join vào project ở thời điểm giữa và người đó phải dành 2-3 tháng research để có thể nhảy vào làm. emoji-joy Ví dụ điển hình ở đây là áp dụng GraphQL cho project chả hạn emoji-joy (mặc dù ai cũng biết là nó hầu như tốt hơn REST trong các dự án lớn nhưng mà có mấy ai đã học nó đâu, với học cũng đâu có nhanh emoji-disappointed, nếu xác định apply mà cả team phải cắm đầu vô research thì… emoji-smirk).

Ngoài ra, JavaScript bản thân nó là một ngôn ngữ cực kì sida rồi emoji-joy, common patternsbest practice rất quan trọng khi code chung trong một project lớn, nếu áp dụng tốt thì code JS của project sẽ dễ đọc dễ hiểu mà dễ maintain hơn, còn nếu không thì chỉ có nước đập đi mà xây lại cái khác emoji-disappointed_relieved. Lấy ví dụ hãy thử nghĩ về đoạn code JS bạn viết 6 tháng hoặc 1 năm trước mà xem emoji-joy. Vì thế hãy chắc chắn rằng teamate của bạn đã nắm rõ các best practice và các convention (thống nhất) chung khi làm việc trong project, apply eslint một cách nghiệm ngặt & check linter trước rồi mới cho commit xài husky, hoặc hoặc sử dụng typescript chẳng hạn, sẽ có ích rất nhiều trong một team lớn đó.

Chọn library xài cho app, cái nào ngon nhất? emoji-flushed

Câu trả lời là: library mà bạn và team của bạn biết xài & hiểu nó nhất, nghiêm túc á emoji-joy. Nếu bạn nắm rõ Redux, thì hãy xài Redux cho State Management. Hoặc nếu bạn và team làm việc với MobX nhiều hơn, xài MobX thôi. Một Stack hay Library chỉ thực sự tốt khi các developers hiểu rõ và biết cách tận dụng nó. Tui không nói là bạn không nên xài mấy cái lib mới. Tui rất thích học xài mấy cái mới, nhưng mà chỉ xài áp dụng trong các dự án cá nhân, các dự án ít quan trọng mà ở đó những sai lầm nhỏ được chấp nhận emoji-smirk. Học cách xài library mới không đơn giản chỉ là cắm mặt vào document của nó, đôi khi những lib khác nhau có những cách tiếp cận khác nhau và những pattern khác nhau đòi hỏi thời gian để master emoji-confused.

Ờ thực ra thì, thêm vào một vài lib mới cũng không sao emoji-smirk, nhưng hãy chắc chắn rằng nó sẽ không ảnh hưởng nhiều tới tiến độ và chất lượng của project cũng như làm khổ anh em trong team. Mặc dù thích hay không thích, bạn cũng sẽ phải nghĩ tới suy nghĩ của các developer khác trong team. “A good developer knows how to code. A great developer knows how to work in a team.” emoji-sunglasses Sẽ tốt hơn nếu team bạn có nhiều điểm chung về technical, nó sẽ khiến tốc độ develop nhanh hơn và thuận lợi hơn (mặc khác sẽ là thảm hoạ nếu để 1 team chuyên code Jquery code chung với team code React mà không có convention kĩ emoji-joy hẳn là nát thôi rồi). Những công nghệ phổ biến (phổ biến chứ không phải quá cũ kĩ hay bị outdated!) có thể tăng tốc độ develop lên nhiều khi team scale về mặt con người.

Lựa chọn của tui khi chọn Libraries

Ở các dự án có business lớn, quan trọng và cần độ ổn định cao, tui không thích xài những cái đang trong giai đoạn “beta” hoặc “experiment” (ngồi chờ issue được approve pull request emoji-unamused). Thay vào đó quality, reliabilityscalability mới là những cái ưu tiên hàng đầu mà bạn cần tập trung vô emoji-smirk. Dưới đây là một vài cái mà tui đã chọn.

Dev stack và Toolkits

Setup project React ngay từ đầu với WebpackBabel khá hay và thú vị, ngầu nữa nhưng để dễ dàng hơn, tui sẽ dùng create-react-app script trước để design các phần chính của project, sau đó eject và mount lại với webpack và babel có vẻ sẽ ổn hơn. Create-react-app phiên bản v2 gần đây thực sự ổn (hơn nhiều so với v1), hỗ trợ hầu hết các tính năng mà app vừa và nhỏ cần, tuy nhiên nó có vẻ vẫn chưa integrate với server-side rendering và để chunking code tốt hơn, tự cấu hình webpack & babel vậy emoji-joy (ngoài ra webpack tui có thể chủ động config alias hoặc làm việc với env file rất sướng). Về phần SSR, có thể viết bằng ngôn ngữ nào cũng được (Java, Go hay .NET cũng được nhé emoji-joy), nhưng ở đây tui chọn Node.js và Express cho dễ, ngoài ra có phần get API trên SSR để SEO, tui xài react-frontloadfetch. Về phần React Dev thì xài webpack-dev-server với Hot Reload Module, Redux Dev Tool, Redux-Logger có vẻ ổn.

Ngoài ra, requirement từ đầu là xây lại một app scalable, có SSR, PWAbuild script riêng, tích hợp vào Docker nữa, thôi eject ra cho chắc vậy emoji-smirk. Tuy vậy nhưng thú thật là phần config webpack, babel đến giờ vẫn khá là painful emoji-joy.

Chốt: Webpack, React-Loadable, Babel, ReactDOM Server và Node.js Express cho Server-side Rendering

UI Library

Đây là phần khó chọn nhất khi có hàng tấn UI Library ngoài kia emoji-joy, như là Material-UI hoặc Ant Design. Có một lưu ý nhỏ là các bạn nên tìm chọn những UI Library được build chuẩn cho React, ngày xưa tui có vô tình được code trong một project React xài MaterializeCSS, lib này xài chung với JQuery để thao tác với dom thật nên rất dễ gây xung đột với DOM ảo của React, theo tui là không nên xài emoji-joy.

Bootstrap 4 cũng đã có mặt trên UI Library: Reactstrap, tui rất thích Bootstrap nhưng do đã xài reactstrap này trước đó và thấy nó khá là sida, build chưa tốt lắm, nên chắc cần thêm thời gian để đợi thằng bootstrap lên bản 4 vậy. Cuối cùng quyết định chọn AntDesign vì thấy xài thử nó khá ngon (tuy là của Trung Quốc), ngôn ngữ thiết kế khá oke, có rất nhiều component sẵn và hỗ trợ API đến tận răng, ngoài ra document của nó cũng khá chi tiết và đàng hoàng, duyệt thôi emoji-heart_eyes. Note: Sau khi xài và optimize thì thấy bộ icon bằng svg của nó hơi nặng khi import vô hết, làm cho kích cỡ file bundle.js tăng lên đáng kể emoji-joy, nhưng không sao, ta có thể custom bundle với webpack mà emoji-smirk, mà thôi chuyện đó để sau.

Chốt: Ant Design

Ant Design

State management

Đơn giản rồi, chọn Redux thôi, vì trước giờ chỉ làm nhiều với thằng này emoji-joy. Kiến trúc và cách tiếp cận của Redux cũng khá đơn giản, ngoài ra Redux còn được xây dựng để code tốt với functional programming, rất rõ ràng và dễ hiểu. Ngoài Redux ra thì còn một vài lựa chọn khác ít phổ biến như Flux (do Facebook phát triển, Redux lấy kiến trúc từ Flux, nhưng dễ xài hơn), hoặc MobX (ít phổ biến hơn Redux).

Một khi bạn đã làm chủ được kiến trúc của redux, có thể thoải mái lựa chọn các lib đi kèm theo. Ở đây tui chọn redux-thunk, reselect, redux-form (để quản lí state của form dễ dàng, thực ra Ant Design có sẵn rồi nhưng phòng trường hợp những form custom riêng thì xài); redux-saga cũng khá tốt, tuy nhiên nó hơi phức tạp quá và document API cũng nhiều, chưa nhiều dev có kinh nghiệm làm với nó nên thôi.

Chốt: Redux, Reselect, Redux Thunk (middleware), Redux Form, Redux Logging (middeware), Redux DevTool

Gợi ý đọc: Redux from a functional programming perspective

Localization

Để setup đa ngôn ngữ cho app tui xài redux-i18n luôn, tích hợp sẵn vào redux và cũng khá dễ để xài + có vài project trước xài nó rồi. Ngoài ra có một lib khác phổ biến hơn là react-i18next cũng khá dễ xài.

Chốt: Redux-i18n

Routing

Về phần routing, react-router là một sự lựa chọn phổ biến và dùng tốt trong hầu hết các project. Tuy concept của sẽ hơi khó hiểu một tí so với các bộ routing khác của Angular hay Vue. Chú ý nếu muốn tối ưu hoá, chunking code theo từng Route (xài với React-Loadble) thì nên viết kĩ phần Routing.

Chốt: React-Router

Linter, Convention, Structure

Define convention và Linter là một bước khá quan trọng trong xây dựng code base, để đáp ứng develop ổn định và tính maintainable. Về Linter, tui xài eslint với một số bộ config sẵn lấy từ airbnb, custom thêm một số phần. Về phần lint check, tui xài huskylintstaged cũng như prettier để kiểm tra lint code, và chặn commit, push code khi chưa Lint-valid. Tất cả sẽ work ngon lành nếu bạn xài VSCode và một số extension cài sẵn.

Về Project Organization, có khá nhiều cách triển khai phù hợp với Business của app và sở thích của từng team, tui quyết định chọn implement theo Feature Based vì khá dễ scale cũng như dễ code ở một project lớn nhiều component. Bạn có thể xem thêm về cách triển khai này ở đây: https://medium.com/@alexmngn/how-to-better-organize-your-react-applications-2fd3ea1920f1.

Phần API/service caller, tui chọn implement ở một repo code base riêng, tạm gọi API SDK, viết bằng TypeScript, để scale thì nên tách Caller ra riêng để có thể apply ở nhiều repos sau này mà không bị lặp code cũng như đảm bảo tính đóng gói. Chốt: viết ra repo lib riêng và xài axios cho phần caller.

Ngoài ra với Unit Test, tui chọn JestEnzyme vì nó khá phổ biến để testing React app.

Về phần containerizedockerize thì cần implement 1 Dockerfile là chạy ổn, có thêm SSR nên sẽ hơi phức tạp tí, build bằng yarn (nhờ chạy parallelcache) hoặc npm ci (lưu cache sẵn vào git repo -> phù hợp với use case build docker image của tui) có vẻ sẽ nhanh hơn npm; đọc thêm về npm ci vs npm install ở đây, cũng tích hợp khá ổn với Jenkins CI/CD (có thể xem file ở dưới phần P/S nhá). Khi cần chơi lớn, có thể dễ dàng triển khai theo dạng container lên Kubernetes Cluster (xài Google Kubernetes Engine chả hạn), bạn sẽ có 1 con load balancer (Nginx Ingress) và muốn tạo bao nhiêu instance cũng được emoji-smirk.

Đọc thêm: Deploying a containerized web application by Google Kubernetes Engine

Vậy đã scalable chưa? emoji-joy

Scale theo hướng nào còn tuỳ thuộc vào business của mỗi project, và phải do bạn tự quyết định emoji-smirk. Cơ bản là nên dễ mở rộng (có thể triển khai theo container app), dễ develop (linter và convention) và dễ extend component (structure) và code có tính reuseable cao. Theo ý kiến cá nhân của tui, để scale nên structureorganization source code theo hướng suy nghĩ mở rộng ra (featured based chả hạn), thiết kế và đặt các helperutils ở nơi phù hợp (để import và reuse nhiều chỗ), không duplicate code, wrap các function được gọi nhiều và có các components để tái sử dụng, áp dụng Higher-Order Function để bootstrap abstraction, giải quyết vấn đề xung đột CSS (CSS War) giữa các components bằng styled-components chả hạn. Code-base dễ đọc dễ hiểu, clean, tính customable cao -> dễ đáp ứng requirement changes, có bộ conventiondocumenting rõ ràng, đảm bảo lint valid ở mọi nơi… vân vân và mây mây.

Feature Based:

|-- project-name
|    |-- src
|    |    +-- common
|    |    |-- features
|    |    |    |-- home
|    |    |    |    +-- redux
|    |    |    |    |-- index.js
|    |    |    |    |-- DefaultPage.js
|    |    |    |    |-- DefaultPage.less
|    |    |    |    |-- route.js
|    |    |    |    |-- styles.less
|    |    |    |    |-- ...
|    |    |    +-- feature-1
|    |    |    +-- feature-2
|    |    +-- styles
|    --- tools
|    |    +-- plugins
|    --- scripts
|    |    |-- build.js
|    |    |-- start.js
|    |    |-- ...
|-- .eslintrc
|-- .gitignore
|-- ...

Đọc thêm về 4 cách để thiết kế CSS Style cho React Component.

Đọc bài về scale ở blog của huydx. Rút ra được kết luận: Để làm một hệ thống “scale”, bạn chỉ cần làm 4 bước: đoán mò -> kiểm thử -> cải thiện -> đoán mò tiếp, và tui vẫn chỉ đang lẩn quần trong vòng lặp này emoji-joy. 2 công cụ benchmark (kiểm thử) hay dùng là Google PagespeedLighthouse.

Tổng kết

Sẽ ổn thôi nếu bạn vẫn xài những lib phổ biến (không phải latest) hoặc làm theo những best pracicepatterns có sẵn. Chúng ta không cần phải chọn những library thật sự mới mẻ, bởi vì: ”business always comes first”, và vì như vậy nên ta cần những thứ reliable và tốt cho team hơn, thứ sẽ giúp mọi thứ đi đúng hướng và nhanh hơn.

Một vài yếu tố quan trọng nữa là dùng những lib có cộng đồng lớn và hỗ trợ lâu dài sẽ tốt cho quá trình develop hơn, bạn sẽ nhận được support của các developers khác trên khắp thế giới thông qua stackoverflowgithub pages, issues cũng có thể được fix và merge rất nhanh từ các contributors.

P/S: Tui có apply code base đó cho một side-project của tui, click vào https://github.com/gosu-team/fptu-fe nếu các bạn muốn xem codebase.

React Docker

Bonus: Rekit - IDE and toolkit for building scalable web applications with React, Redux and React-router

“Rekit là một bộ toolkit được thiết kế All-in-One để build scalable React App từ đầu. Bạn sẽ không phải tốn thời gian chọn lựa đống lib (như những gì mình làm), pattern hay các đoạn config. Rekit dựa trên create-react-app và thiết kế theo kiểu opinionated (giống framework), architect theo kiểu feature oriented, pattern một action cho mỗi file.

Rekit is a toolkit for building scalable web applications with React, Redux and React-router. It’s an all-in-one solution for creating modern React apps.

It helps you focus on business logic rather than dealing with massive libraries, patterns, configurations etc.

  • Rekit Studio: the real IDE for React, Redux development.
  • Command line tools: besides Rekit Studio, you can use command line tools to create/rename/move/delete project elements like components, actions etc. It has better support for Rekit plugin system.

http://demo.rekit.org/

Rekit