Utvikling på FS-kjernen
All utvikling skal skje i db-repoet i FS-prosjektet i Bitbucket. Det er satt opp en synk av dette repoet mot GitLab for å støtte CI/CD-prosessene. På et senere tidspunkt skal vi ta i bruk GitLab også til utvikling. Retningslinjene i dette dokumentet gjelder foreløpig bare for FS-skjemaet.
Gjør bakoverkompatible endringer
Både FS-plattformen og legacy-applikasjonene våre er avhengige av FS-skjemaet. Utvikling på FS-plattformen forutsetter at main-branchen kan deployes til en hver tid. API-kontraktene på FS-plattformen krever også at man varsler i god tid før man gjør endringer i funksjonalitet som klienter kan være avhengige av.
Derfor er det viktig å sørge for at nye versjoner av FS-skjemaet er bakoverkompatibelt med alt som er i produksjon. Til dette vil vi på sikt trenge et regime med varsling og trinnvis innføring av endringer som brekker funksjonalitet som er i produksjon i dag.
Liquibase og migreringer
Liquibase er et DevOps-verktøy for databasen. Det er integrert med Oracle-klienten SQLcl (som erstatter SQL Plus). Dvs. at i tillegg til SQL, PL/SQL, og klientspesifikke kommandoer for formatering o.l. (à la det som finnes i SQL Plus) er Liquibase-kommandoene direkte tilgjengelig i klienten.
Generelle begreper
- Baseline: Initiell struktur for en FS-versjon
- Lokal database: Docker-database (XEPDB1)
- Global database: "Ekte" database
Liquibase-begreper og -prinsipper
- Changeset (CS): En endring i databasen. Har en del attributter, der vi stort bruker
disse (defaultverdier i parentes):
author: Typisk brukernavn ellerfsid: Typisk filnavn (hvis fila inneholder kun ett CS) eller objektnavn (evt. med kvalifikatorer hvis samme objekt forekommer i flere changeset)failOnError(true): Hvorvidt migreringen skal feile hvis CS feilerrunOnChange(false): Hvorvidt CS skal kjøres hvis fila er endretrunAlways(false): Hvorvidt CS alltid skal kjøresstripComments(true): Hvorvidt kommentarer skal fjernes fra PL/SQL-kode i basenendDelimiter(;): Brukes i CS på SQL-format (se under), typisk/for PL/SQL
- Changelog (CL): Fil som inneholder ett eller flere CS som utføres i rekkefølge
- Kan defineres i følgende filformater
- SQL: (PL/)SQL-script som starter med kommentaren
--liquibase formatted sql- Fordel: Koden blir sin egen CL (slipper egen CL som kjører den)
- Merk: Fila må avsluttes med et SQL-statement (og ikke f.eks. en kommentar)
- XML/YAML/JSON: Likeverdige alternativer (vi bruker XML, typisk
dbchangelog*.xml)- Fordel: Kan kjøre andre ting enn rene (PL/)SQL-script
- Oracle har en egen subtype
runOracleScriptsom kan kjøre sære ting som SQLcl-/SQL Plus-kommandoer o.l.
- SQL: (PL/)SQL-script som starter med kommentaren
- Det anbefales å modularisere ved å stykke opp i flere CL som kaller hverandre (én CL
som kaller CLer i underkataloger)
includeAll: Kaller alle CLer i katalog i alfabetisk rekkefølge ("implisitt")- Brukes gjerne når man har mange filer (typisk på SQL-format) og ønsker å slippe å vedlikeholde en eksplisitt liste
include: Kaller en CL eksplisitt- Brukes typisk hvis det ikke er praktisk med
includeAll
- Brukes typisk hvis det ikke er praktisk med
- Tommelfingerregelen er: Implisitt når man kan, eksplisitt når man må
- Kan defineres i følgende filformater
- Migrering: Kjøring av en CL
Databasen holder rede på om/når en gitt migrering er kjørt og om kjøringa gikk bra (i egne
verktøytabeller med navn som begynner på DATABASECHANGELOG).
CS er tenkt å forekomme i 3 former:
- Lokal: Kjøres kun i lokal database
- Global: Kjøres kun i global database
- Generell: Kjøres i alle databaser
De to første er tenkt styrt av Liquibase context, men dette fungerer ikke ennå. Det er
greit inntil videre, da forskjellene mellom lokal og global bygging i første omgang går ut
på at skjemaet ikke skal bygges opp fra scratch i lokal base, og det kan styres ved at
aktuelle skjema-CLer kan markeres som "kjørt" (liquibase changelog-sync) i globale baser.
Oppfanging av feil
ORA-feil fanges opp av Liquibase og terminerer migreringen hvis failOnError = true.
PLS-feil (PL/SQL kompileringsfeil) fanges ikke opp på samme måte (noe som er helt greit, da det kan oppstå temporære feil pga. avhengigheter mellom pakker, som forsvinner ved rekompilering). I stedet gjør byggescriptet en rekompilering og feiler om det fremdeles er ugyldige objekter.
Katalogstruktur
Nivå 0: fskjerne-katalogen
Inneholder nivå 1-kataloger samt noen av nivå 2-katalogene (for script som kan brukes på
tvers av skjema). Dette gjelder foreløbig katalogene adhoc, deploy og grant.
(Se under for beskrivelse av nivå 1 og 2)
I tillegg er det noen oppsettfiler for SQLcl (kjøres automatisk når man starter SQLcl fra katalogen).
Nivå 1: Skjemakataloger
Underkataloger
Foreløbig er det tenkt å reflektere de eksisterende utvikler-skjemaene i FS-basen:
| Katalog | Skjema | Beskrivelse |
|---|---|---|
FS | FS | Det sentrale skjemaet for tabeller og kode |
FS_HIST | FS_HIST | Historikktabeller |
FS_SYSTEM | FS_SYSTEM | Temp.tabeller for rapporter, rutiner o.l. |
FSDBADM | FSDBADM | Administrative/"superbruker"-rutiner |
KJERNEAPI | Se under | KjerneAPI-view |
Der FS er det eneste som hittil er under implementasjon.
KJERNEAPI-skjemaene varierer foreløbig mellom baser:
| Database | Skjema |
|---|---|
| Lokal | KJERNEAPI |
| FSUTV | FSAPI_V1[AB] |
| Demo/Prod | FSAPI_V2[AB] |
De odde navnene i Utvikling, Demo og Prod er historisk betinget, og vil standardiseres til KJERNEAPI_[AB].
Dette vil kunne endre seg etterhvert som objekter i FS-skjemaet skilles ut i egne skjemaer.
Filer
CL på toppnivå (foreløbig i skjemakatalogene) startes av et shellscript build.sh.
Det produserer en loggfil build_<DATABASE>.log. Alle katalogreferanser i shell- og
SQL-script angis relativt, med utgangspunkt i aktuell skjemakatalog.
Nivå 2: Klassifisering av kode
Disse vil kunne variere fra skjema til skjema, men for FS ser det foreløbig slik ut
(med angivelse av om koden representerer logikk som brukes i bygging og/eller drift):
| Katalog | Beskrivelse | Bygging | Drift |
|---|---|---|---|
adhoc | Ad hoc-script | ||
admin | Kode for administrasjon av skjemaet | X | |
deploy | Script for bygging og utrulling | X | |
grant | Tilganger for andre skjema til dette skjemaet | X | |
prog | Kode for forretningsregler | X | |
rapp | Kode for rapporter/rutiner | X | |
skjema | Strukturdefinisjoner (f.eks. tabeller, sekvenser) | X | |
syn | Definisjon av synonymer (ikke operativt for FS) | X | |
trigg | Kode for triggere og triggerpakker | X | |
view | Viewdefinisjoner | X | X |
vpd | VPD-kode (pakke + generering av policies/triggere) | X |
Nivå 3: Noen kataloger er videre splittet opp (for å kunne bruke implisitt CL)
| Katalog | Beskrivelse | Kommentar |
|---|---|---|
dekl | PL/SQL-pakkehoder | |
impl | PL/SQL-pakkekropper | |
impt | PL/SQL-pakkekropper + triggere | Triggerne bør skilles ut i egne filer |
java | Java-kode i basen | |
sql | Frittstående rutiner + pakker i samlet fil | Pakkene bør splittes i hode og kropp |
Kodeendringer
"Kode" regnes i denne sammenheng som CLer som kan redefinere objekter ved å kjøres på nytt
(runOnChange = true) via CREATE OR REPLACE. Som oftest er det snakk om PL/SQL-pakker,
men det kan også være f.eks. triggere, view eller synonymer.
Endret kode
Her er det bare snakk om å endre i aktuelle kodefiler (CLer) på vanlig måte. Liquibase fanger opp at en fil er endret, og kjører den på nytt.
Ny kode
Filformat
Ved oppretting av ny kodefil må man ta stilling til CL-filformat. I de fleste tilfeller vil det være mulig å bruke SQL-format, dvs. at fila skal starte med Liquibase-kommentarer, f.eks:
--liquibase formatted sql
--changeset <author>:<id> runOnChange:true stripComments:false endDelimiter:/
(I prinsippet kan fila inneholde flere CS, men i de fleste tilfeller brukes kun ett)
Navnestandard
Hvis man benytter implisitte CL med includeAll må filtypen være .sql). Vi opererer med
følgende navnestandard:
<Filnavn>-dekl.sql: Pakkehode (tidligere<Filnavn>.pks)<Filnavn>-impl.sql: Pakkekropp (tidligere<Filnavn>.pkb)<Filnavn>-impt.sql: Pakkekropp + triggere (tidligere<Filnavn>.pkt)
dekl-katalogene kjøres før impl/impt - dermed vil alle "hoder" kjøres før alle
"kropper", og man slipper problemer med avhengigheter mellom pakkene.
Skjemaendringer
Her er det først og fremst snakk om tabeller eller sekvenser, som ikke kan redefineres med
CREATE OR REPLACE og som dermed kun skal kjøres én gang (dvs. runOnChange = false).
Nye objekter
Som for kode er det anbefalt å bruke SQL-format, dvs. å starte med Liquibase-kommentarer:
--liquibase formatted sql
--changeset <author>:<id> runOnChange:false failOnError:true endDelimiter:/
CREATE ...
Evt. uten endDelimiter:/ hvis SQLene avsluttes med ;.
Det foreligger ingen klare retningslinjer mhp. antall objektdefinisjoner pr. CL, men intuitivt virker det fornuftig å operere med én CL pr. tabell (tabelldefinisjon + constraints og indekser). Et unntak kan være at man samler alle eksisterende tabelldefinisjoner i en "baseline"-fil som utgangspunkt for videre utvikling.
Endrede tabeller
Siden DROP/CREATE gir tap av informasjon, ønsker man normalt å bruke ALTER i stedet
(et unntak er temporærtabeller der det ikke er viktig å ta vare på dataene). En endring gir
dermed opphav til en ny CL:
--liquibase formatted sql
--changeset <author>:<id> runOnChange:false failOnError:true endDelimiter:/
ALTER ...
Generelt
NB! Hvis man bruker implisitt CL (includeAll) ifm. skjemaendringer, er det viktig å huske
på at navngivingen bestemmer rekkefølgen de kjøres i.
Grants
GRANTs kan (enten de er statiske eller dynamiske) alltid kjøres på nytt uten feil
(runOnChange = true). For dynamiske GRANTs er det anbefalt å bruke runAlways =
true, siden det kan ha kommet til objekter som gir opphav til nye GRANTs.
Dataendringer
Disse vil ofte være aktuelle i forbindelse med skjemaendringer (nye tabeller/kolonner fylles
med initielle data), men i motsetning til skjemaendringene har man her muligheten til å
bruke runOnChange = true (og til og med runAlways = true), hvis DMLen er på
en form som tillater det - f.eks. MERGE, UPDATE eller DELETE som kan kjøres på nytt
uten at det feiler (eller tar for lang tid).
Kjente problemer
Kompilering av PL/SQL-kode gir feil tegnsett for ÆØÅ i basen.
Symptom
Kompilering av PL/SQL-kode i SQL*Plus (formodentlig også i SQLcl) ga feil tegnsett for ÆØÅ i basen.
Løsning
Sette systemvariabel NLS_LANG = NORWEGIAN_NORWAY.AL32UTF8 og starte SQL*Plus/SQLcl på nytt.