Làm thế nào chính xác là logic kinh doanh nên có trong một dịch vụ, không phải trong một mô hình nên?


397

Tình hình

Đầu buổi tối hôm nay tôi đã đưa ra câu trả lời cho một câu hỏi trên StackOverflow.

Câu hỏi:

Chỉnh sửa một đối tượng hiện có nên được thực hiện trong lớp kho lưu trữ hoặc trong dịch vụ?

Ví dụ: nếu tôi có một Người dùng có nợ. Tôi muốn thay đổi nợ của anh ấy. Tôi nên làm điều đó trong UserRep repository hoặc trong dịch vụ, ví dụ như MuaService bằng cách lấy một đối tượng, chỉnh sửa và lưu nó?

Câu trả lời của tôi:

Bạn nên để lại trách nhiệm biến đổi một đối tượng thành cùng một đối tượng đó và sử dụng kho lưu trữ để truy xuất đối tượng này.

Ví dụ tình huống:

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

Một nhận xét tôi nhận được:

Logic kinh doanh nên thực sự trong một dịch vụ. Không phải trong một mô hình.

Internet nói gì?

Vì vậy, điều này khiến tôi tìm kiếm vì tôi chưa bao giờ thực sự sử dụng một lớp dịch vụ. Tôi đã bắt đầu đọc lên mẫu Lớp dịch vụ và mẫu Đơn vị công việc nhưng cho đến nay tôi không thể nói rằng tôi tin rằng một lớp dịch vụ phải được sử dụng.

Lấy ví dụ bài viết này của Martin Fowler về mô hình chống mô hình miền thiếu máu:

Có các đối tượng, nhiều tên được đặt theo danh từ trong không gian miền và các đối tượng này được kết nối với các mối quan hệ và cấu trúc phong phú mà các mô hình miền thực có. Nắm bắt được khi bạn nhìn vào hành vi, và bạn nhận ra rằng hầu như không có bất kỳ hành vi nào trên các đối tượng này, làm cho chúng ít hơn nhiều so với các túi getters và setters. Thật vậy, thường các mô hình này đi kèm với các quy tắc thiết kế nói rằng bạn không được đặt bất kỳ logic miền nào trong các đối tượng miền. Thay vào đó, có một tập hợp các đối tượng dịch vụ nắm bắt tất cả logic miền. Các dịch vụ này nằm trên mô hình miền và sử dụng mô hình miền cho dữ liệu.

(...) Logic nên có trong một đối tượng miền là logic miền - xác thực, tính toán, quy tắc kinh doanh - bất cứ điều gì bạn muốn gọi nó.

Đối với tôi, đây dường như chính xác là tình huống xảy ra: Tôi ủng hộ việc thao túng dữ liệu của một đối tượng bằng cách giới thiệu các phương thức bên trong lớp đó thực hiện điều đó. Tuy nhiên tôi nhận ra rằng đây phải là một trong hai cách nhất định và có lẽ nó liên quan nhiều hơn đến cách các phương thức này được gọi (sử dụng một kho lưu trữ).

Tôi cũng có cảm giác rằng trong bài viết đó (xem bên dưới), Lớp Dịch vụ được coi là mặt tiền mà các đại biểu làm việc cho mô hình bên dưới, hơn là một lớp chuyên sâu thực tế.

Lớp ứng dụng [tên của anh ta cho lớp dịch vụ]: Xác định các công việc mà phần mềm phải làm và chỉ đạo các đối tượng miền biểu cảm để giải quyết các vấn đề. Các tác vụ mà lớp này chịu trách nhiệm có ý nghĩa đối với doanh nghiệp hoặc cần thiết để tương tác với các lớp ứng dụng của các hệ thống khác. Lớp này được giữ mỏng. Nó không chứa các quy tắc hoặc kiến ​​thức kinh doanh, mà chỉ phối hợp các tác vụ và đại biểu làm việc với sự hợp tác của các đối tượng miền trong lớp tiếp theo. Nó không có trạng thái phản ánh tình hình kinh doanh, nhưng nó có thể có trạng thái phản ánh tiến trình của một nhiệm vụ cho người dùng hoặc chương trình.

Được củng cố ở đây :

Giao diện dịch vụ. Các dịch vụ hiển thị giao diện dịch vụ mà tất cả các tin nhắn gửi đến được gửi. Bạn có thể nghĩ về giao diện dịch vụ như một mặt tiền phơi bày logic nghiệp vụ được triển khai trong ứng dụng (thông thường, logic trong lớp nghiệp vụ) cho người tiêu dùng tiềm năng.

đây :

Lớp dịch vụ nên không có bất kỳ ứng dụng hoặc logic kinh doanh nào và nên tập trung chủ yếu vào một vài mối quan tâm. Nó sẽ bao bọc các cuộc gọi của Lớp nghiệp vụ, dịch Miền của bạn bằng ngôn ngữ chung mà khách hàng của bạn có thể hiểu và xử lý phương tiện liên lạc giữa máy chủ và yêu cầu máy khách.

Đây là một sự tương phản nghiêm trọng với các tài nguyên khác nói về Lớp dịch vụ:

Lớp dịch vụ nên bao gồm các lớp với các phương thức là các đơn vị công việc với các hành động thuộc cùng một giao dịch.

Hoặc câu trả lời thứ hai cho câu hỏi tôi đã liên kết:

Tại một số điểm, ứng dụng của bạn sẽ muốn một số logic kinh doanh. Ngoài ra, bạn có thể muốn xác thực đầu vào để đảm bảo rằng không có điều gì xấu hoặc không phù hợp được yêu cầu. Logic này thuộc về lớp dịch vụ của bạn.

"Giải pháp"?

Theo hướng dẫn trong câu trả lời này , tôi đã đưa ra cách tiếp cận sau đây sử dụng Lớp dịch vụ:

class UserController : Controller {
    private UserService _userService;

    public UserController(UserService userService){
        _userService = userService;
    } 

    public ActionResult MakeHimPay(string username, int amount) {
        _userService.MakeHimPay(username, amount);
        return RedirectToAction("ShowUserOverview");
    }

    public ActionResult ShowUserOverview() {
        return View();
    }
}

class UserService {
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }

    public void MakeHimPay(username, amount) {
        _userRepository.GetUserByName(username).makePayment(amount);
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

Phần kết luận

Tất cả cùng nhau không có nhiều thay đổi ở đây: mã từ bộ điều khiển đã chuyển sang lớp dịch vụ (đây là một điều tốt, vì vậy có một mặt trái của phương pháp này). Tuy nhiên điều này không giống như nó có liên quan đến câu trả lời ban đầu của tôi.

Tôi nhận ra các mẫu thiết kế là hướng dẫn, không phải là các quy tắc được thiết lập để thực hiện bất cứ khi nào có thể. Tuy nhiên, tôi đã không tìm thấy một lời giải thích dứt khoát về lớp dịch vụ và cách nó nên được xem xét.

  • Nó có phải là một phương tiện đơn giản để trích xuất logic từ bộ điều khiển và đặt nó bên trong một dịch vụ thay thế không?

  • Có phải hình thành một hợp đồng giữa bộ điều khiển và tên miền?

  • Có nên có một lớp giữa miền và lớp dịch vụ?

Và, cuối cùng nhưng không kém phần quan trọng: theo nhận xét ban đầu

Logic kinh doanh nên thực sự trong một dịch vụ. Không phải trong một mô hình.

  • Điều này có đúng không?

    • Làm thế nào tôi có thể giới thiệu logic kinh doanh của mình trong một dịch vụ thay vì mô hình?

6
Tôi coi lớp dịch vụ là nơi đặt phần không thể tránh khỏi của tập lệnh giao dịch hoạt động dựa trên các gốc tổng hợp. Nếu lớp dịch vụ của tôi trở nên quá phức tạp, nó báo hiệu cho tôi rằng tôi di chuyển theo hướng Mô hình thiếu máu và mô hình miền của tôi cần được chú ý và xem xét. Tôi cũng cố gắng đưa logic vào SL mà sẽ không bị trùng lặp bởi bản chất của nó.
Pavel Voronin

Tôi nghĩ rằng các dịch vụ là một phần của lớp Model. Tôi có sai khi nghĩ vậy không?
Florian Margaine

Tôi sử dụng quy tắc ngón tay cái: không phụ thuộc vào những gì bạn không cần; nếu không, tôi có thể (thường là tiêu cực) bị ảnh hưởng bởi những thay đổi về phần tôi không cần. Kết quả là, tôi kết thúc với rất nhiều vai trò được xác định rõ ràng. Các đối tượng dữ liệu của tôi chứa hành vi miễn là tất cả các máy khách sử dụng nó. Mặt khác, tôi chuyển hành vi vào các lớp thực hiện vai trò cần thiết.
beluchin

1
Logic điều khiển luồng ứng dụng thuộc về một bộ điều khiển. Logic truy cập dữ liệu thuộc về một kho lưu trữ. Logic xác nhận thuộc về một lớp dịch vụ. Lớp dịch vụ là một lớp bổ sung trong ứng dụng ASP.NET MVC làm trung gian giao tiếp giữa bộ điều khiển và lớp kho lưu trữ. Lớp dịch vụ chứa logic xác thực doanh nghiệp. kho. asp.net/mvc/overview/older-versions-1/models-data/...
Kbdavis07

3
IMHO, mô hình miền đúng kiểu OOP thực sự nên tránh "dịch vụ kinh doanh" và giữ logic kinh doanh trong mô hình. Nhưng thực tế cho thấy thật hấp dẫn khi tạo ra một lớp kinh doanh khổng lồ với các phương thức được đặt tên rõ ràng và cũng để đặt một giao dịch (hoặc đơn vị công việc) xung quanh toàn bộ phương thức. Việc xử lý nhiều lớp mô hình trong một phương thức dịch vụ kinh doanh dễ dàng hơn nhiều so với việc lập kế hoạch quan hệ giữa các lớp mô hình đó, bởi vì sau đó bạn sẽ có một số câu hỏi khó trả lời: gốc tổng hợp ở đâu? Tôi nên bắt đầu / cam kết giao dịch cơ sở dữ liệu ở đâu? Vv
JustAMartin

Câu trả lời:


368

Để xác định trách nhiệm của dịch vụ là gì, trước tiên bạn cần xác định dịch vụ là gì.

Dịch vụ không phải là một thuật ngữ phần mềm chính tắc hoặc chung chung. Trong thực tế, hậu tố Servicetrên một tên lớp rất giống với Trình quản lý sai lầm : Nó cho bạn biết hầu như không có gì về những gì đối tượng thực sự làm .

Trong thực tế, những gì một dịch vụ nên làm là đặc thù về kiến ​​trúc:

  1. Trong một kiến ​​trúc lớp truyền thống, dịch vụ đồng nghĩa với lớp logic kinh doanh . Đó là lớp giữa UI và Data. Do đó, tất cả các quy tắc kinh doanh đi vào dịch vụ. Lớp dữ liệu chỉ nên hiểu các hoạt động CRUD cơ bản và lớp UI chỉ nên xử lý ánh xạ các DTO trình bày đến và từ các đối tượng kinh doanh.

  2. Trong kiến ​​trúc phân tán kiểu RPC (SOAP, UDDI, BPEL, v.v.), dịch vụ là phiên bản logic của điểm cuối vật lý . Nó thực chất là một tập hợp các hoạt động mà người bảo trì muốn cung cấp dưới dạng API công khai. Các hướng dẫn thực hành tốt nhất khác nhau giải thích rằng trên thực tế , một hoạt động dịch vụ phải là hoạt động ở cấp độ doanh nghiệp chứ không phải CRUD và tôi có xu hướng đồng ý.

    Tuy nhiên, vì định tuyến mọi thứ thông qua một dịch vụ từ xa thực tế có thể ảnh hưởng nghiêm trọng đến hiệu suất, tốt nhất là không nên để các dịch vụ này thực sự tự thực hiện logic kinh doanh; thay vào đó, họ nên bọc một tập hợp các đối tượng kinh doanh "nội bộ". Một dịch vụ có thể liên quan đến một hoặc một số đối tượng kinh doanh.

  3. Trong kiến ​​trúc MVP / MVC / MVVM / MV *, các dịch vụ hoàn toàn không tồn tại. Hoặc nếu có, thuật ngữ này được sử dụng để chỉ bất kỳ đối tượng chung nào có thể được đưa vào bộ điều khiển hoặc mô hình xem. Logic kinh doanh là trong mô hình của bạn . Nếu bạn muốn tạo "đối tượng dịch vụ" để phối hợp các hoạt động phức tạp, thì đó được xem là một chi tiết triển khai. Đáng buồn là nhiều người đã triển khai MVC như thế này, nhưng nó được coi là một mô hình chống ( Mô hình miền thiếu máu ) vì bản thân mô hình không làm gì cả, nó chỉ là một loạt các thuộc tính cho UI.

    Một số người lầm tưởng rằng việc sử dụng phương pháp điều khiển 100 dòng và đẩy tất cả vào một dịch vụ bằng cách nào đó sẽ tạo ra một kiến ​​trúc tốt hơn. Nó thực sự không; tất cả những gì nó làm là thêm một lớp khác, có lẽ là không cần thiết. Thực tế mà nói, bộ điều khiển vẫn đang thực hiện công việc, nó chỉ thực hiện thông qua một đối tượng "người trợ giúp" được đặt tên kém. Tôi đặc biệt khuyên bạn nên trình bày Mô hình miền xấu xa của Jimmy Bogard cho một ví dụ rõ ràng về cách biến mô hình miền thiếu máu thành mô hình hữu ích. Nó liên quan đến việc kiểm tra cẩn thận các mô hình bạn đang phơi bày và hoạt động nào thực sự hợp lệ trong bối cảnh kinh doanh .

    Ví dụ: nếu cơ sở dữ liệu của bạn chứa Đơn hàng và bạn có một cột cho Tổng số tiền, ứng dụng của bạn có thể không được phép thực sự thay đổi trường đó thành một giá trị tùy ý, bởi vì (a) đó là lịch sử và (b) nó được cho là được xác định bởi những gì theo thứ tự cũng như có thể một số dữ liệu / quy tắc nhạy cảm với thời gian khác. Tạo một dịch vụ để quản lý Đơn hàng không nhất thiết phải giải quyết vấn đề này, vì mã người dùng vẫn có thể lấy đối tượng Đơn hàng thực tế và thay đổi số tiền trên đó. Thay vào đó, chính đơn đặt hàng phải chịu trách nhiệm đảm bảo rằng nó chỉ có thể được thay đổi theo những cách an toàn và nhất quán.

  4. Trong DDD, các dịch vụ được dành riêng cho tình huống khi bạn có một hoạt động không đúng với bất kỳ gốc tổng hợp nào . Bạn phải cẩn thận ở đây, vì thường thì nhu cầu về dịch vụ có thể ám chỉ rằng bạn đã không sử dụng đúng gốc. Nhưng giả sử bạn đã làm, một dịch vụ được sử dụng để phối hợp các hoạt động trên nhiều gốc hoặc đôi khi để xử lý các mối lo ngại không liên quan đến mô hình miền (chẳng hạn như, có thể ghi thông tin vào cơ sở dữ liệu BI / OLAP).

    Một khía cạnh đáng chú ý của dịch vụ DDD là nó được phép sử dụng các tập lệnh giao dịch . Khi làm việc trên các ứng dụng lớn, cuối cùng bạn rất có thể gặp phải các trường hợp trong đó việc thực hiện một cái gì đó dễ dàng hơn với thủ tục T-SQL hoặc PL / SQL so với mô hình miền. Điều này là ổn, và nó thuộc về một Dịch vụ.

    Đây là một sự khởi đầu triệt để từ định nghĩa kiến ​​trúc phân lớp của các dịch vụ. Một lớp dịch vụ đóng gói các đối tượng miền; một dịch vụ DDD gói gọn bất cứ thứ gì không có trong các đối tượng miền và không có ý nghĩa.

  5. Trong Kiến trúc hướng dịch vụ , một dịch vụ được coi là cơ quan kỹ thuật cho khả năng kinh doanh. Điều đó có nghĩa là nó là chủ sở hữu độc quyền của một tập hợp con nhất định của dữ liệu kinh doanh và không có gì khác được phép chạm vào dữ liệu đó - thậm chí không chỉ đọc nó.

    Do sự cần thiết, các dịch vụ thực sự là một đề xuất đầu cuối trong một SOA. Có nghĩa là, một dịch vụ không phải là một thành phần cụ thể như toàn bộ ngăn xếp và toàn bộ ứng dụng của bạn (hoặc toàn bộ doanh nghiệp của bạn) là một tập hợp các dịch vụ này chạy song song không có giao lộ ngoại trừ ở các lớp nhắn tin và giao diện người dùng. Mỗi dịch vụ có dữ liệu riêng, quy tắc kinh doanh riêng và giao diện người dùng riêng. Họ không cần phải phối hợp với nhau vì họ được coi là phù hợp với doanh nghiệp - và cũng giống như doanh nghiệp, mỗi dịch vụ có một bộ trách nhiệm riêng và hoạt động độc lập ít nhiều với các dịch vụ khác.

    Vì vậy, theo định nghĩa của SOA, mọi phần logic kinh doanh ở bất cứ đâu đều được chứa trong dịch vụ, nhưng sau đó, toàn bộ hệ thống cũng vậy . Các dịch vụ trong một SOA có thể có các thành phần và chúng có thể có các điểm cuối , nhưng khá nguy hiểm khi gọi bất kỳ đoạn mã nào là dịch vụ vì nó mâu thuẫn với ý nghĩa của chữ "S" ban đầu.

    Do nói chung, SOA rất quan tâm đến việc nhắn tin, các hoạt động mà bạn có thể đã đóng gói trong một dịch vụ trước đây thường được gói gọn trong các trình xử lý , nhưng tính đa dạng thì khác. Mỗi xử lý xử lý một loại tin nhắn, một hoạt động. Đó là một cách giải thích chặt chẽ về Nguyên tắc Trách nhiệm duy nhất , nhưng tạo ra khả năng duy trì tuyệt vời vì mọi hoạt động có thể đều thuộc về lớp riêng của nó. Vì vậy, bạn không thực sự cần logic kinh doanh tập trung, bởi vì các lệnh đại diện cho hoạt động kinh doanh hơn là kỹ thuật.

Cuối cùng, trong bất kỳ kiến ​​trúc nào bạn chọn, sẽ có một số thành phần hoặc lớp có hầu hết logic kinh doanh. Rốt cuộc, nếu logic kinh doanh nằm rải rác khắp nơi thì bạn chỉ cần có mã spaghetti. Nhưng việc bạn có gọi thành phần đó là dịch vụ hay không và nó được thiết kế theo những thứ như số lượng hoặc quy mô hoạt động, tùy thuộc vào mục tiêu kiến ​​trúc của bạn.

Không có câu trả lời đúng hay sai, chỉ có những gì áp dụng cho tình huống của bạn.


12
Cảm ơn bạn đã trả lời rất công phu, bạn đã làm rõ tất cả những gì tôi có thể nghĩ ra. Mặc dù các câu trả lời khác cũng tốt cho chất lượng tuyệt vời, tôi tin rằng câu trả lời này đứng đầu tất cả vì vậy tôi sẽ chấp nhận câu trả lời này. Tôi sẽ thêm nó vào đây cho các câu trả lời khác: chất lượng và thông tin tuyệt vời, nhưng thật đáng buồn là tôi sẽ chỉ có thể cung cấp cho bạn một upvote.
Jeroen Vannevel

2
Tôi không đồng ý với thực tế là trong kiến ​​trúc phân lớp truyền thống, dịch vụ là từ đồng nghĩa với lớp logic nghiệp vụ.
CodeART

1
@CodeART: Đó là kiến ​​trúc 3 tầng. Tôi đã thấy các kiến ​​trúc 4 tầng trong đó có một "lớp ứng dụng" giữa lớp trình bày và lớp kinh doanh, đôi khi còn được gọi là lớp "dịch vụ", nhưng thành thật mà nói, những nơi duy nhất tôi từng thấy điều này được thực hiện thành công là rất lớn Các sản phẩm chạy toàn bộ doanh nghiệp dành cho bạn hoàn toàn có thể định cấu hình từ các ứng dụng như SAP hoặc Oracle và tôi không nghĩ rằng nó thực sự đáng được đề cập ở đây. Tôi có thể thêm một sự làm rõ nếu bạn muốn.
Aaronaught

1
Nhưng nếu chúng ta sử dụng hơn 100 bộ điều khiển dòng (ví dụ: bộ điều khiển chấp nhận thông báo - sau đó giải tuần tự hóa đối tượng JSON, xác thực, áp dụng quy tắc nghiệp vụ, lưu vào db, trả về đối tượng kết quả) và chuyển một số logic sang một trong những phương thức dịch vụ được gọi là ' t giúp chúng tôi đơn vị kiểm tra riêng từng phần của nó không đau?
artjom

2
@Aaronaught Tôi muốn làm rõ một điều nếu chúng ta có các đối tượng miền được ánh xạ tới db thông qua ORM và không có logic kinh doanh nào trong đó có phải là mô hình miền thiếu máu này hay không?
artjom

40

Đối với tiêu đề của bạn , tôi không nghĩ rằng câu hỏi có ý nghĩa. Mô hình MVC bao gồm dữ liệu và logic nghiệp vụ. Nói logic nên có trong Dịch vụ chứ không phải Mô hình giống như nói, "Hành khách nên ngồi ở ghế chứ không phải ngồi trong xe".

Một lần nữa, thuật ngữ "Mô hình" là một thuật ngữ quá tải. Có lẽ bạn không có nghĩa là Mô hình MVC nhưng bạn có nghĩa là mô hình theo nghĩa Đối tượng truyền dữ liệu (DTO). AKA một thực thể. Đây là những gì Martin Fowler đang nói về.

Theo cách tôi thấy, Martin Fowler đang nói về những điều trong một thế giới lý tưởng. Trong thế giới thực của Hibernate và JPA (ở vùng đất Java), các DTO là một sự trừu tượng hóa siêu rò rỉ. Tôi muốn đặt logic kinh doanh của tôi trong thực thể của tôi. Nó sẽ làm mọi thứ sạch hơn. Vấn đề là những thực thể này có thể tồn tại ở trạng thái được quản lý / lưu trữ rất khó hiểu và liên tục ngăn cản nỗ lực của bạn. Để tóm tắt ý kiến ​​của tôi: Martin Fowler đang đề xuất đúng cách, nhưng các ORM ngăn bạn thực hiện nó.

Tôi nghĩ Bob Martin có một đề nghị thực tế hơn và anh ấy đưa ra nó trong video này không miễn phí . Anh ấy nói về việc giữ cho các DTO của bạn không có logic. Họ chỉ đơn giản là giữ dữ liệu và chuyển nó sang một lớp khác hướng đối tượng nhiều hơn và không sử dụng trực tiếp các DTO. Điều này tránh sự trừu tượng rò rỉ từ việc cắn bạn. Lớp có DTO và bản thân DTO không phải là OO. Nhưng một khi bạn thoát ra khỏi lớp đó, bạn sẽ trở thành OO như Martin Fowler chủ trương.

Lợi ích của sự phân tách này là nó trừu tượng hóa lớp kiên trì. Bạn có thể chuyển từ JPA sang JDBC (hoặc ngược lại) và không logic logic kinh doanh nào sẽ phải thay đổi. Nó chỉ phụ thuộc vào các DTO, nó không quan tâm làm thế nào những DTO đó được phổ biến.

Để thay đổi một chút chủ đề, bạn cần xem xét thực tế rằng cơ sở dữ liệu SQL không hướng đối tượng. Nhưng các ORM thường có một thực thể - là một đối tượng - trên mỗi bảng. Vì vậy, ngay từ đầu bạn đã thua một trận chiến. Theo kinh nghiệm của tôi, bạn không bao giờ có thể đại diện cho Thực thể theo cách chính xác mà bạn muốn theo cách hướng đối tượng.

Đối với " một dịch vụ", Bob Martin sẽ chống lại việc có một lớp được đặt tên FooBarService. Đó không phải là hướng đối tượng. Một dịch vụ làm gì? Bất cứ điều gì liên quan đến FooBars. Nó cũng có thể được dán nhãn FooBarUtils. Tôi nghĩ rằng anh ấy ủng hộ một lớp dịch vụ (một tên tốt hơn sẽ là lớp logic kinh doanh) nhưng mỗi lớp trong lớp đó sẽ có một tên có ý nghĩa.


2
Đồng ý với quan điểm của bạn về ORM; họ tuyên truyền một lời nói dối rằng bạn ánh xạ thực thể của bạn trực tiếp tới db với họ, trong khi thực tế, một thực thể có thể được lưu trữ trên một số bảng.
Andy

@Daniel Kaplan bạn có biết liên kết cập nhật cho video Bob Martin là gì không?
Brian Morearty

25

Tôi đang làm việc với dự án greenfield ngay bây giờ và chúng tôi đã phải đưa ra một vài quyết định kiến ​​trúc chỉ mới ngày hôm qua. Thật thú vị, tôi đã phải xem lại vài chương của 'Các mô hình kiến ​​trúc ứng dụng doanh nghiệp'.

Đây là những gì chúng tôi đã đưa ra:

  • Lớp dữ liệu. Truy vấn và cập nhật cơ sở dữ liệu. Các lớp được tiếp xúc thông qua các kho lưu trữ tiêm.
  • Lớp tên miền. Đây là nơi logic kinh doanh sống. Lớp này sử dụng các kho lưu trữ có thể tiêm và chịu trách nhiệm cho phần lớn logic kinh doanh. Đây là cốt lõi của ứng dụng mà chúng tôi sẽ kiểm tra kỹ lưỡng.
  • Lớp dịch vụ. Lớp này nói chuyện với lớp miền và phục vụ các yêu cầu của máy khách. Trong trường hợp của chúng tôi, lớp dịch vụ khá đơn giản - nó chuyển tiếp các yêu cầu đến lớp miền, xử lý bảo mật và một số mối quan tâm xuyên suốt khác. Điều này không khác nhiều so với bộ điều khiển trong ứng dụng MVC - bộ điều khiển nhỏ và đơn giản.
  • Lớp khách hàng. Nói chuyện với lớp dịch vụ thông qua SOAP.

Chúng tôi kết thúc với những điều sau đây:

Máy khách -> Dịch vụ -> Miền -> Dữ liệu

Chúng tôi có thể thay thế máy khách, dịch vụ hoặc lớp dữ liệu với khối lượng công việc hợp lý. Nếu logic miền của bạn tồn tại trong dịch vụ và bạn đã quyết định rằng bạn muốn thay thế hoặc thậm chí xóa lớp dịch vụ của mình, thì bạn phải chuyển tất cả logic nghiệp vụ sang một nơi khác. Yêu cầu như vậy là rất hiếm, nhưng nó có thể xảy ra.

Đã nói tất cả những điều này, tôi nghĩ rằng điều này khá gần với ý nghĩa của Martin Fowler khi nói

Các dịch vụ này nằm trên mô hình miền và sử dụng mô hình miền cho dữ liệu.

Sơ đồ dưới đây minh họa điều này khá tốt:

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif


2
Bạn đã quyết định cho SOAP chỉ ngày hôm qua? Đó là một yêu cầu hay bạn chỉ không có ý tưởng tốt hơn?
JensG

1
REST sẽ không cắt nó theo yêu cầu của chúng tôi. SOAP hoặc REST, điều này không tạo ra bất kỳ sự khác biệt nào cho câu trả lời. Theo hiểu biết của tôi, dịch vụ là một cổng vào logic miền.
CodeART

Hoàn toàn đồng ý với Cổng. SOAP là (tiêu chuẩn hóa) bloatware, vì vậy tôi phải hỏi. Và có, không có tác động đến câu hỏi / câu trả lời.
JensG

6
Làm cho mình một ưu tiên và giết lớp dịch vụ của bạn. Ui của bạn nên sử dụng tên miền của bạn trực tiếp. Tôi đã nhìn thấy điều này trước đây và tên miền của bạn luôn trở thành một loạt các dtos thiếu máu, không phải là mô hình phong phú.
Andy

Những slide bao gồm các lời giải thích về lớp dịch vụ và đồ họa này trên khá gọn gàng: slideshare.net/ShwetaGhate2/...
Marc Juchli

9

Đây là một trong những điều thực sự phụ thuộc vào trường hợp sử dụng. Điểm chung của một lớp dịch vụ là hợp nhất logic kinh doanh với nhau. Điều này có nghĩa là một số bộ điều khiển có thể gọi cùng một UserService.MakeHimPay () mà không thực sự quan tâm đến cách thanh toán được thực hiện. Những gì diễn ra trong dịch vụ có thể đơn giản như sửa đổi một thuộc tính đối tượng hoặc nó có thể thực hiện logic phức tạp đối phó với các dịch vụ khác (nghĩa là gọi ra các dịch vụ của bên thứ ba, gọi logic xác thực hoặc thậm chí chỉ đơn giản là lưu một cái gì đó vào cơ sở dữ liệu. )

Điều này không có nghĩa là bạn phải loại bỏ TẤT CẢ logic khỏi các đối tượng miền. Đôi khi nó có ý nghĩa hơn khi có một phương thức trên đối tượng miền thực hiện một số tính toán trên chính nó. Trong ví dụ cuối cùng của bạn, dịch vụ là một lớp dự phòng trên đối tượng kho lưu trữ / tên miền. Nó cung cấp một bộ đệm đẹp chống lại các thay đổi yêu cầu, nhưng nó thực sự không cần thiết. Nếu bạn nghĩ rằng bạn cần một dịch vụ, hãy thử sử dụng logic "sửa đổi thuộc tính X trên đối tượng Y" đơn giản thay vì đối tượng miền. Logic trên các lớp miền có xu hướng rơi vào "tính giá trị này từ các trường" thay vì phơi bày tất cả các trường thông qua getters / setters.


2
Lập trường của bạn ủng hộ một lớp dịch vụ với logic kinh doanh có rất nhiều ý nghĩa, nhưng điều này vẫn để lại một số câu hỏi. Trong bài đăng của tôi, tôi đã trích dẫn một số nguồn đáng kính nói về tầng dịch vụ như một khoảng trống mặt tiền của bất kỳ logic kinh doanh nào. Điều này trái ngược với câu trả lời của bạn, có lẽ bạn có thể làm rõ sự khác biệt này?
Jeroen Vannevel

5
Tôi thấy rằng nó thực sự phụ thuộc vào LOẠI logic kinh doanh và các yếu tố khác, chẳng hạn như ngôn ngữ đang được sử dụng. Một số logic nghiệp vụ không phù hợp với các đối tượng miền rất tốt. Một ví dụ là lọc / sắp xếp kết quả sau khi kéo chúng trở lại từ cơ sở dữ liệu. Đó là logic kinh doanh, nhưng nó không có ý nghĩa đối với tên miền. Tôi thấy rằng các dịch vụ được sử dụng tốt nhất cho logic đơn giản hoặc chuyển đổi kết quả và logic trên miền là hữu ích nhất khi nó liên quan đến việc lưu dữ liệu hoặc tính toán dữ liệu từ đối tượng.
Firelore

8

Cách dễ nhất để minh họa tại sao các lập trình viên ngại đưa logic miền vào các đối tượng miền là họ thường phải đối mặt với một tình huống "tôi phải đặt logic xác thực ở đâu?" Lấy đối tượng miền này làm ví dụ:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

Vì vậy, chúng ta có một số logic xác thực cơ bản trong setter (không thể âm). Vấn đề là bạn không thể thực sự sử dụng lại logic này. Ở đâu đó có màn hình hoặc ViewModel hoặc Bộ điều khiển cần xác thực trước khi thực sự cam kết thay đổi đối tượng miền, bởi vì nó cần thông báo cho người dùng trước hoặc khi họ nhấp vào nút Lưu mà họ không thể làm điều đó, và tại sao . Việc kiểm tra ngoại lệ khi bạn gọi setter là một hack xấu xí vì bạn thực sự nên thực hiện tất cả các xác nhận trước khi bạn bắt đầu giao dịch.

Đó là lý do tại sao mọi người di chuyển logic xác thực một số loại dịch vụ, chẳng hạn như MyEntityValidator. Sau đó, thực thể và logic gọi có thể có được một tham chiếu đến dịch vụ xác nhận và sử dụng lại nó.

Nếu bạn không làm điều đó và bạn vẫn muốn sử dụng lại logic xác thực, cuối cùng bạn sẽ đưa nó vào các phương thức tĩnh của lớp thực thể:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

Điều này sẽ làm cho mô hình miền của bạn bớt "thiếu máu" và giữ logic xác thực bên cạnh thuộc tính, điều này thật tuyệt, nhưng tôi không nghĩ có ai thực sự thích các phương thức tĩnh.


1
Vì vậy, giải pháp tốt nhất để xác nhận theo ý kiến ​​của bạn là gì?
Flashrunner

3
@Flashrunner - logic xác thực của tôi chắc chắn nằm trong lớp logic nghiệp vụ, nhưng cũng được sao chép trong lớp thực thể và cơ sở dữ liệu trong một số trường hợp. Lớp nghiệp vụ xử lý nó một cách độc đáo bằng cách thông báo cho người dùng, v.v., nhưng các lớp khác chỉ đưa ra các lỗi / ngoại lệ và giữ cho lập trình viên (bản thân tôi) không mắc lỗi làm hỏng dữ liệu.
Scott Whitlock

6

Tôi nghĩ rằng câu trả lời là rõ ràng nếu bạn đọc bài viết Mô hình miền thiếu máu của Martin Fowler .

Loại bỏ logic nghiệp vụ, là miền, khỏi mô hình miền về cơ bản là phá vỡ thiết kế hướng đối tượng.

Hãy xem lại khái niệm hướng đối tượng cơ bản nhất: Một đối tượng đóng gói dữ liệu và hoạt động. Chẳng hạn, đóng tài khoản là một hoạt động mà đối tượng tài khoản nên tự thực hiện; do đó, có một lớp dịch vụ thực hiện thao tác đó không phải là một giải pháp hướng đối tượng. Đó là thủ tục, và đó là những gì Martin Fowler đang đề cập khi ông nói về một mô hình miền thiếu máu.

Nếu bạn có lớp dịch vụ đóng tài khoản, thay vì đóng đối tượng tài khoản, bạn không có đối tượng tài khoản thực. "Đối tượng" tài khoản của bạn chỉ là một cấu trúc dữ liệu. Những gì bạn kết thúc, như Martin Fowler gợi ý, là một loạt các túi với getters và setters.


1
Đã chỉnh sửa. Tôi thực sự tìm thấy điều này khá là một lời giải thích hữu ích và đừng nghĩ rằng nó đáng bị downvote.
BadHorsie

1
Có một nhược điểm lớn của các mô hình giàu có. Và đó là các nhà phát triển kéo theo bất cứ điều gì hơi liên quan đến mô hình. Là trạng thái mở / đóng một thuộc tính của tài khoản? Còn chủ thì sao? Còn ngân hàng? Tất cả họ có nên được tham chiếu bởi tài khoản? Tôi sẽ đóng tài khoản bằng cách nói chuyện với ngân hàng hoặc thông qua tài khoản trực tiếp? Với các mô hình thiếu máu, các kết nối đó không phải là một phần vốn có của các mô hình, nhưng được tạo ra khi làm việc với các mô hình đó trong các lớp khác (gọi chúng là dịch vụ hoặc người quản lý).
Hubert Grzeskowiak

4

Làm thế nào bạn sẽ thực hiện logic kinh doanh của bạn trong lớp dịch vụ? Khi bạn thực hiện thanh toán từ người dùng, bạn tạo một khoản thanh toán, không chỉ khấu trừ một giá trị từ một tài sản.

Phương thức thanh toán của bạn cần tạo một hồ sơ thanh toán, thêm vào khoản nợ của người dùng đó và duy trì tất cả những điều này trong kho của bạn. Thực hiện điều này trong một phương thức dịch vụ cực kỳ đơn giản và bạn cũng có thể bao quát toàn bộ hoạt động trong một giao dịch. Làm tương tự trong một mô hình miền tổng hợp có nhiều vấn đề hơn.


2

Phiên bản tl; dr:
Kinh nghiệm và ý kiến ​​của tôi nói rằng bất kỳ đối tượng nào có logic nghiệp vụ nên là một phần của mô hình miền. Mô hình dữ liệu có thể không có bất kỳ logic nào. Các dịch vụ có thể sẽ gắn kết hai thứ lại với nhau và giải quyết các mối quan tâm xuyên suốt (cơ sở dữ liệu, ghi nhật ký, v.v.). Tuy nhiên, câu trả lời được chấp nhậncâu trả lời thiết thực nhất.

Phiên bản dài hơn, đã được người khác ám chỉ, là có một sự tương đương trên từ "mô hình". Bài đăng chuyển đổi giữa mô hình dữ liệu và mô hình miền như thể chúng giống nhau, đó là một lỗi rất phổ biến. Cũng có thể có một sự tương đương nhỏ trên từ "dịch vụ".

Trong điều kiện thực tế, bạn không nên có một dịch vụ thay đổi bất kỳ đối tượng miền nào; Lý do cho điều này là dịch vụ của bạn có thể sẽ có một số phương thức cho mọi thuộc tính trên đối tượng của bạn để thay đổi giá trị của tài sản đó. Đây là một vấn đề bởi vì sau đó, nếu bạn có giao diện cho đối tượng của mình (hoặc thậm chí nếu không), dịch vụ không còn tuân theo Nguyên tắc Đóng mở; thay vào đó, bất cứ khi nào bạn thêm nhiều dữ liệu vào mô hình của mình (bất kể tên miền so với dữ liệu), cuối cùng bạn sẽ phải thêm nhiều chức năng hơn vào dịch vụ của mình. Có một số cách nhất định xung quanh nó, nhưng đây là lý do phổ biến nhất mà tôi thấy các ứng dụng "doanh nghiệp" thất bại, đặc biệt là khi các tổ chức đó nghĩ rằng "doanh nghiệp" có nghĩa là "có giao diện cho mọi đối tượng trong hệ thống". Bạn có thể tưởng tượng việc thêm các phương thức mới vào một giao diện không sau đó đến hai hoặc ba triển khai khác nhau (một trong ứng dụng, triển khai giả và gỡ lỗi một, triển khai trong bộ nhớ?), chỉ cho một thuộc tính trên mô hình của bạn? Nghe có vẻ là một ý tưởng khủng khiếp đối với tôi.

Có một vấn đề dài hơn ở đây mà tôi sẽ không gặp phải, nhưng ý chính là: Lập trình hướng đối tượng Hardcore nói rằng không ai bên ngoài đối tượng có liên quan có thể thay đổi giá trị của một tài sản trong đối tượng, thậm chí cả " xem "giá trị của tài sản trong đối tượng. Điều này có thể được giảm bớt bằng cách làm cho dữ liệu chỉ đọc. Bạn vẫn có thể gặp phải các vấn đề như khi nhiều người sử dụng dữ liệu ngay cả khi chỉ đọc và bạn phải thay đổi loại dữ liệu đó. Có thể tất cả người tiêu dùng sẽ phải thay đổi để phù hợp với điều đó. Đây là lý do tại sao, khi bạn làm cho API được sử dụng bởi bất kỳ ai và mọi người, bạn không nên có các thuộc tính / dữ liệu công khai hoặc thậm chí được bảo vệ; Cuối cùng, đó là lý do OOP được phát minh.

Tôi nghĩ rằng phần lớn các câu trả lời ở đây, ngoài câu được đánh dấu được chấp nhận, tất cả đều che giấu vấn đề. Một đánh dấu được chấp nhận là tốt, nhưng tôi vẫn cảm thấy cần phải trả lời và đồng ý rằng viên đạn 4 là cách để đi, nói chung.

Trong DDD, các dịch vụ được dành riêng cho tình huống khi bạn có một hoạt động không đúng với bất kỳ gốc tổng hợp nào. Bạn phải cẩn thận ở đây, vì thường thì nhu cầu về dịch vụ có thể ám chỉ rằng bạn đã không sử dụng đúng gốc. Nhưng giả sử bạn đã làm, một dịch vụ được sử dụng để phối hợp các hoạt động trên nhiều gốc hoặc đôi khi để xử lý các mối lo ngại không liên quan đến mô hình miền ...


1

Câu trả lời là nó phụ thuộc vào trường hợp sử dụng. Nhưng trong hầu hết các kịch bản chung, tôi sẽ tuân thủ logic kinh doanh nằm trong lớp dịch vụ. Ví dụ mà bạn đã cung cấp là một ví dụ thực sự đơn giản. Tuy nhiên, một khi bạn bắt đầu nghĩ đến các hệ thống hoặc dịch vụ tách rời và thêm hành vi giao dịch lên trên nó, bạn thực sự muốn nó xảy ra như một phần của lớp dịch vụ.

Các nguồn bạn đã trích dẫn cho lớp dịch vụ không có bất kỳ logic nghiệp vụ nào giới thiệu một lớp khác là lớp nghiệp vụ. Trong nhiều kịch bản, lớp dịch vụ và lớp nghiệp vụ được nén thành một. Nó thực sự phụ thuộc vào cách bạn muốn thiết kế hệ thống của bạn. Bạn có thể hoàn thành công việc trong ba lớp và tiếp tục trang trí và thêm tiếng ồn.

Những gì bạn có thể làm lý tưởng là các dịch vụ mô hình bao gồm logic nghiệp vụ để hoạt động trên các mô hình miền để duy trì trạng thái . Bạn nên cố gắng tách các dịch vụ càng nhiều càng tốt.


0

Trong mô hình MVC được định nghĩa là logic kinh doanh. Yêu cầu nó phải ở một nơi khác là không chính xác trừ khi anh ta không sử dụng MVC. Tôi xem các lớp dịch vụ tương tự như một hệ thống mô-đun. Nó cho phép bạn kết hợp một tập hợp các chức năng liên quan thành một gói đẹp. Các phần bên trong của lớp dịch vụ đó sẽ có một mô hình thực hiện công việc tương tự như của bạn.

Mô hình này bao gồm dữ liệu ứng dụng, quy tắc kinh doanh, logic và chức năng. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93 điều khiển


0

Khái niệm về lớp Dịch vụ có thể được xem từ phối cảnh DDD. Aaronaught đã đề cập đến nó trong câu trả lời của anh ấy, tôi chỉ giải thích một chút về nó.

Cách tiếp cận phổ biến là có một bộ điều khiển dành riêng cho một loại máy khách. Nói, nó có thể là một trình duyệt web, nó có thể là một ứng dụng khác, nó có thể là một thử nghiệm chức năng. Các định dạng yêu cầu và phản hồi có thể khác nhau. Vì vậy, tôi sử dụng dịch vụ ứng dụng như một công cụ để sử dụng kiến trúc Lục giác . Tôi tiêm ở đó các lớp cơ sở hạ tầng cụ thể cho một yêu cầu cụ thể. Ví dụ: đó là cách bộ điều khiển của tôi phục vụ các yêu cầu trình duyệt web có thể trông như sau:

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

Nếu tôi đang viết một bài kiểm tra chức năng, tôi muốn sử dụng một khách hàng thanh toán giả và có lẽ tôi sẽ không cần phản hồi html. Vì vậy, bộ điều khiển của tôi có thể trông như thế:

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

Vì vậy, dịch vụ ứng dụng là một môi trường mà tôi thiết lập để chạy logic kinh doanh. Đó là nơi các lớp mô hình được gọi - về mặt thực thi cơ sở hạ tầng.

Vì vậy, trả lời câu hỏi của bạn từ quan điểm này:

Nó có phải là một phương tiện đơn giản để trích xuất logic từ bộ điều khiển và đặt nó bên trong một dịch vụ thay thế không?

Không.

Có phải hình thành một hợp đồng giữa bộ điều khiển và tên miền?

Vâng, người ta có thể gọi nó như vậy.

Có nên có một lớp giữa miền và lớp dịch vụ?

Không.


Có một cách tiếp cận hoàn toàn khác nhau mặc dù hoàn toàn phủ nhận việc sử dụng bất kỳ loại dịch vụ nào. Ví dụ, David West trong cuốn sách Tư duy đối tượng của mình tuyên bố rằng bất kỳ đối tượng nào cũng cần có tất cả các nguồn lực cần thiết để thực hiện công việc của mình. Cách tiếp cận này dẫn đến, ví dụ, loại bỏ bất kỳ ORM nào .


-2

Đối với hồ sơ.

SRP:

  1. Model = Dữ liệu, ở đây đi setter và getters.
  2. Logic / Dịch vụ = ở đây đi đến các quyết định.
  3. Kho lưu trữ / DAO = ở đây chúng tôi cho phép lưu trữ hoặc lấy thông tin.

Trong trường hợp này, bạn có thể thực hiện các bước tiếp theo:

Nếu khoản nợ sẽ không yêu cầu một số tính toán:

userObject.Debt = 9999;

Tuy nhiên, nếu nó yêu cầu một số tính toán thì:

userObject.Debt= UserService.CalculateDebt(userObject)

hoặc cũng

UserService.UpdateDebt(userObject)

Nhưng ngoài ra, nếu tính toán được thực hiện trong lớp kiên trì, thì thủ tục lưu trữ như vậy sau đó

UserRepository.UpdateDebt(userObject)

Trong trường hợp này, tôi muốn lấy người dùng từ cơ sở dữ liệu và để cập nhật khoản nợ sau đó, chúng tôi nên thực hiện theo một số bước (thực tế là hai) và không cần phải bọc / đóng gói nó trong chức năng của dịch vụ.

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)

Và nếu nó yêu cầu lưu trữ nó sau đó, chúng ta có thể thêm một bước thứ ba

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)
UserRepository.Save(userobject);

Về giải pháp đề xuất

a) Chúng ta không nên sợ nhà phát triển cuối cùng viết một vài thay vì gói nó trong một hàm.

b) Và về Giao diện, một số nhà phát triển yêu thích giao diện và họ vẫn ổn nhưng trong một số trường hợp, họ không cần gì cả.

c) Mục tiêu của một dịch vụ là tạo ra một thứ không có thuộc tính, chủ yếu là vì chúng ta có thể sử dụng các hàm Shared / static. Nó cũng dễ dàng để kiểm tra đơn vị.


Làm thế nào để trả lời câu hỏi này: Logic kinh doanh chính xác như thế nào trong một dịch vụ, chứ không phải trong một mô hình.
gnat

3
Loại câu nào là "We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function."? Tôi chỉ có thể trích dẫn Lewis Black" nếu nó không dành cho con ngựa của tôi, tôi sẽ không trải qua năm đó ở trường đại học ".
Malachi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.