Indledning

SORES er designet med opslags-performance som den primære drivfaktor, og gør derfor brug af en memory-cache, der skal initialiseres inden den kan besvare requests - og når der er opdateringer i datagrundlaget.

Arkitektur

Data-strukturen, der bærer response-data, ligger i pakken dk.sds.nsp.sor.sores.model, mens logikken i komponenten er inddelt i følgende lag beskrevet herunder.

Servlet

Servlet-laget ligger i pakken dk.sds.nsp.sor.sores.ws og er ansvarlig for at håndtere GET-requests og for at udpakke JSON-requestparametre fra POST-requests, kalde servicen i service-laget og formattere svaret som en JSON-struktur i responset.

Service

Service-laget ligger i pakken dk.sds.nsp.sor.sores.service og er ansvarlig for at hente de forespurgte data fra database-laget.

Cache

Cache-håndteringen ligger i pakken dk.sds.nsp.sor.sores.cache og kan i seneste version kun bruge en database som datagrundlag. Database-strategien baserer sig på SOR2-indlæseren.

Data gemmes i cachen i hashmaps, med nøglerne hhv. SHAKId, SORId, ParentSORId, ProviderId, CVR og EntityTypeName. Disse maps muliggør effektive opslag på servicen.

Database

Database-laget ligger i pakken dk.sds.nsp.sor.sores.dao. Her foregår opslag i databasen via datasourcen.

Data indlæses fra databasen ved et reload-request, hvis seneste database-timestamps er nyere end seneste cachen-timestamp.

Algoritme for indlæsning til cache

Cachen indlæses ved kald til "/reload" som beskrevet i driftsvejledningen. Følgende query danner grundlag for at fylde cachen med entiteter fra tabellen SOR2SorEntity. En tilsvarende query er lavet til at indlæse fra SOR2SorShakMap.

WITH filtered AS (
  SELECT *
  FROM SOR2SorEntity
  WHERE FromDate > :date_cutoff
  UNION ALL
  SELECT t.*
  FROM SOR2SorEntity t
  JOIN (
    SELECT SorId, MAX(FromDate) AS FromDate
    FROM SOR2SorEntity
    WHERE FromDate <= :date_cutoff
    GROUP BY SorId
  ) m
    ON m.SorId = t.SorId
   AND m.FromDate = t.FromDate
  WHERE t.ToDate IS NULL OR t.ToDate > :date_cutoff
)
SELECT f1.*
FROM filtered f1
JOIN (
  SELECT UniqueCurrentKey, MAX(ValidFrom) AS MaxValidFrom
  FROM filtered
  GROUP BY UniqueCurrentKey
) pick
  ON pick.UniqueCurrentKey = f1.UniqueCurrentKey
 AND pick.MaxValidFrom    = f1.ValidFrom;

Parameteren ":date_cutoff" udfyldes med property "sores.retention.period" som beskrevet i driftsvejledningen. 

Indlæsning foregår som et join på SOR2SorEntity fra sig selv. En central del af denne join er følgende:

Der skal vælges de rækker, hvor der for hver gruppering på nøglen UniqueCurrentKey, kun vælge dén række, der har den nyeste ValidFrom (date).

UniqueCurrentKey er en sammensat nøgle, som består af <SorId, FromDate>. Dette er ikke en unik nøgle, da der kan forekomme adskillige rækker af disse. Men ovenstående join vælges kun dén af de rækker, som har nyeste ValidFrom.

FromDate er den forretningsmæssige startdato for hvornår er række er gyldig. Denne kan adskille sig fra indlæsningstidpunktet (hvornår SOR2-importeren har indlæst pågældende data), og ValidFrom benævnes derfor den tekniske fra-dato, som er sat af importeren. Er der flere rækker med samme SorId og FromDate, bruges derfor ValidFrom til at vælge den række, der aktuelt er gyldig på den FromDate for den SorId.

Bemærk, at der i JOIN-betingelsen også forekommer en  "OR (FromDate =" (linje 9). Dette skyldes, at vi ud over historikken for det angivne dato-interval, også har behov for at sikre os, at seneste gyldige SorEntity - uanset FromDate - også altid er med i indlæsningen. Som eksempel kunne man forestille sig en SorEntity, hvor seneste FromDate lå tre år tilbage, og derfor vil den ikke ligge inden for "FromDate > :date_cutoff", hvis ":date_cutoff" kun var to år tilbage. Glemmer vi denne, risikerer vi derfor at have et "hul" i starten af den periode, som har cut-off date som start.

Til den inderste query tilføjes "AND (ToDate is NULL OR ToDate > :date_cutoff)", da en SorEntity med start udenfor intervallet kun har relevans, hvis den stadig er aktiv inden for intervallet. 

ReplacedSORMap

Henter alle erstatningsrelationer mellem SOR-ID’er fra databasen som en flad liste af parent-child forbindelser med gyldighedsperioder. Listen den laver bliver brugt til at lave relationstræet til hurtig opslag i SoresCache.

public List<SorReplacedByDTO> getSorReplacedByMap() {
        String query =
                "SELECT ReplacedSorId, ReplacedBySorId, FromDate, ToDate " +
                "FROM SOR2SorReplacedByEntities";
        List<SorReplacedByDTO> sorReplacedByEntities = jdbcTemplate.query(query, (rs, rowNum) ->
            new SorReplacedByDTO(
                rs.getLong("ReplacedSorId"),
                rs.getLong("ReplacedBySorId"),
                rs.getDate("FromDate").toLocalDate(),
                rs.getDate("ToDate") != null ? rs.getDate("ToDate").toLocalDate() : null
            )
        );

        return sorReplacedByEntities;
    }