Indholdsfortegnelse:
1. Introduktion
1.1. Formål
Formålet med dette dokument er at beskrive hvordan et udviklingsmiljø til videreudvikling af Nationalt eCPR-servicen kan sættes op, samt hvordan koden bygges, deployes og testes.
Først beskrives de softwaremæssige krav der er til miljøet, samt hvordan kode hentes og bygges. Kodestrukturen, kodemæssige afhængigheder til tredjeparts moduler og de forskellige servicemodulers ansvar og design beskrives sidst i dette dokument sammen med testdesign.
1.2. Læsevejledning
Læser forventes at have kendskab til Java softwareudvikling med anvendelse af Maven og WildFly. Derudover forventes kendskab til docker-compose.
Hvor der i teksten er angivet <component base> refereres til topniveaufolderen for kildekoden for komponenten.
1.3. Introduktion til eCPR-servicen
eCPR-servicen udstiller en SOAP service, der følger DGWS. Snitfladen er defineret i et sæt WSDL filer og en række XSD filer.
eCPR-servicen er en Java baseret komponent, der for nuværende baserer sig på Java 8 og Spring frameworket. Den overordnede arkitektur og design er beskrevet i eCPR - Design- og Arkitekturbeskrivelse.
2. Opsætning af udviklingsmiljø
I det følgende antages at koden er hentet fra git: https://git.nspop.dk/projects/COM
2.1. Krav til software
eCPR-servicen deployeres vha. docker, og baserer sig på NSP platformens base image, hvori der findes nødvendigt software til afvikling.
Derudover er der krav til de anvendte udviklingsværktøjer:
- Maven 3.6.3 eller højere
- docker-compose version 3.6 eller højere
Man skal bruge Apache Maven til at bygge eCPR, hvilket gøres ved at køre kommandoen
mvn clean install |
Med denne kommando køres unittest også. For at bygge uden test køres kommandoen
mvn clean install -D maven.test.skip=true -e |
For at bygge og køre integrationstests køres kommandoen
mvn clean install -P=integrationstest |
Efter byg (med eller uden test) kan WAR filen findes under ecpr2-service/target/ecpr2-service-1.28.18
3. Afvikling
For at bygge docker-imaget udføres kommandoen
docker-compose -f compose/development/docker-compose.yml up -d --build |
som både bygger og starter en lokal database og selve servicen ecpr2. Herefter kan eCPR findes på port 8380.
Endpointet kan findes på http://localhost:8380/ecpr2/services/ecpr2.
4. Beskrivelse af systemdesign
Systemarkitekturen er beskrevet i eCPR - Design- og Arkitekturbeskrivelse. Nedenfor beskrives systemdesignet vha. sekvensdiagrammer, der viser flowet gennem systemet. Som eksempel er her udtaget "createPersonRequest", der repræsenterer mange af de hovedtræk der er i et forløb når der modtages et request.
Læsevejledning til sekvensdiagrammet
Sekvensdiagrammet er udarbejdet med inspiration i UML. For at overskueliggøre diagrammet er der dog gjort undtagelser:
- Sekvensdiammet for CreatePersonRequest er her udarbejdet som om createPersonRequest lykkedes uden fejl. Der er kun vist én alternativ situation, nemlig hvis checkPersmission fejler.
- createPersonHandler laver først to kald til validatoren, og herefter er indikeret et metodkald "create stated entities (..)". Dette skal forståes, som at der foretages flere metodekald til forskellige dao'er, hvis en oplysning er angivet i PersonVO. Hvis der eks. er angivet en adresse vil createAdress() blive kaldt for AdressDao. Dette fremgår ikke af sekvensdiagrammet for overskuelighedens skyld
- Intern logik vises ikke i nedenstående sekvensdiagram. Her er fokuseret på kommunikationen mellem klasserne. If-statements og lign kan dermed ikke ses af sekvensdiagrammet, igen for læsbarheden
Der er generelt mange steder hvor en exception kan genereres/udløses. Som udgangspunkt kan alle valideringer udløse en exception samt alle create() kald. I sekvensdiagrammet kunne alle returkald (stiplede linjer) have udløst exception frem for en tom returnering, men det fremgår ikke af diagrammet for læsbarhedens skyld. Alle exceptions bliver grebet (catch) i ECPR2WebServiceImpl. Her bliver de håndteret, hvor en given fejlkode medsendt i den kastede exception bliver tiløjet responset. Et fejlende respons ser ud som følger:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>SOAP-ENV:Server</faultcode> <faultstring xml:lang="en"> Fejlmeddlelse </faultstring> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
5. Beskrivelse af kildekodens strukturering og design
5.1. Kode strukturering
Kildekoden bygges vha Apache Maven. Overordnet består kildekoden af 5 Maven moduler, et parent modul (ecpr2) og 4 øvrige moduler, som kort beskrives nedenfor:
|
Indeholder XSD-filer og WSDL-filer som udgør snitfladebeskrivelsen |
|
Genererer java-filer ud fra XSD-filer og WSDL-filer fra ecpr2-schemas |
|
Indeholder selve sourcekoden for systemet. Denne struktur er yderligere beskrevet nedenfor |
|
Her ligger integrationstest for det samlede systemet |
ecpr-datagenerator |
Modulet kan generere baggrundsdata til performancetesten (Se eCPR - Testvejledning) |
Når systemet bygges med maven er bygge rækkefølgen følgende:
- eCPR2
- ecpr2-schemas
- ecpr2-server-api
- ecpr2-service
- ecpr2-test
- ecpr2-datagenerator
5.2. ecpr2-service kodestrukturering
Selve eCPR-servicens kodestruktur er uddybet nedenfor, hvor hver mappe under ecpr-service → src → main → java → dk.sds.ecpr2 er beskrevet:
Navn | Beskrivelse |
---|---|
advis | advis indeholder de klasser der er nødvendige ift. advisering via NAS |
constants | Konstanter til Lucene felt navne |
dao | Indeholder Data Access Object klasser. (DAO'er) |
domain | Indeholder alle Value Objekter (VO'er) |
rest | Indeholder 4 REST-endpoints. DKSController konfigurerer dcc-endpointet, ExportHistoryController udstiller status på exportHistoryJobbet, IsAliveController udstiller healthendpointet (se endpoints i driftsvejledningen) og WSDLController udstiller et endpoint hvor man kan se wsdl filerne. |
security | Håndtering af roller og rettigheder |
service | Håndtering af den primære logik. Herunder er alle handlers også defineret |
util | Indeholder hjælpeklasser, samt generel fejlhåndtering (Fejlkoder, fejlutil mm) |
webservice | Webservicer håndterer at modtage og sende requests/responses (Mapning af XML) samt videreføre kaldet i systemet, herunder securyty check. |
5.3. Database
5.3.1. navnekonvention
For at sikre en ensartet navngivning af tabeller, kolonner osv. er nedenstående navnekonvention indført i forhold til databasen.
- Tabelnavne er i pascal case
- Kolonner er i pascal case
- Navngivngning af indexer, constraints osv. er ikke under en stringent navnekonvention men ved indexer er der gjort brug af postfixet _idx.
5.3.2. Databaseændringer
Databasemodel styres ved hjælp af liquibase. Det betyder, at når der skal laves ændringer til databasemodellen, må man ikke rette i de eksisterende skemafiler. I stedet skal der laves nye filer, der beskriver ændringerne.
5.3.3. Skemaændringer
Skal der tilføjes f.eks. en ny kolonne eller en ny tabel skal nedenstående gøres.
- Der oprettes en ny fil i folderen compose/database/ddl. Filen skal navngives liquibase-changelog-x.y.z.xml hvor x, y og z er det versionsnummer du forventer at release komponenten som. Filen skal beskrive ændringen der skal laves. Hvis man anvender liquibase SQL syntaxen får man typisk automatisk "rollback" med. Man kan også referere til rå SQL filer.
- Filen fra punkt 1 tilføjes compose/database/liquibase-changelog-master.xml.
- Done
5.3.4. Testdata
Opstår der en situation, hvor der skal tilføjes yderlige testdata, for at integrationstesten kan afvikles, skal nedenstående udføres.
- Der oprettes en ny fil i folderen compose/database/test. Filen skal navngives liquibase-changelog-test-x.y.z.xml hvor x, y og z er det versionsnummer du forventer at release komponenten som. Filen skal beskrive ændringen der skal laves. Hvis man avender liquibase SQL syntaxen får man typisk automatisk "rollback" med. Man kan også referere til rå SQL filer.
- Filen fra punk 1 tilføjes compose/database/liquibase-changelog-test.xml
- Done
5.4. Beskrivelse af testsetup
For at kunne teste systemet er udarbejdet både unittests og integrationstest. Til unittest bruges en in-memory h2 database, mens der til integrationstestene opstartes en dockercontainer. Der er forskel på hvilke tabeller og testdata, der er behov for i de to scenarier. Når unittestene afviklies oprettes de tabeller og testdata der ligger i mappen compose/database/initializeInMemoryDB mens der i dockermiljøet oprettes de tabeller og testdata, der ligger i mappen compose/database/initializeDockerDB.
5.4.1. Unittests (JUnit)
JUnit anvendes til implementering af unit tests. Der er udarbejdet unit tests på kritisk logik i ecpr2-servicen.
Unit tests afvikling under byg med kommandoen angivet under Bygge WAR.
Mix-tests
Unit-testene er i visse tilfælde udarbejdet som integrationstest mellem flere klasser i systemet. Her er stadig gjort brug af JUnit til test (Se eCPR - Testvejledning).
5.4.2. Integrationstests
Integrationstests er udarbejdet under modulet ecpr-test og kan afvikles under byg ved brug af profilen integrationstest, så længe tests ikke skippes.
Dette afvikles op mod det kørende udviklingsmiljø (docker-compose setup).
6. Udvikling af performance test
Performance testen foregår vha. et test framework udviklet af Arosii (version 2.0.0), og er udviklet med afslt i Performancekrav til NSP-services.
Performance testen består af 2 dele:
- Udvikling af selve testen. Dette foregår i JMeter og kræver både java udvikling og efterfølgende opsætning i JMeter.
- Udførsel af testen. Dette foregår gennem JMeter GUI.
Udvikling og opsæt er beskrevet i nærværende dokument, mens den faktiske udførsel af testen er beskrevet i eCPR - Testvejledning.
I det følgende antages at koden er hentet ned fra git: https://git.nspop.dk/projects/NT/repos/performance-framework/ samt at man har docker installeret i sit udviklingsmiljø. JMeter skal også være tilgængelig.
6.1. Kaldemønster for performancetesten
Før performancetesten blev udviklet blev produktionsdata for eCPR undersøgt, for at kortlægge brigscenarier, og hvordan forskellige kald blev brugt. Dette reulterede i følgende mønstre:
Undersøgt i produktionsdata |
|
|
|
Rækkefælge af kald |
Nærmere uddybning |
SearchPerson: |
|
|
GetPersonByID |
|
|
CreateAndUpdate |
|
|
SearchAndCreate |
|
|
Opfundede scenarier* |
|
|
Merge |
|
|
ReservePersonID |
|
|
CompareName |
|
|
* De opfundede scenarier kunne ikke findes produktionsdata
6.1.1. Opsætning af testplan
På baggrund af kaldemønstrene som ses i tabellen ovenfor, er udarbejdet 6 threads, som tilsammen indeholder de identificerede brugscenarier:
- Performancetest - GetOID+GetPermission+SearchPerson: Denne består af GetOID + GetPermission + 6 search person kald. GetOID og GetPermission sker umiddelbart efter hinanden, hvorefter der er delay på 3 sekunder mellem hver search person. For at modulere, at GetOID + GetPermission kan være efterfulgt af en GetPersonById køres hver 10. gang en kombination af GetOID + GetPermission + GetPersonByID.
- Performancetest - Spike of GetPersonByID: Threaden kører 220 kald af GetPersonByID så hurtigt som muligt. Dette gentages hvert 10. sekund.
- GetPersonByID jaevn fordelt: Threaden laver 10 kald af GetPersonByID med hastighed 10 kald/minut (pause på 6 sekunder mellem hvert kald)
- Performancetest - Create and Update: Threaden laver en konstant belastning på CreateAndUpdate + SearchAndCreatePerson (se ovenstpende tavel). Dette skal modulere, at der sidder en bruger og udfører disse opgaver. Derfor bliver hvert kald udført med 3 sekunders mellemrum.
- MergePersons: Dette scenarie er ikke verificeret af produktionsdata, men opfundet. Det forventes, at denne ikke fylder særlig meget i loadbilledet (den er ikke brugt de sidste 5 måneder), og derfor udarbejdes en thread hvor MergePersons kaldes en gang pr. User med en pause på 20 sekunder.
- ReservePersonId og CompareName: Disse scenarier er ikke verificeret af produktionsdata, men opfundet. Det forventes, at de ikke fylder særlig meget i loadbilledet (de er ikke brugt de sidste 5 måneder), og derfor udarbejdes en thread hvor både ReservePersonId og CompareName begge kaldes 10 gange i minuttet.
Nogle af scenarierne kræver et mindre setup, og derfor er Threaden opsat med flere kald, end de kald, der er identificeret i tabellen. Et eksempel er MergePerson. Her kræves der 2 identiske personer. Dette er ikke oprettet at datageneratoren. For at oprette dette udføres 2 kald: GetPersonById → CreatePerson. Herefter udføres det forventede scenarie med SearchPerson → MergePerson → SearchPerson.
6.2. Kildekodens struktur
Kildekoden fra svn.nspop.dk indeholder også performance test til andre services. Nedenfor er givet en introduktion til eCPR servicens. Overordnet er repositoriet inddelt i 2, nemlig modules og tests.
6.2.1. Modules
Mappen 'modules' indeholder samplers med tilhørende samplerGUIs, der bruges i GUI'en. For eCPR performancetesten er udarbejdet en sampler og en GUI for samtlige endpoints, på nær DeletePerson, da denne kun bruges i test.
- GUI klasserne anvendes til indtastning af test parametre. GUI klasserne er at finde i modules/jmeter-ecpr/src/main/java/com/arosii/jmeter/protocol/ecpr/control/gui
- Sampler klasserne anvendes til at lave det faktiske web service kald, og dermed også opsætte requested på den korrekte måde. Samplersne er at finde i modules/jmeter-ecpr/src/main/java/com/arosii/jmeter/protocol/ecpr/sampler
Nedenfor ses en liste over de udarbejdede samplers med tilhørende GUI, samt en kort beskrivelse, hvor dette er relevant:
GUI | Sampler | Beskrivelse/bemærkning |
---|---|---|
eCPRCreatePersonRequestSamplerGUI | eCPRCreatePersonRequestSampler | Her er det valgt, at der ved oprettelse udelukkende angives for- og efternavn, fødselsdato og køn. |
eCPRGetPersonByIDSamplerGUI | eCPRGetPersonByIDSampler | |
eCPRMergePersonsRequestSamplerGUI | eCPRMergePersonsRequestSampler | |
eCPRUpdatePersonSamplerGUI | eCPRUpdatePersonSampler | Ved en updatePerson skal navn og fødselsdato opdateres, og der tilføjes en adresse. |
eCPRSearchPersonRequestSamplerGUI | eCPRSearchPersonRequestSampler | I search person skal angives for- og efternavn samt fødselsdato. Der er mulighed for at angive en identifier, men denne kan som den eneste også efterlades tom. |
eCPRReservePersonIDSamplerGUI | eCPRReservePersonIDSampler | Denne har ingen variable GUI elementer, men har en Random generering af OrganisationsId, for at undgå at generere for mange id'er pr organisation på den samme dag |
eCPRGetOIDSamplerGUI | eCPRGetOIDSampler | Disse har ikke nogen GUI elementer, da requested ikke har nogle variable parametre. Sampleren bruges derfor udelukkende til at lave et setup af performancetesten, og ikke fordi outputtet bruges i de øvrige tests |
eCPRGetPermissionsGUI | eCPRGetPermissions | |
eCPRCompareNameRequestSamplerGUI | eCPRCompareNameRequestSampler | Ved Compare name kan angives et for- og efternavn. I sampleren er der indsat en generator, som i ~50% af tilfældende tilpasser navnet, så CompareName ikke altid returnerer 100. |
Når requested opsættes er der gjort brug af XML-filer, der indeholder et eksempel på et request. Dette forsimpler opsætningen af requested, og dermed overskueliggører koden. XML-filerne er placeret i resourcesfolderen under modules (modules/jmeter-ecpr/src/main/resources/com/arosii/jmeter/protocol/ecpr/sampler)
6.2.2. Tests
Tests mappen indeholder de generede test filer, som for eCPR svarer til testplanen eCPR.templates.jmx samt 3 distributioner (Disse er nærmere omtalt i eCPR - Testvejledning)
- distributioner
- planer
6.3. Versionskontrol
I dokumentet "testvejledning" afsnit performance test angives, hvilken version af performance testen der anvendes med en given version af eCPR servicen.
6.4. Udvikling af test
eCPR-servicens performance test består af ovennævnte java sourcer. Lokal test kan gøres ved at bygge projektet, starte dette i en lokal dockercontainer og starte JMeter op, og hererfter åbne testplanen eCPR.templates.jmx. For at testen kan køre skal bruges noget testdata. Dette data defineres i nogle testfiler som er placeret under /performance-framework/tests/ecpr/src/test/jmeter/templates/testplans. For performancetesten af eCPR bruges følgende filer, hvor parentesen angiver hvilke parametre der er i filen):
- createPersonData2 (GivenName,FamilyName,Birthdate,Gender)
- SearchAndCreatePersonData (GivenName,FamilyName,Birthdate,Gender,XeCPR)
- updatePersonData2 (GivenName,FamilyName,Birthdate,Address,City,PostalCode)
- identifiers (XeCPR)
Testfil 1,2 og 3 kan bruges direkte, men identifiers (XeCPR) i identifier skal befinde sig i databasen. Denne kan genereres ifbm. datageneratoren opretter dummy-data, så testdatabasen ikke er helt tom. Herefter kopieres outputfilen identifiers.csv ind i mappen testplans i JMeter projektet). Alternativt kan lokalt laves et loop af "crestePerson" hvorefter der manuelt udtrækkes XeCPR-numre fra databasen.
6.5. Generering af testfiler
Når man har udviklet og bygget test projektet, startes JMeter. Herefter kan en eksisterende performance test åbnes og køres herfra. Eller en ny kan laves.
For at køre en performancetest skal classpathen defineres i "user.properties" for JMeter (ligger i samme mappe som JMeter.bat).
- Kør "mvn install" i performance-framework rodmappen
- Kør "mvn -DincludeScope=runtime dependency:build-classpath -Dmdep.fileSeparator=/" i performance-framework/tests/ecpr
- Kopier den classpath der udskrives og indsæt den i "user.properties" under jmeter installationen. Den skal kopieres ind som variablerne "user.classpath" og "search_paths". Bemærk at search_paths skal være ;-separeret uanset platform.
Det følgende skærmbillede viser den skærm, som er udviklet i eCPRUpdatePersonRequestSamplerGui hvor der bruges data fra de samplers, der tidligere er kørt (eCPRCreatePersonRequestSampler og eCPRGetPersonByIDSampler):
Når man starter testen (den grønne pil) aktiveres et kald mod den service, der er konfigureret under 'Host configuration' og hermed aktiveres koden fra de respektive samplers. I dette skærmbillede ses, at for tråden Perormancetest - Create and Update aktiveres koden for først eCPRCreatePersonRequestSampler, herefter eCPRGetPersonByIDSampler og til sidst eCPRSearchPersonRequestSampler. Herefter aktiveres koden fra de samplers, der er defineret i loopcontrolleren SearchAndCreatePerson. Til sidst er indsat en Constant Throughput timer der sørger for, at samplersne eksekveres i en forholdsvis realistisk hastighed, her med en hastighed på 12 samplers/minut, svarende til en hver 3. sekundt.
Resultatet kan ses under 'View Result Tree', hvor både kald og svar kan ses. Her skal man være opmærksom på, at JMeter viser kaldene som værende fejlet, selvom de er lykkedes. Derfor skal man se på Sampleresult, hvor HTTP-statuskoden afgører om et request er lykkedes (Status = 200)
I skærmbilledet ses også samtlige tråde, der spinnes op under en performancetest af hele systemet. Hver tråd repræsenterer et brugscenarie. Brugscenarierne er primært identificeret ud fra produktionsdata fra eCPR2 på FMK.
Den endelige kørsel af performance testen skal bruge en test plan (skabes når ovenstående test gemmes) samt en distribution, der indeholder 'Distribution' delen af ovenstående. De gemmes henholdsvis i tests/ecpr/src/test/jmeter/templates/testplans og tests/ecpr/src/test/jmeter/templates/distributions. For at ændre distributionen skal der altså indlæses en ønsket distribution fil fra tests/ecpr/src/test/jmeter/templates/distributions.
Distribution
I distribution kan belastningen og fordelingen styres. Samtlige tråde er sat til at loope forevigt, og længden styres dermed af en Scheduler, hvor tiden kan styres via Distribution paramteren MASTER_LOOP_TIME. Antallet af usersne styres med parameteren USER_THREAD_COUNT, svarende til antallet af tråde der startes af hver handling. Den eneste undtagelse er tråden Performancetest -Spike of GetPersonById. Denne tråd modulerer et spike af GetPersonByID, der ses i produktionsdata, hvor der en gang timen laves 210-220 kald af GetPersonByID. Denne tråd er dermed sat til at udføre 220 kald af GetPersonByID, efterfulgt af en Constant throuhput timer, der efter hver 220. kald er sat til at vente 10 sekunder. Denne parameter på 6 omgang/minut tilpasses alt efter hvorlænge performancetesten udføres. Den reelle værdi er 1 gang i timen, men dette kan være en dårlig testværdi.
7. Tredje parts moduler og software stack
Løsningen er udviklet i Java og den udstillede WebServices er implementeret som Servlet 3 og anvender JAXB til serialisering og de-serialisering af beskeder.
Til håndtering af dependency injection bruges Spring, og til database transaktioner og databaser anvendes Spring JDBC
7.1. eCPR -service
Nedenstående liste er en liste af 3. parts moduler som eCPR direkte afhænger af, opstillet i alfabetisk orden. Det vil sige dem der er listet i POM filerne. Udover det anvendes der også en række biblioteker, der stilles til rådighed af platformen. Disse er ikke listet her.
com.fasterxml.jackson.core:jackson-annotations:2.8.10
com.fasterxml.jackson.core:jackson-core:2.8.10
com.fasterxml.jackson.core:jackson-databind:2.8.10
com.github.mwiede:jsch:0.2.11
com.sun.xml.bind:jaxb-impl
commons-io:commons-io:2.7
javax.servlet:javax.servlet-api:3.1.0:provided
log4j:log4j:1.2.17
org.apache.cxf:cxf-api:2.7.13:provided
org.apache.commons:commons-text:1.10.0
org.apache.httpcomponents:httpclient:4.5.13
org.apache.lucene:lucene-core:8.11.2
org.apache.lucene:lucene-queryparser:8.11.2
org.apache.lucene:lucene-suggest:8.11.2
org.springframework:spring-core:5.2.24.RELEASE
org.springframework:spring-context:5.2.24.RELEASE
org.springframework:spring-jdbc:5.2.24.RELEASE
org.springframework:spring-test:5.2.24.RELEASE
dk.sosi.seal:seal:2.6.19
dk.sdsd.nsp:nsp-util:1.0.11
dk.sds.nsp.security:security-api:1.0.6:provided
dk.sds.nsp.audit:audit-api:1.0.1:provided
7.2. Test
Nedenstående er en liste over de biblioteker der anvendes i forbindelse med test. Det vil sige dem der er angivet i POM filerne med scope test.
org.junit.jupiter:junit-jupiter:5.9.1:test
org.springframework.boot:spring-boot-starter-test:test
org.apache.sshd:sshd-core:0.8.0:test
com.h2database:h2:1.4.200:test
org.junit.vintage:junit-vintage-engine:5.9.1:test
org.mockito:mockito-core:4.11.0:test
Til integrationstestene anvendes derudover
dk.nsp.idp:nsp-test-idp:1.0.7
Ændringslog
1.0 | 2023-12-12 | Indhold publiceret | Trifork |