Den gode DFDG filtrering

Introduktion

For at få en nem og ensartet filtrering af domainobjekter svarende funktionelt til PersonStatusService filtreringen, er der nu lavet en generisk implementation af filtrering. Denne generiske løsning bygger på den filtreringsopsætning der er lavet i tabellen UnemploymentFundAccesFilter.

Nedenfor er beskrevet hvad der skal til for at lægge filtrering på en domain model.

Overblik over filtrerede services og domæner

PersonStatusService forretningssiden er (indtil servicen er fuldt udfaset) samlingspunkt for viden om filtrering. Services med filtrering tagges med "a-kasse_filtrering" hvorved de automatisk listes i samlesiden.

Snitfladestruktur ved filtrering

Hvis en aftager ikke har lov til at kende til et info-objekts tilstedeværelse (fx rehab i statusservice) bør selve info-objektet være NULL når den pågældende aftager kalder. 

Filterable

Filtreringen bruger dels en base klasse Filterable, der implementerer selve filtreringen, og dels en context klasse der indeholder filterreglerne fra databasen og borgerens tilstand (kontaktgruppe, tilmeldestatus, a-kasse medlemskaber, ...). Disse er vist nedenfor

Filterable implementere interfacet IFilterable. Den laver en default implementation af properties BusinessFilters, FilterStartDate og FilterEndDate, samt de to operationer ShouldKeepItem og Trim. BusinessFilters er en liste af uafhængige parameter som et domainobjekt kan filtreres på. For at et item ikke skal filtreres bort, skal alle parameter være opfyldt. 

De to metoder ShouldKeepItem og Trim her en default implementation i Filterable som beskrevet her:

ShouldKeepItem: Løber listen af BusinessFilters igennem og checker op mod reglerne, samt checker at itemet ligger indenfor det vindue som a-kassen må se ift borgeren medlemskab.

Trim: Tom implementation i Filterable. Kan bruges til at lave en trimning af et objekt hvor en a-kasse fx. må se objektet, men dele af det skal nulles.

I eksemplet ovenfor nedarver to domain objekter IntegrationContract og ActivityShortModel begge fra Filterable. Integrationskontrakter skal altid filtreres bort for a-kasser, hvorfor IntegrationContract laver en override af ShouldKeepItem. I denne override spørger man blot på om det er en a-kasse der kalder uden at læse reglerne fra databasen. I ActivityShortModel har vi brug for tre forskellige parameter som alle bliver filtreret på ift. reglerne i databasen.  

Reglerne for de forskellige filtre og borgeren tilstand er gemt i et Context objekt som vist nedenfor:

Filtering er kaldt via en FilterEngine, som selv new'er en FilterContext op. Denne er dannet een gang og herefter cachet for de efterfølgende kald af filter. I denne context kan filterfunktionerne slå regler op, og se borgerens medlemskaber af a-kasser, kontaktgrupper, mm.

Enable filtrering på domainobjektet

Først skal domainobjektet ændres til at nedarve fra klassen "Filterable", og der skal implementeres tre properties som er abstrakte i Filterable. Her er vist hvordan domainobjektet ActivityShortModel ser ud med filtrering.


namespace KC.AMS.Model.Domain.Plan.Model
{
    [Serializable]
    public class ActivityShortModel : Filterable
    {
        public Guid ActivityIdentifier { get; set; }
        public ActivityTypeIdentifierEnum ActivityTypeIdentifier { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public ActivityStatusTypeIdentifierEnum ActivityStatusTypeEnum { get; set; }
        public ResponsibleAuthorityModel ResponsibleAuthority { get; set; }
        public CourseTypeIdentifierEnum? CourseTypeIdentifier { get; set; }
        public JobOrderTypeIdentifierEnum? JobOrderTypeIdentifier { get; set; }
        // Filter properties
        protected override List<BusinessFilter> BusinessFilters => new List<BusinessFilter>
        {
            new BusinessFilter("Activity", (int)ActivityTypeIdentifier),
            new BusinessFilter("SubsidyJob", (int?)JobOrderTypeIdentifier ?? -1),
            new BusinessFilter("CourseActivity", (int?)CourseTypeIdentifier ?? -1)
        };
        protected override DateTime FilterStartDate => StartDate;
        protected override DateTime FilterEndDate => EndDate;
    }
}

De tre filter properties er:

PropertyBeskrivelseEksempel
BusinessFiltersHer udpeges et antal forretningsområder som skal være defineret i filter tabellen UnemploymentFundAccesFilter. Der bliver filtreret for disse en for en og alle skal være opfyldt før end objektet bliver medtaget.

override List<BusinessFilter> BusinessFilters => new List<BusinessFilter>
{ new BusinessFilter("Activity", (int)ActivityTypeIdentifier) }

FilterStartDateStartdato for en entry i domainobjektet. Samme med FilterEndDate bruge datoen til at lave en tidsmæssig filtrering ift borgeren a-kassemedlemskabFilterStartDate => StartDate
FilterEndDateSlutdato for en entry i domainobjektet. Hvis entryen ikke har en tidsmæssig udstrækning (fx. en samtale som vist ovenfor), sættes den til samme værdi som startdatoen. Hvis der ikke er en slutdato (fx. en åben tilmelding eller en åben kontaktgruppe) skal værdien sættes til DateTime.Max.FilterEndDate => (EndTime.HasValue) ? EndTime.Value : DateTime.MaxValue

Udfør filtrering

Når først domainobjektet er filtreringsenablet, er selve kaldet af filtreringen en one-liner (næsten) som skal implementeres i BLL. Her er vist metoden GetInterviewInfo fra InterviewBLL.cs


1        public List<InterviewModel> GetInterviewInfo(string civilRegistrationIdentifier, IDfdgUser dfdgUser)
2        {
3            NullGuard.NotNullOrEmpty(civilRegistrationIdentifier, nameof(civilRegistrationIdentifier));
4
5            // Validate
6            ValidatePersonExists(civilRegistrationIdentifier);
7
8            // Fetch from database
9            List<InterviewModel> interviewCollection = _interviewDAL.GetInterviewInfo(civilRegistrationIdentifier);
10
11           // Filter 
12           interviewCollection = _filterEngine.Filter(personCivilRegistrationIdentifier, interviewCollection);
13
14           // Return result
15           return interviewCollection;
16       }

Ovenstående er BLL metoden for at hente samtaler for en given borger. I linje 9 dannes listen af samtaler, og i linje 12 kalder vi metoden Filter på FilterEngine for hele listen. Filter vil herefter dels filtrere ift baseimplementation af ShouldKeepItem eller den override version hvis en sådan findes, dels kalde Trim for hvert element. 

Når det kun næsten er en one-liner, er det fordi, vi lige skal definere _filterEngine i vores BLL klasse, og den skal med i contructor'en.