5 điểm bởi GN⁺ 2023-10-31 | 1 bình luận | Chia sẻ qua WhatsApp
  • Backend thường bắt đầu dưới dạng monolith, nhưng khi nhiều nhóm tính năng cùng bắt đầu động vào một codebase, mức độ phụ thuộc chặt chẽ có thể tăng lên và tốc độ thay đổi có thể chậm lại
  • Microservice là cách chia dịch vụ theo từng chức năng kinh doanh để có được khả năng triển khai độc lập và sự tự chủ của các nhóm nhỏ
  • Nếu dịch vụ quá nhỏ, gánh nặng vận hành sẽ lớn hơn lợi ích, khiến cái tên “micro” thậm chí có thể làm lu mờ phán đoán
  • Việc áp dụng toàn diện đi kèm với nhiều chi phí rộng khắp, từ chuẩn hóa, provisioning tài nguyên, phòng vệ cho lời gọi từ xa, CI/CD, vận hành cho đến tính nhất quán cuối cùng
  • Thông thường, an toàn hơn là học đủ rõ các ranh giới trong monolith rồi tách dần từng phần khi những “đau tăng trưởng” trở nên lớn; cách tiếp cận ưu tiên microservice phù hợp khi đã có kinh nghiệm và nền tảng sẵn sàng

Những giới hạn lộ ra khi monolith lớn dần

  • Backend của một ứng dụng JavaScript một trang hiện đại thường bắt đầu như một dịch vụ web phi trạng thái duy nhất, cung cấp RESTful HTTP API và dùng cơ sở dữ liệu quan hệ
  • Dịch vụ này được cấu thành từ các component hoặc thư viện triển khai những chức năng kinh doanh khác nhau
  • Khi số nhóm tính năng tham gia cùng một codebase tăng lên, mức độ phụ thuộc giữa các component tăng và phạm vi công việc giữa các nhóm dễ chồng lấn
    • Năng suất giảm, và khó dự đoán phạm vi ảnh hưởng của thay đổi
  • Khi codebase đủ phức tạp, không ai hiểu hoàn toàn toàn bộ hệ thống, khiến việc triển khai tính năng mới và sửa lỗi mất nhiều thời gian
  • Ngay cả khi backend được chia thành các thư viện theo từng nhóm, thay đổi thư viện cuối cùng vẫn dẫn đến triển khai lại dịch vụ
    • Nếu một lỗi như rò rỉ bộ nhớ lọt vào, toàn bộ dịch vụ có thể bị ảnh hưởng
    • Rollback một bản build sai sẽ ảnh hưởng đến tốc độ của tất cả các nhóm, không chỉ nhóm gây ra lỗi

Sự tự chủ mà ranh giới dịch vụ mang lại

  • Một cách để giảm “đau tăng trưởng” của backend monolithic là chia backend thành tập hợp các dịch vụ có thể triển khai độc lập
  • Các dịch vụ giao tiếp qua API, và API tạo ra những ranh giới khó bị xâm phạm hơn ranh giới giữa các component trong cùng một tiến trình
  • Kiến trúc này được gọi là kiến trúc microservice, nhưng cách gọi micro có thể gây hiểu lầm
    • Dịch vụ không nhất thiết phải nhỏ
    • Một dịch vụ có quá ít chức năng có thể tạo ra gánh nặng vận hành nhiều hơn lợi ích
  • Tên gọi phù hợp hơn có thể là kiến trúc hướng dịch vụ, nhưng tên này cũng mang theo những gánh nặng cũ

Tốc độ do nhóm nhỏ và dịch vụ độc lập tạo ra

  • Khi chia backend thành các dịch vụ theo chức năng kinh doanh, mỗi dịch vụ có thể được phát triển và vận hành bởi một nhóm nhỏ duy nhất
  • Nhóm nhỏ hiệu quả hơn vì chi phí giao tiếp tăng theo bình phương quy mô nhóm
  • Khi mỗi nhóm tự quyết lịch phát hành và kiểm soát codebase của mình, việc phối hợp giữa các nhóm giảm đi và ra quyết định nhanh hơn
  • Codebase của dịch vụ nhỏ giúp lập trình viên dễ hiểu hơn và cũng rút ngắn thời gian hòa nhập của nhân viên mới
    • Codebase nhỏ cũng giúp giảm tình trạng IDE chậm, qua đó hỗ trợ năng suất lập trình viên
  • Nhờ ranh giới dịch vụ mạnh, khi thay đổi một phần của backend, lập trình viên chỉ cần hiểu một phần nhỏ thay vì toàn bộ hệ thống
  • Mỗi dịch vụ có thể mở rộng độc lập và chọn stack công nghệ phù hợp với nhu cầu riêng
    • Người tiêu thụ API không cần quan tâm chức năng được triển khai nội bộ như thế nào
    • Dễ thử nghiệm và đánh giá công nghệ mới mà không ảnh hưởng đến các khu vực khác của hệ thống
  • Mỗi microservice có thể có mô hình dữ liệu và kho lưu trữ dữ liệu riêng
    • Lập trình viên có thể thay đổi schema mà không ảnh hưởng đến dịch vụ khác

Chi phí thực sự mà microservice bổ sung

  • Microservice thêm nhiều bộ phận chuyển động hơn vào toàn hệ thống, và chi phí này không miễn phí
  • Chi phí để chấp nhận microservice hoàn toàn chỉ đáng trả khi có thể phân bổ khấu hao trên hàng chục nhóm phát triển
  • Trải nghiệm phát triển

    • Việc mỗi dịch vụ dùng ngôn ngữ, thư viện và kho dữ liệu khác nhau là có thể, nhưng có thể khiến việc bảo trì ứng dụng khó hơn
    • Nếu stack phần mềm hoàn toàn khác nhau, lập trình viên khó chuyển từ nhóm này sang nhóm khác
    • Để cung cấp các chức năng chung mà mọi dịch vụ đều cần, như logging, cần hỗ trợ thư viện cho từng ngôn ngữ đã chọn
    • Cần một mức độ chuẩn hóa nhất định
    • Có thể khuyến khích lỏng lẻo việc dùng một số công nghệ bằng cách cung cấp trải nghiệm phát triển tốt cho các nhóm tuân theo danh mục ngôn ngữ và công nghệ được khuyến nghị
  • Provisioning tài nguyên

    • Để hỗ trợ nhiều dịch vụ độc lập, cần có khả năng tạo dễ dàng máy chủ, kho dữ liệu và các tài nguyên dùng chung khác
    • Không nên để mỗi nhóm tự tạo cách sinh tài nguyên riêng
    • Các tài nguyên đã được provisioning cần được cấu hình lại, và để xử lý việc này cần khá nhiều tự động hóa
  • Giao tiếp

    • Lời gọi từ xa có chi phí lớn và đưa vào những cách mới khiến hệ thống có thể sụp đổ
    • Để chuẩn bị cho sự cố, cần các cơ chế phòng vệ như timeout, retry và circuit breaker
    • Để giảm tổn thất hiệu năng của truyền thông qua mạng, cũng cần tận dụng xử lý bất đồng bộ và batching
    • Những yếu tố này làm tăng độ phức tạp của hệ thống
    • Monolith cũng có thể gặp vấn đề tương tự do truy cập từ client từ xa và sử dụng API bên thứ ba, nhưng quy mô nhỏ hơn
  • Tích hợp, chuyển giao và triển khai liên tục

    • Tích hợp liên tục đảm bảo thay đổi mã được merge vào nhánh chính sau khi build và kiểm thử tự động chạy xong
    • Khi thay đổi mã được merge, nó cần được tự động publish và triển khai vào môi trường tương tự production
    • Trong môi trường đó, cần chạy kiểm thử tích hợp và kiểm thử end-to-end để xác nhận microservice không làm hỏng các dịch vụ khác phụ thuộc vào nó
    • Kiểm thử từng microservice riêng lẻ không khó hơn kiểm thử monolith, nhưng kiểm thử tích hợp của tất cả microservice thì khó hơn nhiều
    • Khi các dịch vụ riêng lẻ tương tác với nhau, có thể xuất hiện những hành vi rất tinh vi và ngoài dự đoán
  • Vận hành

    • Khác với monolith, việc bố trí một nhóm vận hành riêng cho từng nhóm phụ trách dịch vụ tốn kém hơn nhiều
    • Nhóm phát triển một dịch vụ thường cũng đảm nhận on-call cho dịch vụ đó
    • Mỗi sprint, nhóm phải quyết định ưu tiên công việc phát triển hay gánh nặng vận hành, và điều này tạo ra ma sát
    • Việc debug sự cố hệ thống cũng trở nên khó hơn
    • Không thể đưa toàn bộ ứng dụng lên máy local rồi chạy từng bước bằng debugger
    • Có nhiều bộ phận chuyển động hơn nên cũng có nhiều kiểu thất bại hơn
    • Logging và monitoring tốt ở mọi cấp độ trở nên quan trọng
  • Tính nhất quán cuối cùng

    • Khi chia ứng dụng thành nhiều dịch vụ, mô hình dữ liệu không còn chỉ tồn tại trong một kho dữ liệu duy nhất
    • Việc cập nhật nguyên tử các bản ghi nằm ở những kho dữ liệu khác nhau và bảo đảm tính nhất quán mạnh là chậm, tốn kém và khó triển khai
    • Kiến trúc kiểu này thường phải chấp nhận tính nhất quán cuối cùng

Khi nào nên tách

  • Việc chia ứng dụng thành các dịch vụ làm độ phức tạp tổng thể của hệ thống tăng mạnh
  • Nói chung, nên bắt đầu bằng monolith và chỉ tách ra khi có đủ lý do
  • Việc xác định đúng ranh giới giữa các dịch vụ là khó, vì vậy thường dễ hơn khi dịch chuyển ranh giới bên trong monolith để tìm điểm phù hợp
  • Khi monolith đã đủ trưởng thành và “đau tăng trưởng” bắt đầu lớn lên, có thể tách dần từng microservice ra
  • Cách tiếp cận ưu tiên microservice chỉ nên bắt đầu khi đã có kinh nghiệm, và đã xây dựng nền tảng cho nó hoặc đã tính đến thời gian cần để xây dựng nền tảng đó

1 bình luận

 
GN⁺ 2023-10-31
Ý kiến trên Hacker News
  • Nhìn chung tôi đồng ý với bài viết, nhưng nhìn theo chiều ngược lại thì cũng có những trường hợp đáng chấp nhận chi phí: có thể tách các vấn đề tương thích với phụ thuộc bên thứ ba cũ thành một dịch vụ riêng để tránh refactor đau đớn hoặc tự triển khai lại, có thể ngăn các ràng buộc triển khai của hệ thống lõi có tính sẵn sàng cao chặn việc triển khai các hệ thống khác, và có thể cô lập các ràng buộc thiết kế mà khách hàng lớn không thể thay đổi
    Không có nghĩa là miễn phí, mà là đó có thể là cái giá đáng trả để không biến một monolith đơn giản thành bãi rác, không khiến nó chao đảo vì các lần triển khai rủi ro, hoặc không bị trói buộc vào một đối tác kinh doanh có stack công nghệ tệ hơn

    • Tôi nghĩ đây chỉ là việc có một hoặc hai dịch vụ thôi. Tôi không cho rằng nó giống với “microservices”
      Microservices chẳng phải là cách tiếp cận trong đó các thành phần nội bộ về cơ bản được tạo thành dịch vụ thay vì thư viện hay class sao?
    • Thêm một điểm nữa là lợi thế có thể dễ dàng dùng các ngôn ngữ lập trình khác nhau. Không phải ngôn ngữ nào cũng phù hợp với mọi bài toán
      Nếu dịch vụ suy luận machine learning dùng Python, UI render phía server dùng Rails, còn dịch vụ nhiều I/O và concurrency dùng Go, thì chi phí bổ sung để tách ba phần này thành các dịch vụ riêng có thể là hợp lý
    • Có nhiều cách để thiết kế tốt, nhưng điều đó không có nghĩa là nên vội vàng áp dụng microservices
      Tôi khá thích microservices, nhưng tối ưu hóa sớm và mở rộng sớm có thể tệ chẳng kém gì khoản nợ kỹ thuật phát sinh khi sau này phải tối ưu theo một hướng hoàn toàn khác
    • Với ý 1, tôi không hiểu vì sao tách thành dịch vụ riêng lại tốt hơn bọc nó bằng một interface có API rõ ràng
    • Những trường hợp như vậy có lẽ chỉ chiếm dưới 10% toàn bộ codebase
  • Thời điểm nên tách thứ gì đó thành một dịch vụ riêng là khi xuất hiện vấn đề mà nếu không làm vậy thì không thể giải quyết theo cách chấp nhận được
    Tăng độ phức tạp kiến trúc để cưỡng chế ranh giới không thể là lời giải cho việc thiếu kỷ luật tổ chức, nhưng các công ty công nghệ cỡ trung cứ liên tục xem nó như vậy. Nếu vấn đề phát sinh vì ranh giới domain yếu, thì trích xuất dịch vụ có khả năng cũng không giải quyết tốt được

    • Thời điểm chịu trách nhiệm cuối cùng là chiến lược trì hoãn quyết định cho đến khoảnh khắc chi phí của việc trì hoãn thêm trở nên lớn hơn chi phí ra quyết định ngay bây giờ”
      Trích dẫn: https://www.oreilly.com/library/view/software-architects-han...
    • Nói rằng tăng độ phức tạp kiến trúc để cưỡng chế ranh giới không phải là lời giải cho việc thiếu kỷ luật tổ chức, nhưng thực tế thì chúng ta vẫn luôn làm như vậy
      CI/CD chặn PR cho đến khi test pass cũng là một giải pháp kỹ thuật tốn kém để giải quyết vấn đề kỷ luật tổ chức
    • Nghe giống SISP, kiểu giải pháp đi tìm vấn đề. Hoặc giống trường hợp ném giải pháp vào vấn đề mà không hiểu nguyên nhân gốc rễ
    • Trên HN có vẻ đang lan rộng một meme rằng microservices tuyệt đối không thể được biện minh, nhưng toàn ngành thì không như vậy nên thấy lạ
      Mọi người đều công nhận việc phân giải tên miền do dịch vụ bên ngoài thực hiện là hợp lý, và gần như không ai tích hợp resolver DNS đệ quy cùng cache vào monolith. Thế nhưng kiểu tách trách nhiệm lâu đời như vậy lại không thường được công nhận là ví dụ
    • Tôi luôn nghĩ đây là ví dụ hay nhất về trì hoãn thực thi. Thật ngạc nhiên là các công ty sai thường xuyên đến mức nào
      Có vẻ vấn đề nằm ở chỗ nhân viên càng “giỏi” thì càng quá hăng hái chứng minh giá trị, dẫn đến over-engineering, và cuối cùng biến thành địa ngục
  • Microservices
    grug thắc mắc vì sao những bộ não lớn lại nắm lấy bài toán khó nhất là phân rã hệ thống cho đúng, rồi còn nhét thêm lệnh gọi mạng vào đó
    với grug thì chuyện này trông rất khó hiểu
    https://grugbrain.dev/#grug-on-microservices

    • Có vẻ grug chưa từng trải qua cảnh các đội lớn giẫm lên domain và mô hình dữ liệu của nhau, bị khóa chặt vào một triển khai cụ thể, và cần nỗ lực tổ chức khổng lồ để phát hành tính năng theo từng đội. Microservices và cách mỗi đội kiểm soát kho dữ liệu riêng giúp tăng tốc độ của đội
      “Vì SCALE_REASONS, chúng tôi muốn hiện đại hóa cách tiếp cận FOO_TABLE bằng cách chuyển từ MySQL sang DynamoDB, nhưng 32 trong 59 đội đang truy cập trực tiếp FOO_TABLE hoặc gọi thẳng private method trong class của chúng tôi. Do ưu tiên khác nhau, các đội đó không thể chuyển sang dùng FOO_SERVICE hoặc đổi cách query để dùng bảng sharding. Giờ việc mở rộng FOO_TABLE trở thành công việc kéo dài nhiều quý, và phải cho phép các đội cập nhật từ từ. Có lẽ sau 1–2 năm nữa chúng tôi mới có thể cho nghỉ hưu cách làm cũ hiện đang bốc cháy. Trong lúc đó thì cứ tận hưởng on-call thôi”
      Với microservices, đội sẽ nhận ra rằng bảng không scale được, nhưng dữ liệu được cung cấp qua API. Họ lên kế hoạch và thực hiện migration trong sprint tiếp theo, còn người dùng API sẽ cảm thấy mọi thứ nhanh hơn nhiều
    • Static type không phải là bắt buộc để có autocomplete. JetBrains có các IDE hỗ trợ autocomplete ngay cả với những ngôn ngữ như Ruby hay Python
      Mở REPL của Ruby hiện đại lên, bạn có thể nhận được phần lớn những gì thường kỳ vọng từ IDE của ngôn ngữ static type về autocomplete và kiểm tra cú pháp
    • Đưa lệnh gọi mạng vào là một việc rất mạnh. Thực sự sẽ xuất hiện một ranh giới không thể vượt qua được cưỡng chế về mặt vật lý, và hai dịch vụ buộc phải coi nhau là tách biệt
      Không nên xem nhẹ sự cô lập; đó là một trong những tính năng mạnh nhất có thể đưa vào phần mềm. Nó có thể cải thiện hiệu năng, tạo ranh giới lỗi, và cung cấp ranh giới bảo mật
      Điều này giống với khái niệm nền tảng của actor model. Thay vì hai thành phần chia sẻ và thay đổi bộ nhớ của nhau, hai hệ thống cô lập như actor hoặc microservice chỉ giao tiếp qua một giao thức đã định nghĩa
  • Tôi cho rằng mọi người đang hiểu sai về tính mô-đun. Tính mô-đun là quan trọng, nhưng tôi cũng dần nghĩ rằng còn một nguyên tắc kiến trúc quan trọng khác là nguyên tắc một xoáy duy nhất
    Trong hệ thống phần mềm, xoáy là một vòng lặp của luồng dữ liệu. Nếu bạn gửi dữ liệu đi đâu đó rồi nhận lại kết quả đã xử lý, hoặc bị dữ liệu đó tác động theo cách nào đó, thì có một xoáy. Biến có thể thay đổi là một ví dụ về một xoáy rất nhỏ
    Nguyên tắc một xoáy duy nhất có nghĩa là, lý tưởng thì hệ thống phần mềm chỉ nên có một xoáy, hay nói cách khác, mọi thành phần đều phải biết xoáy của mình đang quay theo hướng nào
    Khi gộp các mô-đun tạo thành hai xoáy lại với nhau, nếu hướng xoáy khớp thì việc tổ hợp dễ dàng; nếu ngược hướng thì sẽ khó vì phải quyết định hướng xoáy mới sẽ như thế nào. Vì vậy tốt nhất là xoáy của mọi mô-đun có cùng hướng và tạo thành một xoáy duy nhất
    Nguyên tắc này là sự khái quát hóa các ý tưởng như mẫu Flux, CQRS, event sourcing và tính bất biến

    • Đây là một điểm rất hay, và chỉ riêng chủ đề này có lẽ đã có thể viết nhiều bài. Service A có thể gọi B, B gọi C, rồi C lại gọi A, và khi đó sẽ phát sinh vấn đề
      Hoặc một chuyện không lường trước ở A có thể làm C bị chặn. Lý tưởng là cha chỉ gọi con và không phụ thuộc vào cha; nếu thất bại ở điểm này thì kiến trúc cũng thất bại
    • Tôi thích khái niệm này
      Khi nhìn vào sơ đồ kiến trúc, một nửa trận chiến là xác định cái gì là bất đồng bộ/đồng bộ, push/pull, cũng như hướng và thứ tự của luồng
      Enterprise Service Bus và message queue trông giống như giải pháp để tạo xoáy ở những nơi hai hướng không khớp nhau
    • Khái niệm xoáy này khá thú vị. Không biết có sách hay tài liệu online nào đáng học không
    • Tôi chưa hiểu rõ đoạn áp chót, đặc biệt là phần lập luận. Không biết có thể giải thích kỹ hơn không
  • Có đoạn nói rằng vấn đề là “số lượng thư viện phải hỗ trợ để cung cấp các chức năng chung như logging cần thiết cho mọi service, mỗi ngôn ngữ được dùng một bộ”
    Đây là lý do lớn nhất khiến chúng tôi từ bỏ microservice. Với công cụ của năm 2023, đặc biệt trong môi trường cloud-native thuần túy hoặc FaaS mở rộng vô hạn, việc tốn băng thông tinh thần cho những lo lắng kiểu này là hoàn toàn lãng phí. Điều này càng đúng với nhóm khách hàng như ngân hàng và tổ chức tài chính, nơi họ soi xét cực kỳ khắt khe từng dependency bên thứ ba
    Hiện chúng tôi đang vận hành bằng một bản phân phối binary .NET nguyên khối duy nhất, khoảng 250MB khi gzip, và hoàn toàn không có dấu hiệu rạn nứt. Nếu bạn đang băn khoăn tỉ mỉ kiểu “exe của tôi không còn vừa L2 nữa” với một bản phân phối SaaS 10–100MB, thì cứ yên tâm. Hành trình phần mềm nguyên khối của bạn còn chưa bắt đầu
    Nếu đến lúc phải viết lại một trong những đống rác này, chẳng phải sẽ dễ hơn nhiều khi mọi commit đều nằm ở một nơi nhất quán trên toàn cục sao?

    • Tôi cho rằng đây là lưỡng phân giả. Hầu hết các tổ chức microservice tôi từng gặp đều chỉ cho phép 2–3 ngôn ngữ đã được phê duyệt vì những lý do như thế này, và nếu một team chứng minh được rằng họ không có lựa chọn khác thì lãnh đạo có thể cho ngoại lệ
      Microservice không có nghĩa là vùng vô pháp, nơi mọi team được hành động mà không cần nghĩ đến toàn bộ tổ chức. Có thể và nên có các quy tắc để duy trì một mức độ nhất quán nào đó giữa các team
    • Thực ra điều này dễ hơn nhiều với microservice. Vì có interface rõ ràng cần hỗ trợ, và code không bị đan vào phần còn lại của monolith như một bím tóc kiểu Pháp
      Code tốt nhất là code dễ vứt đi và viết lại. Trên thực tế, code cũ đi qua tay nhiều người hơn, trở nên tệ hơn, và quan trọng nhất là cuối cùng không ai muốn bảo trì nó nữa
    • Điều quan trọng là có bao nhiêu lập trình viên đang làm việc trên monolith đó. Kích thước binary thường không phải là lý do các team bắt đầu tách monolith
    • Câu “chẳng phải sẽ dễ hơn nhiều nếu mọi thứ ở một nơi và mỗi commit nhất quán trên toàn cục sao?” luôn khiến tôi thấy hơi buồn cười. Cả hai phe đều hay nói vậy, nhưng dường như hiếm khi nghĩ từ góc nhìn của phía bên kia
      Phe thích microservice nói rằng vì có contract rõ ràng về cách service phải hoạt động và codebase của service đó nhỏ hơn nhiều, nên việc thay đổi hoặc viết lại sẽ dễ hơn
      Phe monolith nói rằng vì mọi thứ là một cuộn chỉ lớn, nên để thay đổi một sợi chỉ, chỉ cần biết những điểm mà sợi đó đan với các sợi khác, vì vậy việc thay đổi hoặc viết lại sẽ dễ hơn
      Tôi không biết ai đúng. Với những người gắn bó lâu năm đã soi codebase dưới kính hiển vi suốt nhiều năm, monolith có lẽ tốt hơn; với những người còn lại, microservice có thể tốt hơn
    • Không ai nói rằng mọi microservice không được dùng cùng một ngôn ngữ
  • Nhìn từ góc độ quản lý release, việc chuyển từ monolith sang microservice thường được thực hiện vì những lý do sai
    Lý do duy nhất thực sự hợp lý để thay đổi có vẻ là vấn đề khả năng mở rộng do nút thắt hiệu năng. Những lý do còn lại chỉ là chuyển độ phức tạp của phát triển phần mềm sang bảo trì hệ thống
    Tất nhiên các lập trình viên sẽ thích vì giảm được gánh nặng lớn khi phải phối hợp với các team khác. Nhưng việc làm rõ tính năng được triển khai khi nào và như thế nào, được kiểm thử đúng cách trên toàn bộ microservice, được bật trong production và được quản lý tập trung ra sao sẽ khó hơn nhiều nếu giao tiếp giữa các team phát triển chưa đủ trưởng thành. Và chính sự chưa trưởng thành đó thường là lý do thực sự khiến người ta tách monolith ngay từ đầu

    • Có nhiều lý do hợp lý, và cũng nhiều lý do sai. Nếu có nhiều bên liên quan, yêu cầu kinh doanh liên tục thay đổi và có hơn 10 lập trình viên, thì có thể có đủ lý do để có các đơn vị có thể deploy, test và release độc lập
      Ngược lại, với ít lập trình viên và bối cảnh được định nghĩa rõ ràng, việc xử lý nhiều microservice là rất đau đớn
      Câu “những lý do còn lại chỉ là chuyển độ phức tạp của phát triển phần mềm sang bảo trì hệ thống” nghe có lý nếu phần mềm đang được phát triển tích cực. Phát triển rất tốn kém. Chi phí bảo trì hệ thống phân tán của một team lớn có thể thấp hơn chi phí phát triển một monolith khổng lồ, và rốt cuộc còn tùy tình huống
    • Đây cũng là lý do duy nhất chúng tôi muốn tách phần này của ứng dụng thành một thành phần riêng. Nó đã quá lớn nên khó bảo trì hiệu quả, còn phần còn lại của ứng dụng thì sẽ giữ nguyên
  • Bài “Chi phí của monolith” ở đâu? Ở đây tôi không thấy. Chỉ là ai cũng triển khai microservice một cách thiếu hiểu biết nên chỉ thấy vấn đề đó mà thôi
    Nếu ai cũng triển khai monolith một cách thiếu hiểu biết, hẳn đã thấy vô số bài kiểu “monolith tệ” rồi
    Mọi người không hiểu rằng cuối cùng hai hệ thống tạo ra cùng một lượng vấn đề. Đó là khác biệt giữa việc bắt một con voi làm việc và bắt 1000 con chuột làm việc. Cả hai đều gặp vấn đề, chỉ là vấn đề khác nhau
    Chọn một trong hai không làm vấn đề biến mất. Dù chọn bên nào cũng sẽ có vấn đề, và phải chọn một để đưa ra giải pháp. Nếu “kiến trúc nào” là trở ngại lớn nhất của một công ty, tôi muốn làm việc ở đó

    • Lý do hiện nay chúng ta bị cuốn quá sâu vào microservice là vì trước đây từng có khoảng 8 nghìn tỷ bài monolith tệ. Giờ đây là phản ứng ngược khi nhận ra hiệu quả so với chi phí không phù hợp với họ
  • Nếu một monolith được tách module tốt, nó khác gì so với các microservice được đặt cùng vị trí? Có lẽ chỉ là interface. Lời gọi hàm trở thành RPC, đổi lại nhận được lợi ích của việc xử lý từng thành phần riêng lẻ, ví dụ chấp nhận một chút overhead để vá lỗi
    Microservice phân tán khác gì so với microservice nằm cùng vị trí? Triển khai phức tạp hơn, nhưng có thể đặt tiến trình một cách thông minh lên phần cứng phù hợp hơn. Failure mode tăng lên, nhưng đổi lại có khả năng chịu lỗi cao hơn
    Ở đây không có câu trả lời vạn năng. Nếu cần lợi ích thì trả chi phí. Tôi cho rằng những cuộc tranh luận microservice so với monolith như thế này xuất phát từ việc áp dụng sai một pattern, thiếu công cụ giúp cuộc sống dễ dàng hơn, hoặc phần lớn là do cứ tiếp tục không refactor và thiết kế lại phần mềm 10 năm tuổi, khiến nó trở nên khổ sở khi làm việc bất kể kiến trúc ban đầu là gì

    • Chuyển từ monolith sang microservice không phải là thay đổi nhỏ, dù ban đầu có được tách module tốt đến đâu. Tất nhiên nếu thiết kế tệ thì việc chuyển đổi càng khó hơn
      Câu “lời gọi hàm trở thành RPC” nghe có vẻ đơn giản, nhưng ngay khoảnh khắc đó xảy ra, client app phải xử lý mọi đường thực thi như không truy cập được DNS server, phân giải hostname thất bại, không truy cập được host, kết nối bị từ chối, xác thực bị từ chối, chứng chỉ TLS không đáng tin cậy, vượt quá rate limit, 301 Moved Permanently, kết quả chuẩn bị chậm, timeout
      Ngay cả một app được tách module tốt cũng rất có khả năng phải thiết kế lại để xử lý vững chắc các việc này: hàng đợi bất đồng bộ, giới hạn tốc độ yêu cầu, cache kết quả, retry với backoff và logging, thông tin xác thực client/cửa hàng tin cậy/resource URL có thể cấu hình, và logging khi thất bại
      Với lời gọi hàm cục bộ, cũng cần thêm các hàm RPC và tham số để cung cấp dữ liệu vốn đã nằm trong context. Ngoài ra UI của monolith có thể phải truyền đạt cho người dùng độ trễ và lỗi mạng, những thứ trước đây không thể xảy ra
      Ngay cả monolith được làm tốt nhất cũng cần nỗ lực đáng kể để refactor thành microservice
    • Đằng sau câu “lời gọi hàm trở thành RPC” là các failure mode mới và nhiều công việc đường ống hơn
      Có thể retry an toàn lời gọi RPC này không? Exponential backoff thì sao? API load balancer có hoạt động đúng không, và nếu có thì cũng cần monitoring. Theo dõi giữa các microservice sẽ làm thế nào, giờ lại cần những thứ như OpenTelemetry
      Debug bằng breakpoint trong kiến trúc microservice khó hơn bao nhiêu? Làm sao rollback một database transaction giữa hai microservice? Một lời gọi hàm đơn giản bỗng trở nên phức tạp hơn nhiều
    • Khi bàn về microservice, phe ủng hộ gần như luôn quên phần cốt lõi của khái niệm này là micro. Bản thân service thì ai cũng đã biết, cần thì cứ tạo, không phải là khái niệm cần thuyết phục
      Mỗi khi bênh vực khái niệm này, họ bỏ qua sự phân mảnh do yêu cầu service phải rất nhỏ tạo ra. Cụ thể hơn, nếu có một monolith và một microservice thì đó không phải là các microservice. Từ microservice hàm ý mọi thứ được chia thành các service nhỏ và không có monolith
      Phần lớn lập luận ủng hộ microservice sụp đổ ngay khi nhận ra rằng nó nhất thiết phải là “micro”. Và nếu từ bỏ yêu cầu đó thì chẳng còn đề xuất nào mới mẻ hay sâu sắc
    • Nhìn từ góc độ developer, khác biệt nằm ở interface. Có đủ loại công cụ để giữ monolith đồng bộ và đúng đắn, nhưng các công cụ hỗ trợ giao tiếp liên tiến trình không thể đánh giá tĩnh và cũng hoàn toàn không mang tính tương tác
      Nhìn từ góc độ vận hành, nói “triển khai phức tạp hơn” là một cách nói giảm nói tránh khổng lồ. Mỗi package là một đối tượng phải quản lý riêng, với đặc tính và vấn đề riêng
    • Bạn mất transaction nguyên tử. Vì thế mọi hành vi nghiệp vụ trở nên phức tạp hơn vài bậc độ lớn
  • Tương tự như việc các frontend developer nghĩ rằng không thể tạo “ứng dụng web hiện đại” nếu không có frontend framework hay thư viện như React/Vue/Svelte, dạo này dường như có ý nghĩ lan truyền rằng “monolith” đồng nghĩa với việc chạy một cục hắc ín khổng lồ đáng sợ trong một instance duy nhất nên “không scale được”, điều này thật vô lý
    Một điểm khác tôi thấy là tổng lượng code lớn hơn nhiều, và phần lớn service gồm 20% code nghiệp vụ/domain80% xử lý gửi nhận thông điệp qua mạng. Dù che giấu đến đâu thì cuối cùng nó vẫn ở đó, và bằng cách này hay cách khác bạn phải xử lý mạng
    Cũng như cơn điên frontend, giáo phái microservice này chỉ kết thúc khi kinh tế sụp đổ và không còn tiền để chống đỡ những tháp Babel tận thế này nữa
    Nói thêm, microservice cũng có chỗ dùng. Trong một số công ty thu được giá trị lớn hơn chi phí, nó là phù hợp

    • Điều mọi người bỏ lỡ về microservice là mục tiêu thường không phải giải quyết vấn đề kỹ thuật, mà là giải quyết vấn đề tổ chức và con người
      Cũng có các lợi ích kỹ thuật như scale độc lập theo từng service, nhưng lợi ích lớn nhất là ranh giới sở hữu rõ ràng và ngăn chặn sự kết hợp quá mức không cần thiết
      Nếu một team nhỏ xây dựng một sản phẩm đơn lẻ, rất có thể họ chưa có vấn đề tổ chức mà microservice giải quyết, nên có thể không cần. Trong trường hợp đó, microservice là tối ưu hóa quá sớm, tức là trả chi phí để giải quyết một vấn đề chưa tồn tại
    • React/Vue/Svelte không phải framework mà là thư viện view, và tôi sẽ giữ lập trường này đến cùng
      Xa hơn nữa, độ phức tạp frontend đều nằm trong đầu: https://bower.sh/front-end-complexity
    • Tôi đồng ý rằng nhiều website dùng JavaScript và framework quá mức. Nhưng khi làm ứng dụng web cấp desktop, tôi tò mò nên dùng gì để không biến nó thành một mớ hỗn loạn khổng lồ, hoặc rốt cuộc phải phát minh lại những khái niệm vốn đã có trong các framework này
  • Bài viết dường như giả định rằng tổ chức phát triển đã hoàn toàn hỏng hóc và mất kiểm soát. Kiểu như không quyết định được sẽ làm gì trong sprint, lén đưa thư viện bên thứ ba và ngôn ngữ lạ vào, rồi âm thầm triển khai các thay đổi không tương thích lên production
    Nếu các lập trình viên không ngốc thì có vẻ microservices cũng sẽ dễ hơn

    • Đáng tiếc là khá thường gặp trường hợp trong đội có người ngốc, hoặc cả đội cùng làm trên một dự án đều ngốc
      Tôi tin chắc rằng nếu làm việc trong một đội gồm các lập trình viên thông minh và có năng lực, microservices sẽ dễ hơn. Tất nhiên, khi đó thì mọi thứ cũng sẽ dễ hơn
    • Mọi lập trình viên đều không tránh khỏi việc trở thành “kẻ ngốc” trong vài tháng đầu ở công ty mới. Vì cần thời gian để hấp thụ những thông tin cần thiết nhằm hiểu hệ thống hiện có vận hành như thế nào