JavaRush /Blog Java /Random-PL /REST API i kolejne zadanie testowe.
Денис
Poziom 37
Киев

REST API i kolejne zadanie testowe.

Opublikowano w grupie Random-PL
Część I: Początek Od czego zacząć? Dziwne, ale ze specyfikacji technicznych. Niezwykle ważne jest, aby po zapoznaniu się z przesłanym TOR w pełni zrozumieć, co jest w nim napisane i czego oczekuje Klient. Po pierwsze, jest to ważne dla dalszej realizacji, a po drugie, jeśli nie zrealizujesz tego, czego się od Ciebie oczekuje, nie będzie to dla Ciebie korzystne. Aby uniknąć marnowania powietrza, naszkicujmy prostą specyfikację techniczną. Chcę więc usługę, do której będę mógł wysyłać dane, będą one przechowywane w serwisie i zwracane mi według własnego uznania. Muszę także mieć możliwość aktualizacji i usunięcia tych danych, jeśli zajdzie taka potrzeba . Kilka zdań nie wydaje się być oczywistą rzeczą, prawda? Jak chcę tam wysłać dane? Jakie technologie zastosować? W jakim formacie będą te dane? Nie ma też przykładów danych przychodzących i wychodzących. Wniosek - specyfikacja techniczna jest już zła . Spróbujmy ująć inaczej: potrzebujemy usługi, która będzie w stanie przetwarzać żądania HTTP i pracować z przesyłanymi danymi. Będzie to baza danych osobowych. Będziemy mieć pracowników, są oni podzieleni na działy i specjalizacje, pracownicy mogą mieć przydzielone zadania. Naszym zadaniem jest automatyzacja procesu rozliczania pracowników zatrudnionych, zwalnianych, przeniesionych oraz procesu przydzielania i anulowania zadań za pomocą REST API. Jako Faza 1, obecnie współpracujemy wyłącznie z pracownikami. Usługa musi mieć kilka endpointów, aby z nią współpracować: - POST /pracownik - żądanie POST, które musi akceptować obiekt JSON z danymi o pracowniku. Obiekt ten należy zapisać w bazie danych, jeżeli taki obiekt już istnieje w bazie należy uzupełnić informacje w polach o nowe dane. - GET /pracownik - żądanie GET zwracające całą listę pracowników zapisanych w bazie danych - DELETE - DELETE /pracownik aby usunąć konkretnego pracownika Model danych pracownika:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  	//List of tasks, not needed for Phase 1
  ]
}
Część II: Narzędzia do pracy Zatem zakres pracy jest mniej więcej jasny, ale jak go wykonamy? Oczywiście takie zadania w teście mają kilka celów aplikacji: sprawdzenie, jak kodujesz, zmuszenie cię do korzystania ze Springa i trochę pracy z bazą danych. Cóż, zróbmy to. Potrzebujemy projektu SpringBoot z obsługą REST API i bazą danych. Na stronie https://start.spring.io/ znajdziesz wszystko, czego potrzebujesz. REST API lub inne zadanie testowe.  - 1 Możesz wybrać system kompilacji, język, wersję SpringBoot, ustawić ustawienia artefaktów, wersję Java i zależności. Kliknięcie przycisku Dodaj zależności spowoduje wyświetlenie charakterystycznego menu z paskiem wyszukiwania. Pierwszymi kandydatami na słowa odpoczynek i dane są Spring Web i Spring Data - dodamy je. Lombok to wygodna biblioteka, która pozwala na użycie adnotacji w celu pozbycia się kilometrów kodu za pomocą metod pobierających i ustawiających. Klikając przycisk Generuj otrzymamy archiwum z projektem, które można już rozpakować i otworzyć w naszym ulubionym IDE. Domyślnie otrzymamy pusty projekt, z plikiem konfiguracyjnym dla systemu kompilacji (w moim przypadku będzie to gradle, ale w przypadku Mavena nie ma zasadniczej różnicy i jeden wiosenny plik startowy) Uważne osoby mogą zwrócić uwagę na dwie REST API lub inne zadanie testowe.  - 2 rzeczy . Po pierwsze, mam dwa pliki ustawień: application.properties i application.yml. Domyślnie dostaniesz dokładnie właściwości - pusty plik w którym możesz przechowywać ustawienia, ale dla mnie format yml wygląda trochę bardziej czytelnie, teraz pokażę porównanie: Pomimo tego, że REST API lub inne zadanie testowe.  - 3 obrazek po lewej stronie wygląda na bardziej zwarty , łatwo zauważyć dużą ilość duplikatów w ścieżce właściwości. Obraz po prawej stronie to zwykły plik yml o strukturze drzewiastej, który jest dość łatwy do odczytania. Wykorzystam ten plik w dalszej części projektu. Drugą rzeczą, którą uważne osoby mogą zauważyć, jest to, że mój projekt ma już kilka pakietów. Nie ma jeszcze rozsądnego kodu, ale warto się z nim zapoznać. Jak pisze się wniosek? Mając konkretne zadanie musimy je rozłożyć na małe podzadania i rozpocząć ich konsekwentną realizację. Czego się od nas wymaga? Musimy udostępnić API, z którego będzie mógł skorzystać klient; za tę część funkcjonalności odpowiadać będzie zawartość pakietu kontrolera. Drugą częścią aplikacji jest baza danych - pakiet trwałości. Będziemy w nim przechowywać takie rzeczy jak Database Entities (Entities) oraz Repositories – specjalne interfejsy sprężynowe, które pozwalają na interakcję z bazą danych. Pakiet usług będzie zawierał klasy usług. Poniżej porozmawiamy o tym, czym jest usługa typu Spring. I na koniec pakiet utils. Będą tam przechowywane klasy użytkowe z najróżniejszymi metodami pomocniczymi, na przykład klasy do pracy z datą i czasem, czy klasy do pracy z ciągami znaków i kto wie co jeszcze. Zacznijmy wdrażać pierwszą część funkcjonalności. Część III: Kontroler
@RestController
@RequestMapping("${application.endpoint.root}")
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;

    @GetMapping("${application.endpoint.employee}")
    public ResponseEntity<List<Employee>> getEmployees() {
        return ResponseEntity.ok().body(employeeService.getAllEmployees());
    }
}
Teraz nasza klasa EmployeeController wygląda tak. Jest tu kilka istotnych rzeczy, na które warto zwrócić uwagę. 1. Adnotacje nad klasą, pierwszy @RestController informuje naszą aplikację, że ta klasa będzie punktem końcowym. 2. @RequestMapping, choć nieobowiązkowy, jest przydatną adnotacją, pozwala ustawić konkretną ścieżkę do punktu końcowego. Te. aby na to zapukać, będziesz musiał wysyłać żądania nie do localhost:port/employee, ale w tym przypadku do localhost:8086/api/v1/employee Właściwie, skąd pochodzą te api/v1 i pracownik? Z naszego pliku application.yml Jeśli przyjrzysz się uważnie, znajdziesz tam następujące linie:
application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
Jak widać mamy takie zmienne jak application.endpoint.root i application.endpoint.employee, dokładnie to napisałem w adnotacjach, polecam zapamiętać tę metodę - zaoszczędzi to sporo czasu na rozwijaniu czy przepisywania funkcjonalność - zawsze wygodniej jest mieć wszystko w konfiguracji, a nie kodować na stałe całego projektu. 3. @RequiredArgsConstructor to adnotacja Lombok, wygodna biblioteka, która pozwala uniknąć pisania niepotrzebnych rzeczy. W tym przypadku adnotacja jest równoznaczna z faktem, że klasa będzie miała publicznego konstruktora, w którym wszystkie pola zostaną oznaczone jako ostateczne
public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
Ale po co mamy coś takiego pisać, skoro wystarczy jedna adnotacja? :) Swoją drogą, gratulacje, to najbardziej prywatne ostatnie pole to nic innego jak osławiony zastrzyk zależności. Przejdźmy dalej, właściwie jakim polem jest EmployeeService? Będzie to jedna z usług w naszym projekcie, która będzie przetwarzać żądania dla tego punktu końcowego. Pomysł jest tutaj bardzo prosty. Każde zajęcia powinny mieć swoje własne zadanie i nie powinny być przeciążone niepotrzebnymi czynnościami. Jeśli jest to administrator, niech zajmie się przyjmowaniem żądań i wysyłaniem odpowiedzi, ale my wolimy powierzyć przetwarzanie dodatkowej usłudze. Ostatnią rzeczą jaka pozostała w tej klasie jest jedyna metoda zwracająca listę wszystkich pracowników naszej firmy korzystających z wyżej wymienionej usługi. Sama lista jest zawinięta w jednostkę o nazwie ResponseEntity. Robię to po to, aby w przyszłości, jeśli zajdzie taka potrzeba, móc łatwo zwrócić potrzebny mi kod odpowiedzi i komunikat, który zautomatyzowany system będzie w stanie zrozumieć. Czyli np. ResponseEntity.ok() zwróci 200-ty kod, który powie, że wszystko jest w porządku, ale jeśli zwrócę np.
return ResponseEntity.badRequest().body(Collections.emptyList());
wtedy klient otrzyma w odpowiedzi kod 400 - złe żądanie i pustą listę. Zwykle ten kod jest zwracany, jeśli żądanie jest nieprawidłowe. Jednak jeden kontroler nie wystarczy nam do uruchomienia aplikacji. Nasze zależności nam na to nie pozwolą, bo i tak musimy mieć bazę :) No cóż, przejdźmy do dalszej części. Część IV: prosta trwałość Ponieważ naszym głównym zadaniem jest uruchomienie aplikacji, ograniczymy się na razie do kilku wycinków. Widzieliście już w klasie Controller, że zwracamy listę obiektów typu Employee, będzie to nasza encja dla bazy danych. Stwórzmy go w pakiecie demo.persistence.entity.W przyszłości pakiet encji będzie można uzupełnić o inne encje z bazy danych.
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
Jest to klasa prosta jak drzwi, której adnotacje mówią dokładnie co następuje: to jest jednostka bazy danych @Entity, to jest klasa z danymi @Data - Lombok. Pomocny Lombok stworzy dla nas wszystkie potrzebne gettery, settery, konstruktory - kompletne nadzienie. Cóż, małą wisienką na torcie jest @Accessors(chain = true) W rzeczywistości jest to ukryta implementacja wzorca Builder. Załóżmy, że masz klasę z wieloma polami, które chcesz przypisać nie za pomocą konstruktora, ale metod. W innej kolejności, może nie wszystkie naraz. Nigdy nie wiesz, jaka logika będzie zastosowana w Twojej aplikacji. Ta adnotacja jest kluczem do tego zadania. Spójrzmy:
public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
Załóżmy, że mamy w klasie wszystkie te pola😄Możesz je przypisać, nie możesz ich przypisać, możesz je mieszać miejscami. W przypadku zaledwie 3 obiektów nie wygląda to na coś wybitnego. Ale są klasy o znacznie większej liczbie właściwości, na przykład 50. I napisz coś takiego
public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
Nie wygląda zbyt ładnie, prawda? Musimy także ściśle przestrzegać kolejności dodawania zmiennych zgodnie z konstruktorem. Jednak odpuszczę sobie, wróćmy do sedna. Teraz mamy w nim jedno (obowiązkowe) pole - unikalny identyfikator. W tym przypadku jest to numer typu Long, który generowany jest automatycznie przy zapisie do bazy danych. W związku z tym adnotacja @Id wyraźnie wskazuje nam, że jest to unikalny identyfikator; za jego unikalne wygenerowanie odpowiada @GeneratedValue. Warto zaznaczyć, że @Id można dodać do pól, które nie są generowane automatycznie, ale wówczas kwestię unikalności trzeba będzie uporać się ręcznie. Jaki może być unikalny identyfikator pracownika? No cóż, na przykład imię i nazwisko + dział...jednak dana osoba ma pełne imienniki i jest szansa, że ​​będą pracować w tym samym dziale, mała, ale jest - to znaczy, że decyzja jest zła. Można by dodać jeszcze całą masę innych pól, jak np. data zatrudnienia, miasto, ale wydaje mi się, że to wszystko za bardzo komplikuje logikę. Możesz się zastanawiać, jak to w ogóle możliwe, że kilka pól jest jednocześnie unikalnych? Odpowiadam – być może. Jeśli jesteś ciekawy, możesz wyszukać w Google informacje o takich rzeczach, jak @Embeddable i @Embedded. Cóż, sedno mamy już za sobą. Teraz potrzebujemy prostego repozytorium. Będzie to wyglądać tak:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
Tak, to wszystko. Po prostu interfejs, nazwaliśmy go EmployeeRepository, rozszerza JpaRepository, który ma dwa wpisane parametry, pierwszy odpowiada za typ danych, z którym pracuje, drugi za typ klucza. W naszym przypadku są to Pracownik i Długi. To na razie wystarczy. Ostatnim akcentem przed uruchomieniem aplikacji będzie nasza usługa:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
Istnieje już znany RequiredArgsConstructor i nowa adnotacja @Service - tak zwykle określa się warstwę logiki biznesowej. Podczas uruchamiania kontekstu wiosennego klasy oznaczone tą adnotacją zostaną utworzone jako komponenty Beans. Gdy w klasie EmployeeController utworzymy końcową właściwość EmployeeService i dołączymy RequiredArgsConstructor (lub stworzymy ręcznie konstruktor) Spring, to podczas inicjalizacji aplikacji znajdzie to miejsce i wsunie nam obiekt klasy do tej zmiennej. Wartość domyślna to Singleton – tj. dla wszystkich takich linków będzie jeden obiekt, należy to wziąć pod uwagę przy projektowaniu aplikacji. Właściwie to wszystko, aplikację można uruchomić. Nie zapomnij wprowadzić niezbędnych ustawień w konfiguracji. REST API lub inne zadanie testowe.  - 4 Nie będę opisywał jak zainstalować bazę danych, stworzyć użytkownika i samą bazę danych, ale zwrócę tylko uwagę, że w adresie URL używam dwóch dodatkowych parametrów - useUnicore=true i charakterEncoding=UTF-8. Zrobiono to tak, aby tekst był wyświetlany mniej więcej równomiernie w dowolnym systemie. Jeśli jednak jesteś zbyt leniwy, aby majstrować przy bazie danych i naprawdę chcesz grzebać w działającym kodzie, istnieje szybkie rozwiązanie: 1. Dodaj następującą zależność do build.gradle:
implementation 'com.h2database:h2:2.1.214'
2. W pliku application.yml musisz edytować kilka właściwości, dla uproszczenia podam pełny przykład sekcji wiosennej:
spring:
  application:
    name: "employee-management-service"
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.H2Dialect
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:./mydb
    username: sa
    password:
Baza danych będzie przechowywana w folderze projektu, w pliku o nazwie mydb . Ja jednak radziłbym zainstalować pełnoprawną bazę danych 😉 Przydatny artykuł na ten temat: Spring Boot With H2 Database Na wszelki wypadek podam pełną wersję mojego build.gradle, aby wyeliminować rozbieżności w zależnościach:
plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'mysql:mysql-connector-java:8.0.30'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
System jest gotowy do uruchomienia: REST API lub inne zadanie testowe.  - 5 Możesz to sprawdzić wysyłając żądanie GET z dowolnego odpowiedniego programu do naszego punktu końcowego. W tym konkretnym przypadku wystarczy zwykła przeglądarka, ale w przyszłości będziemy potrzebować Postmana. REST API lub inne zadanie testowe.  - 6 Tak, faktycznie nie wdrożyliśmy jeszcze żadnego z wymagań biznesowych, ale mamy już aplikację, która uruchamia się i można ją rozbudowywać o wymaganą funkcjonalność. Ciąg dalszy: API REST i weryfikacja danych
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION