Realiseert Software

B1-K1-W1

Eisen/Wensen

Het project is de OEAPI, een API structuur gebaseerd op REST die gebruikt wordt door instituten om op een universele manier informatie naar buiten te brengen.

De OU wilt dit implimenteren zodat sites zoals Studiekeuze123 actuele informatie van de OU op kan halen, zoals faculteiten en producten.

Eisen:

  • Moet beveiligd zijn.
    • Wordt beveiligd met JWT.
  • De teruggave bij fouten moeten kloppen volgens OEAPI.
    • Dit moet met het JSON schema.
    • De 200 requests moeten ook kloppen, maar dit verschilt per endpoint
  • Alle mapping moet gedocumenteerd worden in DevOps.
    • Dit wordt gedaan met tables, waar informatie uit de database naast de informatie uit de OEAPI wordt gezet met extra informatie als het nodig is.
  • Werkzaamheden moeten gedocumenteerd worden.
  • Alleen de endpoints die afgesproken zijn in de API zetten.
    • /organisations
    • /programmes
    • /courses
    • /course-offerings
    • /programme-offerings
  • Returns moeten met expand groter worden.

Planning

Bewaken voortgang

  • Voortgang wordt elke woensdag besproken

B1-K1-W2

Ontwerp

Beveiliging

Voor beveiliging worden JWT tokens gebruikt. Deze worden samen gebruikt met een secret key uit Azure. Deze secret wordt gebruikt om een nieuwe token op te kunnen halen, en het bepaald ook wat ze kunnen doen met de API. Bijvoorbeeld een Read token kan alleen maar dingen lezen, maar een ReadWrite kan lezen en schrijven.

Hiervoor wordt gebruik gemaakt van middleware. Dit is een tussenstukje wat altijd gebeurt voordat de call geaccepteerd wordt. Hier worden dingen zoals de token gecheckt voordat het bij de echte function komt.

Melding teruggave

Om te zorgen dat de OU aan de eisen van de OEAPI voldoet, moeten ook de goede foutmeldingen teruggegeven worden. Deze zijn in het volgende structuur:

{
    "type": string <uri>,
    "title": string,
    "status": integer <int32>,
    "detail": string or null,
    "instance": string or null <uri>
}

De keys werken als volgt:

  • Type | Een link naar de documentatie over deze melding.
  • Title | De titel, zoals “Resource not found”.
  • Status | De HTTP status code, zoals 404.
  • Detail | Meer details over de status.
  • Instance | Waar de melding gebeurde.

Mapping

Mapping wordt gedaan in DevOps (voor nu, wordt misschien verandert naar Confluence).

Niet alle informatie kan ingevuld worden, maar de required informatie (naast de GUID, deze staat nog niet in de database) kan wel ingevuld worden. Voor mapping zelf wordt AutoMapper gebruikt.

Benodigde endpoints

Niet alle informatie hoeft naar buiten, dus ook niet alle endpoints zullen gebruikt worden. De endpoints die wel gebruikt worden zijn als volgt:

  • /organisations | Staat bekend als Faculty in de database.
    • Hierbij is organisationType dus altijd faculty.
  • /programmes | Alle producten met een PRODUCT_ID van 4 (opleiding).
    • De type is dus ook gewoon programme
  • /courses | Alle producten met een PRODUCT_ID van 0 (standaard product).
  • /course-offerings | T.B.A (moet nog besproken worden)
  • /programme-offerings | T.B.A (moet nog besproken worden)

Schematechnieken

---
config:
    theme: 'dark'
---
sequenceDiagram
    actor User
    participant Backend@{"type": "boundary"}
    participant PHOUNIX@{"type" : "database"}

    Note over Backend,User: OEAPI architecture

    critical Connect to DB
        activate PHOUNIX
        Backend->>PHOUNIX: Query data
    option Success
        PHOUNIX-->>Backend: Return data
        deactivate PHOUNIX
    option Failure
        Backend-->>User: 500
    end

    critical Authorize
        User->>Backend: JWT Token
        Backend->>Backend: Check JWT
    option Valid
        Backend-->>User: Response
    option Token invalid or null
        Backend-->>User: 401
    option Wrong role
        Backend-->>User: 403
    end

Onderbouwing

In het diagram hierboven zijn een aantal dingen te zien:

De user maakt een request via OEAPI. Het stuurt in de request Authorization header een JWT token mee. De backend zal deze bekijken. Als de token klopt en de rol van de token mag bij die endpoint, dan wordt alle benodigde informatie terug gestuurd.

Ook kan een specifieke versie van OEAPI of de consumer terug gevraagd worden. Dit gaat in de Content-Type header (GitHub).

Om de informatie op te kunnen halen, wordt er een query gestuurd naar de oracle database. Deze geeft de informatie terug op basis van de query message.

B1-K1-W3

Gerealiseerde functionaliteit

Binnen de periode van ~6 maanden zijn alle belangrijkste onderdelen en functies af binnnen de geplande tijd.

Eerst was ik gestart met de opzet van de OEAPI zelf. Dit was gedaan met een Dummy project die dan op de correcte manier vernoemt moet woorden naar OEAPI. Met dit dummy project komen pipelines, waarmee je de functions automatisch in Azure kunt zetten. Dit was ook mooi binnen de tijd gedaan, kostte maar 1 dag.

Daarna komt de API zelf. We waren eerst gestart met OEAPI versie 5. Ik had op dit moment nog geen connectie aan de databases die we gingen gebruiken, maar ik heb even rond gespeeld met de mapping om er een gevoel voor te krijgen.

Na een paar dagen waren we begonnen met het echte mappen, en om te bespreken welke database waar het beste zou passen. Ik heb hierbij een generiek object gemaakt om een connectie te maken aan elke database die we gingen gebruiken. Later (als mijn stage al is afgerond) wordt dit ook omgezet naar een centrale database, maar deze is op dit moment nog niet af.

Tijdens deze fase werd ook nog besproken of we nou versie 5 of versie 6 gingen gebruiken. versie 6 was op dit moment namelijk nog in beta, en we wisten niet zeker of het slim was om meteen naar een beta versie te gaan. Toch hebben we besloten om naar versie 6 te gaan, en dit bleek een goede keuze te zijn.

Ik ben in de volgende paar maanden bezig geweest met de echte OOAPI. Ik heb binnen de afgesproken tijden altijd alles afgekregen. Je kunt met de API de programmes, courses en organisation ophalen (ook de objecten die nested zijn). Ook is dit allemaal beveiligd met een JWT token en een secret die aangemaakt kan worden in Azure.

Kwaliteit opgeleverde functionaliteiten

De functionaliteiten voldoen aan de eisen die gesteld werden.

Beveiliging

Beveiliging is gedaan door middel van JWT tokens en secret keys.

Token

De teruggave klopt

De teruggave voor elk endpoint klopt. Bijvoorbeeld een 401 omdat de pagesize niet klopte:

{
    "type": "https://api.example.org/problems/invalid-parameter",
    "title": "Invalid request parameters",
    "status": 400,
    "detail": "The query parameter 'pageSize' must be one of: 10, 20, 50, 100, 250.",
    "instance": "http://localhost:7033/api/courses?pageSize=11&pageNumber=1&expand=organisation"
}

Of een 200 als je de courses ophaalt (zonder expand parameter, maar 1 object voor leesbaarheid.):

{
"pageSize": 10,
"pageNumber": 1,
"hasPreviousPage": false,
"hasNextPage": true,
"totalPages": 338,
"items": [
    {
        "courseId": "ed53c788-73de-4154-8826-5af7060ae90b",
        "primaryCode": {
            "codeType": "product_id",
            "code": "GM0502"
        },
        "name": [
            {
                "language": "nl",
                "value": " Kwaliteit, beleid en governance in de gezondheidszorg"
            }
        ],
        "description": [
            {
                "language": "nl",
                "value": " Kwaliteit, beleid en governance in de gezondheidszorg"
            }
        ],
        "studyLoad": {
            "studyLoadUnit": "ects",
            "value": null
        },
        "duration": null,
        "level": "0",
        "organisationId": "081c074a-ad6b-4eb7-8166-3205e9717934",
        "validFrom": "2022-05-16T00:00:00",
        "validTo": "0001-01-01T00:00:00"
    },
]

En dan ook met de expand:

{
"pageSize": 10,
"pageNumber": 1,
"hasPreviousPage": false,
"hasNextPage": true,
"totalPages": 338,
"items": [
    {
        "courseId": "ed53c788-73de-4154-8826-5af7060ae90b",
        "primaryCode": {
            "codeType": "product_id",
            "code": "GM0502"
        },
        "name": [
            {
                "language": "nl",
                "value": " Kwaliteit, beleid en governance in de gezondheidszorg"
            }
        ],
        "description": [
            {
                "language": "nl",
                "value": " Kwaliteit, beleid en governance in de gezondheidszorg"
            }
        ],
        "studyLoad": {
            "studyLoadUnit": "ects",
            "value": null
        },
        "duration": null,
        "level": "0",
        "organisation": {
            "organisationId": "081c074a-ad6b-4eb7-8166-3205e9717934",
            "primaryCode": {
                "codeType": "organisation_id",
                "code": "19"
            },
            "organisationType": "faculty",
            "name": [
                {
                    "language": "nl",
                    "value": "Psychologie"
                }
            ],
            "shortName": "Psychologie"
        },
        "organisationId": "081c074a-ad6b-4eb7-8166-3205e9717934",
        "validFrom": "2022-05-16T00:00:00",
        "validTo": "0001-01-01T00:00:00"
    },
]

Mapping gedocumenteerd

Course

Endpoints

De correcte endpoints (en alleen de endpoints die we gaan gebruiken) staan er in, behalve de offering objecten omdat deze nog gemapt moeten worden.

Endpoints

Kwaliteit code

De code bestaat grotendeels uit generieke objecten. Dit betekent dat er weinig of geen dubbele code is. Producten is hierbij een goed voorbeeld. Omdat programmes en courses eigenlijk hetzelfde zijn in de database (allebij producten met een andere type), heb ik een generiek object voor deze gemaakt, namelijk ProductService.

Normaal gesproken zou een endpoint er ongeveer zo uitzien (simpel gemaakt voor leesbaarheid):

// We zetten de parameters neer
[OpenApiParameter(...)]

// We laten zien wat de response is
[OpenApiResponseWithBody(...)]

[Function("GetOrganisations")]
[Authorize("Reader")] // Deze behoort niet tot OpenApi, maar is een eigen object voor authorization.
public async Task<HttpResponseData> GetOrganisations(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route="organisations")]
    HttpRequestData req)
{
    // Hier pakken we de parameters en maken wij het reponse object aan.
}

Het bovenstaande is voor organisations. Bij programmes en courses is het anders. Als ze beide toch bijna hetzelfde doen, is het beter om alles via een generiek object te doen:

// Hier hetzelfde als bij organisations

// En in de function:
{
    response = await _productService.GetProducts<ProgrammeDto>(type=4, query=query)
}

Hier hebben we 1 object, _productService die alle informatie ophaalt en mapt. Bij courses gaat dit bijna precies hetzelfde, maar dan met CourseDto en type=0. Hierdoor voorkomen we dubbele code, maar voldoen we toch aan de eisen van de OEAPI.

Generieke objecten komen erg vaak voor in de code. Ook wordt dit gebruikt voor het database object, en het object om enums te checken.

Ook wordt er veel aan beveiliging gedaan, en DTO's zijn een goed voorbeeld. Als je niet alle informatie naar buiten wilt brengen, wordt een Data Transfer Object gebruikt, een DTO. We zetten in deze DTO alleen de informatie die nodig is, zodat niet alles doorgegeven hoeft te worden.

Versiebeheer is effectief toegepast.

Omdat dit een eenmans project is, wordt versiebeheer minder gebruikt dan gewoonlijk, maar het wordt alsnog op een goede manier toegepast.

Elke dag (of elke opdracht) wordt alles gecommit en wordt het gepusht naar de remote. Bij een commit moet altijd een goede beschrijving staan over wat er gedaan is in een korte omschrijving. Dit zorgt ervoor dat er een mooie geschiedenis komt van de commits, en als er iets mis gaat weet je naar welke commit je terug moet gaan.

Bij het algemene project wordt maar 1 branch gebruikt, de develop branch. Hierop wordt alles gerealiseerd. Maar soms is het ook nodig om verschillende dingen te testen over hoe en wat. Een voorbeeld hiervan is het database object. Dit object en het project zelf moesten door elkaar heen gemaakt worden, dus we hebben hiervoor een nieuwe branch gemaakt. Hetzelfde met de JWT tokens. Het was nog niet helemaal zeker hoe en wat, dus heb ik een nieuwe branch gemaakt zodat dit nagekeken kon worden zonder dat het in de weg zat van het echte project.