czy to zła praktyka, że ​​repozytorium wywołań kontrolera zamiast usługi?

43

czy to zła praktyka, że ​​repozytorium wywołań kontrolera zamiast usługi?

wyjaśnić więcej:

Rozumiem, że w dobrym projekcie kontrolery wywołują serwis i repozytorium użytkowania usługi.

ale czasami w kontrolerze nie mam / potrzebuję żadnej logiki i po prostu muszę pobrać z db i przekazać go do przeglądania.

i mogę to zrobić po prostu wywołując repozytorium - nie trzeba dzwonić do serwisu - czy to zła praktyka?

mohsenJsh
źródło
Jak dzwonisz do serwisu? Poprzez interfejs REST?
Robert Harvey
2
Stosuję to podejście do projektowania raczej często. Mój kontroler (lub podstawowa klasa kompozytorów) zażąda danych lub prześle dane do repozytorium, a następnie przekaże je do wszystkich klas usług, które wymagają przetworzenia. Nie ma powodu, aby łączyć klasy przetwarzania danych z klasami pobierania / zarządzania danymi, to różne obawy, chociaż wiem, że typowym podejściem jest robienie tego w ten sposób.
Jimmy Hoffa
3
Meh Jeśli jest to niewielka aplikacja i próbujesz tylko pobrać dane z bazy danych, warstwa usługi jest stratą czasu, chyba że ta warstwa usługi jest częścią publicznego interfejsu API, takiego jak interfejs REST. „Czy mleko jest dla ciebie dobre czy złe?” Zależy od tego, czy nie tolerujesz laktozy.
Robert Harvey
4
Nie ma twardej i szybkiej reguły, że powinieneś mieć Kontrolera -> Serwis -> Struktura repozytorium nad Kontrolerem -> Repozytorium. Wybierz odpowiedni wzór dla właściwej aplikacji. Powiedziałbym, że powinieneś uczynić swoją aplikację spójną.
NikolaiDante
Być może mógłbyś wymyślić ogólną usługę, która tylko przesyła twoje żądanie do repozytorium, a następnie zwraca. Może to być przydatne do utrzymania jednolitego interfejsu i byłoby proste, jeśli w przyszłości będziesz musiał dodać prawdziwą usługę, aby coś zrobić przed wywołaniem repozytorium.
Henrique Barcelos

Odpowiedzi:

32

Nie, pomyśl o tym w ten sposób: repozytorium to usługa (również).

Jeśli podmioty pobierane przez repozytorium obsługują większość logiki biznesowej, nie ma potrzeby korzystania z innych usług. Wystarczy mieć repozytorium.

Nawet jeśli masz jakieś usługi, które musisz przekazać, aby manipulować swoimi bytami. Najpierw pobierz encję z repozytorium, a następnie przekaż ją do wspomnianej usługi. Możliwość podrzucenia HTTP 404 przed próbą jest bardzo wygodna.

Również w przypadku scenariuszy odczytu powszechne jest to, że jednostka musi rzutować ją na model DTO / ViewModel. Posiadanie warstwy usługowej pomiędzy nimi wtedy często powoduje wiele nieprzewidzianych metod.

Joppe
źródło
2
Dobrze powiedziane! Preferuję wywoływanie repozytoriów i tylko w przypadkach, gdy repozytorium nie wystarczy (tzn. Dwa byty muszą zostać zmodyfikowane przy użyciu różnych repozytoriów), tworzę usługę odpowiedzialną za tę operację i wywołuję ją z kontrolera.
Zygimantas,
Zauważyłem dość skomplikowany kod, aby uzasadnić korzystanie z usługi. Absurd, najmniej ...
Gi1ber7
Więc moje repozytorium zwraca listę „obiektów biznesowych”, które muszę przekonwertować na „obiekty xml”, czy to wystarczający powód, aby mieć warstwę usług? Nazywam metodę na każdym obiekcie, aby przekonwertować go na inny typ i dodać do nowej listy.
bot_bot
Bezpośredni dostęp DAO jest niebezpieczny w kontrolerach, może sprawić, że będziesz podatny na zastrzyki SQL i zapewni dostęp do niebezpiecznych działań, takich jak ,, deleteAll ”. Zdecydowanie bym tego uniknął.
Anirudh
4

Nie jest złą praktyką, że kontroler bezpośrednio wywołuje repozytorium. „Usługa” to po prostu kolejne narzędzie, więc używaj jej tam, gdzie ma to sens.

NikolaiDante skomentował:

... Wybierz odpowiedni wzór dla właściwej aplikacji. Powiedziałbym, że powinieneś uczynić swoją aplikację spójną.

Nie uważam, że spójność jest najważniejszym aspektem. Klasa „service” ma za zadanie obudować logikę wyższego poziomu, aby sterownik nie musiał jej implementować. Jeśli dla danej operacji nie jest wymagana „logika wyższego poziomu”, wystarczy przejść bezpośrednio do repozytorium.

Aby promować dobrą separację obaw i testowalność, repozytorium powinno być zależnością wprowadzaną do usługi za pośrednictwem konstruktora:

IFooRepository repository = new FooRepository();
FooService service = new FooService(repository);

service.DoSomething(...);

Jeśli wyszukiwanie rekordów w bazie danych wymaga jakiegoś sparametryzowanego zapytania, klasa usług może być dobrym miejscem do wzięcia pod uwagę modelu widoku i zbudowania zapytania, które jest następnie wykonywane przez repozytorium.

Podobnie, jeśli masz złożony model widoku formularza, klasa usług może zawrzeć logikę tworzenia, aktualizowania i usuwania rekordów poprzez wywoływanie metod w modelach / jednostkach domeny, a następnie utrwalanie ich za pomocą repozytorium.

Idąc w przeciwnym kierunku, jeśli kontroler musi uzyskać rekord według swojego identyfikatora, delegowanie do obiektu usługi jest jak uderzenie palcem w młot kowalski - to znacznie więcej niż potrzebujesz.

Odkryłem, że kontroler jest w najlepszej pozycji do obsługi transakcji lub obiektu Unit Of Work . Kontroler lub obiekt Unit Of Work delegowałby następnie do obsługi obiektów dla złożonych operacji lub przechodził bezpośrednio do repozytorium dla prostych operacji (takich jak znalezienie rekordu według Id).

public class ShoppingCartsController : Controller
{
    [HttpPost]
    public ActionResult Edit(int id, ShoppingCartForm model)
    {
        // Controller initiates a database session and transaction
        using (IStoreContext store = new StoreContext())
        {
            // Controller goes directly to a repository to find a record by Id
            ShoppingCart cart = store.ShoppingCarts.Find(id);

            // Controller creates the service, and passes the repository and/or
            // the current transaction
            ShoppingCartService service = new ShoppingCartService(store.ShoppingCarts);

            if (cart == null)
                return HttpNotFound();

            if (ModelState.IsValid)
            {
                // Controller delegates to a service object to manipulate the
                // Domain Model (ShoppingCart)
                service.UpdateShoppingCart(model, cart);

                // Controller decides to commit changes
                store.SaveChanges();

                return RedirectToAction("Index", "Home");
            }
            else
            {
                return View(model);
            }
        }
    }
}

Myślę, że połączenie usług i bezpośredniej pracy z repozytoriami jest całkowicie do przyjęcia. Możesz dodatkowo zawrzeć transakcję w obiekcie Unit Of Work, jeśli czujesz taką potrzebę.

Podział obowiązków wygląda następująco:

  • Kontroler kontroluje przepływ aplikacji
    • Zwraca „404 nie znaleziono”, jeśli koszyka nie ma w bazie danych
    • Ponownie renderuje formularz z komunikatami sprawdzania poprawności, jeśli sprawdzanie poprawności się nie powiedzie
    • Zapisuje koszyk, jeśli wszystko się sprawdzi
  • Kontroler deleguje się do klasy usługi, aby wykonać logikę biznesową na modelach domeny (lub jednostkach). Obiekty usługowe nie powinny implementować logiki biznesowej! Oni wykonać logikę biznesową.
  • Administratorzy mogą delegować bezpośrednio do repozytoriów w celu wykonywania prostych operacji
  • Obiekty usług pobierają dane w modelu widoku i delegują do modeli domen w celu wykonania logiki biznesowej (np. Obiekt usługi wywołuje metody w modelach domeny przed wywołaniem metod w repozytorium)
  • Obiekty usług delegują się do repozytoriów w celu utrwalenia danych
  • Administratorzy powinni:
    1. Zarządzaj czasem życia transakcji lub
    2. Utwórz obiekt jednostki pracy, aby zarządzać czasem życia transakcji
Greg Burghardt
źródło
1
-1 do umieszczenia DbContext w kontrolerze zamiast repo. Repozytorium ma na celu zarządzanie dostawcami danych, więc nikt inny nie musi w przypadku zmiany dostawcy danych (na przykład z MySQL na płaskie pliki JSON, zmiana w jednym miejscu)
Jimmy Hoffa
@ JimmyHoffa: Naprawdę patrzę wstecz na kod, który napisałem, i szczerze mówiąc, tworzę obiekt „kontekstowy” dla moich repozytoriów, niekoniecznie bazy danych. Myślę, że DbContextto złe imię w tym przypadku. Zmienię to. Korzystam z NHibernate, a repozytoria (lub kontekst, jeśli jest to przydatne) zarządzają końcem bazy danych, więc zmiana mechanizmów trwałości nie wymaga zmian kodu poza kontekstem.
Greg Burghardt
wyglądasz na mylącego kontrolera z repozytorium wyglądem swojego kodu ... Mam na myśli, że twój „kontekst” jest nieprawidłowy i absolutnie nie powinien istnieć w kontrolerze.
Jimmy Hoffa
6
Nie muszę odpowiadać i nie jestem pewien, czy to dobre pytanie, ale odrzucam głosowanie, ponieważ uważam, że twoje podejście jest złe. Bez trudnych uczuć, po prostu odradzam konteksty będące własnością kontrolerów. IMO kontroler nie powinien zaczynać i dokonywać takich transakcji. To zadanie dowolnej liczby innych miejsc, wolę, aby kontrolery delegowały wszystko, co nie tylko spełnia żądanie HTTP, na inne miejsce.
Jimmy Hoffa
1
Repozytorium, zazwyczaj odpowiada za wszystkie informacje kontekstowe danych, aby upewnić się, że nic więcej nie musi wiedzieć o danych, poza tym, co sama domena powinna wiedzieć
Jimmy Hoffa
1

To zależy od twojej architektury. Korzystam z Spring, a transakcjami zawsze zarządzają usługi.

Jeśli wywołujesz repozytoria bezpośrednio w celu wykonania operacji zapisu (lub prostych usług bez logiki, które po prostu delegują się do repozytorium), prawdopodobnie korzystasz z kilku transakcji bazy danych do operacji, którą należy wykonać w jednym. Doprowadzi to do niespójności danych w bazie danych. Zasadniczo operacje na bazach danych powinny działać lub kończyć się niepowodzeniem, ale operacje połowicznej pracy są przyczyną bólów głowy.

Z tego powodu uważam, że wywoływanie repozytoriów bezpośrednio z kontrolerów lub korzystanie z prostych usług delegowania jest złą praktyką. Zaczynasz robić to tylko do czytania, a wkrótce ty lub jeden z twoich kolegów zacznie to robić dla operacji zapisu.

Rober2D2
źródło