Rozwijam nową usługę w środowisku mikrousług. To jest usługa REST. Dla uproszczenia załóżmy, że ścieżka to: / historyBooks
A metoda POST dla tej ścieżki tworzy nowy podręcznik historii.
Załóżmy, że książka historyczna obejmuje jedną lub więcej epok w historii.
Dla zwięzłości załóżmy, że mamy tylko następujące epoki ludzkiej historii:
- Starożytny
- Post Klasyczny
- Nowoczesny
W moim kodzie chciałbym przedstawić je w pliku enum
.
Treść metody (ładunek) ma format JSON i powinna zawierać nazwę pola eras
. To pole zawiera listę era
wartości, które obejmuje ta książka.
Ciało może wyglądać następująco:
{
"name": "From the cave to Einstein - a brief history review",
"author": "Foo Bar",
"eras": ["Ancient", "Post Classical", "Modern"]
}
W tej konkretnej usłudze logika biznesowa jest następująca:
jeśli w danych wejściowych nie podano żadnych epok, uznaje się, że książka obejmuje wszystkie epoki.
W przeglądzie API pojawiła się sugestia:
Dołącz inną wartość, ALL
dla wyliczenia czasów, aby wyraźnie wskazać, że wszystkie czasy są objęte.
Myślę, że ma to wady i zalety.
Plusy:
Jawne dane wejściowe
Cons:
Jeśli podane są dwie pozycje na liście, powiedz ALL
i Ancient
- co zostanie pobrane z aplikacji? Myślę, że to ALL
powinno zastąpić inne wartości, ale to nowa logika biznesowa.
Jeśli uruchomię zapytanie dotyczące książek z konkretnej epoki, w jaki sposób przedstawiłbym książki obejmujące wszystkie epoki? Jeśli ALL
jest również używany dla danych wyjściowych (przy użyciu tej samej logiki), wówczas obowiązkiem konsumenta jest interpretowanie ALL
jako ["Ancient", "Post Classical", "Modern"]
.
Moje pytanie
Myślę, że posiadanie nowych ALL
powoduje więcej zamieszania niż wcale.
Co myślisz? Czy dodałbyś tę ALL
wartość, czy zachowałbyś swój API bez niej?
Odpowiedzi:
Zależy od tego, czy dostępne epoki są dostępne dla aplikacji dzwoniącej. Przypuszczalnie tak, aby użytkownik mógł wybrać to, co ich interesuje. W takim przypadku dostarczenie opcji „WSZYSTKO” jest kwestią frontonu. Gdybym to był ja, wysyła listę wszystkich epok zamiast opcji „wszystkie”. Jeśli nie, to potrzebujesz opcji „WSZYSTKO” i ryzykujesz, że fronton odzyska rzeczy z epoki, której nie zrozumie.
Jak zauważyłeś, WSZYSTKO z innymi opcjami oznacza, że możesz uzyskać sprzeczne żądania. Kolejną kwestią do rozważenia jest to, że można przekazać „WSZYSTKO”, wówczas wszelkie inne wyliczenia stają się raczej wykluczeniami niż inkluzjami, np. „WSZYSTKO”, „Starożytny” oznacza „Wszystko oprócz Starożytnych”. To ma jakiś sens, ale oczywiście interfejs użytkownika musi odzwierciedlać to, co może być zbyt skomplikowane.
TL; DR; Przeważnie „WSZYSTKO” to interfejs użytkownika i możesz osiągnąć to samo na poziomie usługi bez dwuznaczności, więc nie rób tego.
źródło
To, a także specjalny przypadek „WSZYSTKIE”, są złe IMHO - w miarę możliwości interfejsy API powinny być jawne. Posiadanie specjalnych przypadków, takich jak „nic tak naprawdę nie znaczy wszystkiego”, oznacza, że każdy, kto korzysta z interfejsu API, musi również znać wszystkie specjalne przypadki lub, podobnie jak w przypadku „WSZYSTKIEGO”, prowadzić do wniosków, w których zamiar i / lub wynik są niejednoznaczne.
Specjalne przypadki często prowadzą również do bardziej złożonego kodu, zarówno pod względem sprawdzania poprawności danych wejściowych użytkownika, jak i poprawnej obsługi żądania.
źródło
W przypadku zmiennych kategorialnych unikałbym umieszczenia symbolu wieloznacznego „wszystkie” na tym samym poziomie.
Wygląda na to, że masz dwupoziomowe kryterium wyszukiwania dla okresu:
Dzwoniący może określić (fałsz, zignorowany) lub (prawda, {„starożytny”, „nowoczesny”}).
Lub możesz zinterpretować pusty zestaw jako symbol wieloznaczny, oznacza to, że nie jest selektywny.
W twoim konkretnym przypadku wygląda na to, że masz ciągłą zmienną rok, która jest dyskretyzowana do kilku dobrze znanych wartości, a tak naprawdę chcesz robić arytmetykę przedziałową. Tak więc twoje zapytania przyjmowałyby (początkowy, końcowy) zakres lat, a wyliczenia mogłyby wygodnie dostarczyć takie atrybuty.
źródło
tl; dr
Na twoje aktualne pytanie - dotyczące lokalnych struktur danych we wdrażaniu - zgadzam się z twoją konkluzją: Unikaj specjalnej wartości z wszystkich epok, ponieważ w większości zwiększa ona złożoność i możliwości popełniania błędów. Jednak szczególnie interfejs użytkownika końcowego i być może serializowane dane ładunku mogą być różne historie.
Lokalne struktury danych
Spójrzmy na to z perspektywy systemu typów. Zasadniczo wyliczenie jest typem, który definiuje rozłączny zestaw konkretnych kategorii (enumeratorów), jak już wskazano w odpowiedzi @JH . Zmienna typu wyliczeniowego zawiera dokładnie jedną z tych kategorii. Jeśli chcesz reprezentować kolekcję wartości modułu wyliczającego, potrzebujesz drugiego typu:
Moduł
all
wyliczający jest nie do przejścia, ponieważ łączy te dwa typy razem. To nie jest pojedyncza kategoria, ale zbiór wszystkich możliwych kategorii.Na ściśle praktycznym poziomie radzenie sobie z jakąkolwiek wartością specjalną komplikuje implementację - a zatem zwiększa prawdopodobieństwo błędów - ponieważ albo musisz ją wszędzie specjalizować, albo rozpakować, aby pasowała do jej rzeczywistego znaczenia. Aha, a co z jawnym zbiorem wszystkich możliwych modułów wyliczających. Kiedy i gdzie należy to zwinąć do specjalnej
all
wartości? Czy w ogóle powinien się zawalić?Moją preferowaną architekturą jest brak reprezentacji wszystkich epok w kodzie. Obejmuje to
all
moduł wyliczający, ale także pustą kolekcję oznaczającą wszystkie epoki . To dokładnie ten sam specjalny przypadek, tylko w innym przebraniu.W specyfikacji API określiłbym, że znacznik wszystkich epok musi zawsze pojawiać się sam, ponieważ posiadanie czegoś na nim nie ma sensu. Następnie odrzuciłbym to jako naruszenie umowy. Jest to jednak dobry przykład tego, jak wszystkie epoki komplikują zarówno implementację, jak i logikę biznesową.
Główny problem polega na tym, że musisz określić reguły konwersji między wartością specjalną a jej podstawowym znaczeniem. A potem ty i wszyscy inni korzystający z interfejsu API musicie poprawnie wdrożyć te reguły. Cynik we mnie mówi mi, że nie chodzi o to, czy ktoś pomyli się, ale po prostu kiedy .
Dane szeregowe (JSON)
Pierwotnie miałem tutaj całą sekcję na temat modyfikowania zapytań tylko do odczytu i różnych ról dla
"eras"
listy. Ale ostatecznie go usunąłem, ponieważ KISS. Reguły wyliczania / gromadzenia danych nie są nierozsądne w przypadku danych JSON, więc utrzymanie spójności jest najprostszym rozwiązaniem.Interfejs użytkownika dla użytkownika końcowego
Zakładam, że i tak nastąpi transformacja danych między interfejsem użytkownika a bazowymi strukturami danych, najprawdopodobniej dlatego, że masz jakąś architekturę MVC. Używaj więc wszystkiego, co jest najwygodniejsze i intuicyjne dla użytkownika. W swojej odpowiedzi @ Markus dał świetny przykład z różnymi opcjami wyboru dla dni tygodnia.
źródło
Tak.
Jeśli myślisz z perspektywy kolekcji: masz całą kolekcję czegoś. I za każdym razem, gdy chcesz tylko podzbiór tej kolekcji, musisz podać jakiś warunek filtru , gdy element jest uważany za element tego podzbioru. I tak
ALL
jest, gdy nie zastosowano filtra, a nie „warunkiem filtru jestALL
” .Odwróciłbym to do góry nogami: ta książka nie opisuje żadnej konkretnej epoki.
Jest to również spójne z perspektywy zapytania:
Jeśli nie wybrano żadnej ery, wszystkie książki są zwracane (w tym ta z pustym polem ery); a gdy wybrana jest era, zwracane są tylko książki z wybraną epoką - odzyskanie wszystkich książek z epoki specjalnej oraz ogólnych byłoby w jakiś sposób nieoczekiwane.
Jedyną kwestią, w której chciałbym dodać
ALL
kategorię - jest dla wygody użytkownika, ponieważ bardziej irytujące może być wybranie „Nic” zamiast „Wszystko”.źródło
Niedawno wdrożyłem podstawowy harmonogram i jak wspomniano już w @LoztInSpace, większość z nich to cukier z interfejsu użytkownika.
Interfejs użytkownika musi być absolutnie jasny w zakresie tego, co użytkownik osiągnie, wybierając jedną z opcji.
W moim przypadku mam do wyboru dni tygodnia. Oprócz „Wszystkich” jako opcji dodałem „Dni robocze” i „Weekend”. Zaznaczenie każdej opcji spowoduje włączenie jej do późniejszego użycia i będziesz musiał ręcznie odznaczyć wszystkie dni, których nie chcesz uwzględniać.
Technicznie oznacza to, że jeśli użytkownik wybierze „Dni powszednie”, interfejs użytkownika będzie miał pięć zaznaczonych pól wyboru, a wyliczenie użyje wartości „Dni robocze”. Jeśli jedno z pól wyboru jest odznaczone, wartość „Dni robocze” zostaje zastąpiona lub odwzorowaną bitową mapą pozostałych czterech dni.
Nie używaj jednej części opcji, aby uwzględnić wszystko, a jednocześnie wykluczyć coś, aby uniknąć konfliktów i upewnić się, że interfejs użytkownika ma sens dla użytkownika.
Myślę, że dobrą orientacją jest to, że użycie opcji „Wszystko” ma sens, jeśli użytkownik może łatwo zrozumieć pojęcie, co obejmuje „Wszystko” (wszystkie dni w tygodniu) lub jeśli jest raczej nieważne (wyszukaj wszystkie kategorie na Amazon)
źródło
Podoba mi się pomysł jawnego noszenia WSZYSTKIEGO wyliczenia, ale wolałbym ustawić je jako flagi
Używając biblioteki takiej jak flagon lub ręcznie grając bitowymi operacjami, gdy wszystkie wartości są zaznaczone, zamiast tego używasz WSZYSTKICH.
Pamiętaj, że wartości wyliczenia powinny zawsze być podwojone w stosunku do ich poprzedników. I że dla kontroli poczytalności nigdy nie powinieneś usuwać wyliczenia, chociaż możesz go wyłączyć, dostosowując kod, aby zawsze liczył wyłączonych jako część WSZYSTKICH.
Chociaż zamiast tego miałbym flagę BRAK i pozwoliłbym klientowi zdecydować, co zrobić, gdy BRAK zostanie użyty, na wypadek gdyby firma zmieniła się później.
Jeśli ta implementacja zostanie podjęta, zamiast przekazywać enuny, przekażesz sumę wybranych wartości.
źródło