Den gode converter

Introduktion

I DFDG tilstræbes det, at AutoMapper anvendes til mapning mellem klasser. AutoMapper anvendes i dag primært i følgende situationer:

  • Mapning mellem servicetyper og domænemodel (ServiceConverter)
  • Mapning mellem domænemodel og wsrm-typer (WsrmConverter)
  • Mapning mellem domænemodel og eksterne serviceklasser fx ved kald til Jobnet (ExternalServiceMapper)

ServiceConverter

ServiceConvertere aka ServiceMappere anvendes til at konvertere mellem servicetyper og domæneobjekter. For nye services bør følgende principper følges:

Placering og navngivning

For alle services laves en converter klasse, der nedarver fra MapperBase. Converteren navngives efter servicen, fx vil InterviewService have en converter, der hedder InterviewServiceConverter. Det er besluttet, at vi kalder det en converter og ikke en mapper. MapperBase findes under namespacet DFDG.ServiceGateway.Utilities

Kodelister

Når en converter nedarver fra MapperBase, mappes der automatisk mellem kodeliste-enums og kodelistetyper - uanset retning og uanset om de properties, der mappes mellem er nullable eller ej. Eksempel:

Nedenfor vises et tænkt eksempel, hvor der mappes mellem typerne InterviewType og InterviewModel

public class InterviewModel
{
    public InterviewTypeIdentifierEnum InterviewTypeIdentifier { get; set; }
}
[DataContract(Namespace = "http://service.bm.dk/pjaktass/1/InterviewService")]
public class InterviewType
{
    [DataMember(Order = 1)]
    [Required]
    [Description("Samtaletype")]
    public InterviewTypeIdentifierType InterviewTypeIdentifier { get; set; }
}

Modellen har en property, som er en kodeliste-enum mens servicetypen har en property, der er en kodelistetype. Da de to properties hedder det samme, skal rammeværket nok sørge for at mappe mellem de to. Dvs man ender med en mapper konfiguration, der ser således ud:

config.CreateMap<Model.InterviewModel, Service.InterviewType>()


I tilfælde af at de to properties ikke er navngivet ens, ser konfigurationen således ud:

config.CreateMap<Model.InterviewModel, Service.InterviewType>()
    .ForMemberMapFrom(dest => dest.InterviewTypeIdentifierType, src => src.InterviewTypeIdentifierEnum)


Note

For at konvertering mellem enums og kodelistetyper fungerer for nye kodelister, skal der tilføjes en linje i filen KC.AMS.Model.Converters.CodeListMappingInitializer. Eksempel fra eksisterende kodeliste:


yield return CodeListMap.Create<AbsenceCauseTypeIdentifierEnum, AbsenceCauseTypeIdentifierType>();


Konventioner

Undgå explicitte mappings

Undgå at mappe mellem properties med samme navn. AutoMapper gør det for dig. AutoMapper kan selv finde ud af, det, hvis casing er forskellig og propertytyperne for forskellige. Eksempel på, hvad man IKKE skal gøre:


config.CreateMap<Model.InterviewModel, Service.InterviewType>()
    .ForMemberMapFrom(dest => dest.Identifier, src => src.Identifier)


Undgå desuden at kalde UseValue, med angivelse af defaultværdier som vist nedenfor. Værdien false er default for en bool, hvormed dette statement er overflødigt:


config.CreateMap<Model.InterviewModel, Service.InterviewType>()
    .ForMemberUseValue(dest => dest.IsSomthing, false)

Anvend extentionmetoder

For at gøre livet lettere for udviklerne er er udviklet extensionmetoder til de mest gængse mapping-relaterede operationer:

ForMemberMapFrom

// Uden extensionmetode
.ForMember(dest => dest.Identifier, opt => opt.MapFrom(src => src.Id))

// Med extensionmetode
.ForMemberMapFrom(dest => dest.Identifier, src => src.Id)


IgnoreMember

// Uden extensionmetode
.ForMember(dest => dest.Identifier, opt => opt.Ignore())

// Med extensionmetode
.IgnoreMember(dest => dest.Identifier)


ForMemberUseValue

// Uden extensionmetode
.ForMember(dest => dest.Identifier, opt => opt.UseValue(42))

// Med extensionmetode
.ForMemberUseValue(dest => dest.Identifier, 42)



Test

Der findes en unittest - AssertMapperConfigurationsAreValid(), der automatisk kalder AssertConfigurationIsValid på alle convertere, der nedarver fra MapperBase. Dermed er der ikke behov for at skrive selvstændige tests til de enkelte convertere. 

I nogle situationer kan det være en fordel at lave en unittest, hvor converterens metoder kaldes med dummy data. Dermed sandsynliggøres det yderligere, at converteren fungerer efter hensigten. Et eksempel på en sådan test ses her:

[TestMethod]
[TestCategory("UnitTest"), TestCategory("MyPlan")]
public void MyPlanPdfContainerMapper_ConversionSuccessful()
{
	var mapper = new MyPlanPdfContainerMapper();

	MyPlanModel myPlan = _fixture.Create<MyPlanModel>();
	List<ActivityItemModel> activityItems = _fixture.Build<ActivityItemModel>().CreateMany().ToList();
	KC.AMS.Model.ServiceModel.PersonNameStructureType personName = _fixture.Create<KC.AMS.Model.ServiceModel.PersonNameStructureType>();

	var container = new MyPlanContainer(myPlan, activityItems, personName);
	var pdfContainer = new MyPlanPdfContainer(container, _fixture.Create<bool>(), _fixture.Create<bool>());

	var result = mapper.Convert(pdfContainer, _fixture.Create<bool>());
	Assert.IsNotNull(result);  // We don't check that all properties are converted correctly (because there are many), but just that the converter doesn't throw an exception
}


Self-contained

Det tilstræbes, at convertere laves, så de ikke behøver at kommunikere med andre komponenter eller gå i databasen. En converter bør være self-contained og modtage alle nødvendige data gennem dens offentlige metoder

WsrmConverter

For wsrmconvertere gælder stort set samme regelsæt som for serviceconvertere med den forskel, at der laves én converter per wsrm-besked. 

ExternalServiceConverter

For converters brugt til konvertering mellem domænemodel og proxyklasser til en ekstern service, gælder samme regelsæt som for serviceconvertere