Các thách thức kỹ thuật trong thế giới thực: Chọn lựa công nghệ


“Thách thức kỹ thuật trong thế giới thực’ là loạt bài trong đó tôi diễn giải các case study thú vị trong lĩnh vực kỹ thuật phần mềm (software engineering) hoặc quản lý kỹ thuật (engineering management) từ các công ty công nghệ. Rất mong bạn sẽ học được nhiều điều mới mẻ trong các bài viết này khi chúng ta đi sâu vào các khái niệm mà chúng chứa đựng.





Trong bài này, tôi đã thực hiện một cách tiếp cận hơi khác so với các bài trước, bằng việc đặt câu hỏi cho tác giả các bài blog về kỹ thuật, để chia sẻ các chi tiết chưa được công bố trước đây cũng như bài học từ họ.





4 Tình huống lựa chọn công nghệ





Hôm nay, chúng ta sẽ tìm hiểu:





  • Trello chọn Kafka thay vì RabbitMQ để nhắn tin. Trello đã sử dụng RabbitMQ để chạy websocket trong nhiều năm. Tuy nhiên, sau khi nhận thấy các vấn đề về độ tin cậy và mức sử dụng tài nguyên cao, họ đã quyết định giải quyết vấn đề này – và đánh giá 5 giải pháp thay thế.
  • Tại sao Birdie chuyển sang Micro Frontends. Birdie là một ứng dụng web phức tạp dành cho các nhà cung cấp dịch vụ chăm sóc sức khỏe. Họ đã thất vọng vì các bài kiểm thử chạy chậm mỗi khi có sự thay đổi, vì vậy họ đã nghiên cứu cách mô đun hóa cơ sở mã và giảm sự liên kết chặt chẽ giữa các phần của nó.
  • Tại sao MetalBear chọn Rust. Là một công ty mới thành lập được 6 tháng, MetalBear đã chọn khá nhiều ngôn ngữ lập trình cho các stack của mình và đã chọn Rust. Ngoài hiệu suất, các cân nhắc về mặt tuyển dụng cũng là một phần của quyết định.
  • Tại sao Motive chuyển sang Kotlin Multiplatform Mobile (KMM). Nhóm đã xây dựng ứng dụng Motive Fleet cho các doanh nghiệp vận tải, trên iOS và Android. Tuy nhiên, iOS luôn chậm hơn 1-2 tháng và logic nghiệp vụ hơi khác so với Android. Họ đã khám phá các phương án thay thế để chia sẻ logic nghiệp vụ giữa iOS và Android.




1. Trello chọn Kafka thay vì RabbitMQ





Để chạy chức năng websocket, Trello đã sử dụng phương thức Redis Pub/Sub cho đến năm 2015, sau đó là RabbitMQ từ năm 2015-2018. Vào năm 2018, đã xảy ra sự cố phân vùng mạng với RabbitMQ buộc họ phải đánh giá lại lựa chọn công nghệ.





Tổng quan ngắn về RabbitMQ. Để hiểu vấn đề mà nhóm Trello gặp phải với RabbitMQ, trước tiên chúng ta cần hiểu một số điều cơ bản về RabbitMQ:





  • Exchange (sàn): đây là entry point mà tin nhắn được xuất bản.
  • Queue (hàng đợi): Exchange được liên kết với một hoặc nhiều message queue (hàng đợi tin nhắn). Một hàng đợi tin nhắn theo nghĩa đen.
  • Binding (liên kết): kết nối giữa Exchange và Queue. Các binding có thể có các khóa binding (binding key).
  • Routing policy (chính sách định tuyến): chiến lược về cách exchange xử lý định tuyến.




Cách các exchange, binding, queue và routing policy được kết nối trong RabbitMQ
Cách các exchange, binding, queue và routing policy được kết nối trong RabbitMQ




Có bốn chính sách định tuyến phổ biến mà RabbitMQ hỗ trợ:





  • Fanout: gửi tất cả các tin nhắn đến Exchange và tất cả các Queue liên kết với nó, mà không quan tâm các khóa định tuyến (routing keys). Cách tiếp cận này bỏ qua các khóa định tuyến.
  • Direct: tin nhắn được gửi đến hàng đợi mà khóa định tuyến khớp với khóa liên kết (binding keys).
  • Topic: cho phép kết hợp ký tự đại diện giữa khóa định tuyến và khóa liên kết. Ví dụ: bạn có thể thiết lập chủ đề về “tác vụ” và điều này sẽ định tuyến đến cả liên kết “task.important” và “task.unimportant”.
  • Header: sử dụng tiêu đề của yêu cầu để quyết định định tuyến.




Các Queue có thể tạm thời trong RabbitMQ, nghĩa là chúng có thể bị hủy ngay khi kết nối TCP tạo ra chúng đóng lại.





Việc triển khai websocket của Trello hỗ trợ đăng ký và hủy đăng ký và từ một kênh thông báo, có thể dành cho ban quản lý, thành viên, tổ chức, thẻ của Trello hoặc các mô hình dữ liệu Trello khác. Model_id được sử dụng làm mã định danh.





Kiến trúc ban đầu. Trello đã sử dụng RabbitMQ để phân đoạn các tin nhắn gửi đến vào một trong 16 queue. Ở hậu trường, họ đã chạy 3 phiên bản (instance) để xử lý các tin nhắn inbound phân phối đến một trong 16 queue. Sau đó, họ sử dụng plugin Shovel của RabbitMQ để ánh xạ 16 queue này thành 4 cụm outbound. Mỗi cụm tin nhắn gửi đi chạy trên 3 phiên bản và xử lý 4 hàng đợi outbound.





Kiến trúc websocket của Trello, được xây dựng trên RabbitMQ giai đoạn 2015-2018.
Kiến trúc websocket của Trello, được xây dựng trên RabbitMQ giai đoạn 2015-2018.




Vấn đề: gián đoạn cụm và hiệu suất. Team Trello nhận thấy các sự cố lớn xảy ra khi một cụm gặp sự cố do gián đoạn mạng hoặc khi một thành viên của cụm gặp sự cố. Khi điều này xảy ra, họ phải thiết lập lại toàn bộ; loại bỏ tất cả các socket và buộc các máy khách web kết nối lại. Tệ hơn nữa, các tin nhắn biến mất trong quá trình thiết lập lại này.





Các vấn đề với hạ tầng này là cách dữ liệu biến mất trong các trường hợp lỗi đơn lẻ hoặc lỗi mạng.
Các vấn đề với hạ tầng này là cách dữ liệu biến mất trong các trường hợp lỗi đơn lẻ hoặc lỗi mạng.




Một vấn đề khác là việc tạo queue và binding trong RabbitMQ chậm và tốn nhiều tài nguyên. Trong quá trình thiết lập lại hoàn toàn, phải mất khoảng thời gian đáng kể để tạo các queue và binding này.





Lựa chọn thay thế. Vì vậy, team Trello đã tìm kiếm các giải pháp thay thế cho RabbitMQ và đánh giá từng giải pháp dựa trên yêu cầu của họ, chẳng hạn như:





  • Khả năng chuyển đổi dự phòng
  • Gửi tin nhắn theo thứ tự trên mỗi phân đoạn
  • Hỗ trợ phân phối tin nhắn fanout
  • Độ trễ thấp
  • Hỗ trợ thông lượng yêu cầu 2.000 tin nhắn/giây




Nhóm đã khám phá 5 lựa chọn thay thế nhắn tin sau:





  • Kafka
  • Amazon SNS (Dịch vụ thông báo đơn giản) + SQS (Dịch vụ hàng đợi đơn giản)
  • Amazon SNS + FIFO (vào trước ra trước) SQS
  • Amazon Kinesis: dịch vụ truyền dữ liệu không cần máy chủ
  • Redis Streams




Sau khi đánh giá các tùy chọn, nhóm nhận thấy Kafka và Redis Streams phù hợp với yêu cầu của họ và chọn Kafka vì Redis Streams vẫn ở trạng thái không ổn định: chức năng Streams chỉ được cam kết trong một nhánh được đánh dấu là ‘không ổn định’. Nhóm Trello đã xây dựng lại kiến trúc websocket trên Kafka và hiện sử dụng kiến trúc chủ-khách. 





Thật thú vị khi thấy rằng điểm yếu của công nghệ có thể không bộc lộ trong nhiều tháng hoặc thậm chí nhiều năm. Nhóm Trello đã chuyển sang RabbitMQ sau nhiều năm sử dụng giải pháp Redis Pub/Sub. Vì vậy, tại sao họ chuyển ra khỏi nó? Tôi đã hỏi kỹ sư phần mềm Sebastian Mayr – người viết bài – và anh ấy chia sẻ rằng vấn đề lớn nhất mà họ gặp phải là Redis phân cụm không đảm bảo phân phối trong trường hợp xảy ra sự cố mạng hoặc chuyển đổi dự phòng.





Khi Trello phát triển, vấn đề các node bị lỗi trở nên rõ ràng hơn, cũng như chi phí phần cứng để vận hành hạ tầng Websocket. Sau ba năm, Trello quyết định khám phá xem liệu họ có thể giải quyết cả hai vấn đề bằng một dịch vụ nhắn tin khác hay không.





Tôi thích cách nhóm kỹ sư đánh giá kỹ lưỡng nhiều tùy chọn nhắn tin. Họ liệt kê một loạt các lựa chọn thay thế và xem xét kỹ lưỡng từng lựa chọn, dựa trên các yêu cầu và vấn đề của riêng họ.





Điều tôi còn thiếu trong bài báo của Sebastian là thông tin chi tiết về chính quá trình chuyển đổi đó; Tôi chỉ có thể cho rằng công việc này không hề tầm thường. Một điều làm cho việc chuyển đổi như vậy trở nên dễ dàng hơn là ít nhất nó không phải là di chuyển dữ liệu. Vì vậy, tôi cho rằng nhóm có thể thử nghiệm kiến ​​trúc mới trong thực tế bằng cách ẩn chức năng hoặc bằng cách triển khai cho số lượng người dùng ngày càng tăng.





2. Tại sao Birdie chuyển sang Micro Frontends





Birdie là một nền tảng công nghệ chăm sóc sức khỏe tại nhà, có trụ sở tại London. Công ty đã gọi vốn thành công vòng Series B trị giá 30 triệu đô la vào tháng 6 năm nay. Họ đã chia sẻ hành trình chuyển sang Micro Frontends trong bài blog gần đây. Tôi đã nói chuyện với tác giả của bài đăng này, Steve Heyes, một kỹ sư phần mềm tại Birdie.





Micro Frontends là một mẫu kiến trúc hơi giống với microservice. Thay vì giữ tất cả cơ sở mã của Ứng dụng một trang (SPA – Single Page Appplication) trong một cơ sở mã nguyên khối duy nhất, Micro Frontends chia cơ sở mã thành các thành phần riêng biệt được giữ với nhau bằng một trình bao (shell):





Micro Frontends là một cách tiếp cận kiến trúc để cấu trúc các ứng dụng web.
Micro Frontends là một cách tiếp cận kiến trúc để cấu trúc các ứng dụng web.




Birdie team có Ứng dụng một trang được tích hợp sẵn trong React, nói chuyện với một API ở backend. Đây là một ảnh chụp màn hình giao diện của ứng dụng:





Ứng dụng Birdie React. Một ứng dụng được xây dựng cho chăm sóc sức khỏe tại nhà.
Ứng dụng Birdie React. Một ứng dụng được xây dựng cho chăm sóc sức khỏe tại nhà.




Khi ứng dụng phát triển, nhóm kỹ sư nhận thấy một vấn đề ngày càng rõ ràng hơn: các bài kiểm thử mất nhiều thời gian để chạy. Đối với mỗi và mọi thay đổi, tất cả các kiểm thử trong cơ sở mã đều phải chạy, bao gồm một số ít bài kiểm thử đơn vị, nhiều bài kiểm thử tích hợp và một loạt kiểm thử từ đầu đến cuối bằng Cypress. Số lượng lớn các kiểm thử tự động này bắt đầu làm chậm quá trình phát triển, đó là lúc nhóm nảy ra ý tưởng chia ứng dụng thành các phần độc lập bằng cách sử dụng Micro Frontends, mà có thể được kiểm thử độc lập.





Steve rất tử tế khi chia sẻ thêm chi tiết về quá trình này. Anh nói:





‘Vào tháng 7 năm 2021, team đã chạy “sprint cải tiến kỹ thuật.” Mục tiêu là để giải quyết các vấn đề nền tảng đã tích tụ trong nhiều năm. 





‘Trong tuần này, ba kỹ sư frontend đã xây dựng thành phần shell và biến đổi thành công một tình huống khá riêng biệt để trở thành Micro Frontend đầu tiên. Team đã chọn thành phần điều hướng nằm trên cùng của tất cả các trang trên ứng dụng web và code đã đủ tách biệt. Cũng trong tuần đó, team cũng trích xuất một tính năng khác, nhỏ hơn là Micro Frontend thứ hai. Trong cả hai trường hợp, rất ít khi cần tới refactoring, vì hầu hết công việc là chuyển mã hiện có sang cấu trúc mới.





‘Sau đó là hướng dẫn những người còn lại trong team về Micro Frontends. Thay vì chuyển sang một công cụ tái cấu trúc lớn, các kỹ sư đã thực hiện công việc tái cấu trúc ban đầu đã hướng dẫn đồng nghiệp về lý do tại sao Micro Frontends lại hữu ích và cách sử dụng chúng. Nhóm đã chia sẻ tài liệu, gửi các video hướng dẫn và tổ chức một số buổi “lunch and learn”. Khoảng một tháng sau, tất cả các kỹ sư frontend đã bắt kịp các khái niệm.





‘Trong tương lai, ý tưởng là xây dựng các tính năng mới cho ứng dụng trong Micro Frontend của riêng họ. Làm điều này ngay từ đầu sẽ dễ dàng hơn rất nhiều so với việc quay lại và cấu trúc lại mã hiện có. Trong năm qua, một số Micro Frontend mới đã được thêm vào khi sản phẩm phát triển.





Cấu trúc của ứng dụng web Birdie, sau khi giới thiệu Micro Frontends. Theo thời gian, mỗi tính năng có thể trở thành Micro Frontend của riêng nó và Điều hướng chính và Điều hướng phụ cũng là các thành phần riêng của chúng.
Cấu trúc của ứng dụng web Birdie, sau khi giới thiệu Micro Frontends. Theo thời gian, mỗi tính năng có thể trở thành Micro Frontend của riêng nó và Điều hướng chính và Điều hướng phụ cũng là các thành phần riêng của chúng.




‘Quá trình chia nhỏ ứng dụng’ cũ’ thành Micro Frontends chậm hơn chúng tôi mong đợi. CÓ 2 lý do cho điều này là:





Khái niệm ‘trách nhiệm duy nhất’ là điều không phải tất cả các phần của ứng dụng đều tuân theo. Là một công ty khởi nghiệp, chúng tôi cần giao hàng nhanh, điều này khiến chúng tôi phải đánh đổi trong ứng dụng. Khi chúng tôi xây dựng SPA đầu tiên, chúng tôi chưa bao giờ tưởng tượng rằng mình sẽ cần chia nhỏ nó thành các ứng dụng nhỏ hơn. Chính vì điều này mà việc bỏ chọn các tính năng phức tạp hơn, nhưng không có nghĩa là không thể; nó chỉ làm việc nhiều hơn là sao chép mã hiện có từ tệp này sang tệp khác.





Chúng tôi phụ thuộc vào Redux để quản lý trạng thái chung. Khi SPA đầu tiên được xây dựng lần đầu tiên, chúng tôi đã sử dụng Redux và Redux-Saga – trình quản lý tác dụng phụ của Redux – để phát huy hết lợi thế của chúng. Tuy nhiên, vào thời điểm đó, đây là một quyết định đúng đắn, điều đó có nghĩa là rất nhiều logic kinh doanh của chúng tôi đan xen với nhau và việc bỏ chọn một tính năng cho thành phần của chính nó thường phải tái cấu trúc và viết lại rất nhiều.





‘Trong tương lai, chúng tôi sẽ loại bỏ các tính năng phụ thuộc vào Redux, trong đó một ví dụ điển hình là mã phân tích của chúng tôi, được sử dụng để ghi lại các tương tác của người dùng dựa trên các hành động của Redux.





‘Tuy nhiên, chúng tôi vẫn ghi nhớ mục tiêu chính của ứng dụng của chúng tôi là gì. Người dùng sẽ có thể hoàn thành các công việc họ cần. Đây là thước đo thành công đầu tiên của chúng tôi và mọi thứ, bao gồm cả kiến ​​trúc ứng dụng, sẽ xuất hiện sau đó.”





Quyền tự chủ cho các nhóm là một lý do khác khiến Birdie chuyển sang Micro Frontends – và tôi không ngạc nhiên khi họ làm như vậy. Tôi thấy có sự tương đồng thú vị giữa microservice dành cho team backend và Micro Frontend cho team frontend, về cách những phương pháp này giúp các nhóm tự chủ hơn.





Các vấn đề với các ứng dụng backend hoặc frontend nguyên khối là mã được liên kết chặt chẽ, các bài kiểm thử có thể mất nhiều thời gian để chạy và việc thực hiện một thay đổi duy nhất trong ứng dụng có thể làm hỏng thứ gì đó, ở đâu đó không mong muốn.





Cả Micro Frontends và microservice đều giới thiệu nhiều giao diện giữa các phần của ứng dụng. Và miễn là các nhóm tôn trọng các giao diện này, họ có thể di chuyển nhanh hơn trong các phần mã của mình và ít lo lắng hơn về các Micro Frontends. Nhưng một nhược điểm rõ ràng có thể là nhiều nhóm “phát minh lại bánh xe” hoặc sử dụng các cách tiếp cận khác nhau để xây dựng Micro Frontends tương ứng của họ. Mặt khác, thật khó để vừa có quyền tự chủ vừa có cách làm việc chung.





Steve đã lưu ý một điều thú vị trong cuộc trò chuyện, đó là monorepos có thể giảm tải nhận thức khi chuyển ngữ cảnh, không chỉ với microservice mà còn với Micro Frontends. Bằng việc có tất cả mã của các thành phần khác nhau trong cùng một cơ sở mã – điều này giúp bất kỳ kỹ sư nào cũng dễ dàng duyệt qua chúng và thực hiện các thay đổi – tự nhiên có thể dẫn đến các thực hành tương tự giữa các nhóm, đặc biệt nếu có quá trình đánh giá mã diễn ra giữa các nhóm đang làm việc trên các Micro Frontend khác nhau.





Như với bất kỳ sự lựa chọn kiến ​​trúc nào, có sự đánh đổi trong mỗi cách tiếp cận. Tôi thấy use-case của Birdie rất thú vị, đặc biệt là vì số lượng kiểm thử được chạy trong các thay đổi đã kích hoạt việc tìm kiếm các tùy chọn để chia nhỏ cơ sở mã.





3. Tại sao MetalBear quyết định sử dụng Rust





Metalbear là công ty khởi nghiệp xây dựng các công cụ nguồn mở dành cho backend developer. Công ty chỉ mới được thành lập vào tháng 4 năm nay và là một nhóm gồm 6 kỹ sư phần mềm rải rác trên toàn cầu, ở Brazil, Canada, Đức và Israel. Họ đã huy động được khoảng 1 triệu đô vòng tiền hạt giống (pre-seed).





Sản phẩm đầu tiên của họ là Mirrord. Phần mềm cho phép các kỹ sư phần mềm chạy các quy trình cục bộ, chẳng hạn như môi trường staging, trong môi trường đám mây mà không gặp rắc rối khi triển khai lên staging. Tôi biết về công ty sau khi vị đồng sáng lập, Aviram Hassan, viết một bài blog về lý do họ chọn Rust. Tôi thấy kinh nghiệm của một công ty khởi nghiệp nhỏ là một tình huống nghiên cứu thú vị.





Khi bắt đầu phát triển mirrord, nhóm không chọn ngôn ngữ nào trước. Thay vào đó, ba trong số bốn thành phần chính của nó – Agent,Layer, CLI và VS Code extension – mỗi cái đều sử dụng Rust vì những lý do khác nhau.





Bốn thành phần của mirrord. Nguồn ảnh gốc: MetalBear blog.
Bốn thành phần của mirrord. Nguồn ảnh gốc: MetalBear blog.




Dưới đây là những cân nhắc cho từng thành phần:





1. Agent: thành phần đóng vai trò đại diện cho người dùng





  • Chuyển đổi namespace. Namespace bao gồm Linux namespace và networking namespace. Mirrored kết nối các quy trình cục bộ với một pod từ xa (Kubernetes), hoạt động này thực hiện bằng cách sinh ra một agent trên cùng một node với nhóm hiện có. Sau đó, agent sinh ra các luồng cụ thể trong cùng một namespace của pod hiện tại. Bằng cách sử dụng cùng một namespace, agent được sinh ra có thể truy cập cùng tài nguyên mạng, tài nguyên tệp và tài nguyên quy trình. Làm việc với các Linux namespace dễ dàng hơn ở một số ngôn ngữ so với các ngôn ngữ khác. Nó dễ dàng hơn với Rust.
  • Mức chiếm dụng bộ nhớ nhỏ. Nhiều nhà phát triển phải làm việc trong cùng một môi trường bằng cách giảm thiểu tác động đến hiệu suất. Để làm như vậy, cần có một dung lượng bộ nhớ nhỏ, nghĩa là sử dụng một ngôn ngữ mà không cần bộ thu gom rác bộ nhớ. Rust là một trong những ngôn ngữ như vậy.
  • Phân luồng an toàn. Dữ liệu cần được di chuyển an toàn giữa các luồng, vì vậy nhóm cần một ngôn ngữ với các nguyên mẫu xung quanh việc quản lý tác vụ và đồng thời an toàn. Rust hỗ trợ Gửi để chuyển loại an toàn cho luồng và Đồng bộ hóa cho các loại để chia sẻ tham chiếu giữa các luồng.




2. Layer: thư viện dùng chung chạy trong local service. Kết nối các hoạt động đầu vào/đầu ra và ủy quyền chúng cho agent.





  • Yêu cầu cấp thấp. Do nhu cầu thao tác với socket và mọi thứ ở cấp hệ thống tệp, một ngôn ngữ cấp thấp có vẻ là lựa chọn phù hợp cho nhóm.
  • Mức chiếm dụng bộ nhớ nhỏ. Tương tự như Agent, mục tiêu là giảm thiểu việc sử dụng bộ nhớ.




3. CLI: đưa lớp vào quy trình mục tiêu.





  • Nhị phân độc lập (standalone binaries). Nhóm đã tìm kiếm một ngôn ngữ trong đó CLI sẽ được tạo dưới dạng tệp thực thi độc lập, lý tưởng nhất là không có phần phụ thuộc.
  • Bằng chứng trong tương lai. Ngay bây giờ, việc triển khai CLI sẽ trở nên tầm thường khi sử dụng hầu hết mọi ngôn ngữ. Tuy nhiên, nhìn về tiêm tinh vi hơn như sử dụng ptrace, lệnh gọi hệ thống Unix, một ngôn ngữ cấp thấp hơn như Rust cảm thấy phù hợp hơn trong tương lai.




4. Tiện ích VS Code: CLI cho Visual Studio Code





JavaScript. VS Code chỉ hỗ trợ JavaScript, vì vậy việc lựa chọn rất dễ dàng.





Làm cho việc tuyển dụng trở nên dễ dàng hơn bằng cách “Rust-only”. Là một startup, công nghệ có thể giúp cho việc tuyển kỹ sư phần mềm dễ hoặc khó hơn nhiều. Như Aviram đã nói:





“Tôi có cảm giác rằng việc cơ sở mã của chúng tôi chủ yếu ở Rust sẽ giúp việc tuyển dụng kỹ sư dễ dàng hơn rất nhiều. Là một người đam mê Rust, tôi rất thích làm việc ở một nơi mà tôi có thể làm việc với Rust thường xuyên và tôi nghi ngờ rằng nhiều người khác cũng cảm thấy như vậy.”





Câu chuyện của MetalBear là một lời nhắc nhở rằng những lựa chọn công nghệ ban đầu sẽ định hình văn hóa kỹ thuật của công ty – và tuyển dụng những khách hàng tiềm năng! Thực tế là, MetalBear có thể đã chọn viết phần lớn phần mềm của mình bằng C++, Go, hoặc C#, Java hoặc gần như bất kỳ ngôn ngữ nào khác mà mã có thể được tối ưu hóa để hoạt động hiệu quả. Tôi không nghi ngờ gì về việc họ có thể tạo ra mirrord chạy được bằng bất kỳ ngôn ngữ nào, ngay cả khi một số ngôn ngữ sẽ cần nhiều cách giải quyết hơn – bao gồm cả việc có thể chuyển sang kết nối chức năng ở cấp độ Hợp ngữ và sau đó điều chỉnh hiệu suất.





Tuy nhiên, bằng cách chọn ngôn ngữ một cách có mục đích, công ty đã đưa ra lựa chọn có tác động lâu dài đến văn hóa kỹ thuật, tuyển dụng và các lựa chọn trong tương lai.





Có vẻ như Rust đang trở nên phổ biến nhờ đạt được sự cân bằng tốt giữa việc cung cấp các tính năng cấp thấp theo cách thân thiện hơn, chẳng hạn như C hoặc C++. Nó cũng là ngôn ngữ mà nhiều kỹ sư backend muốn học và cân nhắc điều này là một bước đi thông minh; nghĩ xa hơn việc thuê những kỹ sư phần mềm đầu tiên.





4. Tại sao Motive chuyển sang Kotlin Multiplatform Mobile





Motive – trước đây là KeepTruckin – là một nền tảng hoạt động tự động cho lĩnh vực hậu cần. Họ xây dựng nhiều sản phẩm phần mềm hỗ trợ hoạt động vận tải đường bộ, quản lý đội xe và các use case khác mà phần cứng và phần mềm có thể giúp cải thiện cách thức hoạt động của các doanh nghiệp vận chuyển. Công ty đã huy động được khoản tài trợ Series F trị giá 150 triệu đô la vào tháng 5 năm nay.





Sunil Kumar, nhân viên kỹ sư phần mềm của công ty, đã viết một bài blog vào tháng 7 này về lý do tại sao nhóm kỹ sư quyết định chuyển sang Kotlin Multiplatform Mobile (KMM) như một cách tiếp cận để chia sẻ nhiều mã hơn giữa Android và iOS. Anh cũng nêu chi tiết kinh nghiệm của họ với sự thay đổi.





Nhóm kỹ sư xây dựng ứng dụng Motive Fleet muốn đảm bảo tính nhất quán trong logic kinh doanh trên các ứng dụng dành cho thiết bị di động và để thực hiện phát triển nhanh hơn. Họ đã đánh giá ba phương pháp phát triển đa nền tảng:





  • Flutter: một khung đa nền tảng do Google xây dựng bằng ngôn ngữ lập trình Dart, ngôn ngữ này cũng được Google phát triển.
  • React Native: một framework đa nền tảng do Facebook xây dựng ban đầu. Nó sử dụng ngôn ngữ lập trình JavaScript và có một số điểm tương đồng với khung web React.
  • KMM: Một khung được xây dựng bởi Jetbrains. Nó sử dụng ngôn ngữ lập trình Kotlin.




Nhóm đã so sánh thêm, xem xét các khía cạnh hỗ trợ giao diện người dùng và mức độ dễ dàng để xây dựng giao diện người dùng gốc, tích hợp với các ứng dụng hiện có và ý nghĩa hiệu suất. Đây là những gì nhóm đã đánh giá:





So sánh KMM, Flutter và React Native về các thứ nguyên quan trọng đối với nhóm kỹ sư Motive. Nguồn: Blog kỹ thuật động lực.
So sánh KMM, Flutter và React Native về các thứ nguyên quan trọng đối với nhóm kỹ sư Motive. Nguồn: Blog kỹ thuật động lực.




Nhóm đã quyết định sử dụng KMM, chủ yếu là vì đây là cách dễ dàng nhất để làm việc với mã hiện có của họ và họ cảm thấy mình có thể giữ quyền kiểm soát (gốc) nhiều nhất đối với ứng dụng.





Tôi đã liên hệ với Sunil để hỏi về cách họ đưa ra quyết định. Đây là những gì anh ấy chia sẻ:





Những lý do khác dẫn đến KMM?





‘Nhóm ứng dụng Motive Fleet được xây dựng chỉ vài ngày trước khi Covid-19 bắt đầu. Tuy nhiên, chúng tôi thiếu băng thông iOS, dẫn đến việc iOS luôn bị tụt hậu so với Android 1-2 tháng. Độ trễ này có nghĩa là một số tính năng có sự khác biệt về logic nghiệp vụ giữa các ứng dụng.





‘Để loại bỏ những khác biệt này và tăng tốc độ phát triển do thiếu tài nguyên, chúng tôi bắt đầu xem xét các giải pháp đa nền tảng. Tôi đã có kinh nghiệm trước đây với React Native. Khi tôi làm việc với React Native, hiệu suất của ứng dụng mà tôi xây dựng không được tốt. Trong trường hợp của chúng tôi, hiệu suất là một điểm quan trọng khi chúng tôi tương tác với bản đồ rất nhiều. Với Flutter, không ai trong nhóm của chúng tôi có bất kỳ kinh nghiệm nào với nó và sau khi đọc nhiều bài đăng trên blog, chúng tôi quyết định rằng việc học một công nghệ mới và một ngôn ngữ mới là không đáng.’





Bạn đã điều phối việc chuyển sang KMM như thế nào?





‘Chúng tôi đã không di chuyển tất cả mã của ứng dụng hiện có sang KMM. Thay vào đó, chúng tôi bắt đầu nhỏ. Chúng tôi đã viết mã mới cho lớp mạng bằng KMM và chuyển qua quản lý phiên sang KMM bằng cách loại bỏ các phụ thuộc JVM (Máy ảo Java) hiện có. Sau khi hoàn thành công việc này, chúng tôi chỉ sử dụng KMM cho các tính năng mới do chúng tôi xây dựng.





‘Cho đến nay, các tính năng trong ứng dụng sử dụng KMM là:





  • Trung tâm Thông báo. Đây là nơi người dùng có thể xem tổng quan về tất cả các cảnh báo về đội xe của họ. Thành phần này sử dụng cơ sở dữ liệu SQL được chia sẻ.
  • Lịch sử chuyến đi. Người dùng có thể xem lịch sử chuyến đi của phương tiện, tài xế và tài sản.
  • Cài đặt Push notification. Người dùng có thể kiểm soát những cảnh báo mà họ muốn nhận Thông báo đẩy trên thiết bị của họ.
  • Các nhóm. Người dùng có thể lọc dữ liệu nhóm của họ, dựa trên các nhóm khác nhau do người dùng tạo.’




Dưới đây là ảnh chụp màn hình về giao diện của các tính năng này trên iOS và Android:





Nhóm và Trung tâm thông báo: cả hai tính năng được viết bằng Kotlin Multiplatform Mobile.
Nhóm và Trung tâm thông báo: cả hai tính năng được viết bằng Kotlin Multiplatform Mobile.




Cài đặt thông báo đẩy và Lịch sử chuyến đi trong ứng dụng Motive Fleets. Giao diện người dùng gần như giống hệt nhau trên iOS và Android.
Cài đặt thông báo đẩy và Lịch sử chuyến đi trong ứng dụng Motive Fleets. Giao diện người dùng gần như giống hệt nhau trên iOS và Android.




Việc xây dựng các ứng dụng iOS và Android riêng biệt dẫn đến câu hỏi: ‘chúng ta có thể không chỉ sử dụng mã dùng chung không?’ Đây là điều mà nhiều CEO, chuyên gia sản phẩm và thậm chí nhiều kỹ sư di động sẽ hỏi. Đối với khách hàng, họ thường không quan tâm ứng dụng dành cho thiết bị di động sử dụng công nghệ nào và mong đợi chức năng gần như giống nhau trên iOS, Android và thường là trên web.





Một tháng trước, chúng tôi đã khám phá liệu có sự sụt giảm tuyển dụng iOS và Android bản địa tại các startup hay không. Câu trả lời là sắc thái, nhưng tôi đã lưu ý rằng:





‘Ngày nay, Flutter và React Native cuối cùng cũng “đủ tốt” cho các ứng dụng không yêu cầu hiệu năng cao.’





Trong trường hợp của Motive, hiệu suất đủ quan trọng để không chuyển sang React Native hoặc Flutter, họ cũng không sẵn sàng vứt bỏ mã hiện có mà họ đã viết và chuyển sang một tech stack khác.





Tuy nhiên, việc giới thiệu KMM không phải là vấn đề nhỏ, vì hiện tại, một phần tốt của quá trình phát triển đã được thực hiện với Kotlin, đây là ngôn ngữ mà các kỹ sư iOS và Android đều phải làm quen. Và trong trường hợp ứng dụng dành cho thiết bị di động, lợi ích của việc không phải kiểm thử hai triển khai logic nghiệp vụ riêng biệt trên OS và Android là giúp tiết kiệm thời gian thử nghiệm và giảm khả năng xảy ra mâu thuẫn logic nghiệp vụ.





Tôi đã hỏi Sunil rằng anh ấy cảm thấy thế nào về việc di chuyển và thực hiện tất cả công việc để cấu trúc lại mã nhằm tận dụng KMM stack. Anh ấy nói rằng anh ấy hài lòng với quyết định này và ước tính chu kỳ phát triển của họ hiện nhanh hơn khoảng một phần ba so với trước đây, nhờ vào logic kinh doanh thống nhất của Android/iOS. Quan trọng nhất, họ không còn phải đợi trên iOS nữa, ngay cả khi họ có ít băng thông iOS hơn.





Tổng kết





Vấn đề Kỹ thuật trong thế giới thực số này tập trung vào các nghiên cứu điển hình trong đó các nhóm đã chọn công nghệ mới để giải quyết các vấn đề khó khăn hiện có. Với nhiều ví dụ khác nhau, tôi hy vọng nó làm nổi bật quá trình lựa chọn công nghệ mới và cách các nhóm xử lý việc thay đổi.





Chúng ta đã đề cập đến các nghiên cứu điển hình bao gồm việc chọn hàng đợi nhắn tin mới, tái cấu trúc ứng dụng web thành mô hình kiến ​​trúc mới, chọn ngôn ngữ sẽ được sử dụng ở 1 startup và chuyển sang logic kinh doanh iOS và Android được chia sẻ. Đối với tôi, các yếu tố nổi bật là phương pháp đáng để xem xét, bao gồm:





  • Khi thay đổi công nghệ, hãy đảm bảo rằng bạn giải quyết được một điểm yếu đủ lớn. Thay đổi công nghệ – chẳng hạn như thay thế framework hoặc chọn cách tiếp cận kiến ​​trúc mới – là những thay đổi tốn kém về thời gianài nguyên quá cao đã khiến nó trở nên đáng giá. Điều này có đúng trong trường hợp sử dụng của bạn không?
  • Khi xây dựng một dự án mới, hãy nghĩ về những điểm khó khăn trong tương lai. Khi bạn bắt đầu xây dựng một thứ gì đó mới, bạn cần ít lý do hơn để lựa chọn bất kỳ công nghệ nào vì bạn không phải trả bất kỳ chi phí nào cho việc chuyển đổi. Tuy nhiên, hãy cố gắng chọn một công nghệ giúp bạn tránh – hoặc giải quyết – các vấn đề nhức nhối trong tương lai. Đây là những gì MetalBear đã làm bằng cách dự đoán rằng Rust sẽ làm cho nhiều trường hợp sử dụng trong tương lai của họ trở nên dễ dàng hơn để giải quyết, trên hết là phù hợp ngay bây giờ.
  • Khi thay đổi công nghệ, hãy thực hiện từng bước một nếu có thể. Khi chuyển sang Micro Frontends và khi giới thiệu KMM, trước tiên, nhóm đã tạo nguyên mẫu cho phương pháp này, sau đó cung cấp một tính năng sử dụng nó, rồi bắt đầu xây dựng các tính năng mới với phương pháp mới.
  • Trở lại việc cấu trúc lại mọi thứ không nhất thiết là một cách tiếp cận thực dụng. Khi thay đổi hoàn toàn công nghệ – chẳng hạn như chọn phương thức nhắn tin mới – bạn có thể không có lựa chọn nào khác ngoài việc thay thế công nghệ hiện tại của mình. Tuy nhiên, khi thay đổi cách tiếp cận kiến ​​trúc hoặc thực hiện những thay đổi lớn, cách tiếp cận “mọi thứ phải diễn ra” có thể có ý nghĩa hơn. Cả Birdie với Micro Frontends và Motive với KMM, đều chưa chuyển tất cả mã hiện có của họ sang phương pháp hoặc khung mới, ít nhất là chưa.




Tuy nhiên, bài học lớn nhất của tôi là:





Đừng quên ưu tiên số 1 của bạn, với tư cách là một nhóm kỹ sư. Thước đo thành công lớn nhất cho nhóm của bạn là gì? Đối với Birdie, Steve Heyes nói rằng “người dùng sẽ có thể hoàn thành công việc họ cần.” Hãy tự trả lời câu hỏi và đảm bảo rằng bạn ghi nhớ ưu tiên đó: nó chắc chắn đến trước công nghệ bạn chọn để làm việc.





Nguồn: Pragmaticengineer.com