Jak dokładna jest „Logika biznesowa powinna być w usłudze, a nie w modelu”?

397

Sytuacja

Wcześniej tego wieczora udzieliłem odpowiedzi na pytanie dotyczące StackOverflow.

Pytanie:

Edycja istniejącego obiektu powinna odbywać się w warstwie repozytorium czy w serwisie?

Na przykład, jeśli mam Użytkownika, który ma dług. Chcę zmienić jego dług. Czy powinienem to zrobić w UserRepository lub w serwisie, na przykład BuyingService, uzyskując obiekt, edytując go i zapisując?

Moja odpowiedź:

Powinieneś pozostawić odpowiedzialność za mutowanie obiektu do tego samego obiektu i użyć repozytorium, aby pobrać ten obiekt.

Przykładowa sytuacja:

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
    }
}

Komentarz, który otrzymałem:

Logika biznesowa powinna być naprawdę w usłudze. Nie w modelu.

Co mówi Internet?

To sprawiło, że zacząłem szukać, ponieważ nigdy tak naprawdę (świadomie) nie użyłem warstwy usługi. Zacząłem czytać wzorzec warstwy usługi i wzorzec jednostki pracy, ale jak dotąd nie mogę powiedzieć, że jestem przekonany, że należy użyć warstwy usługi.

Weźmy na przykład ten artykuł Martina Fowlera na temat anty-wzorca anemicznego modelu domeny:

Istnieją obiekty, z których wiele nazwano na cześć rzeczowników w przestrzeni domen, i są one powiązane z bogatymi relacjami i strukturą, jakie mają prawdziwe modele domen. Haczyk pojawia się, gdy spojrzysz na zachowanie i zdasz sobie sprawę, że prawie nie ma żadnego zachowania na tych obiektach, co czyni je niewiele więcej niż workami pobierających i ustawiających. Rzeczywiście często modele te mają reguły projektowania, które mówią, że nie należy umieszczać logiki domeny w obiektach domeny. Zamiast tego istnieje zestaw obiektów usług, które przechwytują całą logikę domeny. Usługi te działają na podstawie modelu domeny i wykorzystują model domeny dla danych.

(...) Logiką, która powinna znajdować się w obiekcie domeny jest logika domeny - sprawdzanie poprawności, obliczenia, reguły biznesowe - jakkolwiek chcesz to nazwać.

Wydawało mi się, że o to właśnie chodzi: opowiadałem się za manipulowaniem danymi obiektu, wprowadzając metody wewnątrz tej klasy, które właśnie to robią. Zdaję sobie jednak sprawę, że tak powinno być, i prawdopodobnie ma to więcej wspólnego z tym, jak te metody są wywoływane (przy użyciu repozytorium).

Miałem również wrażenie, że w tym artykule (patrz poniżej) Warstwa usługi jest bardziej uważana za fasadę, która deleguje pracę do podstawowego modelu, niż rzeczywistą warstwę intensywnie pracującą.

Warstwa aplikacji [jego nazwa dla warstwy usług]: określa zadania, które oprogramowanie ma wykonywać, i kieruje ekspresyjne obiekty domeny do rozwiązywania problemów. Zadania, za które odpowiada ta warstwa, mają znaczenie dla firmy lub są niezbędne do interakcji z warstwami aplikacji innych systemów. Ta warstwa jest utrzymywana cienką warstwą. Nie zawiera reguł biznesowych ani wiedzy, a jedynie koordynuje zadania i deleguje pracę do współpracy obiektów domeny w następnej warstwie. Nie ma stanu odzwierciedlającego sytuację biznesową, ale może mieć stan odzwierciedlający postęp zadania dla użytkownika lub programu.

Które jest tutaj wzmocnione :

Interfejsy serwisowe. Usługi udostępniają interfejs usługi, do którego wysyłane są wszystkie wiadomości przychodzące. Interfejs usługi można traktować jako fasadę, która udostępnia potencjalnym konsumentom logikę biznesową zaimplementowaną w aplikacji (zwykle logikę w warstwie biznesowej).

A tutaj :

Warstwa usługi powinna być pozbawiona jakiejkolwiek aplikacji lub logiki biznesowej i powinna koncentrować się przede wszystkim na kilku problemach. Powinien zawijać połączenia w warstwie biznesowej, tłumaczyć domenę na wspólny język, który mogą zrozumieć Twoi klienci, oraz obsługiwać medium komunikacyjne między serwerem a klientem żądającym.

Jest to poważny kontrast z innymi zasobami, które mówią o warstwie usług:

Warstwa usługi powinna składać się z klas z metodami, które są jednostkami pracy z działaniami należącymi do tej samej transakcji.

Lub druga odpowiedź na pytanie, które już powiązałem:

W pewnym momencie Twoja aplikacja będzie potrzebować logiki biznesowej. Możesz także sprawdzić poprawność danych wejściowych, aby upewnić się, że nie żąda się niczego złego lub nieskutecznego. Ta logika należy do twojej warstwy usług.

"Rozwiązanie"?

Postępując zgodnie ze wskazówkami zawartymi w tej odpowiedzi , opracowałem następujące podejście wykorzystujące warstwę usług:

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;
    }
}

Wniosek

Wszystko razem niewiele się tutaj zmieniło: kod z kontrolera został przeniesiony do warstwy usług (co jest dobrą rzeczą, więc ma to swoje zalety). Nie wydaje się jednak, aby miało to coś wspólnego z moją pierwotną odpowiedzią.

Zdaję sobie sprawę, że wzorce projektowe to wytyczne, a nie reguły osadzone w kamieniu, które należy wdrożyć, gdy tylko jest to możliwe. Nie znalazłem jednak ostatecznego wyjaśnienia warstwy usługi i tego, jak należy ją traktować.

  • Czy to jest sposób, aby po prostu wyodrębnić logikę ze sterownika i zamiast tego umieścić ją w usłudze?

  • Czy ma to stanowić umowę między administratorem a domeną?

  • Czy powinna istnieć warstwa między domeną a warstwą usługi?

I wreszcie: po oryginalnym komentarzu

Logika biznesowa powinna być naprawdę w usłudze. Nie w modelu.

  • Czy to jest poprawne?

    • Jak mam wprowadzić moją logikę biznesową w usłudze zamiast modelu?
Jeroen Vannevel
źródło
6
Traktuję warstwę usług jako miejsce, w którym należy umieścić nieuniknioną część skryptu transakcyjnego działającego na zagregowane korzenie. Jeśli moja warstwa usługi stanie się zbyt złożona, oznacza to, że idę w kierunku Anemicznego Modelu, a mój model domeny wymaga uwagi i przeglądu. Staram się również umieścić logikę w SL, która z natury nie będzie dublowana.
Pavel Voronin,
Myślałem, że usługi są częścią warstwy Modelu. Czy mylę się myśląc o tym?
Florian Margaine
Używam ogólnej zasady: nie polegaj na tym, czego nie potrzebujesz; w przeciwnym razie mogą mnie (zwykle negatywnie) wpływać zmiany w części, której nie potrzebuję. W rezultacie otrzymuję wiele jasno określonych ról. Moje obiekty danych zawierają zachowanie, o ile korzystają z niego wszyscy klienci. W przeciwnym razie przenoszę zachowanie do klas realizujących wymaganą rolę.
beluchin
1
Logika sterowania przepływem aplikacji należy do kontrolera. Logika dostępu do danych należy do repozytorium. Logika sprawdzania poprawności należy do warstwy usług. Warstwa usługi to dodatkowa warstwa w aplikacji ASP.NET MVC, która pośredniczy w komunikacji między kontrolerem a warstwą repozytorium. Warstwa usługi zawiera logikę sprawdzania poprawności biznesowej. magazyn. asp.net/mvc/overview/older-versions-1/models-data/…
Kbdavis07
3
IMHO, poprawny model domeny w stylu OOP powinien rzeczywiście unikać „usług biznesowych” i zachować logikę biznesową w tym modelu. Ale praktyka pokazuje, że tak kuszące jest stworzenie ogromnej warstwy biznesowej z wyraźnie nazwanymi metodami, a także objęcie transakcji (lub jednostki pracy) całą metodą. O wiele łatwiej jest przetwarzać wiele klas modeli w jednej metodzie usług biznesowych niż planować relacje między tymi klasami modeli, ponieważ wtedy mielibyśmy trudne pytania: gdzie jest agregowany root? Gdzie mam rozpocząć / zatwierdzić transakcję bazy danych? Itd.
JustAMartin

Odpowiedzi:

368

Aby zdefiniować obowiązki usługi , najpierw musisz zdefiniować, czym jest usługa .

Usługa nie jest kanonicznym ani ogólnym terminem na oprogramowanie. W rzeczywistości sufiks Servicenazwy klasy jest podobny do bardzo złośliwego Menedżera : nie mówi prawie nic o tym, co faktycznie robi obiekt .

W rzeczywistości usługa powinna być wysoce specyficzna dla architektury:

  1. W tradycyjnej architekturze warstwowej usługa jest dosłownie synonimem warstwy logiki biznesowej . To warstwa między interfejsem użytkownika a danymi. Dlatego wszystkie reguły biznesowe dotyczą usług. Warstwa danych powinna rozumieć tylko podstawowe operacje CRUD, a warstwa interfejsu użytkownika powinna zajmować się tylko mapowaniem DTO prezentacji do i od obiektów biznesowych.

  2. W architekturze rozproszonej w stylu RPC (SOAP, UDDI, BPEL itp.) Usługa jest logiczną wersją fizycznego punktu końcowego . Zasadniczo jest to zbiór operacji, które opiekun chce udostępnić jako publiczny interfejs API. Różne przewodniki po najlepszych praktykach wyjaśniają, że operacja usługowa powinna w rzeczywistości być operacją na poziomie biznesowym, a nie CRUD, i raczej się zgadzam.

    Ponieważ jednak przekierowywanie wszystkiego przez rzeczywistą usługę zdalną może poważnie zaszkodzić wydajności, zwykle najlepiej nie jest, aby te usługi faktycznie wdrażały logikę biznesową; zamiast tego powinni zawinąć „wewnętrzny” zestaw obiektów biznesowych. Jedna usługa może obejmować jeden lub kilka obiektów biznesowych.

  3. W architekturze MVP / MVC / MVVM / MV * usługi w ogóle nie istnieją. A jeśli tak, termin ten odnosi się do dowolnego obiektu ogólnego, który można wstrzyknąć do kontrolera lub modelu widoku. Logika biznesowa jest w twoim modelu . Jeśli chcesz tworzyć „obiekty usług” w celu koordynowania skomplikowanych operacji, jest to postrzegane jako szczegół implementacji. Niestety wiele osób wdraża MVC w ten sposób, ale jest to uważane za anty-wzorzec ( Anemic Domain Model ), ponieważ sam model nic nie robi, to tylko garść właściwości dla interfejsu użytkownika.

    Niektórzy ludzie błędnie myślą, że przyjęcie 100-liniowej metody kontrolera i przeniesienie jej do usługi w jakiś sposób poprawia architekturę. Naprawdę nie; dodaje tylko kolejną, prawdopodobnie niepotrzebną warstwę pośrednią. Praktycznie rzecz biorąc, kontroler nadal wykonuje pracę, po prostu robi to przez źle nazwany obiekt „pomocnika”. Bardzo polecam prezentację Jimmy'ego Bogarda Wicked Domain Models, aby uzyskać jasny przykład, jak zmienić anemiczny model domeny w przydatny. Polega ona na dokładnym zbadaniu modeli, które ujawniasz i które operacje są faktycznie ważne w kontekście biznesowym .

    Na przykład, jeśli baza danych zawiera Zamówienia i masz kolumnę Kwota całkowita, prawdopodobnie aplikacja nie powinna mieć możliwości zmiany tego pola na dowolną wartość, ponieważ (a) to historia i (b) powinna to być zależy od tego, co jest w kolejności, a także może od innych danych / reguł wrażliwych na czas. Utworzenie usługi do zarządzania zamówieniami niekoniecznie rozwiązuje ten problem, ponieważ kod użytkownika nadal może pobrać rzeczywisty obiekt zamówienia i zmienić kwotę na nim. Zamiast tego samo zamówienie powinno być odpowiedzialne za zapewnienie, że można je zmienić tylko w bezpieczny i spójny sposób.

  4. W DDD usługi są przeznaczone specjalnie do sytuacji, gdy masz operację, która nie należy właściwie do żadnego zagregowanego katalogu głównego . Musisz być tutaj ostrożny, ponieważ często potrzeba usługi może sugerować, że nie użyłeś właściwych korzeni. Ale zakładając, że to zrobiłeś, usługa służy do koordynowania operacji w wielu katalogach głównych, a czasem do rozwiązywania problemów, które w ogóle nie dotyczą modelu domeny (np. Zapisywanie informacji w bazie danych BI / OLAP).

    Ważnym aspektem usługi DDD jest to, że można używać skryptów transakcyjnych . Podczas pracy z dużymi aplikacjami bardzo prawdopodobne jest, że w końcu natkniesz się na instancje, w których łatwiej jest osiągnąć coś za pomocą procedury T-SQL lub PL / SQL niż w przypadku modelu domeny. To jest OK i należy do Usługi.

    Jest to radykalne odejście od definicji usług w architekturze warstwowej. Warstwa usługi obudowuje obiekty domeny; usługa DDD hermetyzuje to, czego nie ma w obiektach domeny i nie ma sensu.

  5. W architekturze zorientowanej na usługi , usługa jest uważana za organ techniczny dla zdolności biznesowej. Oznacza to, że jest wyłącznym właścicielem określonego podzbioru danych biznesowych i nic innego nie może dotykać tych danych - nawet po prostu je czytać .

    Z konieczności usługi są w rzeczywistości kompleksową propozycją w SOA. Oznacza to, że usługa nie jest tak konkretnym składnikiem, jak cały stos , a cała aplikacja (lub cała firma) to zestaw tych usług, które działają obok siebie bez żadnych przecięć oprócz warstw przesyłania komunikatów i interfejsu użytkownika. Każda usługa ma własne dane, własne reguły biznesowe i własny interfejs użytkownika. Nie muszą się ze sobą aranżować, ponieważ mają być dostosowane do biznesu - i podobnie jak sama firma, każda usługa ma swój własny zestaw obowiązków i działa mniej więcej niezależnie od innych.

    Tak więc, zgodnie z definicją SOA, każda logika biznesowa w dowolnym miejscu jest zawarta w usłudze, ale także cały system . Usługi w SOA mogą zawierać składniki i mogą mieć punkty końcowe , ale nazwanie dowolnego fragmentu kodu usługą jest dość niebezpieczne, ponieważ jest ono sprzeczne z tym, co ma oznaczać oryginalne „S”.

    Ponieważ SOA jest na ogół bardzo chętna do przesyłania wiadomości, operacje, które mogły być wcześniej spakowane w usłudze, są na ogół zawarte w modułach obsługi , ale ich wielość jest inna. Każdy moduł obsługi obsługuje jeden typ komunikatu, jedną operację. Jest to ścisła interpretacja zasady pojedynczej odpowiedzialności , ale zapewnia doskonałą konserwację, ponieważ każda możliwa operacja należy do jej własnej klasy. Tak więc tak naprawdę nie potrzebujesz scentralizowanej logiki biznesowej, ponieważ polecenia reprezentują operacje biznesowe, a nie techniczne.

Ostatecznie, w dowolnej architekturze, którą wybierzesz, będzie jakiś komponent lub warstwa, która ma większość logiki biznesowej. W końcu, jeśli logika biznesowa jest rozrzucona po całym miejscu, to masz po prostu kod spaghetti. Ale to, czy nazwiesz ten komponent usługą , i jak zostanie zaprojektowany pod względem liczby lub wielkości operacji, zależy od twoich celów architektonicznych.

Nie ma dobrej lub złej odpowiedzi, tylko to, co dotyczy twojej sytuacji.

Aaronaught
źródło
12
Dziękuję za bardzo szczegółową odpowiedź, wyjaśniłeś wszystko, co mogę wymyślić. Podczas gdy inne odpowiedzi są również dobre do doskonałej jakości, uważam, że ta odpowiedź przewyższa je wszystkie, więc zaakceptuję tę. Dodam to tutaj, aby uzyskać inne odpowiedzi: znakomitą jakość i informacje, ale niestety będę w stanie wyrazić tylko opinię.
Jeroen Vannevel
2
Nie zgadzam się z tym, że w tradycyjnej architekturze warstwowej usługa jest synonimem warstwy logiki biznesowej.
CodeART
1
@CodeART: Jest to architektura 3-warstwowa. I nie widać architekturę 4-tier gdzie istnieje „warstwy aplikacji” między warstwami prezentacji i biznesowych, co jest czasami nazywane także warstwę „usługa”, ale szczerze mówiąc, jedyne miejsca jakie kiedykolwiek widziałem ten realizowany z powodzeniem są ogromne rozległa nieskończenie konfigurowalne produkty dla całej firmy od SAP lub Oracle, i nie sądziłem, że warto o tym wspomnieć. Mogę dodać wyjaśnienie, jeśli chcesz.
Aaronaught
1
Ale jeśli weźmiemy ponad 100 kontrolerów linii (na przykład, że kontroler przyjmuje komunikat - następnie dokonaj deserializacji obiektu JSON, dokonaj weryfikacji, zastosuj reguły biznesowe, zapisz w db, zwróć wynik) i przenieś logikę do jednej z tak zwanych metod usługi t, które pomagają nam oddzielnie testować każdą część bezboleśnie?
artjom
2
@Aaronaught Chciałem wyjaśnić jedną rzecz, jeśli mamy obiekty domeny odwzorowane na db przez ORM i nie ma w nich logiki biznesowej, czy to anemiczny model domeny, czy nie?
artjom
40

Co do twojego tytułu , nie sądzę, żeby pytanie miało sens. Model MVC składa się z danych i logiki biznesowej. Stwierdzenie, że logika powinna znajdować się w Serwisie, a nie Model, jest jak powiedzenie: „Pasażer powinien siedzieć na siedzeniu, a nie w samochodzie”.

Z drugiej strony termin „model” jest terminem przeciążonym. Być może nie miałeś na myśli modelu MVC, ale miałeś na myśli model w sensie obiektu przenoszenia danych (DTO). AKA an Entity. O tym mówi Martin Fowler.

Z mojego punktu widzenia Martin Fowler mówi o rzeczach w idealnym świecie. W prawdziwym świecie Hibernacji i JPA (w krainie Java) DTO są bardzo nieszczelną abstrakcją. Chciałbym umieścić moją logikę biznesową w mojej jednostce. Sprawiłoby to, że wszystko było czystsze. Problem polega na tym, że te podmioty mogą istnieć w stanie zarządzanym / buforowanym, który jest bardzo trudny do zrozumienia i stale uniemożliwia twoje wysiłki. Podsumowując moją opinię: Martin Fowler zaleca właściwą drogę, ale ORM nie pozwalają ci tego zrobić.

Myślę, że Bob Martin ma bardziej realistyczną sugestię i podaje ją w tym filmie, który nie jest darmowy . Mówi o utrzymywaniu DTO w logice. Po prostu przechowują dane i przenoszą je na inną warstwę, która jest znacznie bardziej obiektowa i nie korzysta bezpośrednio z DTO. Dzięki temu unika się nieszczelnej abstrakcji. Warstwa z DTO i same DTO nie są OO. Ale kiedy wyjdziesz z tej warstwy, możesz stać się takim OO, jak zaleca Martin Fowler.

Korzyścią z tego oddzielenia jest to, że oddziela warstwę trwałości. Możesz przełączyć się z JPA na JDBC (lub odwrotnie) i żadna logika biznesowa nie musiałaby się zmieniać. Zależy to tylko od DTO, nie ma znaczenia, w jaki sposób te DTO się zapełniają.

Aby nieznacznie zmienić tematy, należy wziąć pod uwagę fakt, że bazy danych SQL nie są obiektowe. Ale ORM zwykle mają byt - który jest obiektem - na tabelę. Od samego początku przegrałeś już bitwę. Z mojego doświadczenia wynika, że ​​nigdy nie możesz reprezentować Istoty dokładnie tak, jak chcesz w sposób obiektowy.

Jeśli chodzi o „ na służbie”, Bob Martin byłoby sprzeczne posiadające klasę o nazwie FooBarService. To nie jest obiektowe. Czym zajmuje się usługa? Wszystko związane z FooBars. Równie dobrze może być oznaczony FooBarUtils. Myślę, że byłby zwolennikiem warstwy usług (lepsza nazwa to warstwa logiki biznesowej), ale każda klasa w tej warstwie miałaby sensowną nazwę.

Daniel Kaplan
źródło
2
Zgadzam się z twoim punktem na ORM; propagują kłamstwo, że mapujesz swój byt bezpośrednio na db z nimi, podczas gdy w rzeczywistości byt może być przechowywany w kilku tabelach.
Andy,
@Daniel Kaplan, czy wiesz, co to jest zaktualizowany link do filmu Boba Martina?
Brian Morearty
25

Obecnie pracuję nad projektem greenfield i wczoraj musieliśmy podjąć kilka decyzji architektonicznych. Co ciekawe, musiałem ponownie zapoznać się z kilkoma rozdziałami „Wzorów architektury aplikacji korporacyjnych”.

Oto co wymyśliliśmy:

  • Warstwa danych. Baza danych zapytań i aktualizacji. Warstwa jest eksponowana przez repozytoria do wstrzykiwań.
  • Warstwa domenowa. To tutaj żyje logika biznesowa. Ta warstwa wykorzystuje repozytoria do wstrzykiwań i jest odpowiedzialna za większość logiki biznesowej. To jest rdzeń aplikacji, którą dokładnie przetestujemy.
  • Warstwa usługowa. Ta warstwa komunikuje się z warstwą domeny i obsługuje żądania klientów. W naszym przypadku warstwa usługi jest dość prosta - przekazuje żądania do warstwy domeny, obsługuje bezpieczeństwo i kilka innych problemów przekrojowych. Nie różni się to niczym od kontrolera w aplikacji MVC - kontrolery są małe i proste.
  • Warstwa klienta. Rozmawia z warstwą usługi przez SOAP.

W rezultacie otrzymujemy:

Klient -> Usługa -> Domena -> Dane

Możemy zastąpić warstwę klienta, usługi lub danych rozsądnym nakładem pracy. Jeśli logika domeny mieszkała w usłudze i zdecydowałeś, że chcesz zastąpić, a nawet usunąć warstwę usługi, musisz przenieść całą logikę biznesową w inne miejsce. Taki wymóg jest rzadki, ale może się zdarzyć.

Powiedziawszy to wszystko, myślę, że jest to bardzo zbliżone do tego, co miał na myśli Martin Fowler

Usługi te działają na podstawie modelu domeny i wykorzystują model domeny dla danych.

Poniższy schemat ilustruje to całkiem dobrze:

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

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

CodeART
źródło
2
Zdecydowałeś się na SOAP wczoraj? Czy to wymóg, czy po prostu nie masz lepszego pomysłu?
JensG
1
REST nie wyciszy go zgodnie z naszymi wymaganiami. SOAP lub REST, to nie ma znaczenia dla odpowiedzi. Z mojego zrozumienia, usługa jest bramą do logiki domeny.
CodeART
Absolutnie zgadzam się z Bramą. SOAP to (znormalizowane) bloatware, więc musiałem zapytać. I tak, bez wpływu na pytanie / odpowiedź.
JensG
6
Zrób sobie przysługę i zabij swoją warstwę usług. Interfejs użytkownika powinien korzystać bezpośrednio z domeny. Widziałem to wcześniej, a twoja domena niezmiennie staje się bandą anemicznych dtos, a nie bogatych modeli.
Andy,
Te slajdy zawierają wyjaśnienie dotyczące warstwy usług i powyższą grafikę: całkiem nieźle: slideshare.net/ShwetaGhate2/…
Marc Juchli
9

Jest to jedna z tych rzeczy, które naprawdę zależą od przypadku użycia. Ogólnym celem warstwy usług jest konsolidacja logiki biznesowej. Oznacza to, że kilku kontrolerów może wywoływać tę samą funkcję UserService.MakeHimPay () bez dbania o sposób realizacji płatności. To, co dzieje się w usłudze, może być tak proste, jak modyfikowanie właściwości obiektu lub może wykonywać złożoną logikę związaną z innymi usługami (tj. Wywoływanie usług stron trzecich, wywoływanie logiki sprawdzania poprawności lub nawet po prostu zapisywanie czegoś w bazie danych. )

Nie oznacza to, że musisz usunąć CAŁĄ logikę z obiektów domeny. Czasami sensowniej jest mieć metodę na obiekcie domeny, która wykonuje pewne obliczenia na sobie. W ostatnim przykładzie usługa jest nadmiarową warstwą nad obiektem repozytorium / domeny. Zapewnia niezły bufor przed zmianami wymagań, ale tak naprawdę nie jest konieczny. Jeśli uważasz, że potrzebujesz usługi, spróbuj wykonać prostą logikę „modyfikuj właściwość X na obiekcie Y” zamiast obiektu domeny. Logika klas domen ma tendencję do „obliczania tej wartości z pól”, zamiast ujawniania wszystkich pól za pomocą metod pobierających / ustawiających.

firelore
źródło
2
Twoje stanowisko na korzyść warstwy usług z logiką biznesową ma wiele sensu, ale wciąż pozostawia to kilka pytań. W swoim poście zacytowałem kilka poważnych źródeł, które mówią o warstwie usługowej jako fasadzie pozbawionej jakiejkolwiek logiki biznesowej. Pozostaje to w bezpośredniej sprzeczności z twoją odpowiedzią. Czy mógłbyś wyjaśnić tę różnicę?
Jeroen Vannevel
5
Uważam, że tak naprawdę zależy to od TYPU logiki biznesowej i innych czynników, takich jak używany język. Pewna logika biznesowa nie pasuje zbyt dobrze do obiektów domeny. Jednym z przykładów jest filtrowanie / sortowanie wyników po wyciągnięciu ich z bazy danych. Jest logiką biznesową, ale nie ma sensu w przypadku obiektu domeny. Uważam, że usługi najlepiej nadają się do prostej logiki lub transformacji wyników, a logika w domenie jest najbardziej użyteczna, gdy zajmuje się zapisywaniem danych lub obliczaniem danych z obiektu.
firelore,
8

Najłatwiejszym sposobem zilustrowania, dlaczego programiści unikają umieszczania logiki domeny w obiektach domeny, jest to, że zwykle mają do czynienia z sytuacją „gdzie umieścić logikę sprawdzania poprawności?”. Weźmy na przykład ten obiekt domeny:

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;
        }
    }
}

Mamy więc podstawową logikę sprawdzania poprawności w seterach (nie może być ujemna). Problem polega na tym, że tak naprawdę nie można ponownie użyć tej logiki. Gdzieś jest ekran, model ViewModel lub kontroler, który musi przeprowadzić weryfikację, zanim faktycznie zatwierdzi zmianę w obiekcie domeny, ponieważ musi poinformować użytkownika przed kliknięciem przycisku Zapisz, że nie może tego zrobić, i dlaczego . Testowanie wyjątku podczas wywoływania setera jest brzydkim hackem, ponieważ naprawdę powinieneś był wykonać całą weryfikację jeszcze przed rozpoczęciem transakcji.

Dlatego ludzie przenoszą logikę sprawdzania poprawności do jakiejś usługi, takiej jak MyEntityValidator. Wtedy jednostka i logika wywołująca mogą zarówno uzyskać odwołanie do usługi sprawdzania poprawności, jak i ponownie z niej korzystać.

Jeśli tego nie zrobisz i nadal chcesz ponownie użyć logiki sprawdzania poprawności, ostatecznie umieścisz ją w metodach statycznych klasy encji:

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;
    }
}

To sprawiłoby, że Twój model domeny byłby mniej „anemiczny” i zachowałby logikę sprawdzania poprawności obok właściwości, co jest świetne, ale nie sądzę, aby ktokolwiek naprawdę lubił metody statyczne.

Scott Whitlock
źródło
1
Jakie jest Twoim zdaniem najlepsze rozwiązanie do walidacji?
Flashrunner,
3
@Flashrunner - moja logika sprawdzania poprawności jest definitywnie w warstwie logiki biznesowej, ale w niektórych przypadkach jest również zduplikowana w warstwie encji i bazy danych. Warstwa biznesowa radzi sobie z tym dobrze, informując użytkownika itp., Ale pozostałe warstwy po prostu zgłaszają błędy / wyjątki i powstrzymują programistę (siebie) przed popełnianiem błędów, które uszkadzają dane.
Scott Whitlock,
6

Myślę, że odpowiedź jest jasna, jeśli czytasz artykuł Anemic Domain Model Martina Fowlera .

Usunięcie logiki biznesowej, którą jest domena, z modelu domeny jest w istocie łamaniem projektowania obiektowego.

Przyjrzyjmy się najbardziej podstawowej koncepcji obiektowej: Obiekt hermetyzuje dane i operacje. Na przykład zamknięcie konta to operacja, którą obiekt konta powinien wykonać na sobie; dlatego zlecenie wykonania warstwy usługowej nie jest rozwiązaniem zorientowanym obiektowo. Ma charakter proceduralny i do tego odnosi się Martin Fowler, gdy mówi o anemicznym modelu domeny.

Jeśli warstwa usługi zamyka konto, a nie sam obiekt zamknięcia, nie masz rzeczywistego obiektu konta. „Obiekt” twojego konta jest jedynie strukturą danych. To, co kończysz, jak sugeruje Martin Fowler, to kupa torebek z getterami i seterami.

Carlos A Merighe - Utah
źródło
1
Edytowane. Znalazłem to dość pomocne wyjaśnienie i nie sądzę, że zasługuje na negatywne opinie.
BadHorsie
1
Istnieje jeden w dużej mierze nadzorowany minus bogatych modeli. I to jest programiści wciągający wszystko, co jest nieco związane z modelem. Czy stan otwarcia / zamknięcia jest atrybutem konta? Co z właścicielem? A bank? Czy wszystkie powinny być przywoływane przez konto? Czy zamknę konto, rozmawiając z bankiem lub bezpośrednio przez konto? W przypadku modeli anemicznych połączenia te nie są nieodłączną częścią modeli, ale są raczej tworzone podczas pracy z tymi modelami w innych klasach (nazywaj je usługami lub menedżerami).
Hubert Grzeskowiak
4

Jak zaimplementowałbyś logikę biznesową w warstwie usług? Dokonując płatności od użytkownika, tworzysz płatność, a nie tylko odejmujesz wartość od nieruchomości.

Twoja metoda dokonywania płatności musi utworzyć rekord płatności, zwiększyć zadłużenie tego użytkownika i zachować to wszystko w repozytoriach. Wykonanie tego w metodzie serwisowej jest niezwykle proste, a całą transakcję można również zawrzeć w transakcji. Robienie tego samego w zagregowanym modelu domeny jest znacznie bardziej problematyczne.

Pan Cochese
źródło
2

Wersja tl; dr:
Moje doświadczenia i opinie mówią, że wszelkie obiekty, które mają logikę biznesową, powinny być częścią modelu domeny. Model danych prawdopodobnie nie powinien mieć żadnej logiki. Usługi powinny prawdopodobnie połączyć te dwa elementy i zająć się zagadnieniami przekrojowymi (bazy danych, rejestrowanie itp.). Jednak zaakceptowana odpowiedź jest najbardziej praktyczna.

Dłuższa wersja, do której nawiązywali inni, polega na tym, że słowo „model” jest niejednoznaczne. Post przełącza się między modelem danych a modelem domeny tak, jakby były takie same, co jest bardzo częstym błędem. Słowo „usługa” może być również nieco niejednoznaczne.

W praktyce nie powinieneś mieć usługi, która wprowadza zmiany w jakichkolwiek obiektach domeny; powodem tego jest to, że twoja usługa prawdopodobnie będzie miała jakąś metodę dla każdej właściwości na twoim obiekcie, aby zmienić wartość tej właściwości. Jest to problem, ponieważ wtedy, jeśli masz interfejs dla swojego obiektu (lub nawet jeśli nie), usługa przestaje być zgodna z zasadą otwartego-zamkniętego; zamiast tego za każdym razem, gdy dodasz więcej danych do swojego modelu (niezależnie od domeny w porównaniu do danych), w końcu będziesz musiał dodać więcej funkcji do swojej usługi. Istnieją pewne sposoby na obejście tego, ale jest to najczęstszy powód, dla którego widziałem, że aplikacje „korporacyjne” zawodzą, szczególnie gdy te organizacje myślą, że „przedsiębiorstwo” oznacza „mieć interfejs dla każdego obiektu w systemie”. Czy możesz sobie wyobrazić dodanie nowych metod do interfejsu, następnie do dwóch lub trzech różnych implementacji (jednej w aplikacji, próbnej implementacji i debugowania jednej, w pamięci?), tylko dla jednej właściwości w twoim modelu? Brzmi dla mnie jak okropny pomysł.

Jest tutaj dłuższy problem, w który nie będę wchodził, ale sedno jest następujące: Hardcore programowania obiektowego mówi, że nikt poza odpowiednim obiektem nie powinien być w stanie zmienić wartości właściwości w obiekcie, ani nawet „ patrz „wartość właściwości w obiekcie. Można to złagodzić, czyniąc dane tylko do odczytu. Nadal możesz napotykać problemy, na przykład gdy wiele osób korzysta z danych, nawet jako tylko do odczytu, i musisz zmienić typ tych danych. Możliwe, że wszyscy konsumenci będą musieli się zmienić, aby to uwzględnić. Właśnie dlatego, jeśli tworzysz interfejsy API do użytku przez każdego i wszystkich, odradza się nie mieć publicznych ani nawet chronionych właściwości / danych; to jest właśnie powód, dla którego OOP został wymyślony.

Myślę, że większość odpowiedzi tutaj, oprócz tej oznaczonej jako zaakceptowana, przesłania problem. Ta oznaczona jako zaakceptowana jest dobra, ale nadal czułem potrzebę odpowiedzi i zgodzenia się, że ogólnie rzecz biorąc, punkt 4 to droga.

W DDD usługi są przeznaczone specjalnie do sytuacji, gdy masz operację, która nie należy właściwie do żadnego zagregowanego katalogu głównego. Musisz być tutaj ostrożny, ponieważ często potrzeba usługi może sugerować, że nie użyłeś właściwych korzeni. Ale zakładając, że to zrobiłeś, usługa służy do koordynowania operacji w wielu katalogach głównych, a czasem do rozwiązywania problemów, które w ogóle nie dotyczą modelu domeny ...

WolfgangSenff
źródło
1

Odpowiedź jest taka, że ​​zależy to od przypadku użycia. Ale w większości ogólnych scenariuszy przestrzegałbym logiki biznesowej układanej w warstwie usługowej. Podany przykład jest naprawdę prosty. Jednak gdy zaczniesz myśleć o oddzielonych systemach lub usługach i dodawać do nich zachowania transakcyjne, naprawdę chcesz, aby stało się to w ramach warstwy usług.

Podane przez ciebie źródła dla warstwy usług pozbawionej logiki biznesowej wprowadzają kolejną warstwę, którą jest warstwa biznesowa. W wielu scenariuszach warstwa usługi i warstwa biznesowa są skompresowane w jedną. To naprawdę zależy od tego, jak chcesz zaprojektować swój system. Możesz wykonać pracę w trzech warstwach i nadal ozdabiać i dodawać hałasu.

To, co możesz idealnie zrobić, to modelowe usługi, które obejmują logikę biznesową do pracy na modelach domen w celu zachowania stanu . Powinieneś postarać się oddzielić usługi tak bardzo, jak to możliwe.

słoneczny
źródło
0

W MVC Model jest zdefiniowany jako logika biznesowa. Twierdzenie, że powinno być gdzie indziej, jest nieprawidłowe, chyba że nie używa MVC. Widzę warstwy usług jako podobne do systemu modułów. Pozwala ci połączyć zestaw powiązanych funkcji w ładny pakiet. Elementy wewnętrzne tej warstwy usług miałyby model wykonujący taką samą pracę jak Twoja.

Model składa się z danych aplikacji, reguł biznesowych, logiki i funkcji. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

kamieniste
źródło
0

Pojęcie warstwy usługowej można oglądać z perspektywy DDD. Aaronaught wspomniał o tym w swojej odpowiedzi, po prostu trochę go rozwinęłem.

Powszechnym podejściem jest posiadanie kontrolera specyficznego dla danego typu klienta. Powiedzmy, że może to być przeglądarka internetowa, może to być inna aplikacja, może to być test funkcjonalny. Formaty żądań i odpowiedzi mogą się różnić. Dlatego używam usługi aplikacji jako narzędzia do wykorzystania architektury heksagonalnej . Wprowadzam tam klasy infrastruktury specyficzne dla konkretnego żądania. Na przykład tak mógłby wyglądać mój kontroler obsługujący żądania przeglądarki internetowej:

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

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

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

Jeśli piszę test funkcjonalny, chcę użyć fałszywego klienta płatności i prawdopodobnie nie potrzebowałbym odpowiedzi HTML. Mój kontroler mógłby wyglądać tak:

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

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

        return new JsonData($responseData);
    }
}

Tak więc usługa aplikacji to środowisko, które skonfigurowałem do uruchamiania logiki biznesowej. To tam nazywane są klasy modeli - niezależnie od implementacji infrastruktury.

Odpowiadając na pytania z tej perspektywy:

Czy to jest sposób, aby po prostu wyodrębnić logikę ze sterownika i zamiast tego umieścić ją w usłudze?

Nie.

Czy ma to stanowić umowę między administratorem a domeną?

Można to tak nazwać.

Czy powinna istnieć warstwa między domeną a warstwą usługi?

Nie.


Istnieje jednak zupełnie inne podejście, które całkowicie zaprzecza korzystaniu z jakiejkolwiek usługi. Na przykład David West w swojej książce Object Thinking twierdzi, że każdy obiekt powinien mieć wszystkie zasoby niezbędne do wykonywania swojej pracy. Takie podejście powoduje na przykład odrzucenie dowolnej ORM .

Zapadło
źródło
-2

Dla przypomnienia.

SRP:

  1. Model = Dane, oto setter i getters.
  2. Logika / usługi = tutaj są decyzje.
  3. Repozytorium / DAO = tutaj stale przechowujemy lub odzyskujemy informacje.

W takim przypadku można wykonać następujące czynności:

Jeśli dług nie będzie wymagał pewnych obliczeń:

userObject.Debt = 9999;

Jeśli jednak wymaga to pewnych obliczeń:

userObject.Debt= UserService.CalculateDebt(userObject)

lub też

UserService.UpdateDebt(userObject)

Ale także, jeśli obliczenia są wykonywane w warstwie trwałości, wówczas taka procedura przechowywania

UserRepository.UpdateDebt(userObject)

W takim przypadku chcemy pobrać użytkownika z bazy danych i zaktualizować dług, powinniśmy to zrobić w kilku krokach (w rzeczywistości dwóch) i nie trzeba go owijać / hermetyzować w funkcji usługi.

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

A jeśli wymaga to przechowywania, możemy dodać trzeci krok

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

O proponowanym rozwiązaniu

a) Nie powinniśmy obawiać się, że zostawiliśmy programistę końcowemu, aby napisał kilka zamiast enkapsulować go w funkcji.

b) A jeśli chodzi o interfejs, niektórzy programiści uwielbiają interfejs i są w porządku, ale w kilku przypadkach wcale nie są potrzebni.

c) Celem usługi jest utworzenie jej bez atrybutów, głównie dlatego, że możemy korzystać z funkcji współdzielonych / statycznych. Testowanie jednostkowe jest również łatwe.

magallanes
źródło
w jaki sposób odpowiada to na zadane pytanie: Jak dokładna jest „Logika biznesowa powinna być w usłudze, a nie w modelu”?
komara
3
Co to jest zdanie "We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function.„? Mogę zacytować Lewisa Blacka”, gdyby nie mój koń, nie spędziłbym tego roku na studiach ”
Malachi,