Nguyen Le PhongNguyen Le Phong

Giữ interface ổn định khi implementation thay đổi

Một interface bền là lời hứa rõ với caller: giữ hành vi sản phẩm ổn định trong khi storage, provider và implementation bên dưới tiếp tục tiến hóa.

Buổi thảo luận bắt đầu từ một hình chữ nhật nhỏ trên bảng trắng. Nó đánh dấu ranh giới giữa hai phần của hệ thống. Code phía sau vẫn rối, bảng dữ liệu có thể còn đổi, tên sản phẩm cũng chưa chắc giữ nguyên. Cả nhóm vì thế không cố chốt hình dạng bên trong. Họ quay lại một câu khó hơn: phần này nên hứa điều gì với phần còn lại của codebase?

Ba người ngồi quanh bàn có laptop và các thẻ giấy; phía sau là bảng trắng vẽ sơ đồ nhiều khối nối nhau.
Một ranh giới có ích khi lời hứa của nó rõ hơn những chi tiết đang còn thay đổi bên trong.

Đó là giá trị lặng lẽ của một interface tốt. Nó cho codebase một câu nói ổn định trong khi implementation tiếp tục học. Storage chuyển sang bảng khác, provider được thay, một lối tắt hiệu năng bị gỡ, nhánh xử lý tạm trở thành workflow chính thức. Nếu mọi caller bám vào cấu trúc nội bộ, mỗi thay đổi sẽ phải thương lượng với cả hệ thống. Nếu caller bám vào một lời hứa rõ, phần bên trong có chỗ để tiến hóa.

Interface bền không thể được thiết kế tách rời thực tế. Boundary hữu ích thường lộ ra khi quan sát những thay đổi thật: hành vi nào cứ trở lại trong kế hoạch, luật nào phải giải thích hết lần này đến lần khác, chi tiết nào cứ rò sang màn hình, background job và test dù những nơi đó không nên biết. Các vết rò cho ta chất liệu thiết kế đáng tin hơn một sơ đồ kiến trúc trắng tinh.

“Bền” cũng không đồng nghĩa với “to”. Có contract nên nhỏ vì chỉ cần gọi đúng tên một việc. Có contract phải mang thêm ngữ cảnh, bởi giấu nó đi sẽ buộc mọi caller tự đoán. Không cần tranh xem interface đã tối giản theo lý thuyết chưa. Cần xem nó có che được những chi tiết dễ đổi, nhưng vẫn để lộ hành vi quan trọng mà caller phải hiểu hay không.

Test là nơi lời hứa được viết thành thứ có thể kiểm tra. Bài test quanh interface không chỉ chứng minh implementation hiện tại chạy được; nó phải mô tả hành vi mà implementation tương lai vẫn phải giữ. Khi test nói bằng ngôn ngữ sản phẩm thay vì tên bảng hay tên provider, cả nhóm có một guardrail chống lại việc vô tình viết lại ý nghĩa.

Một interface ổn định còn giảm số lịch sử mà đồng đội phải thuộc lòng trước khi sửa code an toàn. Người đến sau không cần biết mọi lý do khiến phiên bản đầu trông như vậy. Họ cần biết boundary cam kết gì, từ chối cam kết gì và ví dụ nào phơi ra edge case. Sự rõ ràng ấy giải phóng họ khỏi việc phải tái hiện cả quá khứ mới dám thay đổi hiện tại.

Phần khó là chấp nhận interface là trách nhiệm, không phải đồ trang trí kiến trúc. Một khi các phần khác đã tin vào contract, đổi nó tùy tiện sẽ chuyển chi phí sang mọi caller. Bởi vậy versioning, deprecation, hướng dẫn migration và những ví dụ tưởng như nhàm chán đều cần thiết. Chúng cho hệ thống di chuyển mà không giả vờ mọi chuyển động đều miễn phí.

Khi implementation được thay mà những caller xung quanh không hoảng hốt, boundary ấy đã làm đúng việc. Code bên trong có thể sống vài đời; lời hứa bên ngoài vẫn đọc được. Đó là một mảnh kiến trúc không gây ồn, nhưng mua lại rất nhiều tự do cho những thay đổi về sau.

Bạn thấy bài viết thế nào?