Skip to content

Øvelse 25 – Grundlæggende øvelse med domæneprimitiver

Source code

Fornavn

Fornavn.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace domæneprimitiver.primitives
{
    public class Fornavn
    {
        private string fornavn;

        public Fornavn(string fornavn)
        {
            IsFornavnValid(fornavn);
            this.fornavn = fornavn;
        }

        public string GetValue()
        {
            return this.fornavn;
        }

        private void IsFornavnValid(string fornavn)
        {
            if (string.IsNullOrEmpty(fornavn)) throw new ArgumentNullException(nameof(fornavn), $"{nameof(fornavn)} må ikke være null eller tomt.");

            if (fornavn.Length > 20 || fornavn.Length < 2) throw new ArgumentException($"{nameof(fornavn)} skal være mellem 2 og 20 karaktere langt.", nameof(fornavn));

            Regex nameRegex = new Regex(@"^[A-Za-zÆØÅæøå]+$");

            if (!nameRegex.IsMatch(fornavn)) throw new ArgumentException($"{nameof(fornavn)} må kun bestå af store og små bogstaver mellem a og å.", nameof(fornavn));
        }
    }
}

Efternavn

Efternavn.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace domæneprimitiver.primitives
{
    public class Efternavn
    {
        private string efternavn;

        public Efternavn(string efternavn)
        {
            IsEfternavnValid(efternavn);
            this.efternavn = efternavn;
        }

        public string GetValue()
        {
            return this.efternavn;
        }

        private void IsEfternavnValid(string efternavn)
        {
            if (string.IsNullOrEmpty(efternavn)) throw new ArgumentNullException(nameof(efternavn), $"{nameof(efternavn)} må ikke være null eller tomt.");

            if (efternavn.Length > 20 || efternavn.Length < 2) throw new ArgumentException($"{nameof(efternavn)} skal være mellem 2 og 20 karaktere langt.", nameof(efternavn));

            Regex nameRegex = new Regex(@"^[A-Za-zÆØÅæøå]+$");

            if (!nameRegex.IsMatch(efternavn)) throw new ArgumentException($"{nameof(efternavn)} må kun bestå af store og små bogstaver mellem a og å.", nameof(efternavn));
        }
    }
}

Alder

Alder.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace domæneprimitiver.primitives
{
    public class Alder
    {
        private int alder;

        public Alder(int alder)
        {
            IsAlderValid(alder);
            this.alder = alder;
        }

        public int getValue()
        {
            return alder;
        }

        private void IsAlderValid(int alder)
        {
            if (alder < 18 || alder > 99) throw new ArgumentException($"{nameof(alder)} skal være mellem 18 og 99.");
        }
    }
}

CPR-nummer

CprNummer.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace domæneprimitiver.primitives
{
    public class CprNummer
    {
        private string? cprNummer;

        public CprNummer(string cprNummer, Køn køn)
        {
            IsCprValid(cprNummer, køn);
            this.cprNummer = cprNummer;
        }

        public string GetValue()
        {
            // Read once check
            if (string.IsNullOrWhiteSpace(cprNummer)) throw new InvalidOperationException($"Cpr-nummer kan kun læses en gang og er allerede blevet læst.");

            // Kopi til returnering første gang
            string midlertidigtCpr = this.cprNummer;

            // Slet værdi
            this.cprNummer = null;

            // Returner kopi
            return midlertidigtCpr;
        }

        private void IsCprValid(string cprNummer, Køn køn)
        {
            if (string.IsNullOrEmpty(cprNummer)) throw new ArgumentNullException($"{nameof(cprNummer)} må ikke være null.");

            if (cprNummer.Length < 11 || cprNummer.Length > 11) throw new ArgumentException($"{nameof(cprNummer)} skal have en længde på 11 (med bindestreg).");

            Regex regex = new Regex("^\\d{6}-\\d{4}$");

            if (!regex.IsMatch(cprNummer)) throw new ArgumentException($"{nameof(cprNummer)} er i et ugyldigt format.");

            // Check om cpr-nummer og køn stemmer overens
            int sidsteCiffer = Convert.ToInt32(cprNummer.Last());
            bool erLige = sidsteCiffer % 2 == 0;

            if (køn == Køn.Kvinde && !erLige) throw new ArgumentException("Cpr-nummer matcher ikke det givne køn.");

            if (køn == Køn.Mand && erLige) throw new ArgumentException("Cpr-nummer matcher ikke det givne køn.");
        }
    }
}

Køn

Køn.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace domæneprimitiver.primitives
{
    public enum Køn
    {
        Mand,
        Kvinde
    }
}

Person

Person.cs
using domæneprimitiver.primitives;
using System;
using System.Collections.Generic;
using System.Text;

namespace domæneprimitiver.entities
{
    public class Person
    {
        private Fornavn fornavn;
        private Efternavn efternavn;
        private Alder alder;
        private CprNummer cprNummer;

        public Person(Fornavn fornavn, Efternavn efternavn, Alder alder, CprNummer cprNummer)
        {
            if (fornavn == null) throw new ArgumentNullException("Fornavn må ikke være null.");
            if (efternavn == null) throw new ArgumentNullException("Efternavn må ikke være null.");
            if (alder == null) throw new ArgumentNullException("Alder må ikke være null.");
            if (cprNummer == null) throw new ArgumentNullException("CprNummer må ikke være null.");

            this.fornavn = fornavn;
            this.efternavn = efternavn;
            this.alder = alder;
            this.cprNummer = cprNummer;
        }

        public string GetFornavn()
        {
            return this.fornavn.GetValue();
        }

        public string GetEfternavn()
        {
            return this.efternavn.GetValue();
        }

        public int GetAlder()
        {
            return this.alder.getValue();
        }

        public string GetCprNummer()
        {
            return this.cprNummer.GetValue();
        }
    }
}

Program

Program.cs
using domæneprimitiver.entities;
using domæneprimitiver.primitives;

Person person = new Person(new Fornavn("Egon"), new Efternavn("Olsen"), new Alder(99), new CprNummer("120427-2303", Køn.Mand));

Console.WriteLine("Fornavn: " + person.GetFornavn());
Console.WriteLine("Efternavn: " + person.GetEfternavn());
Console.WriteLine("Alder: " + person.GetAlder());
Console.WriteLine("Cpr-nummer: " + person.GetCprNummer());

// Check af read once
Console.WriteLine("Cpr-nummer igen: " + person.GetCprNummer());

Reflekterende spørgsmål

  1. Hvordan kan brugen af domæneprimitiver bidrage til at sikre bedre softwarekvalitet og robusthed?

Det sikrer at hver gang et domøneprimitiv oprettes, så bliver værdien valideret. På den måde kan man sikre validering og på den måde kvalitet og robusthed i hele systemet.

  1. Hvorfor er immutability (at objekter ikke kan ændres efter oprettelse) vigtig i forhold til software-sikkerhed?

Det er vigitgt da man så altid kan stole på den validerede værdi, da den netop ikke kan ændres. For at ændre den skal et nyt objekt oprettes og derfor tvinges validering igen.

  1. Hvilke typer fejl og sikkerhedsproblemer kan forebygges ved at håndhæve invarians i domæneprimitiver?

F.eks. fejl hvor man glemmer at validere sine værdier, her især i domæne objekter hvor man i forvejen har andet kompleks kode, hvor man så evt. samtidigt ville skulle huske at validere alle properties.

  1. Hvordan kan man balancere brugen af domæneprimitiver for at sikre robusthed uden at gøre koden unødvendigt kompleks eller svær at vedligeholde?

Man skal kun oprette domæneprimitiver for de værdier der har forretningsregler. Hvis domænet tillader at en værdi kan være alle tænkelige muligheder, så behøves et domæneprimitiv med invarians ikke.

For at gøre det letter kan et domæneprimitv muligvis også oprettes på følgende måde i c# (Dog skal man være opmærksom på at struct ikke kan være null og istedet vil tildele en default værdi, tjek derfor for default værdi i domæne model istedet for null):

Efternavn.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace domæneprimitiver.primitives
{
    // record: Indbygget uforanderlighed (Immutability), Værdi-baseret sammenligning (==), Data i ToString()
    // struct: Er en værditype fremfor klasse. Gemmes i hukommelses stacken som er hurtigere end heapen (klasser). Ændringer i kopi ændres ikke i originalen.
    public readonly record struct Efternavn
    {
        // get uden set: skrivebeskyttelse, værdi kan kun tildeles i constructor 
        public string Value { get; }

        public Efternavn(string efternavn)
        {
            // Validering
            if (string.IsNullOrWhiteSpace(efternavn) || efternavn.Length < 2 || efternavn.Length > 20)
                throw new ArgumentException("Efternavn skal være mellem 2 og 20 tegn.");

            Regex nameRegex = new Regex(@"^[A-Za-zÆØÅæøå]+$");

            if (!nameRegex.IsMatch(efternavn)) throw new ArgumentException($"{nameof(efternavn)} må kun bestå af store og små bogstaver mellem a og å.", nameof(efternavn));

            Value = efternavn;
        }
    }
}

Kobling til standarder

Element i øvelsen Relateret standard / princip Kommentar
Inputvalidering ISO/IEC 27002:2022, afsnit 8.26 (applikationssikkerhed) Anbefaler validering og integritetstjek af input til en applikation.
OWASP ASVS 5.0.0, kontrol 16.5 (Error Handling) Krav om korrekt håndtering af fejl(undtagelser)
Secure by Design (Johnsson et al.) Fremhæver princippet om at skabe robuste domænemodeller og sikre objekters tilstand gennem konstruktion. Herunder domæne primitiver.