JavaRush /Java Blog /Random-IT /API REST e un'altra attività di test.
Денис
Livello 37
Киев

API REST e un'altra attività di test.

Pubblicato nel gruppo Random-IT
Parte I: Inizio Da dove cominciare? Stranamente, ma dalle specifiche tecniche. È estremamente importante assicurarsi che dopo aver letto il TOR inviato si comprenda appieno cosa è scritto in esso e cosa si aspetta il cliente. In primo luogo, questo è importante per l'ulteriore implementazione e, in secondo luogo, se non implementi ciò che ci si aspetta da te, non sarà a tuo vantaggio. Per evitare sprechi d’aria, abbozziamo una semplice specifica tecnica. Quindi, voglio un servizio a cui posso inviare dati, verranno archiviati nel servizio e restituiti a mio piacimento. Devo anche essere in grado di aggiornare e cancellare questi dati, se necessario . Un paio di frasi non sembrano una cosa chiara, vero? Come voglio inviare i dati lì? Quali tecnologie utilizzare? In che formato saranno questi dati? Non ci sono nemmeno esempi di dati in entrata e in uscita. Conclusione: le specifiche tecniche sono già pessime . Proviamo a riformulare: abbiamo bisogno di un servizio in grado di elaborare le richieste HTTP e funzionare con i dati trasferiti. Questo sarà il database dei registri del personale. Avremo dipendenti, sono divisi per dipartimenti e specialità, ai dipendenti possono essere assegnati compiti. Il nostro compito è automatizzare il processo di contabilità dei dipendenti assunti, licenziati e trasferiti, nonché il processo di assegnazione e cancellazione delle attività utilizzando l'API REST. Come Fase 1, attualmente stiamo lavorando solo con i dipendenti. Il servizio deve avere diversi endpoint con cui funzionare: - POST /employee - Richiesta POST, che deve accettare un oggetto JSON con i dati sul dipendente. Questo oggetto deve essere salvato nel database; se tale oggetto esiste già nel database, le informazioni nei campi devono essere aggiornate con nuovi dati. - GET /employee - Richiesta GET che restituisce l'intero elenco dei dipendenti salvati nel database - DELETE - DELETE /employee per eliminare un dipendente specifico Modello dati del dipendente:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  	//List of tasks, not needed for Phase 1
  ]
}
Parte II: Strumenti per il lavoro Quindi, l'ambito del lavoro è più o meno chiaro, ma come lo faremo? Ovviamente, tali compiti nel test vengono assegnati con un paio di obiettivi applicativi, per vedere come codifichi, per costringerti a usare Spring e a lavorare un po' con il database. Bene, facciamolo. Abbiamo bisogno di un progetto SpringBoot con supporto API REST e un database. Sul sito https://start.spring.io/ puoi trovare tutto ciò di cui hai bisogno. API REST o un'altra attività di test.  -1 È possibile selezionare il sistema di compilazione, la lingua, la versione di SpringBoot, impostare le impostazioni degli artefatti, la versione di Java e le dipendenze. Facendo clic sul pulsante Aggiungi dipendenze verrà visualizzato un menu caratteristico con una barra di ricerca. I primi candidati per le parole rest e data sono Spring Web e Spring Data: li aggiungeremo. Lombok è una comoda libreria che consente di utilizzare le annotazioni per eliminare chilometri di codice con metodi getter e setter. Cliccando sul pulsante Genera riceveremo un archivio con il progetto che potrà già essere decompresso e aperto nel nostro IDE preferito. Per impostazione predefinita, riceveremo un progetto vuoto, con un file di configurazione per il sistema di compilazione (nel mio caso sarà gradle, ma con Maven le cose non fanno differenze fondamentali, e un file di avvio primaverile). Le persone attente potrebbero prestare attenzione a due API REST o un'altra attività di test.  - 2 cose . Innanzitutto, ho due file di impostazioni application.properties e application.yml. Per impostazione predefinita, otterrai esattamente proprietà: un file vuoto in cui puoi memorizzare le impostazioni, ma a me il formato yml sembra un po' più leggibile, ora mostrerò un confronto: nonostante il fatto che l'immagine API REST o un'altra attività di test.  - 3 a sinistra sembri più compatta , è facile vedere una grande quantità di duplicazioni nel percorso delle proprietà. L'immagine a destra è un normale file yml con una struttura ad albero abbastanza facile da leggere. Utilizzerò questo file più avanti nel progetto. La seconda cosa che le persone più attente potrebbero notare è che il mio progetto ha già diversi pacchetti. Non esiste ancora un codice sensato, ma vale la pena esaminarli. Come si scrive una domanda? Avendo un compito specifico, dobbiamo scomporlo: suddividerlo in piccole sottoattività e iniziare la loro implementazione coerente. Cosa ci viene richiesto? Dobbiamo fornire un'API che il client possa utilizzare; il contenuto del pacchetto controller sarà responsabile di questa parte della funzionalità. La seconda parte dell'applicazione è il database: il pacchetto persistenza. In esso memorizzeremo cose come entità di database (entità) e repository: speciali interfacce a molla che consentono di interagire con il database. Il pacchetto di servizi conterrà classi di servizio. Parleremo di cosa sia il servizio di tipo primaverile di seguito. E ultimo ma non meno importante, il pacchetto utils. Lì verranno archiviate classi utilitaristiche con tutti i tipi di metodi ausiliari, ad esempio classi per lavorare con data e ora, o classi per lavorare con stringhe e chissà cos'altro. Iniziamo a implementare la prima parte della funzionalità. Parte III: Controllore
@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());
    }
}
Ora la nostra classe EmployeeController assomiglia a questa. Ci sono una serie di cose importanti a cui vale la pena prestare attenzione qui. 1. Annotazioni sopra la classe, il primo @RestController indica alla nostra applicazione che questa classe sarà un endpoint. 2. @RequestMapping, sebbene non obbligatoria, è un'annotazione utile; consente di impostare un percorso specifico per l'endpoint. Quelli. per bussare dovrai inviare le richieste non a localhost:port/employee, ma in questo caso a localhost:8086/api/v1/employee In realtà, da dove provengono queste api/v1 e Employee? Dal nostro application.yml Se guardi attentamente, puoi trovare le seguenti righe lì:
application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
Come puoi vedere, abbiamo variabili come application.endpoint.root e application.endpoint.employee, queste sono esattamente ciò che ho scritto nelle annotazioni, ti consiglio di ricordare questo metodo: farà risparmiare molto tempo nell'espansione o nella riscrittura del file funzionalità: è sempre più conveniente avere tutto nel file config e non codificare l'intero progetto. 3. @RequiredArgsConstructor è un'annotazione di Lombok, una comoda libreria che ti consente di evitare di scrivere cose non necessarie. In questo caso l'annotazione equivale al fatto che la classe avrà un costruttore pubblico con tutti i campi contrassegnati come final
public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
Ma perché dovremmo scrivere una cosa del genere se basta una sola annotazione? :) A proposito, congratulazioni, questo campo finale più privato non è altro che la famigerata Dependency Injection. Andiamo avanti, in realtà, che tipo di campo è EmployeeService? Questo sarà uno dei servizi del nostro progetto che elaborerà le richieste per questo endpoint. L'idea qui è molto semplice. Ogni classe dovrebbe avere il proprio compito e non dovrebbe essere sovraccaricata di azioni non necessarie. Se si tratta di un titolare del trattamento, lasciamo che sia lui a occuparsi di ricevere le richieste e inviare le risposte, ma preferiamo affidare il trattamento ad un servizio aggiuntivo. L'ultima cosa rimasta in questa classe è l'unico metodo che restituisce un elenco di tutti i dipendenti della nostra azienda che utilizzano il servizio sopra menzionato. L'elenco stesso è racchiuso in un'entità chiamata ResponseEntity. Lo faccio in modo che in futuro, se necessario, posso facilmente restituire il codice di risposta e il messaggio di cui ho bisogno, che il sistema automatizzato possa comprendere. Quindi, ad esempio, ResponseEntity.ok() restituirà il 200esimo codice, che dirà che va tutto bene, ma se ritorno, ad esempio
return ResponseEntity.badRequest().body(Collections.emptyList());
quindi il client riceverà il codice 400 - richiesta errata e un elenco vuoto nella risposta. In genere questo codice viene restituito se la richiesta non è corretta. Ma un controller non sarà sufficiente per avviare l'applicazione. Le nostre dipendenze non ci permetteranno di farlo, perché dobbiamo ancora avere una base :) Bene, passiamo alla parte successiva. Parte IV: semplice persistenza Poiché il nostro compito principale è avviare l'applicazione, per ora ci limiteremo a un paio di stub. Hai già visto nella classe Controller che restituiamo un elenco di oggetti di tipo Employee, questa sarà la nostra entità per il database. Creiamolo nel pacchetto demo.persistence.entity In futuro il pacchetto entità potrà essere integrato con altre entità dal database.
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
Questa è una classe semplice come una porta, le cui Annotazioni dicono esattamente quanto segue: questa è un'entità del database @Entity, questa è una classe con dati @Data - Lombok. L'utile Lombok creerà per noi tutti i getter, setter, costruttori necessari: imbottitura completa. Bene, una piccola ciliegina sulla torta è @Accessors(chain = true) In effetti, questa è un'implementazione nascosta del pattern Builder. Supponiamo di avere una classe con una serie di campi che desideri assegnare non tramite il costruttore, ma tramite metodi. In ordine diverso, forse non tutti nello stesso momento. Non sai mai quale tipo di logica sarà nella tua applicazione. Questa annotazione è la chiave per questo compito. Guardiamo:
public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
Supponiamo di avere tutti questi campi nella nostra classe😄Puoi assegnarli, non puoi assegnarli, puoi mescolarli in alcuni punti. Nel caso di sole 3 proprietà, questo non sembra qualcosa di eccezionale. Ma ci sono classi con un numero molto maggiore di proprietà, ad esempio 50. E scrivi qualcosa del genere
public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
Non sembra molto carino, vero? Dobbiamo anche seguire rigorosamente l'ordine di aggiunta delle variabili in accordo con il costruttore. Comunque sto divagando, torniamo al punto. Ora abbiamo un campo (obbligatorio): un identificatore univoco. In questo caso si tratta di un numero di tipo Lungo, che viene generato automaticamente quando viene salvato nel database. Di conseguenza, l'annotazione @Id ci indica chiaramente che si tratta di un identificatore univoco; @GeneratedValue è responsabile della sua generazione univoca. Vale la pena notare che @Id può essere aggiunto a campi che non vengono generati automaticamente, ma in tal caso il problema dell'unicità dovrà essere risolto manualmente. Quale potrebbe essere un identificatore univoco del dipendente? Bene, ad esempio, nome completo + dipartimento... tuttavia, una persona ha omonimi completi e c'è la possibilità che lavoreranno nello stesso dipartimento, piccola, ma c'è - ciò significa che la decisione è sbagliata. Sarebbe possibile aggiungere un sacco di altri campi, come data di assunzione, città, ma tutto questo, mi sembra, complica troppo la logica. Potresti chiederti, come è possibile che un gruppo di campi siano unici contemporaneamente? Rispondo: forse. Se sei curioso, puoi cercare su Google cose come @Embeddable e @Embedded Bene, con l'essenza abbiamo finito. Ora abbiamo bisogno di un semplice repository. Apparirà così:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
Sì, questo è tutto. Solo un'interfaccia, l'abbiamo chiamata EmployeeRepository, estende JpaRepository che ha due parametri tipizzati, il primo è responsabile del tipo di dati con cui funziona, il secondo del tipo di chiave. Nel nostro caso, questi sono Employee e Long. Per ora basta. Il tocco finale prima di lanciare l'applicazione sarà il nostro servizio:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
C'è il già familiare RequiredArgsConstructor e la nuova annotazione @Service: questo è ciò che di solito denota il livello della logica aziendale. Quando si esegue un contesto spring, le classi contrassegnate con questa annotazione verranno create come Beans. Quando nella classe EmployeeController abbiamo creato la proprietà finale EmployeeService e collegato RequiredArgsConstructor (o creato un costruttore manualmente), Spring, durante l'inizializzazione dell'applicazione, troverà questo posto e ci inserirà un oggetto di classe in questa variabile. L'impostazione predefinita qui è Singleton, ovvero ci sarà un oggetto per tutti questi collegamenti; questo è importante da tenere in considerazione quando si progetta l'applicazione. In realtà, questo è tutto, l'applicazione può essere avviata. Non dimenticare di inserire le impostazioni necessarie nel file config. API REST o un'altra attività di test.  - 4 Non descriverò come installare un database, creare un utente e il database stesso, ma noterò semplicemente che nell'URL utilizzo due parametri aggiuntivi: useUnicore=true e characterEncoding=UTF-8. Ciò è stato fatto in modo che il testo venisse visualizzato più o meno equamente su qualsiasi sistema. Tuttavia, se sei troppo pigro per armeggiare con il database e vuoi davvero curiosare nel codice funzionante, c'è una soluzione rapida: 1. Aggiungi la seguente dipendenza a build.gradle:
implementation 'com.h2database:h2:2.1.214'
2. In application.yml devi modificare diverse proprietà, per semplicità fornirò un esempio completo della sezione spring:
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:
Il database verrà archiviato nella cartella del progetto, in un file chiamato mydb . Ma consiglierei di installare un database completo 😉 Articolo utile sull'argomento: Spring Boot con database H2 Per ogni evenienza, fornirò una versione completa del mio build.gradle per eliminare discrepanze nelle dipendenze:
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()
}
Il sistema è pronto per l'avvio: API REST o un'altra attività di test.  - 5 puoi verificarlo inviando una richiesta GET da qualsiasi programma adatto al nostro endpoint. In questo caso particolare, andrà bene un normale browser, ma in futuro avremo bisogno di Postman. API REST o un'altra attività di test.  - 6 Sì, in effetti non abbiamo ancora implementato nessuno dei requisiti aziendali, ma disponiamo già di un'applicazione che si avvia e può essere estesa alle funzionalità richieste. Continua: API REST e convalida dei dati
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION