Muster: Dependency-Injection für Dekorierer bei Castle

Version: 1.0
.NET Version: .NET, .NET Framework

Ein Dekorierer inerweitert die Funktionalität einer Klasse. Ein typisches Beispiel ist ein Gateway, welches Daten von einem anderen Service liest und um Caching erweitert werden soll. Die Implementierung eines Decorators in C# ist prinipiell leicht, daher wird in diesem Muster beschrieben, wie dieses in Schleupen.CS 3.0 mit Castle umgesetzt werden kann.

Design

Das prinzipielle Design ist in Decorators beschrieben.

Implementierung

Wir betrachten das Beispiel eines Gateways. Die Schnittstelle habe dabei folgende Gestalt:

public interface IBestellAuftragEntityServiceGateway
{
	Task<BestellAuftrag> QueryAsync(BestellAuftragId bestellAuftragId);

	...
}

Analog sei die Implementierung wie folgt gegeben:

public class BestellAuftragEntityServiceGateway : IBestellAuftragEntityServiceGateway
{
	...

	public async Task<BestellAuftrag> QueryAsync(BestellAuftragId bestellAuftragId)
	{
		if (bestellAuftragId == null) { return null;}

		using IBestellAuftragEntityServiceClient serviceClient = serviceClientFactory.Create();

		QueryByIdResponse requestResult = await serviceClient.QueryByIdAsync(new QueryByIdRequest(null, new ArrayOfBestellAuftragId(bestellAuftragId.Value)));
		BestellAuftrag bestellAuftrag = bestellAuftragAssembler.ToDomainObject(requestResult.BestellAuftragListe.First());
		return bestellAuftrag;
	}

Wir implementieren nun einen Dekorierer, in dem wir per Konstruktor-Injection das eigentliche Gateway entgegennehmen (nicht erkennbar am Typ, da eine Schnittstelle übergeben wird).

public class CachingBestellAuftragEntityServiceGateway : IBestellAuftragEntityServiceGateway
{
	private readonly IBestellAuftragEntityServiceGateway bestellAuftragEntityServiceGateway;
	...

	public CachingBestellAuftragEntityServiceGateway(IBestellAuftragEntityServiceGateway bestellAuftragEntityServiceGateway, ...)
	{
		this.bestellAuftragEntityServiceGateway = bestellAuftragEntityServiceGateway ?? throw new ArgumentNullException(nameof(bestellAuftragEntityServiceGateway));
		...
	}

	public async Task<BestellAuftrag> QueryAsync(BestellAuftragId bestellAuftragId)
	{
		if (bestellAuftragId == null) { return null; }

		return await bestellAuftragCache.GetOrSetAsync(
			bestellAuftragId,
			bestellAuftragEntityServiceGateway.QueryAsync);
	}

Die Konfiguration, dass Castle die Typen wie erwartet zusammensetzt, ist recht einfach. Da der WcfContainerConfigurator mithilfe der Property Assemblies bereits nach allen Typen, die der Namenskonvention entsprechen, sucht, muss das CachingBestellAuftragEntityServiceGateway gefiltert werden. Dieses muss die Default-Implementierung sein: Hierzu wird dieser Type per windsorContainer.Register(...) registriert.

Für windsorContainer.Register(...) muss der Typ per string aufgelöst werden, da dieser nur per Laufzeit und nicht per Compile-Zeit zur Verfügung steht.

public class ServiceHostFactoryConfigurator : WcfContainerConfigurator
{
	protected override IEnumerable<Assembly> Assemblies
	{
		get
		{
			yield return Assembly.Load(AssemblyNames.Services);
			yield return Assembly.Load(AssemblyNames.Contracts);
			yield return Assembly.Load(AssemblyNames.BusinessCases);
			yield return Assembly.Load(AssemblyNames.Repositories);
			yield return Assembly.Load(AssemblyNames.Gateways);
			yield return Assembly.Load(AssemblyNames.GatewaysContracts);
		}
	}

	protected virtual bool IsInsideWcfOperation => true;

	...
	protected override void OnContainerConfiguring(IWindsorContainer windsorContainer)
	{
		windsorContainer.Register(Component.For<IBestellAuftragEntityServiceGateway>()
										.ImplementedBy(ResolveType("Schleupen.AS.MT.BIB.Bestellungen.Auftraege.EntityService.V3_1.Caching.CachingBestellAuftragEntityServiceGateway", AssemblyNames.Gateways))
										.IsDefault());
	
		base.OnContainerConfiguring(windsorContainer);
	}

	protected override bool UseType(Type type)
	{
		...
		if (type.Name == "CachingBestellAuftragEntityServiceGateway") { return false; }

		return base.UseType(type);
	}

	protected Type ResolveType(string fullTypeName, string assemblyName) 
	{
		return Assembly.Load(assemblyName)
			.GetTypes()
			.Single(t => t.FullName == fullTypeName);
	}

	protected override void SetupWiring(Assembly assembly, BasedOnDescriptor descriptor)
	{
		if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); }

		descriptor.WithServiceAllInterfaces();
	}

Um zu prüfen, ob die Registrierung wie erwartet funktioniert, testen wir dies nun.

Unittest

Den Test implementieren wir einfach im GenericServiceTest, in dem generell das Zusammenstellen der Objekte getestet wird.

[TestFixture]
internal sealed class GenericServiceTest : GenericServiceTestBase
{
	...

	[Test]
	public void Resolve_CachingBestellAuftragEntityServiceGateway_ShouldReturnDecorator()
	{
		DependencyInjectionContainer diContainer = CreateDependencyInjectionContainer();
		using IDisposable scope = diContainer.Windsor.BeginScope();
		ContainerConfigurator.Configure(diContainer.Windsor);

		IBestellAuftragEntityServiceGateway gateway = diContainer.Windsor.Resolve<IBestellAuftragEntityServiceGateway>();

		Assert.That(gateway.GetType().Name, Is.EqualTo("CachingBestellAuftragEntityServiceGateway"));
	}

Abgrenzung

Das Muster zeigt nicht das Dekorierer-Muster der Gang of Four an sich, sondern nur die Registrierung.

Stärken und Schwächen

Stärken
  • Kapselung
Schwächen
  • Nicht so explizites Design

Benutzte Muster

Chatbot not allowed
Cookie Consent mit Real Cookie Banner