thảo luận Vấn đề về separate Repository/Service/Controller

  • Người tạo chủ đề Người tạo chủ đề soledad86
  • Ngày bắt đầu Ngày bắt đầu
Hi bạn,
Mô hình của bạn đưa ra thì đúng là thích hợp hơn so với mô hình cũ. Còn việc thuyết phục thì bạn cứ đem mấy cái examples của Spring MVC là các ông ấy im hết thôi mà.

Nhưng mô hình của bạn (cũng như nhiều người khác) thực ra cũng không phải là thích hợp lắm đâu. Để mình phân tích một vài điểm "chưa tương thích" nhé:

- Sự chồng chéo trong code: với mỗi controller, service, repo thì sẽ có sự chồng chéo lẫn nhau.
Eg: UserController sẽ phải có trong đó UserService, OrderService,... Xong rồi trong mỗi Service sẽ lại đẻ ra cả đống repo ở dưới. Tới lượt các controller khác cũng cần các services khác.
--> Sự chồng chéo trong code này một khi có bug (điều hiển nhiên) sẽ rất khó để sửa vì mỗi thằng lại liên quan tới cả đống thằng khác.

- Khó trong việc maintain: với mỗi function khi maintain, thêm/sửa các chức năng, dev sẽ phải tìm tất cả những thằng liên quan --> Việc miss reqs là chuyện rất dễ xảy ra.

Việc có 1 project structure ngon cũng cần nhiều exp và kĩ năng đấy.
 
Một trong những cách chia hợp lý và dễ áp dụng là dùng Kiến Trúc Củ Hành (Onion Architecture). Nó dễ tiếp cận hơn là Hexagonal Architecture hay Clean Architecture.

Core của ứng dụng là domain chứa Model, Entity, Repository Interface.
Layer ngay bên ngoài core là Application hoặc Service
Ngoài cùng là transportation layer chính là HTTP Controller, gRPC Controller, AMQP Handler, CLI, Test...

Các layer bên trong không biết không quan tâm các layer bên ngoài.

Chia đơn giản 3 layers vậy thôi, phức tạp hơn thì chia nhỏ hơn nữa tùy yêu cầu.
 
Hi bạn,
Mô hình của bạn đưa ra thì đúng là thích hợp hơn so với mô hình cũ. Còn việc thuyết phục thì bạn cứ đem mấy cái examples của Spring MVC là các ông ấy im hết thôi mà.

Nhưng mô hình của bạn (cũng như nhiều người khác) thực ra cũng không phải là thích hợp lắm đâu. Để mình phân tích một vài điểm "chưa tương thích" nhé:

- Sự chồng chéo trong code: với mỗi controller, service, repo thì sẽ có sự chồng chéo lẫn nhau.
Eg: UserController sẽ phải có trong đó UserService, OrderService,... Xong rồi trong mỗi Service sẽ lại đẻ ra cả đống repo ở dưới. Tới lượt các controller khác cũng cần các services khác.
--> Sự chồng chéo trong code này một khi có bug (điều hiển nhiên) sẽ rất khó để sửa vì mỗi thằng lại liên quan tới cả đống thằng khác.


- Khó trong việc maintain: với mỗi function khi maintain, thêm/sửa các chức năng, dev sẽ phải tìm tất cả những thằng liên quan --> Việc miss reqs là chuyện rất dễ xảy ra.

Việc có 1 project structure ngon cũng cần nhiều exp và kĩ năng đấy.
Vấn đề là bạn không hiểu cách vận hành của nó, cách bạn giải thích nó ko logic, ko clear.

Theo pattern như đã nói ở #1, các layers gồm:
  • Repo: mỗi table sẽ chỉ có tối đa 1 repo làm nhiệm vụ CRUD cho table đó. (xem nó như là lính canh của từng bảng, mình bảo m cập nhật giúp t cái này, xóa cái kia trong bảng, tìm... nhiệm vụ của nó chỉ có v, ko hơn ko kém).
  • Service: Service nào cần CRUD vào bảng nào sẽ inject đến đúng Repo tương ứng.
=> n-Service -> 1 repo
Example: ví dụ
1. ProductService: cần lưu thông tin vào table Product và table Storage (images path) khi đó cần inject ProductRepo và StorageRepo
2. FeedbackService tương tự cho table Feedback và table Storage (images path)
...
Do đó không có chuyện mỗi service đẻ ra một đống repo ở dưới.

* Func/Controller:
- API nào cần sử dụng đến service nào thì call service đó. Việc unitest, debug, maintain thì dựa theo
business trong service. Cái này cực dễ vì mỗi method call từ service đã rõ ràng từ input và output.

Tôi lấy ví dụ:
Trong UserController / API Login : có 2 actions chính.
  • userService.login(loginPayload); input payload = output logged credentials (1)
  • userService.getBasicInfo(); output = user logged dto; (2)

Do đó việc debug, unit-test là cực kì dễ dàng ... dựa vào (1)(2)...(x) để debug.
Và code theo pattern nào thì mấy ông cũng phải đảm bảo cái SOLID giùm chứ không thì có bn pattern cũng vậy.
 
Một trong những cách chia hợp lý và dễ áp dụng là dùng Kiến Trúc Củ Hành (Onion Architecture). Nó dễ tiếp cận hơn là Hexagonal Architecture hay Clean Architecture.

Core của ứng dụng là domain chứa Model, Entity, Repository Interface.
Layer ngay bên ngoài core là Application hoặc Service
Ngoài cùng là transportation layer chính là HTTP Controller, gRPC Controller, AMQP Handler, CLI, Test...

Các layer bên trong không biết không quan tâm các layer bên ngoài.

Chia đơn giản 3 layers vậy thôi, phức tạp hơn thì chia nhỏ hơn nữa tùy yêu cầu.
Đồng tình với bác. Về view của người chị hàng xóm của tôi thì architecture dần theo hướng làm giảm sự phụ thuộc càng nhiều càng tốt. (no dependencies). Khi đó mỗi chức năng, tính năng sẽ có nhiệm vụ riêng (single-responsibility). dễ dàng mang vác, đóng gói (Open-Close) -> bundle module từ đó cũng tiện lợi hơn.
 
Vấn đề là bạn không hiểu cách vận hành của nó, cách bạn giải thích nó ko logic, ko clear.

Theo pattern như đã nói ở #1, các layers gồm:
  • Repo: mỗi table sẽ chỉ có tối đa 1 repo làm nhiệm vụ CRUD cho table đó. (xem nó như là lính canh của từng bảng, mình bảo m cập nhật giúp t cái này, xóa cái kia trong bảng, tìm... nhiệm vụ của nó chỉ có v, ko hơn ko kém).
  • Service: Service nào cần CRUD vào bảng nào sẽ inject đến đúng Repo tương ứng.
=> n-Service -> 1 repo
Example: ví dụ
1. ProductService: cần lưu thông tin vào table Product và table Storage (images path) khi đó cần inject ProductRepo và StorageRepo
2. FeedbackService tương tự cho table Feedback và table Storage (images path)
...
Do đó không có chuyện mỗi service đẻ ra một đống repo ở dưới.

* Func/Controller:
- API nào cần sử dụng đến service nào thì call service đó. Việc unitest, debug, maintain thì dựa theo
business trong service. Cái này cực dễ vì mỗi method call từ service đã rõ ràng từ input và output.

Tôi lấy ví dụ:
Trong UserController / API Login : có 2 actions chính.
  • userService.login(loginPayload); input payload = output logged credentials (1)
  • userService.getBasicInfo(); output = user logged dto; (2)

Do đó việc debug, unit-test là cực kì dễ dàng ... dựa vào (1)(2)...(x) để debug.
Và code theo pattern nào thì mấy ông cũng phải đảm bảo cái SOLID giùm chứ không thì có bn pattern cũng vậy.

Bạn suy nghĩ quá ngây thơ, điều này chứng tỏ rằng bạn không có quá nhiều kinh nghiệm về cái này. Để mình chỉ ra vài chỗ "chưa đúng" của bạn nhé.

1. Repo mà đơn giản tới mức chỉ có CRUD thì quẳng Service đi được rồi. Hay đơn giản hơn, giả sử có 1 query dạng join tables thì bạn để query đấy ở đâu?

2. Dù bạn phân chia có tốt tới ntn thì chắc chắn sẽ phải có trường hợp 1 Service đi kèm với N Repos, và 1 Controller đi kèm với N Service, chưa kể cái trò Service trong Service. Và một khi đã đụng tới vấn đề kiểu 1 - N thì việc chồng chéo code là chắc chắn xảy ra.

Còn việc bạn đưa ra 1 good example đâu có nghĩa là tất cả các case đều ok như thế? Gặp nhiều case tréo nghoe, đặt chỗ này thì lỡ cỡ, đặt chỗ kia cũng không ổn thì làm thế nào?

3. Bạn phải hiểu rằng việc phân chia project/code structure hợp lý là để áp dụng cho cả team. Có nghĩa là bạn làm đúng theo rule, nhưng người khác chưa chắc đã có thể áp dụng đúng rule ấy được. Nó phụ thuộc vào quá nhiều yếu tố. Vậy nên mình mới nói việc này cần cả về kĩ năng lẫn kinh nghiệm.

4. Cái SOLID là cái có quá nhiều người suốt ngày ra rả về nó, nhưng số người "hiểu" và "áp dụng" được vào code của mình (không cần 100%, khoảng > 80% là ok lắm rồi) lại không quá nhiều đâu.
 
Bạn suy nghĩ quá ngây thơ, điều này chứng tỏ rằng bạn không có quá nhiều kinh nghiệm về cái này. Để mình chỉ ra vài chỗ "chưa đúng" của bạn nhé.

1. Repo mà đơn giản tới mức chỉ có CRUD thì quẳng Service đi được rồi. Hay đơn giản hơn, giả sử có 1 query dạng join tables thì bạn để query đấy ở đâu?

2. Dù bạn phân chia có tốt tới ntn thì chắc chắn sẽ phải có trường hợp 1 Service đi kèm với N Repos, và 1 Controller đi kèm với N Service, chưa kể cái trò Service trong Service. Và một khi đã đụng tới vấn đề kiểu 1 - N thì việc chồng chéo code là chắc chắn xảy ra.

Còn việc bạn đưa ra 1 good example đâu có nghĩa là tất cả các case đều ok như thế? Gặp nhiều case tréo nghoe, đặt chỗ này thì lỡ cỡ, đặt chỗ kia cũng không ổn thì làm thế nào?

3. Bạn phải hiểu rằng việc phân chia project/code structure hợp lý là để áp dụng cho cả team. Có nghĩa là bạn làm đúng theo rule, nhưng người khác chưa chắc đã có thể áp dụng đúng rule ấy được. Nó phụ thuộc vào quá nhiều yếu tố. Vậy nên mình mới nói việc này cần cả về kĩ năng lẫn kinh nghiệm.

4. Cái SOLID là cái có quá nhiều người suốt ngày ra rả về nó, nhưng số người "hiểu" và "áp dụng" được vào code của mình (không cần 100%, khoảng > 80% là ok lắm rồi) lại không quá nhiều đâu.
Cái 1 ban hơi nhầm.
Đúng là nó có thể đơn giản có thể cắt bỏ đc tầng services.
Nhưng mình khuyên là không nên. Vì có 1 thứ là code (style) consistency. Cấu trúc code của bạn tránh vụ ở nơi này làm kiểu nầy , ở nơi khác làm kiểu khác cho cùng 1 dạng vấn đề.
 
Chia lắm làm gì cho đau đầu nhỉ. Chính vì oop các bố đẻ ra lắm pattern quá nên thành ra ko thống nhất, code càng to càng rối rắm
bọn fp nó coi thường oop là vì thế. Chỉ phân chia thành các function thôi ko đc hay sao. Vẽ ra lắm thì sau này càng đau đầu về sau. Việc viết logic trong controller cũng chả sao cả, miễn là chia nhỏ thành các method mỗi method đặt tên cho đúng chức năng nó đảm nhận là đc
 
Chia lắm làm gì cho đau đầu nhỉ. Chính vì oop các bố đẻ ra lắm pattern quá nên thành ra ko thống nhất, code càng to càng rối rắm
bọn fp nó coi thường oop là vì thế. Chỉ phân chia thành các function thôi ko đc hay sao. Vẽ ra lắm thì sau này càng đau đầu về sau. Việc viết logic trong controller cũng chả sao cả, miễn là chia nhỏ thành các method mỗi method đặt tên cho đúng chức năng nó đảm nhận là đc
Chung tôi bàn là bàn vụ kiến trúc và module hóa chứ nếu a áp thuần tuý cái anh vừa nêu trên thì tôi tuong tượng trong 1 file lẫn lộn 50+ hàm ( đặt tên đúng và rõ ràng nha) của 2 3 4 loại logic 1 chỗ thì đúng là thảm họa.

Việc chọn cấu trúc này ko liên quan OOOP hay FP j đâu, thằng nào cũng cần
 
Chung tôi bàn là bàn vụ kiến trúc và module hóa chứ nếu a áp thuần tuý cái anh vừa nêu trên thì tôi tuong tượng trong 1 file lẫn lộn 50+ hàm ( đặt tên đúng và rõ ràng nha) của 2 3 4 loại logic 1 chỗ thì đúng là thảm họa.

Việc chọn cấu trúc này ko liên quan OOOP hay FP j đâu, thằng nào cũng cần
Ờ, thảm họa như nào vậy bạn? Nó chỉ thảm họa khi rơi vào tay 1 ông lead kém, không biết dẫn dắt team đi theo 1 thể thống nhất ( mà thực ra khi đã kém thì khi áp pattern vào thì nó sẽ ra 1 đống bùi nhùi, chỉ có đập đi làm lại). Chia function thôi cũng là nghệ thuật đấy!.
Tôi ví dụ đơn giản như là windows 32 API , hay linux kernel API nó toàn là 1 đống function đấy, bạn cũng chê nó là thảm họa đúng không?
 
Mô hình cơ bản là đúng, nhưng tuỳ từng framework và scope nó có những biến thể khác nhau. Nhiều dự án còn ko có cả repository chứ đừng nói service. Không đủ thông tin ko thể nói lựa chọn bỏ service là đúng hay sai.
 
Có kinh nghiệm mình hay áp dụng đó là ko nên để 1 project base phình lên quá to. Thay vì chia thành các internal service thì tôi tách hẳn nó thành 1 project base khác, coi như là nó là 1 big service độc lập, liên kết với nhau có thể qua HTTP, hoặc MYSQL, redis, queue gì đó...
 
Ờ, thảm họa như nào vậy bạn? Nó chỉ thảm họa khi rơi vào tay 1 ông lead kém, không biết dẫn dắt team đi theo 1 thể thống nhất ( mà thực ra khi đã kém thì khi áp pattern vào thì nó sẽ ra 1 đống bùi nhùi, chỉ có đập đi làm lại). Chia function thôi cũng là nghệ thuật đấy!.
Tôi ví dụ đơn giản như là windows 32 API , hay linux kernel API nó toàn là 1 đống function đấy, bạn cũng chê nó là thảm họa đúng không?
đọc kỹ lại giùm. đọc thôi mà cũng thọt chữ nữa hả ông? Mắt toét à ông thần.
thảm họa khi: 1 FILE có nhiều hàm 50+ func. lẫn lộn của 2 3 4 logic khác biệt chung 1 chỗ. Còn mã nguồn Win32 API nó có bị như tôi đã nói đâu mà nát.
 
đọc kỹ lại giùm. đọc thôi mà cũng thọt chữ nữa hả ông? Mắt toét à ông thần.
thảm họa khi: 1 FILE có nhiều hàm 50+ func. lẫn lộn của 2 3 4 logic khác biệt chung 1 chỗ. Còn mã nguồn Win32 API nó có bị như tôi đã nói đâu mà nát.
1 file có 50+ function thôi mà bạn đã sợ à bạn. thế mời bạn xem redis source code với git source code.
https://github.com/redis/redis/blob/unstable/src/server.c
https://github.com/git/git/blob/master/diff.c
Ngoài ra anh em có thể xem root source tree của git và redis nó chia cực đơn giản
p/s: Hình như hồi đầu redis còn đút tất cả source code vào 1 file luôn cơ
Nói thật source của 2 ông trên mới có 6k LOC 1 file, tôi còn maintain 1 file source controller có 12k LOC cơ. Nhưng hệ thống nó vẫn ngon lành chả vấn đề gì căn bản mỗi hàm của nó chia ra cực kì clear, đọc tên hàm đã hiểu chưa cần đến comment
 
Sửa lần cuối:
Cái 1 ban hơi nhầm.
Đúng là nó có thể đơn giản có thể cắt bỏ đc tầng services.
Nhưng mình khuyên là không nên. Vì có 1 thứ là code (style) consistency. Cấu trúc code của bạn tránh vụ ở nơi này làm kiểu nầy , ở nơi khác làm kiểu khác cho cùng 1 dạng vấn đề.
Đồng ý ở chỗ bôi đen nhé!
Thực ra tuy code lởm lởm chút nhưng vẫn giữ được tính consistency thì maintain vẫn dễ hơn đống hổ lốn.

Còn ý mình ở đây là nếu business logic của app đơn giản tới mức chỉ có CRUD đơn thuần thì có thể bỏ luôn tầng Service đi cũng được (Vẫn giữ được tính consistency nhé :D )
 
Để ở repo được mà anh? Ông thớt bảo repo sẽ làm việc với entity (table) đấy thôi?

via theNEXTvoz for iPhone
Lập luận logic là hơi kém à nha.

Giờ có 2 bảng: User và Order --> Có 2 repo UserRepo và OrderRepo.
Chắc chắn có 1 query liên quan tới cả User và Order, cứ gọi nó là 1 join query nhé.
Câu query này bảo đặt ở đâu cũng được phải ko nhỉ? Giờ đặt tạm ở UserRepo nhé.
Xong rồi trong OrderService cũng cần câu query ấy (logic rất thường gặp phải không nào?) thì lại phải invoke thằng UserRepo à? Thế thì làm sao còn đảm bảo tính 1 - 1 nữa :D
 
Nếu mà chức năng quá đơn giản thuần tuý là CRUD và transportation chỉ có HTTP Controller thì bypass luôn service layer cũng ổn.
Nhưng mà nếu transportation có những đường khác ngoài HTTP REST như gRPC, Thrift thì service layer lại hoàn toàn cần thiết vì nó đóng gói logic ứng dụng, mặc kệ đường đi vào của nó là gì :haha:
 
Lập luận logic là hơi kém à nha.

Giờ có 2 bảng: User và Order --> Có 2 repo UserRepo và OrderRepo.
Chắc chắn có 1 query liên quan tới cả User và Order, cứ gọi nó là 1 join query nhé.
Câu query này bảo đặt ở đâu cũng được phải ko nhỉ? Giờ đặt tạm ở UserRepo nhé.
Xong rồi trong OrderService cũng cần câu query ấy (logic rất thường gặp phải không nào?) thì lại phải invoke thằng UserRepo à? Thế thì làm sao còn đảm bảo tính 1 - 1 nữa :D
Nếu vậy thì giải quyết vấn đề này như thế nào vậy bác?
 
Giờ mới thấy thớt này. Không thấy ai nhắc đến vụ merge conflict nhỉ. Không chia nhiều service nhỏ mà gom vào controller thì team 10 người làm chung chắc suốt ngày resolve conflict quá. Chưa kể còn feature toggle để release, cái này merge trước nhưng disable đợi release sau...

Còn ông nào bảo source code gì 6k Sloc kia chắc là thánh rồi, tôi không hiểu merge code kiểu gì.

Sent from Samsung SM-G973F using vozFApp
 

Thống kê chủ đề

Ngày tạo
soledad86,
Người trả lời cuối
freedom.9,
Trả lời
217
Lượt xem
33.248
Quay lại
Lên đầu trang