Work in Progress
Diese Seite ist aktuell im Review! Die Seite wurde noch nicht qualitätsgesichert und kann Fehler enthalten.
Die verlinkten Seiten sind ggf. nur für Schleupen-Mitarbeiter sichtbar.

Anschluss des Domänenmodells an die Service-Fassade - Assembler

Dieses Dokument beschreibt das Muster ServiceFacade, Part=Assembler. In diesem Muster wird definiert, wie Datenverträge in Domänenobjekte und umgekehrt überführt werden. Hierdurch wird eine Entkopplung als sogenannter Anticorruption Layer erreicht.

Design

Wird ein Typ T von einem Datenvertrag TContract nach T und umgekehrt konvertiert, so ist ein Assembler (Übersetzer) zu implementieren. Die Implementierung erfolgt als Klasse in der Services-Assembly.

Implementierung anhand eines Beispiels

Die Methode ToDomainObject() wandelt einen oder mehrere Datenverträge in Domänenobjekte um; die Methode ToDataContract() Domänenobjekte zu Datenverträgen. Als Name für den Assembler ist der Typ des Domänenobjekts maßgebend, also z.B. BuchAssembler, wenn der Assembler Domänenobjekte vom Typ Buch zu Datenverträgen vom Typ BuchContract und umgekehrt umwandelt. Hierzu wird die Basisklasse Assembler verwendet.

Es ist ein Assembler pro Aggregat im Standard zu implementieren.

Es wird entsprechend eine Schnittstelle für den Zugriff definiert: IBuchAssembler. Namenskonvention: {AggregatRootTyp}Assembler.

Wird das Muster Aggregat verwendet, so muss die Schnittstelle den Listentyp beinhalten und intern auf die Basisklassen-Implementierung schwenken!

In der Standard-Implementierung werden die Felder feldweise übersetzt. Mit Hilfe von Fagento kann auf die internen Felder der Domänenklasse über die IData-Schnittstelle zugegriffen werden.
Anmerkung: In der Implementierung kann auch der AutoMapper verwendet werden.

Verwendung von DateTimeOffset

Siehe Verwendung von Datum und Uhrzeit in einer Landimplementierung.

Beispiel
public interface IBuchAssembler
{
    IEnumerable<BuchContract> ToDataContract(IEnumerable<Buch> domainObjects);
    IEnumerable<Buch> ToDomainObject(IEnumerable<BuchContract> contracts);
    BuchContract ToDataContract(Buch domainObject);
    Buch ToDomainObject(BuchContract contract);
}

public sealed class BuchAssembler : Assembler<BuchContract, Buch>, IBuchAssembler
{
    public override BuchContract ToDataContract(Buch domainObject)
    {
        if (domainObject == null)
        {
            return null;
        }

        IBuchData domainObjectData = domainObject;

        BuchContract result = new BuchContract();
        result.Id = domainObjectData.Id;
        result.Autor = domainObjectData.Autor;
        // ...

        result.Inhaltsangabe = ToDataContract(domainObjectData.Inhaltsangabe);
      
        return result;
    }

    public override Buch ToDomainObject(BuchContract dataContract)
    {
        if (dataContract == null)
        {
            return null;
        }

        Buch result = (Buch)Activator.CreateInstance(typeof(Buch), true);
        IBuchData resultData = result;
        resultData.Id = dataContract.Id;
        resultData.Autor = dataContract.Autor;
        // ...
      
        resultData.Inhaltsangabe = ToDomainObject(dataContract.Inhaltsangabe);
      
        return result;
    }
}

Unit-Tests können mithilfe der Klasse GenericAssemblerTestBase wie folgt codiert werden: Testklasse für generische Tests.

Variation bei komplexen Strukturen (Optional)

Bei komplexen Strukturen kann für jede referenzierte Entität ein entsprechender Assembler implementiert und angebunden werden. Dies ist im folgenden Beispiel dargestellt.

Beispiel
public sealed class BuchAssembler : Assembler<BuchContract, Buch>, IBuchAssembler
{
    private readonly IInhaltsangabeAssembler inhaltsangabeAssembler;

    public BuchAssembler(IInhaltsangabeAssembler inhaltsangabeAssembler)
    {
        if (inhaltsangabeAssembler == null) { throw new ArgumentNullException(nameof(inhaltsangabeAssembler)); }

        this.inhaltsangabeAssembler = inhaltsangabeAssembler;
    }

    // ...
  
    public Buch ToDomainObject(BuchContract contract)
    {
        if (contract == null)
        {
            return null;
        }
        
        // ...
        resultData.Inhaltsangabe = inhaltsangabeAssembler.ToDomainObject(dataContract.Inhaltsangabe);
    }

    public BuchContract ToDataContract(Buch domainObject)
    {
        if (domainObject == null)
        {
            return null;
        }
        
        // ...
        result.Inhaltsangabe = inhaltsangabeAssembler.ToDataContract(domainObjectData.Inhaltsangabe);

        return result;
    }
}
Variation bei Listentypen (Optional)
internal sealed class BuchAssembler : Assembler<BuchContract, Buch>, IBuchAssembler
{
	public IEnumerable<BuchContract> ToDataContract(BuchListe buecher)  // Listentyp!
	{
		if (buecher == null)
		{
			return null;
		}

		return ToDataContract(buecher.Items);
	}

	public override BuchContract ToDataContract(Buch buch)
	{
		// ... wie oben
    }

	public BuchListe ToDomainObjectListe(IEnumerable<BuchContract> buchContracts) // Listentyp!
	{
		if (buchContracts == null)
		{
			return new BuchListe();
		}

		return new BuchListe(base.ToDomainObject(buchContracts));
	}

	public override Buch ToDomainObject(BuchContract buchContract)
	{
		// ... wie oben
	}
}
Optimitic Locking

Für das Optimistic Locking muss die Version-Eigenschaft von Datenvertrag zu Domänenobjekt und umgekehrt transportiert werden. Dieses darf nicht geändert werden!

Benutzte Muster

Cookie Consent mit Real Cookie Banner