hoctrokha
Member
Chào mọi người,
Hôm nay lượn lờ thấy có 1 topic nói về repository pattern anh em bàn tán sôi nổi quá:
https://voz.vn/t/thao-luan-repository-pattern-la-cai-bullsh-t-nhat-khi-da-co-orm-framework.490455/
Khổ cái mình không có nhiều thời gian nên không thể nào follow hết cả 10 trang comment được, nhưng nhìn chung mình thấy vấn đề mọi người đang tranh cãi là:
Thấy ỏm tỏi quá nên mình viết 1 bài chia sẻ kiến thức của mình về repository pattern, nếu có gì hơi lạc đề hoặc không đúng với những gì mọi người đang thảo luận trong bài kia thì cũng mong mọi người bỏ qua, cứ xem như đây là 1 bài chia sẻ kiến thức là được.
Một xíu về bản thân thì mình hiện đang làm technical architect cho một công ty ở việt nam thôi, không phải làm cho amazone hay google j hết.
Tất cả kiến thức mình có đều từ kinh nghiệm bản thân mà ra thôi.
Ok, trả lời luôn với mọi người là repository pattern vừa BS vừa không hề BS.
Nhưng đầu tiên, Repository pattern dùng để abstract cái ORM đúng là BS.
Vâng nó đúng là BS, lý do nó BS thì mình đoán mọi người cũng đã nói nhiều trong post kia. Mình chỉ muốn nói thêm về trải nghiệm của mình với nó thôi.
Hồi sinh viên chắc ai cũng được thầy cô hướng dẫn mô hình ba lớp, n lớp các thứ, theo lý thuyết thì nó abstract cách lấy data, sau này thay thế tầng đó thì chỉ cần viết lại là xong. Nghe đúng là tuyệt vời và mình vẫn thấy nhiều người làm theo kiểu này, cho dù họ chưa hề có kinh nghiệm về việc thay nguyên tầng DAL trong 1 project lâu năm (chính mình nhiều năm trước cũng đã làm y chang như vậy).
Tất nhiên dần dần những "củ khoai" của tầng DAL cũng lộ diện khiến ai cũng bực mình. Nào là code bị lặp lại, gần như phải cover toàn bộ các API của ORM, rồi về transaction, làm sao để giữ transaction giữa các repository mà vẫn đảm bảo được tính đóng gói, sự abstract của tầng DAL ...
Rồi tiếp đến nếu bạn viết test cho 1 controller thì sao?
Đến bước viết test thì không thể nào các bạn mock nguyên cả tầng DAL được, các bạn phải mock thằng DB bằng in-memory db chứ. Mà in-memory db lại được cung cấp bởi thằng ORM.
OH sh*t, vậy việc abstract ORM của các bạn còn ý nghĩa gì nữa khi test của các bạn bị couple với ORM?
Kinh nghiệm của mình là "Tránh tối đa việc mock thư viện bạn đang dùng". Ví dụ bạn dùng EF thì đừng bao giờ đi mock EF, ở dưới client side bạn dùng axios thì đừng bao giờ mock axios, bởi nó là một phần trong code của các bạn, là một phần của hệ thống.
Trên production bọn nó nói chuyện với nhau, tương tác với nhau, nhưng khi test các bạn quăng 1 thằng fake vào thì khả năng chạy thật bị lỗi là điều dễ hiểu.
Thay vào đó hãy mock các "edge", mock db để ORM nó query, mock các api để trả về data cho axios ...
OK, vậy nếu theo cách tiếp cận đó thì việc thay tầng DAL hoặc thay ORM đúng là thảm cmn họa.
Các bạn sẽ phải viết lại tất cả các test ... coi như toang luôn dự án chứ còn j nữa.
Có thể các bạn sẽ hỏi: "chứ mình muốn đổi DB từ NoSQL qua SQL bởi hiện có những hạn chế, theo chủ thớt nói thì chịu ah?"
Ừ thì đập lại code build lại thôi, đó cũng là lý do microservice ra đời,
Cố gắng làm theo microservice để tránh những rủi do kiểu đó nhé, cố gắng đập đi build lại trong vòng 2 tuần.
Vậy khi nào repository pattern không phải BS?
Tất nhiên là khi mới tốt nghiệp repository là thần thánh rồi, Đùa thôi.
Trường hợp 1: CQRS
Khi mình bắt đầu thử làm với CQRS thì tự nhiên repository là một thứ được áp dụng vào khá tự nhiên.
Các repository giờ không còn là proxy để các bạn truy vấn các bảng dưới db nữa, nó sẽ thành repository cho các "Aggregate root".
Mục đích của việc dùng repository trong hoàn cảnh này không phải để thay ORM hay là thay tất cả Aggregate root, mà chỉ đơn giản là nó phù hợp thôi.
Nhưng chỉ nên dùng repository cho bên write thôi, còn bên read thì mình dùng thẳng ORM luôn, việc dùng repository để lấy cả Aggregate root ra là điều không cần thiết.
Mình không nghĩ mình cần giải thích thêm vì nếu bạn đã biết CQRS, bạn có lẽ đã hiểu, nếu bạn chưa biết, khả năng cao giải thích cũng vậy.
Trường hợp 2: ORM không hỗ trợ in-memory db
nếu ORM các bạn dùng đủ ngon, có hỗ trợ In-Memory db thì khỏi nói, cứ thế mà viết test thôi. Nhưng nếu ORM của các bạn ko hỗ trợ thì khi test các bạn có 2 option:
Nếu bạn làm theo TDD thì dùng docker là điều ko thể, ko ai ngồi đợi 1 phút để có kết quả test cho 1 unit test cả.
Bởi vậy bắt buộc phải dùng repository pattern trong trường hợp này, nhưng tốt nhất bạn nên chọn ORM cẩn thận, không bao giờ chọn mấy thằng ko hỗ trợ in-memory db nhé.
Kết luận
Cũng chả có kết luận nào mang tính đao to búa lớn đâu, các bạn nếu không làm CQRS thì mình nghĩ nên dẹp repository pattern đi, chọn ORM cho chuẩn và tập trung viết test cho đàng hoàng.
Code thì lỗi nhoe mà ngồi đó pattern này với chả pattern nọ.
Hôm nay lượn lờ thấy có 1 topic nói về repository pattern anh em bàn tán sôi nổi quá:
https://voz.vn/t/thao-luan-repository-pattern-la-cai-bullsh-t-nhat-khi-da-co-orm-framework.490455/
Khổ cái mình không có nhiều thời gian nên không thể nào follow hết cả 10 trang comment được, nhưng nhìn chung mình thấy vấn đề mọi người đang tranh cãi là:
- Repository pattern có phải là BS không?
- Có nên abstract cái ORM không?
Thấy ỏm tỏi quá nên mình viết 1 bài chia sẻ kiến thức của mình về repository pattern, nếu có gì hơi lạc đề hoặc không đúng với những gì mọi người đang thảo luận trong bài kia thì cũng mong mọi người bỏ qua, cứ xem như đây là 1 bài chia sẻ kiến thức là được.
Một xíu về bản thân thì mình hiện đang làm technical architect cho một công ty ở việt nam thôi, không phải làm cho amazone hay google j hết.
Tất cả kiến thức mình có đều từ kinh nghiệm bản thân mà ra thôi.
Ok, trả lời luôn với mọi người là repository pattern vừa BS vừa không hề BS.
Nhưng đầu tiên, Repository pattern dùng để abstract cái ORM đúng là BS.
Vâng nó đúng là BS, lý do nó BS thì mình đoán mọi người cũng đã nói nhiều trong post kia. Mình chỉ muốn nói thêm về trải nghiệm của mình với nó thôi.
Hồi sinh viên chắc ai cũng được thầy cô hướng dẫn mô hình ba lớp, n lớp các thứ, theo lý thuyết thì nó abstract cách lấy data, sau này thay thế tầng đó thì chỉ cần viết lại là xong. Nghe đúng là tuyệt vời và mình vẫn thấy nhiều người làm theo kiểu này, cho dù họ chưa hề có kinh nghiệm về việc thay nguyên tầng DAL trong 1 project lâu năm (chính mình nhiều năm trước cũng đã làm y chang như vậy).
Tất nhiên dần dần những "củ khoai" của tầng DAL cũng lộ diện khiến ai cũng bực mình. Nào là code bị lặp lại, gần như phải cover toàn bộ các API của ORM, rồi về transaction, làm sao để giữ transaction giữa các repository mà vẫn đảm bảo được tính đóng gói, sự abstract của tầng DAL ...
Rồi tiếp đến nếu bạn viết test cho 1 controller thì sao?
Đến bước viết test thì không thể nào các bạn mock nguyên cả tầng DAL được, các bạn phải mock thằng DB bằng in-memory db chứ. Mà in-memory db lại được cung cấp bởi thằng ORM.
OH sh*t, vậy việc abstract ORM của các bạn còn ý nghĩa gì nữa khi test của các bạn bị couple với ORM?
Kinh nghiệm của mình là "Tránh tối đa việc mock thư viện bạn đang dùng". Ví dụ bạn dùng EF thì đừng bao giờ đi mock EF, ở dưới client side bạn dùng axios thì đừng bao giờ mock axios, bởi nó là một phần trong code của các bạn, là một phần của hệ thống.
Trên production bọn nó nói chuyện với nhau, tương tác với nhau, nhưng khi test các bạn quăng 1 thằng fake vào thì khả năng chạy thật bị lỗi là điều dễ hiểu.
Thay vào đó hãy mock các "edge", mock db để ORM nó query, mock các api để trả về data cho axios ...
OK, vậy nếu theo cách tiếp cận đó thì việc thay tầng DAL hoặc thay ORM đúng là thảm cmn họa.
Các bạn sẽ phải viết lại tất cả các test ... coi như toang luôn dự án chứ còn j nữa.
Có thể các bạn sẽ hỏi: "chứ mình muốn đổi DB từ NoSQL qua SQL bởi hiện có những hạn chế, theo chủ thớt nói thì chịu ah?"
Ừ thì đập lại code build lại thôi, đó cũng là lý do microservice ra đời,
Cố gắng làm theo microservice để tránh những rủi do kiểu đó nhé, cố gắng đập đi build lại trong vòng 2 tuần.
Vậy khi nào repository pattern không phải BS?
Tất nhiên là khi mới tốt nghiệp repository là thần thánh rồi, Đùa thôi.
Trường hợp 1: CQRS
Khi mình bắt đầu thử làm với CQRS thì tự nhiên repository là một thứ được áp dụng vào khá tự nhiên.
Các repository giờ không còn là proxy để các bạn truy vấn các bảng dưới db nữa, nó sẽ thành repository cho các "Aggregate root".
Mục đích của việc dùng repository trong hoàn cảnh này không phải để thay ORM hay là thay tất cả Aggregate root, mà chỉ đơn giản là nó phù hợp thôi.
Nhưng chỉ nên dùng repository cho bên write thôi, còn bên read thì mình dùng thẳng ORM luôn, việc dùng repository để lấy cả Aggregate root ra là điều không cần thiết.
Mình không nghĩ mình cần giải thích thêm vì nếu bạn đã biết CQRS, bạn có lẽ đã hiểu, nếu bạn chưa biết, khả năng cao giải thích cũng vậy.
Trường hợp 2: ORM không hỗ trợ in-memory db
nếu ORM các bạn dùng đủ ngon, có hỗ trợ In-Memory db thì khỏi nói, cứ thế mà viết test thôi. Nhưng nếu ORM của các bạn ko hỗ trợ thì khi test các bạn có 2 option:
- dùng docker tạo 1 db lên dùng chung cho tất cả các test: cái này tù chày, chậm và dễ sinh ra các flaky test do các test chạy song song
- dùng repository pattern và mock tất cả các method khi test => max tù chày và độ confident cực thấp
Nếu bạn làm theo TDD thì dùng docker là điều ko thể, ko ai ngồi đợi 1 phút để có kết quả test cho 1 unit test cả.
Bởi vậy bắt buộc phải dùng repository pattern trong trường hợp này, nhưng tốt nhất bạn nên chọn ORM cẩn thận, không bao giờ chọn mấy thằng ko hỗ trợ in-memory db nhé.
Kết luận
Cũng chả có kết luận nào mang tính đao to búa lớn đâu, các bạn nếu không làm CQRS thì mình nghĩ nên dẹp repository pattern đi, chọn ORM cho chuẩn và tập trung viết test cho đàng hoàng.
Code thì lỗi nhoe mà ngồi đó pattern này với chả pattern nọ.