Giter Site home page Giter Site logo

vng-realisatie / api-kennisbank Goto Github PK

View Code? Open in Web Editor NEW
6.0 6.0 8.0 1.22 MB

Afspraken, werkinstructies, tutorials, enz. voor de co-creatie en het beheer van API standaarden

Home Page: https://vng-realisatie.github.io/API-Kennisbank/

Gherkin 100.00%
vngr-tooling

api-kennisbank's People

Contributors

adgerrits avatar fsamwel avatar joeribekker avatar johanboer avatar melsk-r avatar melvlee avatar michielverhoef avatar sergei-maertens avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

api-kennisbank's Issues

Probleem met schrijfwijze 'GeoJSONGeometrie' component in Generieke-Datatypen-Gemeenten.yaml

We maken in de Regels bij activiteiten API gebruik van het Generieke datatype 'GeoJsonGeometrie'.
Daarvoor heb jij in het conceptualschema ook een entry met dezelfde naam gemaakt.
Bij gebruik levert dat in de yaml de volgende referentie op:

$ref: "https://raw.githubusercontent.com/VNG-Realisatie/API-Kennisbank/master/common/Generieke-Datatypen-Gemeenten.yaml#/components/schemas/GeoJsonGeometrie"

In het schema 'https://raw.githubusercontent.com/VNG-Realisatie/API-Kennisbank/master/common/Generieke-Datatypen-Gemeenten.yaml' heb je daarvoor ook een component opgenomen echter met een naam die niet overeen komt met de in EA en het conceptualschema gehanteerde naam nl. 'GeoJSONGeometrie'.

Daardoor krijg ik in Swagger een foutmelding. We moeten dus een van de volgende correcties uitvoeren:

  1. We hernoemen het component in 'https://raw.githubusercontent.com/VNG-Realisatie/API-Kennisbank/master/common/Generieke-Datatypen-Gemeenten.yaml' naar 'GeoJsonGeometrie'. Dit heeft als nadeel dat het yamlschema in de GitHub voor de Regels bij activiteiten API meteen invalid wordt en ook aangepast moet worden.

  2. We hernoemen het component in EA en in het conceptualschema naar 'GeoJSONGeometrie', waarmee het meteen opgelost is. Nadeel daarvan is wel dat we afwijken van de wijze waarop het in de API voor de omgevingsdocumenten is gedaan.

Wat heeft de voorkeur?

Beperk de lengte van enumeratiewaarden

De lengte van enumeratiewaarden wordt beperkt. Bijvoorbeeld "Opstalhouder Nutsvoorzieningen op gedeelte van perceel" krijgt als code "nutsvoorzieningen_gedeelte".

Ontsluiting groepsattribuut altijd inline

Indien een object een groepattribuutsoort heeft (een groep van bij elkaar behorende attribuutsoorten), al dan niet herhalend d.w.z. kardinaliteit nul of meer (een ‘lijst’ van waarden van de attributen van het groepattribuut), dan wordt het groepsattribuut inline (binnen de resource voor het object) ontsloten. Het krijgt dus geen eigen resource, het heeft immers geen zelfstandig bestaansrecht. Het heeft alleen bestaansrecht als eigenschap van het object, net zoals attribuutsoorten. Typische voorbeelden zijn (zaak-)kenmerken en (zaak-)eigenschappen waarin Zaak het hoofdobject betreft, en Kenmerk en Eigenschap de groepattributen.

Voorbeeld:

{
    "url": "https://example.com/zrc/api/v1/zaken/6232ba6a-beee-4357-b054-6bde6d1ded6c",
    "zaaktype": "https://example.com/ztc/api/v1/zaaktypen/6232ba6a-beee-4357-b054-6bde6d1ded6c"
    // ...
    "kenmerken": [
        {
            "kenmerk": "test",
            "bron": "http://www.example.com/"
        }
    ]
}

We gebruiken als enumeratiewaarden betekenisvolle waarden.

Dus niet M en V, maar man en vrouw.

Ratio Een developer moet bij het coderen begrijpen wat de code betekent, om fouten in de implementatie te voorkomen. Kanttekening De lengte van de enumeratiewaarden zal zoveel mogelijk beperkt moeten worden (Zie de design rule over lengtes van enumeratiewaarden #38).

Relaties aangeven in query parameters

Om relaties zowel naar een andere resource (zoals status) als naar een genest attribuut (zoals kenmerken) te gebruiken binnen query parameters, wordt de __ (dubbele underscore) gebruikt tussen de attribuutnamen.

Voorbeelden

Voor filteren:

?zaak__kenmerken=test
?zaak__status=http://example.com

Voor de fields query parameter:

?fields=zaak__kenmerken
?fields=zaak__status

Gebruik benamingen zoals gedefinieerd in een gegevenswoordenboek

Wanneer er een gegevenswoordenboek (gegevenscatalogus, informatiemodel) bestaat, gebruiken we voor corresponderende resources of voor corresponderende properties in een resource de naam zoals die in het gegevenswoordenbook staat, met inachtneming van de Design Rules voor naamgeving, zoals gebruik (Upper)snakeCase.

Van de naam in het gegevenswoordenboek kan worden afgeweken in o.a. de volgende situaties:

  • Weglaten van redundatie in de naam. Bijvoorbeeld "geboortedatum" in een gegevensgroep geboorte nemen we op als 'datum'.
  • Uitschrijven van afkortingen. Bijvoorbeeld "BSN" nemen we op als 'burgerservicenummer'.
  • Toevoegen van context, bijvoorbeeld wanneer het gegeven in een andere context wordt gebruikt dan in het gegevenswoordenboek. Bijvoorbeeld het opnemen van gegeven 'identificatie' van een woonplaats bij een nummeraanduiding wordt property 'woonplaatsIdentificatie'.
  • Een van het gegeven via een algoritme afgeleide property. Bijvoorbeeld 'leeftijd' van 'geboortedatum'.

HAL-gerelateerd: Alleen gerelateerde resources uit dezelfde bron kunnen embed worden

Bijvoorbeeld in de resource van een ingeschreven natuurlijk persoon bij de BRP-bevraging kunnen de relaties partner(schap), ouders en kinderen embed worden opgenomen met gebruik van de expand-parameter. Dit betekent dat het mogelijk is in één aanroep de ingeschreven persoon te krijgen, met daarbij gegevens over de relaties met eventuele partner(s), ouders en kinderen.
Wanneer een gerelateerde resource expand wordt, wordt de gehele sub-resource teruggegeven, tenzij in de expand parameter alleen een deel van de gerelateerde resource gevraagd is.

Gegevens uit een andere bron/registratie (bijvoorbeeld het BAG-adres van een persoon) kunnen niet embed worden. Deze worden alleen als link opgenomen.

Ratio : We willen "tight coupling" met andere bronnen voorkomen. Over de domeinen heen wordt alleen met links verwezen.

Kanttekening Er zijn grofweg twee categoriën Hal-links waar we gebruik van maken. Links naar resources binnen het eigen domein en links naar resources die in een ander domein beheerd worden. Om discoverability te bereiken, worden voor beide categorieën de Hal-link opgenomen naar de gerelateerde resource.

Gegevensgroepen worden inline opgenomen bij een resource als zegeen zelfstandig bestaansrecht hebben.

Indien een object een groepattribuutsoort heeft (een groep van bij elkaar behorende attribuutsoorten), al dan niet herhalend d.w.z. kardinaliteit nul of meer (een ‘lijst’ van waarden van de attributen van het groepattribuut), dan wordt het groepattribuut inline (binnen de resource voor het object) ontsloten. Het krijgt dus geen eigen resource, het heeft immers geen zelfstandig bestaansrecht. Het heeft alleen bestaansrecht als eigenschap van het object, net zoals attribuutsoorten. Typische voorbeelden zijn (zaak-)kenmerken en (zaak-)eigenschappen waarin Zaak het hoofdobject betreft, en Kenmerk en Eigenschap de groepattributen.

Voorbeeld:

{
    "url": "https://example.com/zrc/api/v1/zaken/6232ba6a-beee-4357-b054-6bde6d1ded6c",
    "zaaktype": "https://example.com/ztc/api/v1/zaaktypen/6232ba6a-beee-4357-b054-6bde6d1ded6c"
    // ...
    "kenmerken": [
        {
            "kenmerk": "test",
            "bron": "http://www.example.com/"
        }
    ]
}

Plaats bij het gebruik van 'allOf' het hergebruikte component als eerste

Bij het gebruik van 'allOf' staat de component die hergebruikt wordt altijd eerst, en staan de toegevoegde properties als tweede.

Voorbeeld van het correct gebruik van 'allOf':

       allOf:
         - $ref: "#/components/schemas/Naam"
         - description: "Gegevens over de naam van de persoon"
           properties:
             aanhef:
               type: "string"  

Voorbeeld van foutief gebruik van 'allOf':

 	NaamPersoon:
 	      allOf:
 	        - description: "Gegevens over de naam van de persoon"
 	          properties:
 	            aanhef:
 	              type: "string"
 	        - $ref: "#/components/schemas/Naam"

Ratio

Afwijken van deze regel leidt tot problemen bij het genereren van code uit de API specificaties.

Onvolkomenheden in aanhaling landelijke lijst met Design Rules en Principles

In deze Kennisbank staan in de aangehaalde landelijke lijst met Design Rules en Principles staan een aantal onvolkomenheden.

  1. Ten eerste worden er in de Principles en Best Practices items genoemd die al bij de Design Rules staan. Dit geldt voor alle genoemde Principles en mogelijk voor alle genoemde Best Practices (omdat de 2e en 3e wellicht gelijk zijn aan resp. de Design Rules API-19 en API-20).
  2. Ten tweede mis ik in de lijst op deze kennisbank diverse Design Rules (API-53 t/m API-57) en Principles (API-11 t/m API-15, API-21 t/m API-47, API-49, API-50 en API-52).
  3. API-09 wordt genoemd maar bestaat volgens mij helemaal niet meer op https://publicatie.centrumvoorstandaarden.nl/api/adr/. Klopt dat?
  4. API-05 heeft in deze kennisbank de titel 'Use plural nouns to indicate resources' in de landelijke API strategie echter 'Use nouns to name resources'. Het gebruik van meervoud in endpoints lijkt dus geen wet van Meden en Perzen meer of zie ik dat verkeerd? Alleen als er sprake is van collections moet je meervoudige namen gebruiken maar dat is een andere Design Rule (API-54). Betekent dit nog wat voor onze API's?
  5. Tenslotte zie ik dat de in deze kennisbank gebruikte titels voor de Design Rules afwijken van de op https://publicatie.centrumvoorstandaarden.nl/api/adr/ gebruikte titels. Was er een reden af te wijken van de naamgeving daar?

Ik wil deze aanpassing met alle plezier even uitvoeren maar wil dan graag op de bovengenoemde punten 3, 4 en 5 een antwoord hebben.

Bij het gebruik van 'allOf' is er slechts 1 component waarnaar gerefereerd wordt

Bij gebruik van allOf is er altijd exact één component waarnaar gerefereerd wordt en één gedefinieerd object met ten minste één property.

Voorbeeld van het foutief gebruik van allOf:

     NaamPersoon:
       allOf:
         - $ref: "#/components/schemas/Naam"
         - $ref: "#/components/schemas/Aanschrijfwijze"
         - description: "Gegevens over de naam van de persoon"
           properties:
             aanhef:
               type: "string"

Er wordt hier uit twee componenten overgeërfd wat niet correct is.

Voorbeeld van het foutief gebruik van allOf:

     NaamPersoon:
       allOf:
         - $ref: "#/components/schemas/Naam"
         - description: "Gegevens over de naam van de persoon"

NaamPersoon heeft geen eigen properties wat niet correct is.

Voorbeeld van correct gebruik van allOf:

     Naam:
      type: "object"
      properties:
        geslachtsnaam:
          type: "string"
          description: |
                        De achternaam van een persoon.
          example: "Vries"
        voorletters:
          type: "string"
          description: |
                        De voorletters van de persoon, afgeleid van de voornamen.
          example: "P.J."

      NaamPersoon:
       allOf:
         - $ref: "#/components/schemas/Naam"
         - description: "Gegevens over de naam van de persoon"
           properties:
             aanhef:
               type: "string"
             aanschrijfwijze:
               type: "string"

Ratio

Afwijken van deze regel leidt tot problemen bij het genereren van code uit de API specificaties.

De API filtert terug te geven gegevens op autorisatie van de organisatie

De API levert alleen gegevens terug waar de vragende organisatie voor geautoriseerd is. Er worden geen aparte endpoints of resources gedefinieerd per autorisatieprofiel.

De bronhouder die gegevens ter beschikking stelt is verantwoordelijk voor het leveren van alleen gegevens waarvoor de juiste autorisatie bestaat. Bij de bronhouder gaat het om autorisatie op niveau van de vragende organisatie (gemeente). De gemeente is er verantwoordelijk voor de resultaten van de API aanvraag te filteren op autorisatie van de betreffende gebruiker.

Ratio

Uitgangspunt in de architectuur is gedelegeerde autorisatie.

Neem geen reden op over het afwezig/leeg zijn van een gegeven

We nemen geen reden op over het leeg/afwezig zijn van een waarde van een gegeven (zoals met StUF:noValue werd gedaan), tenzij duidelijk is dat er bij de gebruikers behoefte is om dit te weten.

Ratio: Deze reden werd opgenomen vanuit synchronisatie-overwegingen. In principe willen we REST/Json API's niet inzetten voor synchronisatie-doeleinden.

Dynamische domeinwaarden worden in de query-parameters met de code opgenomen

Noot: Tekst is aangepast n.a.v. opmerking van RM.

Voor een query-parameter waarin een entry uit een waardelijst of een landelijke tabel als selectie-criterium wordt gebruikt wordt de code van de entry gebruikt.

Ratio: De omschrijving is human readable tekst. Daar kunnen verschillen in staan, bijvoorbeeld hoofd- of kleine letters. Voor een computer een verschil, voor een mens niet. Daarnaast levert het gebruik van codes ook kortere URL's op.

Namen van parameters die geen onderdeel zijn van de op te vragen resource wijken af

Indien een parameter een element betreft dat geen onderdeel van de op te vragen resource is, maar onderdeel van een gerelateerde resource, een subresource of een gegevensgroep, dan wordt de elementnaam voorafgegaan door de betreffende resourcenaam of gegevensgroepnaam en vervolgens twee underscores.

Bijvoorbeeld:

  • ingeschrevenpersoon__burgerservicenummer
  • verblijfplaats__postcode

Vertaling van relaties 0-n of 1-n

Een 0-op-N of 1-op-N relatie tussen objecttypen A en B in een informatiemodel zoals RGBZ of ImZTC kan eenvoudig vertaald worden naar een REST API door een hyperlink op te nemen in de resource dat meerdere keren kan voorkomen in de relatie, B in dit geval. Het veld met de hyperlink in resource B verwijst naar de gerelateerde resource A.

Dit is het principe van ‘linked data’.

Bijvoorbeeld in het RGBZ kan een zaak kan nul of meer statussen hebben en een status kan niet bestaan zonder een zaak, oftewel er is sprake van een 1-op-N relatie tussen Zaak en Status. De resource Status bevat naast zijn eigen gegevens het (toegevoegde) veld "zaak" dat de url naar de resource van de gerelateerde zaak bevat.

Opmerking: We gaan er gemakshalve vanuit dat 0-op-N of 1-op-N relaties geen eigen gegevens hebben. In het RGBZ komt dit in ieder geval niet voor.

Bij namen van relaties (links naar gerelateerde resources) gebruiken we in principe de naam van de betreffende resource als propertynaam voor de link

Wanneer de gerelateerde resource één keer kan voorkomen wordt de resourcenaam omgezet naar enkelvoud. Wanneer de relatie meerdere keren kan voorkomen, wordt de resourcenaam in meervoud gebruikt.

Bij gebruik van de resourcenaam als propertynaam wordt lowerCamelCase toegepast (zie DD1.2).

Wanneer de resourcenaam niet voldoende beschrijvend is voor de betekenis van de relatie ("adres" is "verblijfplaats" van een persoon), of wanneer de resourcenaam niet onderscheidend is (ouders, partners en kinderen zijn allen relaties naar ingeschrevenpersonen) kan gekozen worden om bijvoorbeeld de relatienaam te gebruiken, of bijvoorbeeld de relatienaam gevolgd door de resourcenaam. Zo nodig wordt dit ingekort of aangepast om tot een bondige en duidelijke naam te komen.

Bijvoorbeeld:
woonplaats (resource "woonplaatsen")
openbareRuimte (resource "openbareruimten")
verblijfplaats (resource "adressen")
kinderen (resource "ingeschrevenpersonen" en relatie "heeft kinderen")

Neem voor properties geen waarden op met een speciale betekenis

We nemen geen waarden op met een speciale betekenis die afwijkt van de normale betekenis van het gegeven.

bijvoorbeeld datum "0000-00-00" om aan te geven dat een datum onbekend is
bijvoorbeeld landcode "0000" om aan te geven dat het land onbekend is

Ratio: Als er informatie beschikbaar is moet die als zondanig onderkend en gemodelleerd worden.

Many-to-many relaties verspreid over API’s

Deze beslissing komt voort uit issue #166 en is bijgesteld op basis van user story issue #1677 n.a.v. user story issue #979.

Tussen twee componenten A en B (met component A de component waar in het IM de relatie naar component B loopt), wordt de relatie in beide componenten bewaard. Eventuele metagegevens (extra informatie op de relatie) komt in component A te liggen, en component B bevat enkel de relatieinformatie zelf.

Dit is een technische oplossing zodat beide componenten de relaties kunnen opvragen zonder alle (mogelijks honderden) componenten af te moeten lopen om te vragen of ze een relatie hebben.

Een consumer maakt 1x een relatie aan, tussen component A en B, met metagegevens over de relatie in component A. Component A is vervolgens verantwoordelijk om de relatie in component B aan te maken.

We stemmen de objectTypes af zodat die mappen op de namen van resources om generieke synchronisatieimplementaties mogelijk te maken.

Component A moet aan de volgende spelregels voldoen:

de naam van de resource is {resourceA}{resourceB}, in component A
de resource accepteert een URL-veld met de naam resourceB

Component B moet dan aan de volgende spelregels voldoen:

de naam van de resource is object{resourceB} in component B
de resource accepteert een URL-veld met de naam object
de resource heeft een enumeratie objectType met een waarde voor resourceA

Deze spelregels en interactie worden actief getest in de integratietests om compliancy af te kunnen dwingen.

Een concreet voorbeeld hiervan is een INFORMATIEOBJECT in het DRC en een ZAAK in het ZRC:

De consumer maakt in het ZRC een relatie (met polymorfe relatieinformatie):

     POST https://zrc.nl/api/v1/zaakinformatieobjecten

     {
         "informatieobject": "https://drc.nl/api/v1/enkelvoudigeinformatieobjecten/1234",
         "zaak": "https://zrc.nl/api/v1/zaken/456789",
         "titel": "",
         "beschrijving": "",
         "registratiedatum": "2018-09-19T17:57:08+0200"
     }

Het ZRC doet vervolgens een request naar het DRC (op basis van de URL van object):

     POST https://drc.nl/api/v1/objectinformatieobjecten

     {
         "informatieobject": "https://drc.nl/api/v1/enkelvoudigeinformatieobjecten/1234",
         "object": "https://zrc.nl/api/v1/zaken/456789",
         "objectType": "zaak",
     }

relatienaam gebruiken als de resourcenaam niet beschrijvend genoeg is.

Wanneer de resourcenaam niet voldoende beschrijvend is voor de betekenis van de relatie ("adres" is "verblijfplaats" van een persoon), of wanneer de resourcenaam niet onderscheidend is (ouders, partners en kinderen zijn allen relaties naar ingeschrevenpersonen) kan gekozen worden om bijvoorbeeld de relatienaam te gebruiken, of bijvoorbeeld de relatienaam gevolgd door de resourcenaam. Zo nodig wordt dit ingekort of aangepast om tot een bondige en duidelijke naam te komen. Bijvoorbeeld: woonplaats (resource "woonplaatsen") openbareRuimte (resource "openbareruimten") verblijfplaats (resource "adressen") kinderen (resource "ingeschrevenpersonen" en relatie "heeft kinderen")

Hergebruik API specificaties van een andere bron indien daarvan gegevens zijn opgenomen

Wanneer in een API (comfort)gegevens worden opgenomen die authentiek zijn opgeslagen in een andere bron, worden deze bij voorkeur gemodelleerd op dezelfde manier als in die bron. Wanneer mogelijk wordt hergebruik gemaakt van de API specificatie van die andere bron (via $ref).

Bijvoorbeeld het adres (BAG) van een ingeschreven persoon (BRP) of vestiging (HR). Bijvoorbeeld de naam en geboortedatum (BRP) van een eigenaar in BRK of functionaris in HR.

We nemen geen (inverse) relaties uit een ander domein op

Vanuit andere registraties bestaan er relaties naar ingeschreven natuurlijk personen. Een persoon kan bijvoorbeeld zakelijk gerechtigde zijn van een Kadastraal object of functionaris zijn van een bedrijf. Deze inverse relaties uit een andere bron worden niet opgenomen bij de ingeschreven natuurlijk persoon. Wanneer er functionele behoefte is aan deze gegevens moeten deze bij de betreffende bron (bijvoorbeeld BRK of HR) worden opgevraagd.

Ratio : We bevragen gegevens bij de bron. Een bron kan alleen gegevens leveren die ze zelf heeft.

Alleen HTTP-foutcodes die kunnen voorkomen worden opgenomen in de specificatie

Bij verschillende operaties zijn verschillende foutcodes van toepassing. Zo zal bij het bevragen van een collectie geen 404 optreden als er geen resultaat is, maar wordt er een lege collectie geretourneerd. Dan hoeft de 404 foutcode ook niet gespecificeerd te worden. Als "best practice" worden de volgende foutcodes bij per operatie gedefinieerd.

Bij een get operatie, zonder pad-parameter in de url, die een array als response oplevert:

  • 200 Geslaagd
  • 400 Bad request
  • 401 Unauthorized
  • 403 Forbidden
  • 406 Not Acceptable
  • 412 Precondition Failed (indien er request-headers zijn opgenomen)
  • 415 Unsupported Media Type (Alleen als content-negotiation wordt toegepast)
  • 500 Internal Server Error
  • 503 Service Unavailable
  • default

Bij een get operatie waarbij een pad-parameter in de url is opgenomen:

  • 200 Geslaagd
  • 400 Bad request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 406 Not Acceptable
  • 412 Precondition Failed (indien er request-headers zijn opgenomen)
  • 415 Unsupported Media Type (Alleen als content-negotiation wordt toegepast)
  • 500 Internal Server Error
  • 503 Service Unavailable
  • default

Neem 'tot' of 'totEnMet' op in de naam van een einddatum

Als voor een einddatum geen functioneel duidende naam is (bv. datumOntbindingHuwelijk) neem dan voor einddatums altijd expliciet in de naam de string "tot" of "totEnMet" op.

Ratio : Het is niet eenduidig of een einddatum een "tot"-datum is of een "totEnMet"-datum is. Dat is afhankelijk van de functionele context. Door deze postfix te gebruiken maak je het expliciet.

Toepassing van Haal Centraal Actions (in aangepaste vorm) op VNG-R repo's

Binnen Haal Centraal passen we op de meeste repositories een aantal actions toe:

  • een action voor het valideren van de openapi.yaml, het genereren van de genereervariant daarvan en het valideren van die genereervariant;
  • een action voor het automatisch genereren van sdk's;
  • een action voor het automatisch genereren van postmanscripts.

Het lijkt mij goed om deze actions in principe en mogelijk in aangepaste vorm ook toe te passen op andere repositories van VNG-R waarbinnen een openapi.yaml te vinden is.
In principe omdat er natuurlijk altijd redenen te bedenken zijn waarom je een action juist niet wil toepassen. In aangepaste vorm omdat er binnen Haal Centraal natuurlijk wel een paar ontwerp-keuzes zijn gemaakt die niet voor alle andere repositories gelden waardoor de validatie regels zoals die voor Haal Centraal gelden niet 1 op 1 ook voor de andere repositories gelden.
Overigens kan dat laatste natuurlijk ook tussen de andere repositories onderling spelen.

Voor een goede inrichting van deze validatie regels stel ik de volgende structuur voor:

  • In een nader te bepalen repo brengen we een .spectral.yml onder waarin we, voor zover mogelijk, alle VNG-R Design Rules definiëren. Hierin wordt tevens verwezen naar de algemeen geldende spectral rules. Indien gewenst worden specifieke uit de algemene spectral rules afkomstige regels uitgezet.
  • Binnen elke repository nemen we een .spectral.yml op waarin we verwijzen naar de .spectral.yml in de hierboven bedoelde repo. Eventueel kunnen hierin specifieke VNG-R Design Rules worden uitgeschakeld (en zo gewenst specifieke voor het project gewenste Design Rules worden gedefinieerd).

Meer informatie over de werking van de in dit issue bedoelde actions vind je binnen de daarvoor bedoelde folder in deze kennisbank.

Suggesties voor polymorphisme stuk in tutorial

Reactie op: 156b9c9#diff-0384e80de7a6ad576d2a7c8fd97d8872R48-R54

Polymorphisme

M.b.t. Polymorphisme bestaat er een verschil in aanpak tussen de projecten.

Project Beargumentatie
ZGW Binnen de ZGW API's wordt polymorphisme vermeden wat wordt doorgetrokken naar > het gegevensmodel. De attributen in een superclass worden opgenomen in de subclasses en de superclass wordt in het gegevensmodel verwijderd. De beargumentatie voor deze keuze is dat polymorphisme de complexiteit verhoogt.
HaalCentraal Binnen HaalCentraal is het gebruikt van superclasses een common practice o.a. omdat daarmee de werkelijkheid beter weergegeven kan worden.

Het stuk over polymorphisme vind ik wat vaag en het lijkt incorrect. Ik denk dat het ook geen stukje op zichzelf moet zijn maar als een keuze voor een bepaald probleem, namelijk:

Een supertype (IM-term) of abstracte klasse (dev-term, deze gebruik ik even) kan op verschillende manieren worden vertaald naar een API-specificatie in termen van resources. Neem als voorbeeld Persoon als abstracte klasse en Docent en Student als concrete klasses die overerven van Persoon (het zijn immers beide personen maar gedeelde en specifieke attributen). Je kan nu 3 dingen doen:

  1. Niets doen. De abstracte klasse opnemen als concrete resource. Op basis van het voorbeeld krijg je nu 3 resources: Persoon, Docent en Student. Je kan nooit een Persoon alleen aanmaken, maar als je een Student aanmaakt, moet je ook een Persoon aanmaken (en deze koppelen).
  2. Platslaan. Je neemt de resources Student en Docent op als concrete resources. Deze resources bevatten beide de attributen uit Persoon.
  3. Polymophisme. Je neemt alleen Persoon op als resource, en op basis van een type-attribuut (discriminator met de keuzes student en docent) wordt bepaald wat de specifieke attributen er in deze resource zitten.

(bovenstaande ook als PR ingestuurd)

Gebruik polymorphisme in ZGW

ZGW gebruikt polymorphisme vooral voor de verwijzing naar een externe API/object waarbij het object kan verschillen (bijv. zaakobjecten of betrokkenen). Hierdoor krijg je 3 attributen:

  • objectUrl -- link naar het object in de andere API (leeg indien er geen API beschikbaar is voor dit object)
  • objectType -- type van het object (tevens discriminator)
  • objectIdentificatie -- op basis van objectType is dit een polymorpisch attribuut waar de gegevens in kunnen worden opgenomen.

Verder ben ik van mening dat polymorphisme (optie 3) vooral complexiteit en onvoorspelbaarheid met zich meeneemt. Je verwacht typisch dat een object er altijd hetzelfde uitziet, maar opeens introduceer je variabele attributen. Dat is lastig en daarom gebruiken we het verder niet vaak. Volgens mij HaalCentraal ook niet. Dat staat dan niet goed in je tutorial

Optie 1 ben ik geen fan van omdat je dan 2 calls nodig hebt voor het aanmaken van 1 object. Het is als het ware een samengesteld object.

Optie 2 blijft het eenvoudigst in alle gevallen. Echter, als er (m2m/o2m) relaties zijn met de abstracte klasse en dus per definitie ook met de concrete klasses dan kan dit leiden tot veel extra resources om deze relatie vorm te geven (net zoals in databases).

Vertaling van n-m relaties in een API-specificatie

Een N-op-M relatie tussen objecttypen A en B wordt vertaald als een nieuwe resource R die fungeert als een kruistabel zoals in databases.

De resource R bevat de volgende velden:

  • url naar zichzelf (resource R),
  • url naar resource A,
  • url naar resource B,
  • de gegevens van de relatie zelf (als die er zijn).

Het voordeel om een N-op-M relatie als resource te vertalen is dat je POST en DELETE operaties kunt uitvoeren om relaties toe te voegen of te verwijderen. Als je dat niet doet en je de relaties bijvoorbeeld bijhoudt bij één of beide gerelateerde resources, dan belandt je al snel in een minder handige situatie waarbij je de PATCH (of PUT)-operaties moet gebruiken om het lijstje met hyperlinks naar de gerelateerde resources bij te werken.

Bijvoorbeeld in het RGBZ is de relatie tussen Zaak en Informatieobject N-op-M. Deze relatie is vertaald naar de resource ZaakInformatieObject met de volgende velden:

  • url (hyperlink naar zichzelf),
  • zaak (hyperlink naar de gerelateerde zaak),
  • informatieobject (hyperlink naar het gerelateerde informatieobject).

In geval van de ZGW API’s is de relatie tussen zaken en informatieobjecten gedistribueerd over meerdere ZRC- en DRC-instanties en daarmee een speciaal geval. Zie Many-to-many relaties verspreid over API’s (#64) voor de designkeuzes hierbij.

Toepassen van requestbodies

N.a.v. het toepassen van /components/requestBodies in de Logging-API ben ik er nog even ingedoken omdat ik bang was dat ik het in Open RaadsInformatie fout gedaan had.
Het valt mee. Ik heb het anders gedaan, maar dat is ook een valide werkwijze.

Ik schets even de situatie ter leering ende vermaeck

Keuze in Logging API was de volgende (zeer ingekort voor de leesbaarheid.

paths:
  /verwerkingsacties
    post:
       tags:
         - verwerkingsacties
        summary: Maak een VERWERKINGSACTIE aan.
        operationId: verwerkingsactie_create       
        description: Maak een VERWERKINGSACTIE aan.
       requestBody:
         $ref: '#/components/requestBodies/Verwerkingsactie'

components:
   requestBodies:
     Verwerkingsactie:
       content:
         application/json:
           schema:
             $ref: '#/components/schemas/Verwerkingsactie'
       required: true

  schemas:
    Verwerkingsactie:
       title: Verwerkingsactie
       description: Een verwerkingsactie is een operatie die wordt uitgevoerd door een geautomatiseerd systeem waarbij er (persoons)gegevens verwerkt worden. Een verwerkingsactie wordt uitgevoerd als onderdeel van (een handeling van) een verwerking.
       type: object
       properties:
         url:
           description: URL-referentie naar deze resource. Deze link wordt automatisch aangemaakt door het log.
           type: string
         actieId:
           type: string 

In Open RaadsInformatie heb ik de requestbody op de volgende manier gebruikt.

paths:
  /agendapunten   
   post:
     operationId: PostAgendapunt
     summary: Opslaan agendapunt
     tags: 
      - Agendapunten
     requestBody:
       content:
         application/json:
            schema:
              $ref: '#/components/schemas/Agendapunt'
        required: true 

components:
  schemas:
    Agendapunt:
      type: "object"
      description: |
                    Een onderdeel van de agenda dat als één geheel wordt behandeld. Een AGENDAPUNT kan weer onderverdeeld zijn naar subAGENDAPUNTen.
        agendapuntKenmerk:
          type: "string"
        agendapuntnummer:
          type: "string" 

Misschien moeten we hier ook een check op doen wat dit voor code-generatie betekent. Mijn vermoeden is dat de manier waarop jij dit hebt opgelost vriendelijker is voor code-generatoren dan hoe ik het heb opgelost.

Naamgeving pull request

De naam van een pull request moet voldoen aan de volgende conventie:

fixit [nummer]

nummer is daarbij het nummer van het issue waar het pull request invulling aan geeft.

Gebruik geen oneOf of anyOf constructies voor polymorfe gegevens

We gebruiken geen oneOf of anyOf constructies voor polymorfe gegevens.

Alhoewel de oneOf constructie een valide OAS3 constructie is levert deze bij het genereren van code problemen op. Deze constructie wordt ontweken door twee mogelijke alternatieven.

De subtypes worden samengevoegd tot 1 object. Deze keuze is logisch als de subtypes grotendeels overlappen en er geen strijdigheid is tussen de verschillende mogelijke types.
De subtypes worden volledig opgenomen als property van een object, met daarin de eigenschappen van dat type. In het response is altijd maar 1 van deze properties gevuld. Deze keuze is logisch als de subtypes weing gemeenschappelijke properties hebben.
In beide gevallen nemen we ook een type property (discriminator) op waaruit de gebruiker kan opmaken welk type het betreft.

Ratio: Diverse code-generatoren gaan dus niet goed om met deze constructie en genereren foutieve code.

Kanttekening : Mochten code-generatoren in de toekomst wel goed met deze constructie om kunnen gaan dan is het het overwegen waard om deze constructie aan te passen bij de eerstvolgende major (breaking) change.

Correcties in github_tutorial.md

In github_tutorial.md wordt in de uitleg van de term branch de opmerking

In Git worden branches dus op een andere manier gebruikt dan in SVN

geplaatst. Dit klopt echter niet. Ook in SVN kan het de bedoeling zijn een branch weer te mergen met de trunk. Dat we dit niet doen i.h.k.v. de Nieuwe aanpak heeft zo zijn reden.

Ook wil ik een aantal andere kleine tekstuele correcties aanbrengen.

Gebruik de 'allOf' constructie indien er meerdere componenten zijn met dezelfde properties

Wanneer er meerdere componenten zijn met meerdere dezelfde properties, moet er hergebruik worden gemaakt via een 'allOf' constructie. Dit sluit aan op object oriëntatie in de verschillende programmeeromgevingen.

Bijvoorbeeld een woonadres en een postadres zijn identiek, behalve dat postadres ook een postbusnummer kent. Dan is postadres een extensie op woonadres.
Bijvoorbeeld een natuurlijk persoon en een niet-natuurlijk persoon hebben beide een naam en een adres, maar beide hebben ook eigen gegevens (natuurlijk persoon heeft geboortedatum, niet-natuurlijk persoon heeft eigenaar), dan zijn beide een extensie op een bovenliggende component "Persoon".
Bijvoorbeeld bij een zakelijkGerechtigde worden alleen minimale identificerende gegevens van een persoon opgenomen (alleen naam en identificatie), maar bij de persoon (resource) worden meer eigenschappen van de persoon opgenomen (zoals adres). Dan gebruikt zakelijkGerechtigde het component "persoonBeperkt" en is de uitgebreide persoon een extensie hierop.

Gebruik bij bevraging-API's zo mogelijk een string i.p.v. een enumeration

Noot: Onderstaande tekst aangepast n.a.v. opmerking RM :

Wanneer een gegeven in het informatiemodel gedefinieerd is als een enumeratie, maar de enumeratiewaarde wordt door gebruikers van de API alleen gebruikt om als tekst te tonen (bevraging-endpoints) aan eindgebruikers (mensen), definieer het gegeven dan als string (niet als enumeratie) in de API.

Wanneer de enumeratiewaarde gebruikt wordt in code (algoritmes), definieer dan een betekenisvolle maar bondige code.

Ratio : Het opnemen van een enumeratie in de API-definitie betekent dat voor het kunnen tonen van een nieuwe of gewijzigde waarde in de enumeratie een nieuwe versie van de API moet worden uitgebracht. Voor bevraging-API's is dat een ongewenste vorm van tight coupling
.
Als het gegeven echter wordt gebruikt in code (algoritme) dan is het van belang dat er niet afgeweken kan worden van de bekende waarden en is een definitie als enumeratie op zijn plaats.

Neem een indicator op als het gevuld zijn van een datum functionele betekenis heeft

Wanneer het gevuld zijn van een datum functionele betekenis heeft, ook wanneer deze volledig onbekend is, wordt een indicator opgenomen om dit aan te geven.

Indien in het onderstaande voorbeeld er helemaal niets bekend is van de overlijdensdatum, maar er is wel bekend dat de persoon overleden is, dan kan dat nergens uit worden afgeleid. Uit de reponse kan je niet opmaken of de overlijdensdatum leeg is omdat iemand niet is overleden of omdat de overlijdensdatum niet bekend is.

    Persoon:
      type: object
      description: "Natuurlijkpersoon of NietNatuurlijkPersoon"
      properties:
        identificatie:
          type: string
        datumOverleden:  
          $ref: "#/components/schemas/TypePersoonEnum"type: string
        voorletters: 
          type: string

Door voor een dergelijke situatie een boolean op te nemen waar de betreffende informatie wordt gerepresenteerd wordt duidelijk wat de situatie is zonder dat er invalide waardes hoeven te worden opgenomen in de datum-velden.

    Persoon:
      type: object
      description: "Natuurlijkpersoon of NietNatuurlijkPersoon"
      properties:
        identificatie:
          type: string
        datumOverleden:  
          $ref: "#/components/schemas/TypePersoonEnum"type: string
        indicatieOverleden:
          type: boolean
        voorletters: 
          type: string

Ratio : Bijvoorbeeld of een persoon overleden is kan niet worden afgeleid uit het bestaan van een overlijdensdatum wanneer die datum onbekend is. Daarvoor kan een boolean "indicatieOverleden" worden gedefinieerd.

Vertaling van n-m relaties verspreid over meer dan een API-specificatie

Als er tussen twee componenten (A en B) in 2 verschillende API-specificaties een relatie kan bestaan (waarbij component A het component is van waaruit in het IM de relatie naar component B loopt), wordt het vastleggen van die relatie in beide componenten mogelijk gemaakt. Eventuele metagegevens (extra informatie op de relatie) worden daarbij bij component A gespecificeerd terwijl bij component B enkel de informatie over de relatie zelf wordt gespecificeerd.

Ratio: Met deze technische oplossing wordt voorkomen dat voor het achterhalen van de relaties die een component heeft alle (mogelijk honderden) componenten in een andere API moeten worden afgelopen.

Een consumer maakt 1x een relatie aan, tussen component A en B, met de metagegevens over de relatie in component A. component A is vervolgens verantwoordelijk om de relatie in component B, en dus in een andere API, aan te maken.

We stemmen de objectTypes af zodat die mappen op de namen van resources om generieke synchronisatieimplementaties mogelijk te maken.

Component A moet aan de volgende spelregels voldoen:

  • de naam van de resource is {resourceA}{resourceB}, in component A
  • de resource accepteert een URL-veld met de naam resourceB

Component B moet dan aan de volgende spelregels voldoen:

  • de naam van de resource is object{resourceB} in component B
  • de resource accepteert een URL-veld met de naam object
  • de resource heeft een enumeratie objectType met een waarde voor resourceA

Deze spelregels en interactie worden actief getest in de integratietests om compliancy af te kunnen dwingen.

Een concreet voorbeeld hiervan is een INFORMATIEOBJECT in het DRC en een ZAAK in het ZRC:

De consumer maakt in het ZRC een relatie (met polymorfe relatieinformatie):

 POST https://zrc.nl/api/v1/zaakinformatieobjecten

 {
     "informatieobject": "https://drc.nl/api/v1/enkelvoudigeinformatieobjecten/1234",
     "zaak": "https://zrc.nl/api/v1/zaken/456789",
     "titel": "",
     "beschrijving": "",
     "registratiedatum": "2018-09-19T17:57:08+0200"
 }

Het ZRC doet vervolgens een request naar het DRC (op basis van de URL van object):

 POST https://drc.nl/api/v1/objectinformatieobjecten

 {
     "informatieobject": "https://drc.nl/api/v1/enkelvoudigeinformatieobjecten/1234",
     "object": "https://zrc.nl/api/v1/zaken/456789",
     "objectType": "zaak",
 }

Naamgeving van de API velden binnen een resource

Prefixes die slaan op de eigen resource, worden niet gebruikt. Voorbeeld: Attribuutsoort informatieobjecttype.informatieobjecttype-omschrijving in RGBZ2 krijgt de naam “omschrijving” in de API, aangezien het een veld is binnen de resource informatieobjecttype. Dit komt overeen met de in RGBZ genoemde XML-tag.

{
"url": "https://example.com/drc/api/v1/informatieobjecttypen/8534ba6a-bcde-4387-b054-6bde6d1ded8f",
"omschrijving": "Document"
// ...
}

Ratio:

Voor developers zijn kortere namen binnen een object prettiger en overzichtelijker.

De description van een property moet semantisch overeenkomen met de betekenis van het gegeven in een gegevenswoordenboek

We nemen bij een property een description op die semantisch overeenkomt met de beschrijving in het gegevenswoordenboek. Deze kan ingekort, vereenvoudigd, of uitgebreid zijn, maar mag de betekenis van het gegeven niet laten afwijken van de betekenis van het corresponderende gegeven in het gegevenswoordenboek.

De description kan worden weggelaten wanneer evident is dat de gebruikers van de API uit de propertynaam weten wat bedoeld wordt (bijvoorbeeld huisnummer).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.