JavaRush /Java-Blog /Random-DE /REST-API und eine weitere Testaufgabe.
Денис
Level 37
Киев

REST-API und eine weitere Testaufgabe.

Veröffentlicht in der Gruppe Random-DE
Teil I: Anfang Wo soll ich anfangen? Seltsamerweise, aber von den technischen Spezifikationen her. Es ist äußerst wichtig, sicherzustellen, dass Sie nach dem Lesen der eingereichten TOR vollständig verstehen, was darin steht und was der Kunde erwartet. Erstens ist dies wichtig für die weitere Umsetzung, und zweitens ist es nicht von Vorteil, wenn Sie nicht das umsetzen, was von Ihnen erwartet wird. Um Luftverschwendung zu vermeiden, skizzieren wir eine einfache technische Spezifikation. Ich möchte also einen Dienst, an den ich Daten senden kann. Diese werden auf dem Dienst gespeichert und nach Belieben an mich zurückgegeben. Außerdem muss ich in der Lage sein, diese Daten bei Bedarf zu aktualisieren und zu löschen . Ein paar Sätze scheinen keine klare Sache zu sein, oder? Wie möchte ich Daten dorthin senden? Welche Technologien sollen verwendet werden? In welchem ​​Format werden diese Daten vorliegen? Es gibt auch keine Beispiele für ein- und ausgehende Daten. Fazit: Die technische Spezifikation ist bereits schlecht . Versuchen wir es anders zu formulieren: Wir brauchen einen Dienst, der HTTP-Anfragen verarbeiten und mit übertragenen Daten arbeiten kann. Dies wird die Personalaktendatenbank sein. Wir werden Mitarbeiter haben, sie sind nach Abteilungen und Fachgebieten unterteilt, den Mitarbeitern können Aufgaben zugewiesen werden. Unsere Aufgabe ist es, den Prozess der Abrechnung von eingestellten, entlassenen und versetzten Mitarbeitern sowie den Prozess der Zuweisung und Stornierung von Aufgaben mithilfe der REST-API zu automatisieren. Als Phase 1 arbeiten wir derzeit nur mit Mitarbeitern. Der Dienst muss über mehrere Endpunkte verfügen, um damit arbeiten zu können: – POST /Mitarbeiter – POST-Anfrage, die ein JSON-Objekt mit Daten über den Mitarbeiter akzeptieren muss. Dieses Objekt muss in der Datenbank gespeichert werden. Wenn ein solches Objekt bereits in der Datenbank vorhanden ist, müssen die Informationen in den Feldern mit neuen Daten aktualisiert werden. - GET /employee – GET-Anfrage, die die gesamte Liste der in der Datenbank gespeicherten Mitarbeiter zurückgibt. - DELETE – DELETE /employee, um einen bestimmten Mitarbeiter zu löschen. Mitarbeiterdatenmodell:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  	//List of tasks, not needed for Phase 1
  ]
}
Teil II: Werkzeuge für den Job Der Umfang der Arbeit ist also mehr oder weniger klar, aber wie machen wir das? Offensichtlich werden solche Aufgaben im Test mit ein paar Anwendungszielen gegeben, um zu sehen, wie Sie programmieren, um Sie zu zwingen, Spring zu verwenden und ein wenig mit der Datenbank zu arbeiten. Nun, lasst uns das machen. Wir benötigen ein SpringBoot-Projekt mit REST-API-Unterstützung und einer Datenbank. Auf der Website https://start.spring.io/ finden Sie alles, was Sie brauchen. REST-API oder eine andere Testaufgabe.  - 1 Sie können das Build-System, die Sprache, die SpringBoot-Version auswählen, Artefakteinstellungen festlegen, die Java-Version und Abhängigkeiten festlegen. Wenn Sie auf die Schaltfläche „Abhängigkeiten hinzufügen“ klicken, wird ein charakteristisches Menü mit einer Suchleiste angezeigt. Die ersten Kandidaten für die Wörter rest und data sind Spring Web und Spring Data – wir werden sie hinzufügen. Lombok ist eine praktische Bibliothek, die es Ihnen ermöglicht, mithilfe von Annotationen Kilometer an Code mit Getter- und Setter-Methoden loszuwerden. Durch Klicken auf die Schaltfläche „Generieren“ erhalten wir ein Archiv mit dem Projekt, das bereits entpackt und in unserer Lieblings-IDE geöffnet werden kann. REST-API oder eine andere Testaufgabe.  - 2 Standardmäßig erhalten wir ein leeres Projekt mit einer Konfigurationsdatei für das Build-System (in meinem Fall Gradle, aber bei Maven gibt es keinen grundlegenden Unterschied, und einer Spring-Startup-Datei). Aufmerksame Leute könnten auf zwei Dinge achten . Erstens habe ich zwei Einstellungsdateien application.properties und application.yml. Standardmäßig erhalten Sie genau die Eigenschaften – eine leere Datei, in der Sie Einstellungen speichern können, aber für mich sieht das YML-Format etwas lesbarer aus, jetzt zeige ich einen Vergleich: Obwohl das Bild REST-API oder eine andere Testaufgabe.  - 3 links kompakter aussieht ist es leicht, eine große Menge an Duplikaten im Eigenschaftenpfad zu erkennen. Das Bild rechts ist eine normale YML-Datei mit einer Baumstruktur, die recht einfach zu lesen ist. Ich werde diese Datei später im Projekt verwenden. Das zweite, was aufmerksamen Leuten auffallen könnte, ist, dass mein Projekt bereits mehrere Pakete enthält. Es gibt dort noch keinen vernünftigen Code, aber es lohnt sich, sie durchzugehen. Wie wird eine Bewerbung verfasst? Wenn wir eine bestimmte Aufgabe haben, müssen wir sie zerlegen – sie in kleine Teilaufgaben aufteilen und mit deren konsequenter Umsetzung beginnen. Was wird von uns verlangt? Wir müssen eine API bereitstellen, die der Client verwenden kann; der Inhalt des Controller-Pakets ist für diesen Teil der Funktionalität verantwortlich. Der zweite Teil der Anwendung ist die Datenbank – das Persistenzpaket. Darin speichern wir Dinge wie Datenbankentitäten (Entities) sowie Repositories – spezielle Spring-Schnittstellen, die Ihnen die Interaktion mit der Datenbank ermöglichen. Das Servicepaket enthält Serviceklassen. Wir werden im Folgenden darüber sprechen, was der Spring-Typ-Service ist. Und zu guter Letzt das utils-Paket. Dort werden nützliche Klassen mit allerlei Hilfsmethoden abgelegt, zum Beispiel Klassen für die Arbeit mit Datum und Uhrzeit oder Klassen für die Arbeit mit Strings und wer weiß was noch. Beginnen wir mit der Implementierung des ersten Teils der Funktionalität. Teil III: Controller
@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());
    }
}
Jetzt sieht unsere EmployeeController-Klasse so aus. Hier gibt es einige wichtige Dinge, die es zu beachten gilt. 1. Anmerkungen über der Klasse. Der erste @RestController teilt unserer Anwendung mit, dass diese Klasse ein Endpunkt sein wird. 2. @RequestMapping ist zwar nicht obligatorisch, aber eine nützliche Annotation; es ermöglicht Ihnen, einen bestimmten Pfad für den Endpunkt festzulegen. Diese. Um darauf zuzugreifen, müssen Sie Anfragen nicht an „localhost:port/employee“ senden, sondern in diesem Fall an „localhost:8086/api/v1/employee“. Woher kommen diese „api/v1“ und „employee“ eigentlich? Aus unserer application.yml findet man dort bei genauem Hinsehen folgende Zeilen:
application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
Wie Sie sehen können, haben wir Variablen wie application.endpoint.root und application.endpoint.employee. Das sind genau das, was ich in den Anmerkungen geschrieben habe. Ich empfehle, sich diese Methode zu merken – sie spart viel Zeit beim Erweitern oder Umschreiben Funktionalität – es ist immer bequemer, alles in der Konfiguration zu haben und nicht das gesamte Projekt fest zu codieren. 3. @RequiredArgsConstructor ist eine Lombok-Annotation, eine praktische Bibliothek, mit der Sie das Schreiben unnötiger Dinge vermeiden können. In diesem Fall entspricht die Annotation der Tatsache, dass die Klasse über einen öffentlichen Konstruktor verfügt, bei dem alle Felder als endgültig markiert sind
public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
Aber warum sollten wir so etwas schreiben, wenn eine Anmerkung ausreicht? :) Übrigens, herzlichen Glückwunsch, dieses höchst private letzte Feld ist nichts anderes als die berüchtigte Dependency Injection. Lassen Sie uns weitermachen. Was für ein Feld ist eigentlich EmployeeService? Dies wird einer der Dienste in unserem Projekt sein, der Anfragen für diesen Endpunkt verarbeitet. Die Idee hier ist sehr einfach. Jede Klasse sollte ihre eigene Aufgabe haben und nicht mit unnötigen Aktionen überlastet werden. Wenn es sich um einen Verantwortlichen handelt, überlassen Sie ihm den Empfang von Anfragen und das Versenden von Antworten. Wir würden die Verarbeitung jedoch lieber einem zusätzlichen Dienst anvertrauen. Als letztes verbleibt in dieser Klasse die einzige Methode, die eine Liste aller Mitarbeiter unseres Unternehmens zurückgibt, die den oben genannten Dienst nutzen. Die Liste selbst ist in eine Entität namens ResponseEntity eingeschlossen. Ich mache das, damit ich in Zukunft bei Bedarf problemlos den benötigten Antwortcode und die benötigte Nachricht zurückgeben kann, die das automatisierte System verstehen kann. So gibt ResponseEntity.ok() beispielsweise den 200. Code zurück, der besagt, dass alles in Ordnung ist, aber wenn ich zum Beispiel zurückkomme
return ResponseEntity.badRequest().body(Collections.emptyList());
dann erhält der Client den Code 400 – Bad Reuqest und eine leere Liste in der Antwort. Normalerweise wird dieser Code zurückgegeben, wenn die Anfrage falsch ist. Ein Controller wird uns jedoch nicht ausreichen, um die Anwendung zu starten. Unsere Abhängigkeiten erlauben uns dies nicht, da wir immer noch eine Basis haben müssen :) Nun, fahren wir mit dem nächsten Teil fort. Teil IV: Einfache Persistenz Da unsere Hauptaufgabe darin besteht, die Anwendung zu starten, beschränken wir uns vorerst auf ein paar Stubs. Sie haben bereits in der Controller-Klasse gesehen, dass wir eine Liste von Objekten vom Typ Employee zurückgeben. Dies wird unsere Entität für die Datenbank sein. Erstellen wir es im Paket demo.persistence.entity . In Zukunft kann das Entitätspaket um weitere Entitäten aus der Datenbank ergänzt werden.
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
Dies ist eine Klasse so einfach wie eine Tür, deren Anmerkungen genau Folgendes sagen: Dies ist eine Datenbankentität @Entity, dies ist eine Klasse mit Daten @Data – Lombok. Der hilfsbereite Lombok wird für uns alle notwendigen Getter, Setter und Konstruktoren erstellen – komplettes Stuffing. Nun, ein kleines Sahnehäubchen ist @Accessors(chain = true). Tatsächlich handelt es sich hierbei um eine versteckte Implementierung des Builder-Musters. Angenommen, Sie haben eine Klasse mit einer Reihe von Feldern, die Sie nicht über den Konstruktor, sondern über Methoden zuweisen möchten. In unterschiedlicher Reihenfolge, vielleicht nicht alle gleichzeitig. Sie wissen nie, welche Art von Logik in Ihrer Anwendung enthalten sein wird. Diese Anmerkung ist Ihr Schlüssel zu dieser Aufgabe. Lass uns nachsehen:
public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
Nehmen wir an, wir haben alle diese Felder in unserer Klasse😄Sie können sie zuweisen, Sie können sie nicht zuweisen, Sie können sie stellenweise mischen. Bei nur 3 Objekten scheint dies nichts Besonderes zu sein. Aber es gibt Klassen mit einer viel größeren Anzahl von Eigenschaften, zum Beispiel 50. Und schreiben Sie so etwas wie
public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
Sieht nicht sehr hübsch aus, oder? Außerdem müssen wir die Reihenfolge beim Hinzufügen von Variablen gemäß dem Konstruktor strikt einhalten. Ich schweife jedoch ab, kommen wir zurück zum Punkt. Jetzt haben wir ein (Pflicht-)Feld darin – eine eindeutige Kennung. In diesem Fall handelt es sich um eine Long-Typ-Nummer, die beim Speichern in der Datenbank automatisch generiert wird. Dementsprechend zeigt uns die @Id-Annotation deutlich, dass es sich um einen eindeutigen Bezeichner handelt; @GeneratedValue ist für seine eindeutige Generierung verantwortlich. Es ist erwähnenswert, dass @Id zu Feldern hinzugefügt werden kann, die nicht automatisch generiert werden. Dann muss das Problem der Eindeutigkeit jedoch manuell gelöst werden. Was könnte eine eindeutige Mitarbeiterkennung sein? Nun, zum Beispiel vollständiger Name + Abteilung... Eine Person hat jedoch vollständige Namensvetter, und es besteht die Möglichkeit, dass sie in derselben Abteilung arbeiten, klein, aber vorhanden – das bedeutet, dass die Entscheidung schlecht ist. Es wäre möglich, eine Reihe weiterer Felder hinzuzufügen, wie z. B. Einstellungsdatum, Stadt, aber all das verkompliziert meines Erachtens die Logik zu sehr. Sie fragen sich vielleicht, wie es überhaupt möglich ist, dass mehrere Felder gleichzeitig eindeutig sind? Ich antworte – vielleicht. Wenn Sie neugierig sind, können Sie nach so etwas wie @Embeddable und @Embedded googeln. Nun, wir sind mit dem Wesentlichen fertig. Jetzt brauchen wir ein einfaches Repository. Es wird so aussehen:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
Ja, das ist alles. Nur eine Schnittstelle, wir haben sie EmployeeRepository genannt. Sie erweitert JpaRepository, das über zwei typisierte Parameter verfügt. Der erste ist für den Datentyp verantwortlich, mit dem es arbeitet, der zweite für den Schlüsseltyp. In unserem Fall sind dies Employee und Long. Das ist genug für jetzt. Der letzte Schliff vor dem Start der Anwendung wird unser Service sein:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
Es gibt den bereits bekannten RequiredArgsConstructor und die neue @Service-Annotation – dies bezeichnet normalerweise die Geschäftslogikschicht. Beim Ausführen eines Spring-Kontexts werden mit dieser Annotation markierte Klassen als Beans erstellt. Wenn wir in der EmployeeController-Klasse die endgültige Eigenschaft EmployeeService erstellt und RequiredArgsConstructor angehängt haben (oder einen Konstruktor von Hand erstellt haben), findet Spring beim Initialisieren der Anwendung diese Stelle und fügt uns ein Klassenobjekt in diese Variable ein. Der Standardwert ist hier Singleton, d. h. Für alle derartigen Links wird es ein einziges Objekt geben; dies muss beim Entwerfen der Anwendung berücksichtigt werden. Das ist eigentlich alles, die Anwendung kann gestartet werden. Vergessen Sie nicht, die notwendigen Einstellungen in der Konfiguration vorzunehmen. REST-API oder eine andere Testaufgabe.  - 4 Ich werde nicht beschreiben, wie man eine Datenbank installiert, einen Benutzer und die Datenbank selbst erstellt, sondern nur darauf hinweisen, dass ich in der URL zwei zusätzliche Parameter verwende – useUnicore=true und CharacterEncoding=UTF-8. Dies geschah, damit der Text auf jedem System mehr oder weniger gleich angezeigt wird. Wenn Sie jedoch zu faul sind, an der Datenbank herumzubasteln, und wirklich im Arbeitscode herumstöbern möchten, gibt es eine schnelle Lösung: 1. Fügen Sie build.gradle die folgende Abhängigkeit hinzu:
implementation 'com.h2database:h2:2.1.214'
2. In application.yml müssen Sie mehrere Eigenschaften bearbeiten. Der Einfachheit halber gebe ich ein vollständiges Beispiel des Federabschnitts:
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:
Die Datenbank wird im Projektordner in einer Datei namens mydb gespeichert . Aber ich würde empfehlen, eine vollwertige Datenbank zu installieren 😉 Nützlicher Artikel zum Thema: Spring Boot mit H2-Datenbank Für alle Fälle stelle ich eine Vollversion meines build.gradle zur Verfügung, um Diskrepanzen in den Abhängigkeiten zu beseitigen:
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()
}
Das System ist startbereit: REST-API oder eine andere Testaufgabe.  - 5 Sie können es überprüfen, indem Sie eine GET-Anfrage von einem beliebigen geeigneten Programm an unseren Endpunkt senden. In diesem speziellen Fall reicht ein normaler Browser aus, aber in Zukunft werden wir Postman benötigen. REST-API oder eine andere Testaufgabe.  - 6 Ja, tatsächlich haben wir noch keine der Geschäftsanforderungen umgesetzt, aber wir haben bereits eine Anwendung, die startet und auf die erforderliche Funktionalität erweitert werden kann. Fortsetzung: REST-API und Datenvalidierung
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION