Usecase Controller - Fassade zu Domänenmodell und Repository
Der Einstieg in Application Core erfolgt über einen Usecase Controller, der den Anwendungsfall im fachlichen Kontext kapselt. Application Core ist technologieneutral und ruft die Domänenlogik auf. Der Usecase Controller orchestriert im Normalfall folgende Aktionen
- lädt / speichert das Domänenmodell über das Repository
- führt Operationen auf dem Domänenmodell aus
- [optional] nutzt DomainServices
- [optional] initiiert das Auslösen von Domain Events
- ruft ggf. weitere Services auf
- löst ggf. Business Events aus
Der Begriff Usecase Controller ist ein historischer Begriff. Er ist identisch mit den sogenannten Begriff des Application Service in DDD. Wichtig ist, dass die hier implementierte Logik technologieneutral ist und somit eine hohe Stabilität hinsichtlich von Änderungen aufweist. Application Services beinhalten keine fachliche Logik und stellen den Einstieg in die Applikation dar! Eine Service-Fassade (z.B. REST oder SOAP) stellt nur einen technischen Adapter zur Umwelt dar.
Dabei werden also Daten der eigenen Applikation über das Repository geladen. Daten, die nicht der Verantwortlichkeit der eigenen Applikation obliegen, können auch über fremde Services über Gateways gelesen werden.
Der Usecase Controller stößt demnach die Domänenlogik an, wobei die eigentliche Logik im Domänenmodell implementiert wird. Da Usecase Controller dem Application Core zugeordnet werden und intern Domänenlogik anstoßen, sollten auch hier die Begrifflichkeiten der Ubiquitous Language verwendet werden. Daher heißt der Controller im Beispiel auch BuecherAusleihen.
Im Gegensatz zu Usecase Controllern kapseln DomainServices Geschäftslogik!
Usecase Controller haben folgende Namenskonvention:
<TueEtwas>Controller
Der nachfolgende Code zeigt exemplarisch einen Usecase Controller für das Ausleihen von Büchern:
public class BuecherAusleihenController: IBuecherAusleihenController { private readonly IReadOnlyBenutzerkontoRepository benutzerkontoRepository; private readonly IReadOnlyBuchRepository buchRepository; private readonly IAusleiheRepository ausleiheRepository; public BuecherAusleihenController( IReadOnlyBenutzerkontoRepository benutzerkontoRepository, IReadOnlyBuchRepository buchRepository, IAusleiheRepository ausleiheRepository) { this.benutzerkontoRepository = benutzerkontoRepository ?? throw new ArgumentNullException(nameof(benutzerkontoRepository)); this.buchRepository = buchRepository ?? throw new ArgumentNullException(nameof(buchRepository)); this.ausleiheRepository = ausleiheRepository ?? throw new ArgumentNullException(nameof(ausleiheRepository)); } public async Task<IAusleihen> LeiheAusAsync(Sid benutzerSid,IEnumerable<BuchId> buecherIds) { if(benutzerSid == null) { throw new ArgumentNullException(nameof(benutzerSid)); } if(buecherIds == null) { throw new ArgumentNullException(nameof(buecherIds)); } Benutzerkonto benutzerkonto = await QueryBenutzerkontoAsync(benutzerSid); IBuecher auszuleihendeBuecher = await QueryBuecherAsync(buecherIds); IAusleihen ausleihen = benutzerkonto.LeiheAus(auszuleihendeBuecher); ausleiheRepository.Add(ausleihen); ValidationResult validationResult = ValidateRegardingLeiheAus(benutzerkonto,ausleihen,auszuleihendeBuecher); validationResult.ThrowIfInvalid(); await ausleiheRepository.FlushAsync(); // Löst Domain Events aus return ausleihen; } ... }
Im Allgemeinen soll genau ein Aggregate pro Operation des Usecase Controllers geändert werden, wobei im Allgemeinen aber auch Informationen und Logik aus anderen Aggregaten benötigt wird. Um dies im Code besser erkennen zu können, implementiert das Repository, das Aggregate nur für den lesenden Zugriff liefert eine sogenannte Readonly-Schnittstelle, z.B. IReadOnlyBuchRepository
:
publik Interface IReadOnlyBuchRepository { IBuecher QueryById(IEnumerable<BuchId> buchIds); ... } public interface IBuchRepository : IReadOnlyBuchRepository { IBuecher Add(IBuecher buecher); ... }
Hier würde es sich auch anbieten, das IAusleiheRepository
und das IReadOnlyBuchRepository
als DomainService IBenutzerkontoDomainService
fachlich zu kapseln.
Änderungen an mehreren Aggregaten werden über Domain Events und Business Events propagiert. Im obigen Code erfolgt diese über eine entsprechende Konfiguration des Repositorys. Wir werden hierauf im folgenden Abschnitt genauer eingehen.