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:
Property | Beskrivelse | Eksempel |
---|---|---|
BusinessFilters | Her 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> |
FilterStartDate | Startdato for en entry i domainobjektet. Samme med FilterEndDate bruge datoen til at lave en tidsmæssig filtrering ift borgeren a-kassemedlemskab | FilterStartDate => StartDate |
FilterEndDate | Slutdato 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.