Spring Boot 2.0 - Prosjektstruktur og beste praksis (del 2)

I fortsettelse til den første delen av serien om Spring Boot v2.0, er dette den andre og avsluttende delen der vi vil gå gjennom applikasjonsprosjektstrukturen sammen med noen viktige utdrag og beste fremgangsmåter som bør vurderes mens vi utfører Spring Boot-basert utvikling .

Bare en liten ansvarsfraskrivelse, etter at du har satt opp dette startpakken og foretatt en grundig gjennomgang, kan du føle at dette ikke egentlig er en implementering av mikrotjenester ved bruk av Spring Boot, jeg må si jeg er helt enig med deg i at det ikke er det, snarere monolit utviklet i Spring Boot. Denne artikkelen handler mer om å lære å kode med den beste fremgangsmåten i Spring Boot, og ideelt sett bør et reservasjonssystem ha mer enn bare en tjeneste eller kodebase. Jeg lover at jeg snart vil publisere en ny serie om Spring Boots bruk i å lage en distribuert applikasjon ved hjelp av Netflix OSS-stack. Til da kan du behandle dette som et utgangspunkt og få prosjektstrukturen og relaterte verktøy justert.

Kildekode

Kildekoden for dette startsettet kan klones fra følgende GitHub-lager:

Søknadsstruktur

Spring Boot er et meningsfullt rammeverk som gjør livet vårt veldig enkelt siden vi ikke trenger å velge versjoner av forskjellige avhengigheter basert på versjonen av Spring framework, alt sammen ivaretatt av Spring Boot.

Jeg har prøvd å følge den samme ideologien mens jeg opprettet prosjektstrukturen. Til å begynne med kan det virke som overveldende, men tro meg når du begynner å skrive stykkene dine, vil strukturen hjelpe deg enormt ved å spare deg og tenke på spørsmål som allerede er besvart. . Strukturen ser ut som følger:

Modeller og DTO-er

De forskjellige modellene for applikasjonen er organisert under modellpakken, deres DTO-er (dataoverføringsobjekter) er til stede under dto-pakken. Det er forskjellige meninger om vi skal bruke DTO-er eller ikke, jeg tilhører det settet av tanker som synes vi absolutt burde og ikke å bruke DTO-er gjør modelllaget ditt veldig tett koblet med UI-laget, og det er noe som ikke noe bedriftsprosjekt noensinne bør komme inn i.

DTO-er lar oss bare overføre dataene vi trenger å dele med brukergrensesnittet og ikke hele modellobjektet som vi kan ha samlet ved hjelp av flere underobjekter og vedvarte i databasen. Kartleggingen av modeller til DTO-ene kan håndteres ved å bruke ModelMapper-verktøyet, men det er bare nyttig når DTO-en din nesten er (bokstavelig talt) lik de berørte modellene, noe som ikke alltid er tilfelle, og derfor foretrekker jeg å bruke tilpassede kartleggingsklasser. Du kan finne noen eksempler under pakken “dto / mapper”.

For eksempel, la oss se på hvordan Trip.java-modellen vår er organisert:

Trip.java

Som vi kan se, har modellen referanser til samlingene Stop, Bus and Agency. Dette kreves for å opprettholde forholdet mellom disse forskjellige samlingene med en tur. Selv om dette ikke er det samme som et fremmed nøkkelkonsept i MySQL, og det ikke er noen standardkaskader som brukes her, gir det oss en måte å etterligne det samme på MongoDB. Det tilsvarende dataoverføringsobjektet ser ut som følger:

TripDto.java

Som jeg beskrev tidligere, er ikke DTO-er ment å være speilbildet til modellens kolleger, de burde heller være en refleksjon av hva brukergrensesnittet eller api-svaret krever. I dette tilfellet var det ingen mening i å lage et komposisjonsforhold mellom en tur og dens buss eller byrå eller stopper, snarere kan deres primære nøkler faktisk gjøre susen. Dette kobler ikke bare disse DTO-ene ut, det reduserer også den samlede størrelsen på svarpakken som vil reise over HTTP fra server til klient.

Det aller neste spørsmålet kan være hvordan du konverterer modellen POJO til denne DTO, det er vel mer enn én måter å gjøre det på, men min preferanse er å være eksplisitt og DIY på følgende måte:

Tjenester og DAO-er

Datatilgangsobjektene (DAO-er) er til stede i depotpakken. De er alle utvidelser av MongoRepository-grensesnittet som hjelper servicelaget til å vedvare og hente dataene fra MongoDB.

Tjenestelaget er definert under servicepakken, med tanke på den aktuelle casestudien som det ga mening å lage to grunnleggende tjenester:

  1. Brukerservice og
  2. BusReservationService

Å forklare hver metode i disse tjenestene er utenfor omfanget av dette blogginnlegget, men jeg vil liste opp grensesnittene som viser operasjonene som støttes av disse tjenestene.

UserService.java

BusReservationService.java

Bortsett fra å legge merke til metodene til å navngi konvensjoner, er jeg sikker på at du har lagt merke til at servicelaget aldri godtar en modell som input og aldri returnerer en. Dette er en annen god praksis som Spring-utviklere bør følge i en lagdelt arkitektur. Kontrollerlaget samhandler med servicelaget for å gjøre en jobb når den mottar en forespørsel fra visningen eller api-laget, når det gjør det, skal det ikke ha tilgang til modellobjektene og skal alltid snakke i form av nøytrale DTO-er.

Når tjenesten mottar et DTO-objekt, bør den prøve å være fornuftig av det ved å spørre etter det tilsvarende modellobjektet fra databasen og deretter utføre den nødvendige operasjonen og opprette et svar DTO som skal sendes tilbake til den ringte tjenesten. Denne tilnærmingen lar deg endre visning og modeller uavhengig uten å måtte bekymre deg for at den ene kan bryte den andre.

For å illustrere den nevnte tilnærmingen, la oss ta et eksempel på en serviceoperasjon “updateProfile” som brukes til å oppdatere brukerens informasjon. Metodedefinisjonen ser slik ut:

Sikkerhet

Sikkerhetsinnstillingen er til stede under konfigurasjonspakken, og de faktiske konfigurasjonene gjøres under klassen som er tilstede i sikkerhetspakken. Applikasjonen har forskjellige sikkerhetskonsepter for adminportalen og REST APIene, for portalen har jeg brukt standard vårsesjonsmekanisme som er basert på konseptet sessionID og informasjonskapsler. For REST API-ene har jeg brukt JWT-tokenbasert autentiseringsmekanisme.

Sikkerheten for web og apis er konfigurert begge i samme klasse MultiHttpSecurityConfig.java. Den har to statiske klasser som strekker seg fra WebSecurityConfigurerAdapter, som lar oss konfigurere http-sikkerheten for innkommende forespørsler.

@Order-merknaden gjør at forespørslene kan skannes gjennom de forskjellige konfigurasjonene i den angitte rekkefølgen. Så en API-forespørsel går gjennom ApiWebSecurityConfigurationAdapter og blir absorbert der, men en Admin-forespørsel går først gjennom den, men siden den ikke samsvarer med kriteriene, prøver Spring Security å få den til å gå gjennom den neste konfigurasjonen med en øyeblikkelig høyere ordre som i dette tilfellet er FormLoginWebSecurityConfigurerAdapter.

API-forespørslene må gå gjennom ApiJWTAuthenticationFilter og ApiJWTAuthorizationFilter som er ansvarlige for opprettelse og validering av JWT-token utstedt ved innlogging. Hvis du lurer på hvilken URL som skal brukes til API-godkjenning (innlogging), så er den her:

http: // localhost: 8080 / api / auth

Og hvis du lurer på hvordan dette ble konfigurert, ligger svaret i klassen ApiJWTAuthenticationFilter, konstruktøren har følgende informasjon kodet:

this.setRequiresAuthenticationRequestMatcher (new AntPathRequestMatcher ("/ api / authentic", "POST"));

Dette forteller AbstractAuthenticationProcessingFilter til å koble AuthenticationRequestMatcher til “/ api / autor” -banen for API-forespørsler.

Administrasjonsprogrammet er bare tillatt for de brukerne som har rollen "ADMIN". Alle passordene er kryptert ved hjelp av BCryptPasswordEncoder, og vi kan aldri se deres faktiske verdi når de er lagret i databasen.

Controllers

Sist, men den viktigste delen er kontrolleren. Det binder alt sammen helt fra det øyeblikket en forespørsel blir oppfanget til svaret er forberedt og sendt tilbake. Kontrollerlaget er til stede i kontrollpakken, den beste fremgangsmåten antyder at vi holder dette laget versjonert for å støtte flere versjoner av applikasjonen, og den samme fremgangsmåten blir brukt her.

Foreløpig er koden bare til stede under v1, men over tid regner jeg med å ha forskjellige versjoner med forskjellige funksjoner. De administratorportalrelaterte kontrollerne er til stede i ui-pakken, og dens angående formkommandobjekter er lokalisert under kommandopakken. REST API-kontrollerne er lokalisert under api-pakken, og de tilsvarende forespørselsklassene er lokalisert under forespørselspakken.

Administratorpanelkontrollene jobber med Spring WebMVC-konseptet. De svarer på innkommende nettforespørsler med Spring's ModelAndView-objekter som inneholder dataene som skal vises på de aktuelle visningene / skjemaene og navnet på visningen som skal gjengis, et eksempel fra DashboardController-klassen er som følger:

Forespørsel og skjema kommandoer

Igjen er det forskjellige meninger blant utviklermiljøet angående bruken av separate klasser for å kartlegge den innkommende forespørselen vs å bruke DTO-ene vs å bruke modellene direkte, jeg er personlig tilhenger av å skille dem så langt som mulig for å fremme løs kobling mellom UI og kontrollerlag.

Forespørselobjektene og skjemakommandoene gir oss en måte å bruke et ekstra nivå på valideringer på de innkommende forespørslene før de blir konvertert til DTOene som overfører gyldig informasjon til tjenestelaget for utholdenhet og innhenting av data. Vi kan bruke DTO-er her, og noen utviklere foretrekker den tilnærmingen da den reduserer noen ekstra klasser, men jeg foretrekker vanligvis å holde valideringslogikken adskilt fra overføringsobjektene, og er derfor tilbøyelig til å bruke forespørsel / kommando-objektene foran seg.

Et eksempel på en kommandomønsterbasert klasse BusFormCommand ser ut som følger:

Og et eksempel på en forespørsel sendt via API, er BookTicketRequest som følger:

De statiske ressursene er gruppert under ressurskatalogen. Alle UI-objektene og deres stylingsaspekter kan du finne her.

Lombok

En av de største klagene mot Java er hvor mye støy du finner i en enkelt klasse. Prosjekt Lombok så dette som et problem og har som mål å redusere støyen til noen av de verste lovbryterne ved å erstatte dem med et enkelt sett med merknader. Du finner Lombok ansatt overalt i dette startsettet, det har faktisk hjulpet med å redusere kodelinjene, spare mye utviklingstid og krefter og gjøre koden mye mer lesbar. Noen av de viktigste merknadene som jeg foretrekker å bruke er:

@Getter / @ Setter

Skriv aldri offentlig int getFoo () {return foo;} igjen.

@ToString

Du trenger ikke å starte en feilsøking for å se feltene dine: Bare la lombok generere en toString for deg!

@EqualsAndHashCode

Likhet gjort enkelt: Genererer hashCode og tilsvarer implementeringer fra feltene til objektet ..

@Data

Alt sammen nå: En snarvei for @ToString, @EqualsAndHashCode, @Getter på alle felt og @Setter på alle ikke-endelige felt, og

@RequiredArgsConstructor!

I hovedsak er noe så ordlyst som:

Kan skrives ganske enkelt som:

Du kan godt se forskjellen, ikke bare den senere ser mye renere ut, vi har kommet ned fra 59 linjer med kjedelige POJO til en 8 linjers basert Java-klassefil.

API-respons og unntakshåndtering

Jeg har prøvd å eksperimentere litt med RuntimeExceptions og kommet med et mini-rammeverk for å håndtere hele applikasjonens unntak ved hjelp av noen få klasser og egenskapsfilen. Hvis du følger unntakspakken nøye, består den av to enums:

  1. EntityType og
  2. ExceptionType

EntityType enum inneholder alle enhetsnavnene som vi bruker i systemet for utholdenhet, og de som kan resultere i et unntak av kjøretid. ExceptionType-enumet består av de forskjellige unntakene for enhetsnivå som unntaket EntityNotFound og DuplicateEntity.

BRSException-klassen har to statiske klasser EntityNotFoundException og DuplicateEntityException som er de to mest kastede unntakene fra tjenestelaget. Den inneholder også et sett med overbelastede metoder throwException som tar EntityType, ExceptionType og argumenter for å komme med en formatert melding hvis mal er til stede under filen custom.properties.

Ved å bruke denne klassen var jeg i stand til å styrke hele tjenestelaget til å kaste en rekke entitets unntak på en enhetlig måte uten å tøffe kodebasen med alle slags unntak fra NOT_FOUND og DUPLICATE.

For eksempel, mens du logger inn hvis du prøver å bruke en e-postadresse som ikke er registrert, blir et unntak hevet og kastet ved å bruke følgende kodelinje -

kast unntak (USER, ENTITY_NOT_FOUND, userDto.getEmail ());

Dette resulterer i at klubbene til disse to enumelementene USER (“bruker”) og ENTITY_NOT_FOUND (“not.found”) blir klubbet, og kommer med en nøkkel user.not.found som er til stede i de tilpassede.properties-filene som følger:

user.not.found = Forespurt bruker med e-post - {0} eksisterer ikke.

Ved å bare bytte ut {0} parameteren med e-postadressen som er inkludert i det kastede unntaket, kan du få en godt formatert melding som skal vises til brukeren eller sendes tilbake som svar fra REST API-samtalen. Definisjonen av BRSEeksepsjonsklassen er som følger:

En annen viktig del av dette minirammet er klassen CustomizedResponseEntityExceptionHandler som ser ut som følger:

Denne klassen tar seg av disse RuntimeExceptions før du sender svar på API-forespørsler. Det er et råd om kontroller som kontrollerer om en påkalling av tjenestelag resulterte i en EntityNotFoundException eller en DuplicateEntityException og sender et passende svar til innringeren.

Til slutt blir API-svaret alle sendt på en enhetlig måte ved bruk av Response-klassen som er til stede i dto / response-pakken. Denne klassen lar oss lage ensartede objekter som resulterer i et svar som vist nedenfor (denne er et svar for api / v1 / reservasjon / stopper):

{
 “Status”: “OK”,
 “Nyttelast”: [
   {
    “Kode”: “STPA”,
    “Navn”: “Stopp A”,
    “Detalj”: “Nære åser”
   }
   {
    “Kode”: “STPB”,
    “Navn”: “Stopp B”,
    “Detalj”: “Nær elv”
   }
 ]
}

Og når det er et unntak (for eksempel å søke etter en tur mellom to holdeplasser som ikke er koblet med noen buss) blir følgende svar sendt tilbake (resultat av "api / v1 / reservasjon / turer til stopp" GET-forespørsel):

{
  “Status”: “NOT_FOUND”,
  “Feil”: “Ingen turer mellom kildestopp -‘ STPD ’og destinasjonsstopp -‘ STPC ’er tilgjengelige på dette tidspunktet.»
}
{
  “Status”: “NOT_FOUND”,
  “feil”:
  {
    “Tidsstempel”: “2019–03–13T07: 47: 10.990 + 0000”,
    "Melding": "Forespurt stopp med kode - STPF eksisterer ikke."
  }
}

Som du kan observere, endrer ikke begge typer svar, ett med en HTTP-200 og en annen med HTTP-404, nyttelasten ikke strukturen, og den rammende rammen kan blindt akseptere svaret, vel vitende om at det er status og feil eller nyttelastfelt i JSON-objektet.

API-dokumentasjon

Det er like viktig å dokumentere arbeidet ditt (som det er utviklingen) og sørge for at Spring Boot API-er er tilgjengelige på en lesbar måte for frontend-team (interne) eller eksterne forbrukere. Verktøyet for API-dokumentasjon som brukes i dette startsettet er Swagger2, du kan åpne det samme i en nettleser på følgende url -

http: // localhost: 8080 / sprade-ui.html

Den vil gi deg et godt strukturert brukergrensesnitt som har to spesifikasjoner:

1. Bruker
2. BRS

Du kan bruke brukerspesifikasjonen for å utføre påloggings-api for generering av bærertoken. Tokenet skal deretter brukes i "Autoriser" -vinduet, som som standard vil bruke det på alle sikrede apis (få og legg inn begge). Husk at du bør legge til ordet "Bærer" etterfulgt av et mellomrom foran symbolet før du bruker det i autoriseringsdialogboksen.

Konfigurasjonen av Swagger blir ivaretatt av klassen BrsConfiguration. Jeg har definert to spesifikasjoner der ved hjelp av "swaggerBRSApi" og "swaggerUserApi" metoder. Siden innloggingsdelen er som standard ivaretatt av Spring Security, får vi ikke avsløre apis implisitt som resten av apisene som er definert i systemet, og av samme grunn har jeg definert en kontroller i konfigurasjonspakken med navnet “FakeController”:

Formålet er å legge til rette for generering av swagger-dokumentasjon for innlogging og utlogging av apis, den vil aldri komme til å eksistere i løpet av applikasjonens livssyklus ettersom “/ api / authentic” api blir håndtert av sikkerhetsfiltrene definert i kodebasen. Her er noen eksempler på skjermbilder som hjelper deg å visualisere ting litt bedre:

Swagger UI/ api / authentic login, høflighet FakeControllerAutoriser dialog for registrering av bærertegnBRS APIerBRS APIer oppført

For å bruke Swagger-brukergrensesnittet og utføre de sikrede API-ene, må du først betjene “/ api / autor” fra brukerspesifikasjonen og generere et bærertoken. Når symbolet er utstedt, kan du registrere det i Autoriser-popup og deretter gå videre til BRS-spesifikasjonen for å utføre de sikrede API-ene. Hvis du ikke registrerer tokenet, fortsetter du å motta HTTP 401-feilen.

Du kan komme i problemer mens du prøver Swagger-Ui for de HTTP-GET-forespørslene som har et organ med parametere, ikke fortvil det ikke er feilen i koden, det er Swaggers beslutning om å slutte å støtte paramenter for GET- og SLET-forespørsler . Som en løsning, kan du kopiere curl-forespørselen fra swagger og utføre den i et terminalvindu, og det skal fungere fint, eller du kan velge å gå for Postman eller et lignende verktøy som de ikke (ennå) håndhever en så stram begrensning. Etter mine synspunkter, så lenge Open API-spesifikasjonene ikke begrenser karosseriparamenter i GET-forespørsler, burde ikke verktøy som Swagger også gjøre det, men det er en samtale de trenger å ringe og ikke oss som utviklere.

UI-arkitektur

Brukergrensesnittet for adminportalen er designet ved hjelp av materialdesign ved hjelp av Bootstrap og responsivt webapp-konsept. Brukergrensesnittet er serversiden gjengitt ved bruk av Thymeleaf-maler (foretrukket templeringsmotor på våren).

Den vanlige måten å jobbe med Thymeleaf er å bruke inkluderer. Dette fører ofte til repeterende koder, spesielt når et nettsted har mange sider og hver side har flere gjenbrukbare komponenter (f.eks. Topptekst, navigasjon, sidefelt og bunntekst). Det er repeterende ettersom hver innholdsside må inkludere de samme fragmentene på de samme stedene. Dette har også en negativ effekt når sideoppsettet endres, f.eks. når du setter sidefeltet fra venstre til høyre side.

Dekoratormønsteret som brukes av dialekten Thymeleaf Layout løser disse problemene. I forbindelse med malmotorer fungerer dekoratormønsteret ikke med inkluderer på innholdssider lenger, men det refererer til en vanlig malfil. Hver side gir i utgangspunktet bare hovedinnholdet og ved å beskrive hvilken grunnmal som skal brukes til å bruke malmotoren som kan bygge den endelige siden. Innholdet blir dekorert med malfilen. Denne tilnærmingen har fordeler sammenlignet med den vanlige måten å inkludere fragmenter på:

  • Selve siden trenger bare å gi innholdet.
  • Som en malfil blir brukt til å bygge den endelige siden, kan globale endringer enkelt brukes.
  • Koden blir kortere og renere.
  • Når hver innholdsside refererer til hvilken malfil som skal brukes, er det enkelt å bruke forskjellige maler for forskjellige områder av applikasjonen (f.eks. Offentlig område og administrasjonsområde).

Oppsettet for adminportalen er utformet som følger:

Blogg som layout

De enkelte områdene i denne utformingen tjener følgende formål:

  • Overskrift: dette fragmentet brukes til statisk import (CSS og JavaScript), tittelen og metakodene.
  • Navigasjon: navigasjonslinjen med brukerprofil øverst til høyre.
  • Innhold: plassholderen for innholdet som vil bli erstattet av den forespurte siden.
  • Sidefelt: en sidefelt for ytterligere informasjon.
  • Bunntekst: Bunntekstområdet som inneholder informasjon om copyright.

Disse komponentene kan være lokalisert i ressursene / maler-katalogen ved roten, samt under underkatalogenes fragmenter og layout. Innholdsområdet i denne oppsettet vil være vert for følgende sider:

  • dashbord
  • Byrå
  • Buss
  • Tur
  • Profil

En feilside for ubehandlet unntak er designet med navnet "error.html". Innloggings- og påmeldingssidene er designet separat fra portalen tilgjengelig for en pålogget bruker.

Kjør serveren lokalt

For å kunne kjøre denne Spring Boot-appen må du først bygge den. For å bygge og pakke Spring Boot-appen i en enkelt kjørbar Jar-fil med en Maven, bruker du kommandoen nedenfor. Du må kjøre den fra prosjektmappen som inneholder pom.xml-filen.

maven pakke

eller du kan også bruke

mvn ren installasjon

For å kjøre appen fra et terminalvindu kan du kommandoen java -jar. Dette gis Spring Spring-appen din ble pakket som en kjørbar krukkefil.

java -jar target / springboot-starterkit-0.0.1-SNAPSHOT.jar

Du kan også bruke Maven-plugin til å kjøre appen. Bruk eksemplet nedenfor for å kjøre Spring Boot-appen din med Maven-plugin:

mvn spring-boot: run

Du kan følge hvilken som helst / alle ovennevnte kommandoer, eller ganske enkelt bruke kjørekonfigurasjonen levert av din favoritt IDE og kjøre / feilsøke appen derfra for utviklingsformål. Når serveren er konfigurert, skal du kunne få tilgang til administratorgrensesnittet på følgende URL:

http: // localhost: 8080

Og REST API-er kan nås over følgende basisbane:

http: // localhost: 8080 / api /

Noen av de viktige api-endepunktene er som følger:

  • http: // localhost: 8080 / api / v1 / bruker / påmelding (HTTP: POST)
  • http: // localhost: 8080 / api / auth (HTTP: POST)
  • http: // localhost: 8080 / api / v1 / reservasjon / stopper (HTTP: GET)
  • http: // localhost: 8080 / api / v1 / reservasjon / tripbystops (HTTP: GET)
  • http: // localhost: 8080 / api / v1 / reservasjon / trippler (HTTP: GET)
  • http: // localhost: 8080 / api / v1 / reservasjon / bookticket (HTTP: POST)

Docker-beholder

Bruk følgende kommando til å bygge containerbilde:

docker build -t vår / startkit.

Og følgende kommando for å kjøre containeren:

docker run -p 8080: 8080 spring / starterkit

Vær oppmerksom på når du bygger beholderbildet, og hvis mongodb kjører lokalt på systemet ditt, må du oppgi systemets IP-adresse (eller skyvert databasens IP) i applikasjonen.properties-filen (eller env vars) for å kunne koble til til databasen fra containeren.

Konklusjon

Som du kan se, er startpakken designet for å spare deg for timevis med kodingstid ved å gi deg et strømlinjeformet, effektivt og rent kodet grensesnitt og arkitektur å basere din egen utvikling på. Prøv det, og husk å gi meg beskjed om tilbakemeldingene dine.

Jeg håper denne serien med to artikler har hjulpet deg med å forbedre Spring Boot ferdighetssettet ditt, og at det har gitt deg en solid plattform til å begynne å skrive din neste Spring Boot-app.

Glad koding alle sammen!

Hvis det var interessant eller nyttig for deg, kan du trykke på klappknappen og hjelpe andre med å finne denne historien.

Visste du at du kunne gi opptil 50 klapper?