Dostosowuję projektowanie oparte na domenie od około 8 lat i nawet po tylu latach wciąż mnie wkurza. To sprawdza unikalny rekord w przechowywaniu danych w stosunku do obiektu domeny.
We wrześniu 2013 r. Martin Fowler wspomniał o zasadzie TellDon'tAsk , która, jeśli to możliwe, powinna być stosowana do wszystkich obiektów domeny, która powinna następnie zwrócić komunikat o przebiegu operacji (w projektowaniu obiektowym odbywa się to głównie poprzez wyjątki, gdy operacja zakończyła się niepowodzeniem).
Moje projekty są zwykle podzielone na wiele części, przy czym dwie z nich to Domena (zawierająca reguły biznesowe i nic więcej, domena jest całkowicie ignorancka) i Usługi. Usługi znające warstwę repozytorium używaną do danych CRUD.
Ponieważ unikalność atrybutu należącego do obiektu jest domeną / regułą biznesową, moduł domeny powinien być długi, więc reguła jest dokładnie tam, gdzie powinna być.
Aby móc sprawdzić unikatowość rekordu, musisz zapytać o aktualny zestaw danych, zwykle bazę danych, aby dowiedzieć się, czy Name
istnieje już inny rekord z na przykład powiedzmy .
Biorąc pod uwagę, że warstwa domeny jest uporczywa w ignorancji i nie ma pojęcia, jak odzyskać dane, ale tylko jak wykonać na nich operacje, tak naprawdę nie może dotknąć samych repozytoriów.
Projekt, który następnie dostosowywałem, wygląda następująco:
class ProductRepository
{
// throws Repository.RecordNotFoundException
public Product GetBySKU(string sku);
}
class ProductCrudService
{
private ProductRepository pr;
public ProductCrudService(ProductRepository repository)
{
pr = repository;
}
public void SaveProduct(Domain.Product product)
{
try {
pr.GetBySKU(product.SKU);
throw Service.ProductWithSKUAlreadyExistsException("msg");
} catch (Repository.RecordNotFoundException e) {
// suppress/log exception
}
pr.MarkFresh(product);
pr.ProcessChanges();
}
}
Prowadzi to do tego, że usługi definiują reguły domeny zamiast samej warstwy domeny, a reguły są rozproszone po wielu sekcjach kodu.
Wspomniałem o zasadzie TellDon'tAsk, ponieważ jak widać, usługa oferuje akcję (zapisuje Product
lub zgłasza wyjątek), ale w metodzie operujesz na obiektach przy użyciu podejścia proceduralnego.
Oczywistym rozwiązaniem jest utworzenie Domain.ProductCollection
klasy za pomocą Add(Domain.Product)
metody rzucającej ProductWithSKUAlreadyExistsException
, ale bardzo brakuje jej wydajności, ponieważ trzeba by uzyskać wszystkie Produkty z przechowywania danych, aby dowiedzieć się w kodzie, czy Produkt ma już tę samą SKU jako produkt, który próbujesz dodać.
Jak rozwiązujecie ten konkretny problem? Sam w sobie nie stanowi to problemu, ponieważ warstwa usług reprezentuje pewne reguły domenowe od lat. Warstwa usługowa zazwyczaj obsługuje również bardziej złożone operacje na domenach, po prostu zastanawiam się, czy natknąłeś się na lepsze, bardziej scentralizowane rozwiązanie podczas swojej kariery.
Odpowiedzi:
Nie zgodziłbym się z tą częścią. Zwłaszcza ostatnie zdanie.
Chociaż prawdą jest, że domena powinna być ignorantem uporczywym, wie, że istnieje „Kolekcja podmiotów domeny”. I że istnieją reguły domeny dotyczące tej kolekcji jako całości. Wyjątkowość jest jednym z nich. A ponieważ implementacja rzeczywistej logiki w dużej mierze zależy od określonego trybu trwałości, musi istnieć pewien rodzaj abstrakcji w domenie, która określa potrzebę tej logiki.
Jest to więc tak proste, jak utworzenie interfejsu, który może zapytać, czy nazwa już istnieje, który jest następnie implementowany w magazynie danych i wywoływany przez osobę, która musi wiedzieć, czy nazwa jest unikalna.
Chciałbym podkreślić, że repozytoria to usługi DOMAIN. Są abstrakcjami wokół wytrwałości. Jest to implementacja repozytorium, które powinno być niezależne od domeny. Nie ma absolutnie nic złego w tym, że jednostka domeny wywołuje usługę domeny. Nie ma nic złego w tym, że jedna jednostka może korzystać z repozytorium w celu pobrania innej encji lub pobrania określonych informacji, których nie można łatwo przechowywać w pamięci. To jest powód, dla którego Repozytorium jest kluczową koncepcją w książce Evansa .
źródło
Domain.ProductCollection
co miałem na myśli, biorąc pod uwagę, że są one odpowiedzialne za pobieranie obiektów z warstwy domeny?Domain.ProductCollection
, zamiast tego pytam repozytorium, pytając, czyDomain.ProductCollection
zawiera Produkt z minęło SKU, tym razem ponownie pytając repozytorium zamiast tego (tak naprawdę jest to przykład), który zamiast iteracji nad wstępnie załadowanymi produktami odpytuje bazową bazę danych. Nie mam zamiaru przechowywać całego Produktu w pamięci, chyba że muszę, to byłoby kompletnym nonsensem.IProductRepository
interfejsDoesNameExist
, oczywiste jest, co powinna zrobić metoda. Ale upewnienie się, że Produkt nie może mieć takiej samej nazwy jak istniejący produkt, powinno być gdzieś w domenie.Musisz przeczytać Greg Young na temat sprawdzania poprawności zestawu .
Krótka odpowiedź: zanim zejdziesz zbyt daleko w gnieździe szczurów, musisz upewnić się, że rozumiesz wartość tego wymagania z perspektywy biznesowej. Jak naprawdę kosztowne jest wykrywanie i łagodzenie duplikacji, a nie zapobieganie jej?
Dłuższa odpowiedź: widziałem menu możliwości, ale wszystkie mają kompromisy.
Możesz sprawdzić duplikaty przed wysłaniem polecenia do domeny. Można to zrobić w kliencie lub w serwisie (twój przykład pokazuje technikę). Jeśli nie jesteś zadowolony z logiki wyciekającej z warstwy domeny, możesz osiągnąć ten sam wynik za pomocą
DomainService
.Oczywiście zrobione w ten sposób implementacja usługi deduplikacji będzie musiała wiedzieć coś o tym, jak sprawdzić istniejący skus. A więc wpycha część pracy z powrotem do domeny, wciąż masz do czynienia z tymi samymi podstawowymi problemami (potrzebujesz odpowiedzi na sprawdzenie poprawności zestawu, problemy z warunkami wyścigu).
Możesz dokonać weryfikacji w samej warstwie trwałości. Relacyjne bazy danych są naprawdę dobre w sprawdzaniu poprawności zestawu. Nałóż ograniczenie unikatowości na kolumnę SKU Twojego produktu i możesz zacząć. Aplikacja po prostu zapisuje produkt w repozytorium, a gdy wystąpi problem, pojawi się bulgotanie naruszenia ograniczenia. Więc kod aplikacji wygląda dobrze, a twoja rasa została wyeliminowana, ale przeciekają reguły „domeny”.
Możesz utworzyć oddzielny agregat w swojej domenie, który reprezentuje zestaw znanych skusów. Mogę wymyślić tutaj dwie odmiany.
Jednym z nich jest katalog produktów; produkty istnieją gdzie indziej, ale związek między produktami i skusami jest utrzymywany przez katalog, który gwarantuje wyjątkowość SKU. Nie oznacza to, że produkty nie mają skus; skus są przypisywane przez ProductCatalog (jeśli potrzebujesz skus, aby był unikalny, osiągasz to poprzez posiadanie tylko jednego agregatu ProductCatalog). Przejrzyj wszechobecny język ze swoimi ekspertami w dziedzinie - jeśli coś takiego istnieje, może to być właściwe podejście.
Alternatywą jest coś więcej niż usługa rezerwacji SKU. Podstawowy mechanizm jest taki sam: agregat wie o wszystkich skusach, więc może zapobiec wprowadzeniu duplikatów. Ale mechanizm jest nieco inny: kupujesz dzierżawę SKU przed przypisaniem go do produktu; podczas tworzenia produktu przekazujesz dzierżawę do SKU. W grze nadal występują warunki rasowe (różne agregaty, a zatem różne transakcje), ale ma inny smak. Prawdziwym minusem jest to, że projektujesz w modelu domeny usługę leasingową bez prawdziwego uzasadnienia w języku domeny.
Możesz przyciągnąć wszystkie jednostki produktów do jednego agregatu - tzn. Opisanego powyżej katalogu produktów. Dzięki temu zyskujesz absolutnie wyjątkowość skusu, ale koszt to dodatkowe spory, modyfikowanie dowolnego produktu naprawdę oznacza modyfikację całego katalogu.
Może nie musisz. Jeśli przetestujesz swój SKU z filtrem Bloom , możesz odkryć wiele unikalnych skusów bez ładowania zestawu.
Jeśli Twój przypadek użycia pozwala ci na dowolne ustalenie, który skus odrzucasz, możesz odeprzeć wszystkie fałszywe alarmy (nie jest to wielka sprawa, jeśli pozwolisz klientom przetestować skus, które proponują przed wysłaniem polecenia). Pozwoliłoby to uniknąć ładowania zestawu do pamięci.
(Jeśli chcesz być bardziej akceptowalny, możesz rozważyć leniwe ładowanie skus w przypadku dopasowania w filtrze kwitnienia; nadal ryzykujesz czasem załadowaniem całego skus do pamięci, ale nie powinno to być częstym przypadkiem, jeśli pozwolisz kod klienta, aby sprawdzić komendę pod kątem błędów przed wysłaniem).
źródło