Khi bắt đầu xây dựng Finvoras, mục tiêu của tôi rất đơn giản: tạo ra một ứng dụng tài chính cá nhân và đồng thời nâng cao kỹ năng Flutter engineering.
Như nhiều side project khác, mọi thứ ban đầu đều nằm trong một ứng dụng Flutter duy nhất.
Lúc đầu, cách làm này hoạt động tốt một cách đáng ngạc nhiên.
Tuy nhiên, khi dự án phát triển, tôi bắt đầu tách ra các component tái sử dụng, công cụ nội bộ và các tiện ích dùng chung. Dần dần, tôi nhận ra rằng dự án không còn chỉ là một ứng dụng nữa — nó đang trở thành một hệ sinh thái.
Đó là lúc tôi quyết định chuyển toàn bộ dự án sang Monorepo.
Khởi Đầu: Một Ứng Dụng Flutter Đơn Lẻ
Phiên bản đầu tiên của Finvoras tuân theo cấu trúc dự án Flutter thông thường.
finvoras/
├── lib/
├── assets/
├── test/
└── pubspec.yamlTất cả đều nằm trong một codebase duy nhất:
- UI Components
- Business Logic
- Themes
- Assets
- Utilities
- Localization
Với một dự án nhỏ, cách tiếp cận này hoàn toàn ổn.
Vấn đề xuất hiện khi tôi bắt đầu xây dựng các module có thể tái sử dụng.
Vấn Đề Gặp Phải
Theo thời gian, tôi tách ra một số component có khả năng được tái sử dụng trong các ứng dụng tương lai:
- Shared widgets
- Design system
- Localization utilities
- App configuration layer
- Internal developer tools
- Custom lint rules
Kế hoạch ban đầu của tôi là duy trì chúng trong các repository riêng biệt.
Sau khi thử nghiệm cách tiếp cận này, tôi gặp phải một số vấn đề.
Quản Lý Dependency Trở Nên Phức Tạp
Mỗi khi chỉnh sửa một shared package, tôi phải:
- Publish version mới
- Cập nhật phiên bản dependency
- Pull cập nhật vào ứng dụng
- Kiểm tra tính tương thích
Dù chỉ là thay đổi nhỏ, quy trình này cũng trở nên nặng nề không cần thiết.
Chia Sẻ Code Bị Chậm Lại
Trong quá trình phát triển nhanh, tôi thường cần thay đổi đồng thời nhiều package cùng một lúc.
Làm việc trên nhiều repository riêng biệt tạo ra nhiều trở ngại.
Một refactor đơn giản có thể yêu cầu thay đổi ở nhiều nơi, nhiều commit, và nhiều lần release.
Developer Experience Bắt Đầu Giảm Sút
Khi số lượng package tăng lên, việc bảo trì dự án ngày càng khó khăn hơn.
Tôi thấy mình dành nhiều thời gian quản lý repository hơn là xây dựng tính năng.
Lúc đó, rõ ràng là kiến trúc cần phải thay đổi.
Tại Sao Chọn Monorepo?
Monorepo cho phép nhiều ứng dụng và package cùng tồn tại trong một repository duy nhất.
Thay vì quản lý nhiều repository độc lập, tất cả trở thành một phần của cùng một workspace.
finvoras/
├── apps/
│ └── finvoras_app/
│
├── packages/
│ ├── app_orchestrator/
│ ├── finvoras_gen/
│ ├── design_system/
│ ├── widgets/
│ ├── localization/
│ └── custom_lint/
│
└── melos.yamlCấu trúc này mang lại nhiều lợi ích ngay lập tức.
Refactor Dễ Dàng Hơn
Vì tất cả package đều nằm trong cùng một repository, các thay đổi lớn trở nên dễ dàng hơn đáng kể.
Một commit duy nhất có thể cập nhật nhiều package cùng lúc.
Điều này đặc biệt có giá trị khi phát triển kiến trúc hoặc design system.
Tái Sử Dụng Code Tốt Hơn
Các chức năng dùng chung trở thành công dân hạng nhất trong codebase.
Thay vì sao chép code giữa các project, các ứng dụng có thể trực tiếp sử dụng local package.
Tiêu Chuẩn Kỹ Thuật Đồng Nhất
Monorepo thúc đẩy tôi chuẩn hóa:
- Cấu trúc thư mục
- Quản lý dependency
- Linting rules
- CI/CD workflows
- Quy ước dự án
Kết quả là một trải nghiệm phát triển nhất quán và dễ đoán hơn nhiều.
Tại Sao Chọn Melos
Sau khi nghiên cứu các công cụ hiện có, tôi chọn Melos.
Melos được thiết kế đặc biệt cho Dart và Flutter workspace.
Nó cung cấp nhiều khả năng giúp đơn giản hóa việc quản lý Monorepo:
- Package bootstrapping
- Dependency linking
- Script execution
- Workspace filtering
- Version management
Ví dụ:
melos bootstrapTự động liên kết các local package với nhau.
melos run analyzeChạy static analysis trên toàn bộ workspace.
melos run testThực thi test cho tất cả các package.
Những tính năng này giúp giảm đáng kể chi phí bảo trì.
Điều Gì Thay Đổi Sau Khi Migration
Việc migration không chỉ là một cải tiến kỹ thuật.
Nó thay đổi cách tôi suy nghĩ về kiến trúc phần mềm.
Thay vì xây dựng một ứng dụng đơn lẻ, tôi bắt đầu xây dựng các hệ thống có thể tái sử dụng.
Ngày nay, Finvoras bao gồm:
- Shared packages
- Internal tooling
- Design system foundations
- Code generation workflows
- CI/CD automation
Mỗi component có thể phát triển độc lập trong khi vẫn là một phần của hệ sinh thái thống nhất.
Bài Học Rút Ra
Nếu dự án của bạn chỉ có một ứng dụng duy nhất và không có kế hoạch mở rộng, Monorepo có thể là không cần thiết.
Tuy nhiên, khi bạn bắt đầu xây dựng:
- Nhiều ứng dụng
- Shared packages
- Internal tooling
- Design system tái sử dụng
Monorepo có thể mang lại lợi ích lâu dài đáng kể.
