2023-utgaven av Matvaretabellen, en løsning som så dagens lys i form av publikasjonen "Norske Næringsmidler" allerede i 1945.
Datasettet i Matvaretabellen samles inn fra mange kilder og bearbeides internt i Mattilsynet, og publiseres én gang i året. Det er med andre ord ingen kontinuerlig flyt av data ut. Med blant annet dette bakteppet har vi bygget løsningen på idéen om at vi ikke trenger en server kjørende, men heller trykker statiske sider med HTML i et byggesteg. Da får vi:
- lynraske sider
- færre kjørende prosesser å overvåke og betale for
- lavere karbonfotavtrykk
Når vi i tillegg bygger dette opp som krysslenka innholdssider på egen URL, får vi også:
- være treff på søkemotorer
- lettere å lenke til
- lettere å bokmerke
Alt i alt et par Kinderegg* av godsaker der, altså.
* forøvrig en merkevare som ikke er å finne i matvaretabellen i dag
Serveren bygger en indeks som serveres som en (statisk) JSON payload (~200kB gzippet), og så har vi implementert en liten søkemotor som kjører i nettleseren.
Du kan lese litt om strategien i disse to blogginnleggene:
Disse legger opp til en JavaScript-løsning. Vi valgte den bort til fordel for en ClojureScript-løsning for at klienten kunne dele tokenizing-koden med backenden (som er skrevet i Clojure) - et kritisk punkt for at søket skal fungere.
Dette oppsettet antar for øyeblikket at du sitter på en Mac. Du kan lese mer om hvordan dette er skrudd sammen lenger ned.
-
Skaff Clojure
brew install clojure
-
Sørg for at du har FontAwesome-ikonene:
make prepare-dev
-
Start ClojureScript-bygget (Emacs-brukere kan se nedenfor)
clj -M:dev -m figwheel.main -b dev -r
-
Kopier eksempelkonfigurasjonen:
cp config/local-config.sample.edn config/local-config.edn
-
Kjør opp databasen
make start-transactor
-
Last inn FoodCASE-data i databasen (trenger kun å gjøres første gang):
clj -M:dev (require '[matvaretabellen.foodcase-import :as foodcase-import]) (foodcase-import/create-database-from-scratch (:foods/datomic-uri config))
-
Start backenden:
clj -M:dev (require 'matvaretabellen.dev) (matvaretabellen.dev/start)
Dersom du bruker Emacs - noe vi anbefaler på det aller varmeste - er det
cider-jack-in
og deretter cider-connect-sibling-cljs
som gjelder for å få
opp både backenden og frontenden.
-
Få tak i bearer token fra FoodCASE. Snakk med en av utviklerne eller Jorån.
-
Dra ned dataene fra Sveits:
export FC_BEARER=<bearer> curl https://foodcase.prod.nfsa.foodcase-services.com/FoodCASE_WebAppMattilsynet/ws/dataexport/food_norwegian -H "Authorization: Bearer $FC_BEARER" > foodcase-food-nb.json curl https://foodcase.prod.nfsa.foodcase-services.com/FoodCASE_WebAppMattilsynet/ws/dataexport/food_english -H "Authorization: Bearer $FC_BEARER" > foodcase-food-en.json curl https://foodcase.prod.nfsa.foodcase-services.com/FoodCASE_WebAppMattilsynet/ws/dataexport/data_norwegian -H "Authorization: Bearer $FC_BEARER" > foodcase-data-nb.json curl https://foodcase.prod.nfsa.foodcase-services.com/FoodCASE_WebAppMattilsynet/ws/dataexport/data_english -H "Authorization: Bearer $FC_BEARER" > foodcase-data-en.json
-
Skaff deg
jq
hvis det mangler:brew install jq
-
Fløtt dem over til vår datakatalog ferdig formatert:
jq '.' foodcase-food-nb.json > data/foodcase-food-nb.json jq '.' foodcase-food-en.json > data/foodcase-food-en.json jq '.' foodcase-data-nb.json > data/foodcase-data-nb.json jq '.' foodcase-data-en.json > data/foodcase-data-en.json
-
Prøv å kjøre en import. Det gjør du fra
dev/matvaretabellen/dev.clj
ved å kjøre koden som ligger der, som ser omtrent sånn ut:(def config (load-local-config)) (foodcase-import/create-database-from-scratch (:foods/datomic-uri config))
Kryss fingre for at ting er i orden. Vurder å skrive litt valideringskode.
-
Oppdater "Nytt i Matvaretabellen" i fila
data/new-food-ids.edn
Denne vil gjerne ha en liste med nye matvare-ID'er. Oppdater samtidig årstallet.
-
Vurder om du skal fjerne
outdated-food-group-ids
i foodcase-importHer er det gamle ID'er som forhåpentligvis har blitt ryddet bort i FoodCASE.
Etter alt dette skal det bare være å dytte en commit til origin/main, og du kan gå og spise kake sammen med klagesaksavdelingen.
Du må ha noen verktøy:
brew install terraform gh
For å sette opp miljøet må du ha en GCP-konto og tilgang til relevante prosjekter.
Du må være autentisert mot GCP. Deretter setter du parenteser-prosjektet som default og autentiserer maskinen din mot dette prosjektet:
gcloud auth login
gcloud config set project matvaretabellen-b327
gcloud auth application-default login
Terraform henter noen moduler over https-git som ikke er åpne. For at det skal funke kan du bruke github sin CLI for å autentisere deg for https:
gh auth login
Velg HTTPS og fullfører flyten som følger.
NÅ! Nå, er du klar for å kjøre opp ting:
cd tf/app
terraform init
terraform plan
terraform apply
Dette vil sette opp nødvendig infrastruktur. Merk at terraform-oppsettet vårt har et "hello world" image. Dette imaget brukes kun ved første gangs oppsett. Github Actions-arbeidsflyten ber CloudRun om å kjøre nye images ved push.
DNS-oppsettet lever i en egen terraform-modul:
cd tf/dns
terraform init
terraform plan
terraform apply
Verdt å merke seg: prosjekt-id-en som brukes med workload_identity_provider
når vi autentiserer oss mot GCP for å oppdatere Cloud Run-konfigurasjonen vår
kan finnes på følgende vis:
gcloud projects list \
--filter="$(gcloud config get-value project)" \
--format="value(PROJECT_NUMBER)"
Det skal normalt ikke være nødvendig å hverken bygge eller publisere Docker images fra lokal maskin. Allikevel ønsker man av og til å gjøre nettopp det - kanskje for å sjekke akkurat hvilke ting som ikke fungerer eller lignende.
Bygging er rett frem:
make docker
For å publisere må du først logge deg selv inn i GCP, og deretter sørge for at Docker-prosessen også får være med på moroa:
gcloud auth login
gcloud auth configure-docker europe-north1-docker.pkg.dev
Noen viktige beslutninger er dokumentert som ADR-er.
Som nevnt innledningsvis er Matvaretabellen en statisk site. Det betyr at vi i byggesteget bygger alle HTML-sidene som utgjør løsningen til disk, og så serveres disse bare av nginx i produksjon. Produksjonsmiljøet er dermed svært enkelt, og kan ta unna store mengder trafikk uten videre innblanding fra vår side.
Løsningen er bygget på Powerpack, som delvis er bygget opp i parallel med Matvaretabellen. Powerpack sørger for at assets serveres med URL-er som kan caches lenge i produksjon, gir oss et veldig responsivt og levende utviklingsmiljø, og legger til rette for en god dataflyt.
Matvaretabellen har flere datakilder. Dataene som utgjør den store verdien - næringsinformasjonen - kommer fra systemet FoodCASE. I tillegg har vi data om dagsinntak, samt redaksjonelt tilleggsinnhold.
Fagseksjonen jobber med dataene i FoodCASE kontinuerlig, men publiserer årlige versjoner av datasettet. Det er dermed ingen behov for å strømme disse dataene kontinuerlig fra kilden.
Siden dataene endrer seg så sjelden har vi valgt å sjekke dem inn i dette
repoet. Det er rene JSON-filer som kun er formattert med jq
. For å bygge appen
blir disse dataene massert inn i vår datamodell
og lest inn i Datomic.
For produksjon leses data fra JSON i repoet til en in-memory Datomic-database før bygget starter. Under utvikling anbefales det å kjøre en Datomic-instans med persistering. Da trenger du kun å kjøre importen en gang (og eventuelt når datasettet endrer seg).
Nye data importeres og sjekkes inn etter oppskriften over.
I tillegg til datamatrialet fra FoodCASE har vi noe redaksjonelt innhold, samt data om hvilke sider som er tilgjengelig osv. Redaksjonelt innhold ligger på disk under resources/content. Innholdet i disse leses inn i en separat in-memory Datomic-database under oppstart. Under utvikling vil innholdet i disse filene også automatisk leses inn når filene endres.
Anbefalt Dagsinntak finnes som en CSV-fil som er
eksportert fra et Excel-ark. Disse dataene leses inn i in-memory
Datomic-databasen under oppstart. Det er ingen live reload for akkurat disse
dataene under utvikling (uten at det egentlig er noen god grunn til det - annet
enn at de sjeldent endrer seg), så for å speile endringer her må appen restartes
(evaluer (restart)
i dev-navnerommet).
Når appen kjører under utvikling -- eller man kjører eksport, som i byggeprosessen -- er flyten som følger:
- Powerpack leter i Datomic etter en side med URL-en som ble etterspurt
- Powerpack kaller på vår kode (
render-page
, se nedenfor) for å rendre siden. "Siden" i dette tilfellet er et map med:page/uri
,:page/kind
og:page/locale
. - De forskjellige side-typene er implementert med hver sin funksjon, som plukker frem data fra foods-databasen og bygger HTML for siden.
- Powerpack beriker responsen vår
Powerpack-applikasjonen er konfigurert i
matvaretabellen.core
. Du kan følge all flyt
herfra.
Navnerommet matvaretabellen.ingest
leser
inn sidedefinisjonene som utgjør mulige URL-er på siten.
Navnerommet matvaretabellen.pages
har
funksjonen render-page
som ser på hvilken sidetype som er etterspurt og
sparker ballen videre til riktig funksjon for å rendre siden.
Matvaretabellen har noe interaktiv funksjonalitet: søk, filtreringer, sidebar på mobil osv. Denne funksjonaliteten er implementert i ClojureScript, og følger prinsippene i "progressive enhancement". Det vil si at siten i all hovedsak fungerer uten dette scriptet, men når det kjører så gir det enkelte elementer mer funksjonalitet. Søket er et hederlig unntak fra dette, ettersom det ikke fungerer i det hele tatt uten scriptet.
Vi valgte ClojureScript til frontenden primært for at søket skulle kunne dele implementasjon av tokenization. Avvik i denne ville ført til elendig opplevelse med søket.
Frontend-koden sparkes igang fra
matvaretabellen.ui.main
.
Dersom du har spørsmål, tilbakemeldinger, eller har lyst til å få tak i oss av en annen grunn kan du enten bruke issues/pull requests her på Github, eller sende oss en epost på team.mat@mattilsynet.no.
Har du funnet en feil, eller ønsker du deg en eller annen liten feature? Ja, så kom med det. Vi er åpne for både issues og pull requests. Hvis du har lyst til å legge til kode så vil vi anmode om å starte med å ta en diskusjon om ønsket endring så vi er enige om at hva enn du har lyst til å gjøre passer inn før du legger for mye jobb i saken.