Bạn đã bao giờ gặp tình huống cả team cùng làm một tính năng, ai cũng hoàn thành task đúng hạn, pull request được merge liên tục, nhưng đến lúc chuẩn bị release thì mọi thứ bắt đầu rối tung lên chưa?

Backend đã hoàn thành API, Frontend đã tích hợp xong giao diện, database cũng được cập nhật đầy đủ. Nhìn vào Jira, gần như tất cả task đều chuyển sang Done. Thế nhưng khi QA bắt đầu kiểm tra toàn bộ luồng nghiệp vụ, hàng loạt vấn đề mới xuất hiện. Dữ liệu giữa các service không khớp nhau, trạng thái được cập nhật sai thứ tự, một số trường hợp lỗi chưa được xử lý và có những API phải sửa lại vì hai bên hiểu yêu cầu theo những cách khác nhau.

Lúc này, team bắt đầu họp để tìm nguyên nhân, sửa code, thay đổi API contract, bổ sung logic và kiểm thử lại. Những công việc tưởng chừng đã hoàn thành bỗng quay trở lại trạng thái In Progress. Deadline release bị đẩy lùi, còn các developer thì phải dành thêm nhiều ngày để xử lý những vấn đề đáng lẽ có thể được phát hiện sớm hơn.

Điều đáng nói là không ai thực sự làm việc thiếu trách nhiệm. Mỗi người đều cố gắng hoàn thành phần việc của mình, thậm chí có người còn chủ động làm thêm giờ để kịp tiến độ. Nhưng cả team đã bỏ qua một việc quan trọng: thống nhất cách hệ thống sẽ hoạt động trước khi bắt đầu xây dựng nó.

Đây là một trong những bài học mà mình thấy đặc biệt có giá trị khi làm Software Engineer, nhất là với những hệ thống Backend có nhiều service, nhiều nguồn dữ liệu và nghiệp vụ phức tạp.

Chúng ta thường được khuyến khích code nhanh, xử lý task nhanh và release tính năng càng sớm càng tốt. Tuy nhiên, sau một thời gian làm việc với những hệ thống thực tế, mình nhận ra rằng tốc độ của một team không chỉ nằm ở khả năng viết code. Nó còn phụ thuộc rất nhiều vào việc mọi người có hiểu đúng bài toán, thống nhất được giải pháp và biết chính xác mình đang xây dựng điều gì hay không.

1. Khi mỗi developer đều làm đúng, nhưng cả hệ thống lại hoạt động saiLiên kết tới mục này

Hãy bắt đầu bằng một ví dụ đơn giản.

Giả sử một công ty thương mại điện tử muốn xây dựng tính năng đặt hàng và thanh toán. Hệ thống có ba service chính: Order Service quản lý đơn hàng, Payment Service xử lý thanh toán và Inventory Service quản lý tồn kho.

Vì muốn release nhanh, Tech Lead chia công việc cho ba Backend Engineer. Mỗi người phụ trách một service, tự thiết kế phần logic của mình và thống nhất sơ bộ API để các bên có thể tích hợp.

Developer thứ nhất xây dựng Order Service với logic tạo đơn hàng ở trạng thái PENDING, sau đó gọi Payment Service để thanh toán. Developer thứ hai thiết kế Payment Service theo hướng trả về kết quả thanh toán thành công hoặc thất bại. Developer thứ ba triển khai Inventory Service, chỉ trừ tồn kho sau khi nhận được thông báo thanh toán thành công.

Nghe qua, mọi thứ có vẻ hợp lý. Các API hoạt động tốt, unit test đều pass và từng service có thể được triển khai độc lập.

Nhưng khi kết nối toàn bộ hệ thống, một câu hỏi xuất hiện: Điều gì xảy ra nếu khách hàng thanh toán thành công nhưng sản phẩm đã hết hàng?

Order Service cho rằng thanh toán thành công đồng nghĩa với đơn hàng hoàn tất. Payment Service đã xử lý tiền và không có trách nhiệm kiểm tra tồn kho. Trong khi đó, Inventory Service không thể trừ số lượng hàng không còn trong kho.

Cả ba service đều hoạt động đúng theo logic mà developer của chúng đã viết, nhưng kết quả cuối cùng lại sai về mặt nghiệp vụ.

Team buộc phải quyết định lại thứ tự xử lý: nên giữ hàng trước khi thanh toán hay thanh toán trước rồi xử lý hoàn tiền nếu hết hàng? Nếu giữ hàng thì thời gian giữ bao lâu? Nếu khách hàng thanh toán thất bại thì ai giải phóng tồn kho? Nếu một service mất kết nối thì làm sao biết giao dịch đang ở trạng thái nào?

Những câu hỏi này không thể giải quyết chỉ bằng việc sửa một vài dòng code. Chúng liên quan đến cách thiết kế toàn bộ luồng nghiệp vụ và trách nhiệm giữa các service.

Một tình huống khác cũng rất phổ biến là timeout.

Giả sử Order Service gọi Payment Service để thanh toán. Payment Service đã xử lý thành công nhưng response bị mất do lỗi mạng. Order Service không nhận được kết quả nên gửi lại request. Nếu hệ thống chưa thống nhất cơ chế idempotency, cùng một giao dịch có thể bị xử lý nhiều lần.

Để giải quyết, team phải bổ sung idempotency key, cơ chế truy vấn trạng thái giao dịch và quy tắc retry. Nếu những yêu cầu này xuất hiện sau khi các service đã hoàn thành, nhiều phần code sẽ phải thay đổi cùng lúc.

Điểm chung của các vấn đề trên là chúng không bắt nguồn từ một thuật toán quá khó. Chúng xuất phát từ việc các developer chưa có cùng một cách hiểu về toàn bộ hệ thống.

Nếu trước khi chia task, cả team dành một buổi để vẽ sequence diagram, thảo luận luồng thành công và những trường hợp thất bại, nhiều câu hỏi đã có thể được làm rõ ngay từ đầu.

Thay vì mỗi người tự suy đoán, mọi người sẽ có một bản thiết kế chung để triển khai và kiểm tra.

2. System Design không phải là vẽ sơ đồ cho đẹp, mà là giúp mọi người hiểu cùng một hệ thốngLiên kết tới mục này

Khi nhắc đến System Design, nhiều người thường nghĩ đến những bài toán phỏng vấn như thiết kế YouTube, Netflix, hệ thống chat hoặc một nền tảng có hàng triệu người dùng.

Thực tế, System Design xuất hiện trong những công việc rất bình thường của một Backend Engineer.

Khi chúng ta quyết định một service nên sở hữu dữ liệu nào, một API nên được gọi đồng bộ hay bất đồng bộ, khi nào nên sử dụng message queue hoặc làm sao để xử lý một request bị gửi trùng, chúng ta đã đang đưa ra những quyết định về thiết kế hệ thống.

Vấn đề là những quyết định ấy thường được đưa ra riêng lẻ trong quá trình coding. Developer gặp vấn đề gì thì giải quyết vấn đề đó, đôi khi không có đủ thông tin về những thành phần khác.

Đối với một tính năng nhỏ, cách làm này có thể vẫn hiệu quả. Nhưng khi hệ thống có nhiều service phụ thuộc lẫn nhau, những quyết định riêng lẻ rất dễ tạo ra sự thiếu nhất quán.

Chẳng hạn, một developer quyết định cập nhật dữ liệu trực tiếp vào database để giảm độ trễ. Một developer khác lại xây dựng luồng đồng bộ thông qua message queue. Người thứ ba thêm một background job chạy định kỳ để xử lý những dữ liệu chưa được cập nhật.

Cả ba giải pháp đều có thể hợp lý trong những điều kiện nhất định. Nhưng nếu chúng cùng tồn tại mà không có một kiến trúc thống nhất, hệ thống sẽ dần xuất hiện nhiều đường cập nhật dữ liệu khác nhau. Khi dữ liệu sai lệch, việc xác định nguyên nhân trở nên rất khó khăn vì không ai chắc chắn luồng nào là luồng chính.

Đây là lúc những bản thiết kế kỹ thuật phát huy giá trị.

Một Architecture Diagram giúp team hình dung hệ thống gồm những thành phần nào và trách nhiệm của từng thành phần. Một Sequence Diagram cho thấy request và dữ liệu di chuyển qua các service theo thứ tự nào. Một State Machine giúp xác định các trạng thái hợp lệ của một đối tượng nghiệp vụ, chẳng hạn đơn hàng hoặc giao dịch.

Điều quan trọng không nằm ở việc sử dụng công cụ nào. Bạn có thể vẽ bằng Excalidraw, Mermaid, PlantUML, draw.io hoặc đơn giản là phác thảo trên bảng trắng. Giá trị thực sự nằm ở cuộc thảo luận diễn ra trong quá trình thiết kế.

Khi một developer nhìn vào sơ đồ và hỏi: “Nếu service này timeout thì sao?”, đó là một cơ hội để phát hiện rủi ro. Khi người khác nhận ra hai service đang cùng chịu trách nhiệm cập nhật một trạng thái, team có thể làm rõ quyền sở hữu dữ liệu trước khi vấn đề đi vào codebase.

Một sơ đồ tốt giúp mọi người nhìn thấy những giả định của nhau. Nhờ vậy, những bất đồng về cách hiểu được giải quyết khi chi phí thay đổi vẫn còn thấp.

System Design không chỉ giúp chúng ta biết hệ thống nên được xây dựng như thế nào, mà còn giúp cả team thống nhất vì sao phải xây dựng theo cách đó.

3. Bài học từ những hệ thống giao dịch: Một thay đổi nhỏ có thể ảnh hưởng đến rất nhiều serviceLiên kết tới mục này

Trong công việc Backend tại lĩnh vực fintech, mình có cơ hội tiếp xúc với những bài toán liên quan đến xử lý lệnh giao dịch, đồng bộ vị thế và tính toán tài sản. Đây là những hệ thống mà một thay đổi tưởng chừng nhỏ ở service này có thể ảnh hưởng trực tiếp đến nhiều thành phần khác.

Chẳng hạn, hãy xét một hệ thống giao dịch có Order Service, Portfolio Service và một adapter kết nối đến hệ thống giao dịch bên ngoài.

Khi khách hàng đặt lệnh mua, Order Service tiếp nhận yêu cầu, kiểm tra các điều kiện cần thiết và gửi lệnh đến adapter. Adapter giao tiếp với hệ thống bên ngoài, sau đó nhận các sự kiện cập nhật trạng thái hoặc khớp lệnh. Portfolio Service sử dụng những dữ liệu liên quan để cập nhật vị thế, tài sản và sức mua của khách hàng.

Nếu chỉ xét luồng đặt lệnh thành công, bài toán có vẻ không quá phức tạp.

Nhưng thực tế, một lệnh có thể được khớp một phần, bị hủy, bị từ chối hoặc thay đổi trạng thái trong lúc người dùng đang thực hiện thao tác khác. Kết nối đến hệ thống bên ngoài cũng có thể bị gián đoạn, khiến các sự kiện đến chậm hoặc cần được đồng bộ lại.

Giả sử khách hàng đặt mua 10 hợp đồng nhưng mới khớp được 4 hợp đồng. Lúc này, hệ thống cần xác định rõ số lượng đã khớp, số lượng còn chờ và tác động của từng phần đến vị thế cũng như sức mua. Nếu khách hàng tiếp tục hủy phần còn lại, các service phải hiểu thống nhất cách giải phóng khoản tiền đang được phong tỏa.

Nếu Order Service và Portfolio Service sử dụng những quy tắc khác nhau, khách hàng có thể nhìn thấy một trạng thái lệnh nhưng lại có số dư khả dụng không tương ứng.

Một tình huống khác là đồng bộ dữ liệu sau khi mất kết nối. Giả sử adapter kết nối lại và nhận được những sự kiện khớp lệnh đã từng được xử lý. Nếu Portfolio Service không có cơ chế chống xử lý trùng, vị thế có thể bị cộng thêm lần nữa. Ngược lại, nếu cơ chế lọc trùng được thiết kế sai, một sự kiện hợp lệ có thể bị bỏ qua.

Những lỗi này không đơn thuần là bug của một hàm xử lý. Chúng liên quan đến cách hệ thống xác định nguồn dữ liệu có thẩm quyền, quản lý trạng thái và bảo đảm tính nhất quán giữa các service.

Với những bài toán như vậy, mình cho rằng việc thống nhất thiết kế trước khi triển khai đặc biệt quan trọng.

Team cần làm rõ đâu là nguồn xác nhận trạng thái khớp lệnh, đâu là nơi tính toán tài sản, dữ liệu nào được cập nhật theo sự kiện và dữ liệu nào cần đối soát. Đồng thời, các developer phải thống nhất cách xử lý duplicate event, out-of-order event, timeout và những trường hợp dữ liệu giữa hai hệ thống không khớp nhau.

Ví dụ, một sequence diagram cho luồng đồng bộ vị thế không nên chỉ thể hiện rằng adapter gửi sự kiện và Portfolio Service cập nhật database. Nó cần làm rõ sự kiện được định danh thế nào, điều kiện nào cho phép cập nhật, việc xử lý trùng được ngăn chặn ở đâu và hệ thống sẽ phục hồi như thế nào nếu cập nhật thất bại.

Tương tự, đối với luồng tính toán tài sản, team cần thống nhất công thức nghiệp vụ, trách nhiệm của từng service và thời điểm các giá trị thay đổi. Những khái niệm như available balance, blocked balance, pending amount hay margin không nên được mỗi developer tự diễn giải theo một cách riêng.

Khi các quy tắc ấy được làm rõ ngay từ giai đoạn thiết kế, developer có thể tập trung vào việc triển khai logic thay vì liên tục phải hỏi lại nghiệp vụ hoặc sửa những giả định chưa chính xác.

Từ những bài toán mình từng tiếp xúc, điều mình đánh giá cao nhất ở planning không phải là khả năng dự đoán trước mọi lỗi. Đó là việc giúp team nhận ra những chỗ chưa rõ trước khi chúng trở thành những đoạn code phụ thuộc lẫn nhau.

4. Khi chạy theo deadline, chúng ta thường tạo ra technical debt mà không nhận raLiên kết tới mục này

Một điều khá thú vị là technical debt không phải lúc nào cũng xuất hiện vì code xấu.

Có những hệ thống có code được tổ chức tương đối tốt, các hàm được đặt tên rõ ràng và test coverage không thấp, nhưng vẫn rất khó thay đổi. Nguyên nhân có thể nằm ở kiến trúc thiếu nhất quán, trách nhiệm giữa các service bị chồng chéo hoặc những quyết định kỹ thuật không được ghi lại.

Hãy tưởng tượng một startup đang phát triển nhanh và liên tục bổ sung tính năng. Ban đầu, team có một service chịu trách nhiệm quản lý thông tin khách hàng. Khi cần phát triển một tính năng mới, developer nhận thấy gọi sang service này hơi bất tiện nên quyết định lưu thêm một bản sao dữ liệu vào database của service mình.

Cách làm ấy giúp tính năng được triển khai nhanh hơn. Tuy nhiên, sau vài tháng, nhiều service bắt đầu lưu những bản sao khác nhau của cùng một dữ liệu. Một service cập nhật thông tin khách hàng ngay lập tức, service khác đồng bộ qua message queue, còn một service khác chỉ cập nhật bằng cron job.

Mọi thứ vẫn hoạt động cho đến khi khách hàng thay đổi thông tin và các service bắt đầu hiển thị những giá trị khác nhau.

Để sửa lỗi, team phải truy tìm toàn bộ những nơi đang sử dụng dữ liệu, xác định nguồn dữ liệu chính, bổ sung cơ chế đồng bộ và xử lý dữ liệu cũ. Công việc này có thể tốn nhiều thời gian hơn rất nhiều so với việc thống nhất data ownership ngay từ đầu.

Đây là một ví dụ minh họa về cách technical debt được tích lũy qua nhiều quyết định nhỏ. Không nhất thiết có một developer nào đưa ra quyết định hoàn toàn sai. Vấn đề nằm ở chỗ những lựa chọn cục bộ được tối ưu cho tốc độ trước mắt nhưng thiếu một định hướng chung cho toàn bộ hệ thống.

Các tài liệu về Architecture Decision Records của AWS, Google Cloud và Microsoft đều nhấn mạnh việc ghi lại bối cảnh, phương án và lý do của những quyết định kiến trúc quan trọng. Một quyết định có thể phù hợp ở thời điểm hiện tại nhưng không còn phù hợp khi hệ thống phát triển. Nếu biết vì sao quyết định ấy được đưa ra, những người đến sau sẽ có cơ sở tốt hơn để đánh giá và thay đổi nó.

Điều này cũng liên quan đến một dạng chi phí ít được nhìn thấy: thời gian để hiểu hệ thống.

Khi một developer mới tham gia team, họ không chỉ cần biết service nào có những API gì. Họ cần hiểu luồng nghiệp vụ, mối quan hệ giữa các service, các quy tắc dữ liệu và những giới hạn đã được đặt ra.

Nếu không có tài liệu thiết kế, quá trình onboarding có thể trở thành việc đọc hàng nghìn dòng code, hỏi nhiều đồng nghiệp và tự suy luận từ những hành vi hiện tại của hệ thống. Ngay cả khi làm như vậy, họ vẫn có thể bỏ sót những lý do lịch sử hoặc những edge case quan trọng.

Một bản thiết kế được duy trì hợp lý có thể giúp giảm đáng kể sự phụ thuộc vào kiến thức nằm trong đầu một vài cá nhân.

5. Planning tốt còn giúp code review trở nên có ý nghĩa hơnLiên kết tới mục này

Mình nghĩ một lợi ích rất lớn của technical planning là giúp cải thiện chất lượng code review, nhưng điều này thường ít được nhắc đến.

Trong nhiều team, code review chủ yếu tập trung vào implementation. Reviewer kiểm tra cách tổ chức code, tên biến, xử lý lỗi, hiệu năng truy vấn và test case. Đây đều là những công việc cần thiết, nhưng chúng chưa đủ để bảo đảm một tính năng được thiết kế đúng.

Giả sử một developer gửi pull request bổ sung logic tính sức mua vào Order Service. Code được viết sạch sẽ, test pass và không có lỗi rõ ràng. Nếu reviewer chỉ nhìn vào implementation, pull request ấy hoàn toàn có thể được approve.

Nhưng nếu trước đó team đã thống nhất Portfolio Service chịu trách nhiệm tính toán sức mua, reviewer sẽ nhận ra ngay vấn đề: logic nghiệp vụ đang được đưa vào sai nơi.

Lúc này, cuộc thảo luận không còn xoay quanh việc đoạn code có đẹp hay không, mà là liệu implementation có tuân theo kiến trúc đã thống nhất hay không.

Đây chính là sự khác biệt giữa review code và review giải pháp kỹ thuật.

Một bản thiết kế rõ ràng giúp reviewer có cơ sở để đánh giá. Thay vì phải suy đoán ý định của người viết code, họ có thể đối chiếu implementation với sequence diagram, API contract, state machine hoặc những quyết định kiến trúc đã được ghi lại.

Nó cũng giúp giảm những cuộc tranh luận không cần thiết trong pull request.

Nếu team chưa thống nhất nên sử dụng synchronous API hay asynchronous messaging, cuộc tranh luận có thể chỉ bắt đầu khi developer đã hoàn thành phần lớn implementation. Khi đó, việc thay đổi phương án không còn đơn giản vì người viết đã đầu tư nhiều thời gian vào giải pháp hiện tại.

Ngược lại, nếu những lựa chọn này được thảo luận trước khi code, developer sẽ biết rõ phạm vi triển khai và reviewer cũng hiểu lý do của quyết định.

Đặc biệt với các bạn Middle hoặc Junior Engineer, việc được tham gia technical design review là một cơ hội rất tốt để phát triển tư duy hệ thống. Thay vì chỉ nhận task và triển khai theo yêu cầu, các bạn có thể học cách đặt câu hỏi về nghiệp vụ, phân tích trade-off và nhìn thấy tác động của một thay đổi đến những thành phần khác.

Một team mạnh không phải là team mà chỉ có Tech Lead hoặc Senior Engineer hiểu toàn bộ kiến trúc. Đó là team mà nhiều thành viên có thể giải thích được hệ thống hoạt động như thế nào, vì sao các quyết định quan trọng được đưa ra và điều gì cần cân nhắc khi thay đổi chúng.

6. Vậy có phải trước khi code, chúng ta cần họp thật nhiều và viết tài liệu thật dài?Liên kết tới mục này

Đến đây, có thể sẽ có một ý kiến phản biện khá hợp lý: nếu dành quá nhiều thời gian cho planning, chẳng phải chúng ta đang làm chậm tiến độ sao?

Mình đồng ý rằng planning cũng có chi phí. Một team có thể mất nhiều ngày để tranh luận về những kiến trúc rất phức tạp trong khi bài toán thực tế chỉ cần một giải pháp đơn giản. Một tài liệu dài hàng chục trang nhưng không ai đọc hoặc không được cập nhật cũng không mang lại nhiều giá trị.

Vì vậy, điều quan trọng không phải là planning càng nhiều càng tốt, mà là planning đủ sâu với mức độ rủi ro và độ phức tạp của thay đổi.

Nếu task chỉ là bổ sung một trường dữ liệu vào API hiện có, sửa validation hoặc tối ưu một truy vấn đơn giản, có thể chỉ cần mô tả ngắn trong ticket và một pull request rõ ràng.

Nhưng nếu tính năng liên quan đến nhiều service, thay đổi dữ liệu quan trọng, bổ sung trạng thái nghiệp vụ hoặc tích hợp với hệ thống bên ngoài, mình nghĩ team nên có một bước technical design trước khi chia task triển khai.

Một quy trình nhẹ có thể bắt đầu từ việc người phụ trách trình bày bài toán, phạm vi và những yêu cầu quan trọng. Sau đó, developer phác thảo luồng xử lý, xác định các service tham gia và trách nhiệm của từng thành phần.

Khi đã có bản thiết kế ban đầu, cả team cùng review những trường hợp có thể xảy ra lỗi: request timeout, sự kiện đến trễ, dữ liệu bị xử lý trùng, service không khả dụng hoặc migration thất bại. Những câu hỏi chưa có lời giải cần được ghi lại, thay vì âm thầm để từng developer tự quyết định trong lúc coding.

Sau khi thống nhất những điểm quan trọng, team có thể chia task dựa trên các API contract và dependency đã rõ ràng. Lúc này, việc triển khai song song mới thực sự phát huy hiệu quả, vì mọi người không phải liên tục chờ nhau thay đổi thiết kế.

Một điểm nữa mình cho rằng rất quan trọng là cần xác định kế hoạch release ngay từ lúc thiết kế, đặc biệt với hệ thống production.

Ví dụ, nếu một thay đổi cần cập nhật database schema và nhiều service, team phải biết phiên bản cũ có tương thích với phiên bản mới không, thứ tự deploy như thế nào và có thể rollback khi xảy ra sự cố hay không. Một thiết kế chỉ mô tả hệ thống sau khi hoàn thành nhưng không giải thích cách chuyển từ trạng thái hiện tại sang trạng thái mới vẫn chưa phải là một kế hoạch triển khai đầy đủ.

Với những thay đổi có rủi ro cao, team có thể bổ sung feature flag, rollout theo từng giai đoạn, monitoring và kế hoạch rollback. Những quyết định này giúp việc release an toàn hơn, thay vì chỉ tập trung vào thời điểm code được merge.

Tất nhiên, không phải mọi thứ đều có thể dự đoán trước. Có những vấn đề chỉ xuất hiện khi thử nghiệm hoặc khi hệ thống vận hành thực tế. Nhưng planning tốt giúp chúng ta biết đâu là giả định cần kiểm chứng, đâu là rủi ro cần theo dõi và khi nào cần điều chỉnh thiết kế.

7. Từ người hoàn thành task đến người có thể dẫn dắt giải phápLiên kết tới mục này

Khi mới bắt đầu sự nghiệp, một Software Engineer thường tập trung vào việc học ngôn ngữ lập trình, framework, database và cách viết code tốt. Chúng ta muốn hoàn thành task nhanh hơn, xử lý bug hiệu quả hơn và tạo ra những pull request chất lượng hơn.

Đó là những nền tảng quan trọng. Nhưng khi kinh nghiệm tăng lên, mình nghĩ một kỹ sư cũng cần dần chuyển từ tư duy “Làm thế nào để hoàn thành task này?” sang “Làm thế nào để giải quyết bài toán này đúng cách và giúp cả team triển khai hiệu quả?”

Sự thay đổi ấy thể hiện qua những việc khá cụ thể.

Thay vì nhận yêu cầu rồi bắt đầu coding ngay, bạn chủ động làm rõ nghiệp vụ và các trường hợp chưa được mô tả. Thay vì chỉ quan tâm đến service mình phụ trách, bạn tìm hiểu những service liên quan và ảnh hưởng của thay đổi đến toàn bộ hệ thống. Thay vì chờ đến code review mới thảo luận kiến trúc, bạn đề xuất một bản thiết kế để mọi người cùng góp ý trước khi triển khai.

Với mình, đây là một trong những khác biệt quan trọng giữa việc trở thành một developer có khả năng thực thi tốt và một engineer có thể đảm nhận trách nhiệm lớn hơn.

Một Senior Engineer không nhất thiết phải là người viết nhiều code nhất hay giải quyết mọi vấn đề một mình. Đôi khi, đóng góp lớn nhất của họ là phát hiện một điểm chưa hợp lý trong thiết kế, giúp team tránh một thay đổi tốn kém hoặc tạo ra sự thống nhất để nhiều developer có thể làm việc hiệu quả cùng nhau.

Và đây không phải trách nhiệm riêng của Senior. Một bạn Middle Engineer hoàn toàn có thể chủ động vẽ sequence diagram cho tính năng mình phụ trách, đặt câu hỏi về failure scenarios hoặc đề xuất một phương án rõ ràng để team review.

Những hoạt động ấy cũng là một phần của công việc engineering. Chúng không tạo ra nhiều dòng code, nhưng giúp những dòng code được viết sau đó có mục đích và cấu trúc rõ ràng hơn.

Mình không nghĩ rằng cứ có System Design là hệ thống sẽ không còn bug, hoặc một bản planning tốt có thể thay thế khả năng coding và kiểm thử. Thiết kế vẫn có thể sai, yêu cầu vẫn có thể thay đổi và production luôn có những tình huống khó dự đoán.

Nhưng có một sự khác biệt rất lớn giữa việc cả team cùng triển khai một giải pháp đã được thảo luận và việc mỗi người tự xây dựng một phần hệ thống dựa trên những giả định riêng.

Quay lại câu chuyện ban đầu, có lẽ team ấy đã có thể bắt đầu code muộn hơn một chút nếu dành thời gian vẽ luồng nghiệp vụ, thống nhất trách nhiệm và review những trường hợp lỗi. Đổi lại, họ sẽ bước vào giai đoạn triển khai với một hướng đi rõ ràng hơn, giảm những thay đổi dây chuyền khi tích hợp và có cơ sở tốt hơn để review cũng như kiểm thử.

Cuối cùng, mục tiêu của một team engineering không phải là viết được nhiều code nhất trong thời gian ngắn nhất. Mục tiêu là đưa những tính năng đúng, ổn định và có thể bảo trì đến tay người dùng một cách hiệu quả.

Code nhanh giúp bạn hoàn thành phần việc của mình. System Design và Planning tốt giúp cả team cùng đi đúng hướng, giảm thời gian sửa sai và release với sự tự tin lớn hơn.

Đôi khi, một giờ ngồi cùng nhau trước bảng trắng không làm chúng ta chậm đi. Nó giúp tránh việc phải dành nhiều ngày sau đó để tìm hiểu vì sao những phần code vốn đều chạy đúng lại không thể hoạt động đúng khi kết hợp với nhau.

---

Nguồn tham khảoLiên kết tới mục này

  1. AWS — Architecture Decision Records — Cách ghi lại và chia sẻ những quyết định kiến trúc quan trọng.
  2. Google Cloud — Architecture Decision Records — Lưu trữ bối cảnh, lựa chọn và lý do của các quyết định kỹ thuật.
  3. Microsoft Learn — Maintain an Architecture Decision Record — Thực hành quản lý quyết định kiến trúc trong team.
  4. AWS Builders' Library — Timeouts, Retries, and Backoff with Jitter — Những rủi ro cần thiết kế trước trong hệ thống phân tán.
  5. Martin Fowler — Design Stamina Hypothesis — Phân tích mối quan hệ giữa chất lượng thiết kế và tốc độ phát triển phần mềm theo thời gian.