Nazewnictwo adresów REST API

Nazewnictwo adresów REST API
KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0

Posiadam w mojej aplikacji REST kontroler z kilkoma metodami pod adreseami

Kopiuj
@PostMapping(value = "/sendMessage")
@GetMapping(value = "/getSentMessages")
@GetMapping(value = "/getReceivedMessages")
@DeleteMapping(value = "/removeSentMessage")
@DeleteMapping(value = "/removeReceivedMessage")
@GetMapping(value = "/searchSentMessages")

i chciałym przerobić te adresy na typowo RESTowe, więc np. adres

Kopiuj
@PostMapping(value = "/sendMessage")

mogę zmienić na

Kopiuj
@PostMapping(value = "/message")

A co w przypadku gdybym chciał z adresu

Kopiuj
@GetMapping(value = "/getReceivedMessages")

również utworzyć typowo RESTowy adres, to jak powinien on wyglądać, może

Kopiuj
@GetMapping(value = "/messages/received")

Czy takie nazewnictwo jest odpowiednie?

edytowany 1x, ostatnio: Klawiatur
rubaszny_karp
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 6 lat
  • Postów:152
0

jak chcesz wysłać wiadomość to
POST /message
chcesz pobrać wiadomość
GET /message/{id}


small data and high latency
edytowany 2x, ostatnio: rubaszny_karp
KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0

Mam dwa DTO:

Kopiuj
public class SentMessageDTO {
    ...
    private String recipient;

i

Kopiuj
public class ReceivedMessageDTO {
     ...
    private String sender;

W przypadku pobierania danych wiadomości wysłane i odebranej trochę się różnią, dlatego pobieranie danych za pomocą jednego kontrolera trochę nie pasuje, bo według tego sposobu, aby pobierać wiadomość tylko zapytaniem

Kopiuj
@GetMapping("/message/{id}")

konieczne by było stworzenie DTO z nadawcą i odbiorcą, a potrzebuje tylko jednej z tych zmiennych w zależności jaka to jest wiadomość. W sytuacji, gdy potrzebuję wiadomości wysłanej, to w DTO nie potrzebuję kto jest nadawcą i ta zmienna nie będzie użyta.

edytowany 3x, ostatnio: Klawiatur
Interpod
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:81
1

Słabe rozwiązanie. Jak chcesz zrobić jako zasób REST to zrób to solidnie bez zbędnego skomplikowania i tyle. Jak nie wiesz jak coś ma działać to najlepiej zasugerować się czymś co już istnieje. W przypadku wiadomości dobrym przykładem jest poczta. Każda wiadomość składa się z odbiorcy, nadawcy, treści i załączników. Normalnie możesz wyświetlić wszystkie wiadomości (GET na kolekcji wiadomości), wyświetlić wiadomości wysłane i odebrane (pewnego typu filtr na kolekcji). Jakiś czas temu pisałem podobny kod, co prawda nie jestem z niego dumny no ale skoro wisi na gh to niech tam wisi. W moim przypadku wiadomość sie tworzy tylko podając adresata, nadawca jest ustawiany jako zauthentykowany użytkownik, z kolei przy pobraniu wiadomości dostaje się wszystko: odbiorce, nadawce, czas wysłania itp. Dodatkowo możesz filtrować całość za pomocą enuma który pozwala ci pobrać wiadomości wysłane, otrzymane oraz wszystkie (tym razem bym wywalił enuma ALL bo jest trochę bezużyteczny).

Controller

PS: Polecam lombok'a

edytowany 2x, ostatnio: Interpod
KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0

@Interpod: dzięki za rady. Przeanalizuję Twój projekt, aby zobaczyć jak powinno to wyglądać.
Mam jeszcze takie pytanko. Otóż posiadam jeden kontroler z metodami, które służą do walidacji pól w jQuery

Kopiuj
@ApiOperation(value = "Return false if the user by username exists")
    @GetMapping("/checkUsernameAtRegistering")
@ApiOperation(value = "Return false if the user by e-mail exists")
    @GetMapping("/checkEmailAtRegistering")
@ApiOperation(value = "Return true if the user by username exists")
    @GetMapping("/checkUsername")
@ApiOperation(value = "Return true if the given password is the same as the database")
    @GetMapping("/checkPassword")

jak powinno wyglądać REST API dla tego typu metod. A potrzebuję tych metod do walidowania formularzy w js.

edytowany 1x, ostatnio: Klawiatur
Interpod
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:81
1

Ja osobiście zrobilem validacje na backendzie, w taki sposób ze skleiłem customowego validatora:

RegisterValidator

Następnie ten validator validator jest używany w controllerze przy rejestracji:

AuthController

Gdzie jak coś sie posypie to jest rzucany exception który następnie jest tłumaczony na odpowiedni response.
Jeżeli chciałbyś faktycznie to na frontendzie zrobić co wg mnie troche mija sie z celem to jedynym sensownym rozwiązaniem mogło by być:

Kopiuj
GET users/{username}

Tylko tutaj fajnie by było dodać case'a który robił by jakaś walidacje czy jeżeli masz prawo do obejrzenia profilu użytkownika to zwraca ci DTO całe (również zawierające poufne dane typu hasło itp), a jeżeli nie to zwróciłoby jakieś okrojone i wtedy we froncie mógłbyś sprawdzić czy sie matchują jakieś pola i zwrócić informacje o błędzie.
Ewentualnie coś takiego jeszcze:

Kopiuj
GET publicprofiles/{username}

I wtedy ten endpoint zwracałby pola które musisz sprawdzić przy rejestracji. (PS: Obydwa sposoby są co najmniej słabe).

No tyle że jednak lepszym rozwiązaniem jest po prostu wysłanie POST'a do rejestracji i jak wróci ci status odpowiedzi i ResponseBody po prostu sprawdzic czy status jest 404 i wtedy odczytać z ResposeBody opis błedów, ewentualnie 201 i wtedy wyciągnąć z headera location link i wypluć użytkownikowi.

**Teraz zauważ że w przypadku pierwszych 2 sposobów aby użytkownik się zarejestrował i dał mu odpowiedź musi zawsze polecieć 2 zapytania do backendu. Co jest tragicznym rozwiązaniem. **Dlatego że może wystąpić sytuacja że jak już pobierzesz te profile będziesz sprawdzał czy istnieje juz cos takiego na backendzie i w tym czasie inny użytkownik zrobi to samo, albo nawet zaczął przed tobą. I wystąpi coś na wzór wyścigu. Między twoim pierwszym i drugim requestem poleci POST utworzenia użytkownika o danych które do tej pory nie istniały. Ty będziesz posiadał informacje że użytkownik o tych danych może być utworzony, a tutaj się w miedzy czasie ktos zarejestrował tymi danymi. Wyślesz POST'a i dostaniesz albo InternalServerError albo inna odpowiedź jeżeli ustawiłeś sobie mapowanie Internali (chyba że nie ustawiłeś sobie constrains'ów na bazie na unique). Dlatego lepszym rozwiązaniem jest jednak korzystanie z jedno krokowej rejestracji.

edytowany 3x, ostatnio: Interpod
KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0

Dużo solidnej lekcji. Dzisiaj przez cały dzień będę próbował to zrobić i zrefaktoryzować swój kod według tego co mi napisałeś. W takim razie na froncie zostawię tylko walidację długości i składni, a takie elementy jak sprawdzanie czy username istnieje itd., to zastosuję ten walidator tak jak Ty to zrobiłeś.

edytowany 1x, ostatnio: Klawiatur
KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0

@Interpod:

A co sądzicie o takim rozwiązania?

Kopiuj
@GetMapping(value = "/api/message/{id}")
    public
    HttpEntity<? extends MessageDTO> getMessage(
            @ApiParam(value = "Message type", required = true) @RequestParam MessageType messageType,
            @ApiParam(value = "The message ID", required = true) @PathVariable Long id
    ) {
        return messageService.getMessage(id, authorizationService.getUserId(), messageType)
                .map(message -> {
                    if(messageType == MessageType.RECEIVED) {
                        return ResponseEntity.ok().body(converterMessageToReceivedMessageDTO.convert(message));
                    } else /* if(messageType == MessageType.SENT) */ {
                        return ResponseEntity.ok().body(converterMessageToSentMessageDTO.convert(message));
                    }
                }).orElseGet(() -> ResponseEntity.notFound().build());
    }

Musi być podział na dwa typy wiadomości, wysłaną i odebraną, bo np. przy odebranej odbiorca może zobaczyć swoją datę przeczytania, ale z punktu nadawcy nie właściwe jest, aby on widział również datę przeczytania. To taka tajemnica niedostępna dla nadawcy.
Z obiektami zrobiłem to tak:

Kopiuj
@Data
public class MessageDTO {
    private Long id;
    private String sender;
    private String recipient;
    private String subject;
    private String text;
    private Date dateOfRead;
}
Kopiuj
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ReceivedMessageDTO extends MessageDTO {} // Przy odbiorcy usuwam pole 'recipient' dzięki @JsonIclude
Kopiuj
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SentMessageDTO extends MessageDTO {} // Przy nadawcy usuwam pole 'dateOfRead' i 'sender' dzięki @JsonIclud

PS: Trochę nie pasują mi te obiekty. Macie pomysł jak zrobić to lepiej? Albo np. przyjmowany MessageType zadeklarować jako Optional i w razie jego braku zaprogramować ustalenie typu i dopiero przejść do bloku return.

edytowany 5x, ostatnio: Klawiatur
zyxist
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 6 lat
  • Postów:101
1

URL - jest OK, o to właśnie chodzi :).

Twoje uzasadnienie dot. logiki biznesowej jest jak najbardziej słuszne. Osobiście natomiast zaimplementowałbym to trochę inaczej. Wg mnie jeżeli pytam się o zasób /ap/message, to oczekuję, że zawsze w dokumencie wynikowym będę mieć przewidywalny zestaw atrybutów. Innymi słowy, nie powinno być tak, że jak pytam się o wiadomość nr 1, to atrybut dateOfRead istnieje, a jak o wiadomość nr 2, to go nie ma. Zamiast tego, możesz po prostu przysyłać w tych atrybutach puste wartości.

Kiedy lista atrybutów może być zmienna? Wtedy, kiedy mam na to wpływ:

  • wersjonowanie API - tu mam wpływ na to, z jaką wersją API gadam i jaki zestaw atrybutów mnie obowiązuje,
  • kiedy API pozwala np. poprzez jakiś query param poprosić o konkretne atrybuty.

KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0

@zyxist: dzięki za odpowiedź. Dostałem jeszcze taką odpowiedź na https://stackoverflow.com/questions/46203232/rest-method-for-getmessages?noredirect=1#comment79369937_46203232, aby utworzyć oddzielne adresy dla pobierania wiadomości wysłane i odebranej. Co o tym myślisz? Ważny jest fragment 'Furthermore, if a new messageType is added, the existing method would have to be changed which violates the open/close principle'.

edytowany 2x, ostatnio: Klawiatur
zyxist
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 6 lat
  • Postów:101
0

Na StackOverflow masz jeszcze jedną fajną propozycję, mianowicie by zbudować sobie adresy w stylu /api/message/sent/ oraz /api/message/received/ - wtedy widać, że chodzi o różne typy tej samej wiadomości, ale będące różnymi zasobami. I tu wg mnie już mogą być różne zestawy pól.

To, które z rozwiązań jest lepsze, należy już bardziej do zagadnień logiki biznesowej. Dla każdego z nich można znaleźć dobre uzasadnienie. Jeśli klient wymaga, by móc odpytać się o każdą wiadomość, podając tylko jej ID, to rozwiązanie ze StackOverflow odpada. Jeśli klient będzie oddzielnie pracował z wiadomościami odebranymi i wysłanymi i zawsze zna typ wiadomości, o który chce się zapytać, podejście ze StackOverflow jest OK i pozwoli na zróżnicowanie zestawów atrybutów.

PS. Jeśli chodzi o zasadę otwarte/zamknięte, to da się to zaimplementować tak, by można było dodawać nowe typy wiadomości bez jej łamania :).


KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0

@zyxist:
Teraz mój kontroler wygląda w taki sposób https://pastebin.com/XuHgNLVW. Jednak według mnie trochę panuje tutaj za duży burdel. Używanych jest bardzo dużo obiektów i potrzebnych do nich konwerterów, co trochę psuje wygląda całościowy. Mimo to wiadomo co gdzie jest.

edytowany 1x, ostatnio: Klawiatur
Interpod
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:81
0
Klawiatur napisał(a):

@zyxist:
Teraz mój kontroler wygląda w taki sposób https://pastebin.com/XuHgNLVW. Jednak według mnie trochę panuje tutaj za duży burdel. Używanych jest bardzo dużo obiektów i potrzebnych do nich konwerterów, co trochę psuje wygląda całościowy. Mimo to wiadomo co gdzie jest.

O kurde,

  1. Lombok
  2. Wywal autowired
  3. Autowired Constructor > Getter > ............ nigdy? > field
  4. Robisz coś takiego
Kopiuj

addMessage(@ApiParam(value = "Message", required = true) @RequestBody @Valid SendMessageDTO sendMessageDTO,  UriComponentsBuilder uriComponentsBuilder)

Pierwsze pytanie co daje ci annotacja valid w tym przypadku? Czy ona po prostu jest żeby byla? Żeby coś to robilo to musi być coś w stylu:

Kopiuj
addMessage(@RequestBody @Valid SendMessageDTO messageDTO, Errors errors, ...,) {
   ifErrorsThrowRuntime(errors);
}

Zwracanie HttpEntity wgl trochę tylko rozwala jakość tego. Jak znajdę chwile to ogarnę resztę tego kodu.

KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 7 lat
  • Postów:35
0
  1. Co jest nie tak z Lombok?
    2,3) W kontrolerze nie ma chyba znaczenia czy autowired czy konstruktor. Jakbym testował kontroler, to używałbym MockMvc i nie ma potrzeby tworzenia konstruktora.
  2. To jest pod kontrolą https://github.com/JonkiPro/REST-Web-Services/blob/master/src/main/java/com/service/app/rest/controller/advice/ErrorFieldsExceptionHandler.java Może wpiszę coś w komentarzu, bo ktoś też może pomyśleć, że ta walidacja nie działa.

Teraz moje adresy wyglądają w ten sposób https://zapodaj.net/b1a1febdc87fc.png.html https://zapodaj.net/3e5b6cb8c2488.png.html

edytowany 2x, ostatnio: Klawiatur
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)