Diagnose / Observability
Im Folgenden wird der Anschluss der unterschiedlichen Protokollierungsformen innerhalb eines Landes beschrieben. Die allgemeine Konzeption auf Makroebene ist in Diagnose / Observability beschrieben.
Diagnoseprotokoll
Das Diagnoseprotokoll enthält zeitbezogene Detailinformationen über den inneren Zustand der Software und dient der Protokollierung von Ausnahmen, Warnungen und Informationen, die für den Betrieb zur Beauskunftung relevant sind. Insbesondere Exceptions sind relevant, um fehlerhafte Konfigurationen, Konstellation oder auch Bugs erkennen zu können.
Zur Nutzung wird die Schnittstelle Schleupen.CS.PI.SB.Diagnostics.Logging.ILogger
<> verwendet, um Einträge in das Diagnoseprotokoll zu schreiben. Diese Schnittstelle kann einfach per Dependency Injection angebunden werden. Dies zeigt exemplarisch der folgende Code.
public class BuecherAusleihenController : IBuecherAusleihenController { ... private readonly ILogger<BuecherAusleihenController> logger; public BuecherAusleihenController( ... ILogger<BuecherAusleihenController> logger) { ... this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task VerlaengereAusleihfristAsync(Sid benutzerSid, IEnumerable<AusleiheId> ausleihenIds, TimeSpan fristverlaengerung) { .. try { ... ausleihen.VerlaengereUm(fristverlaengerung); ... } catch (Exception exception) { logger.LogError(exception); } } ... }
Über diesen Weg können Meldung der Levels Error, Warning, Information etc. manuell eingestellt werden.
Wir kapseln Microsoft.Extensions.Logging.ILogger
<> um die direkte Kopplung zu vermeiden und langfristig in unseren Bibliotheken kompatibel bleiben zu können.
Exceptions werden durch einen AOP-Interceptor für Services automatisch protokolliert, d.h. dass obiger Code eigentlich nicht notwendig ist!
Datenänderungsprotokoll
Mit dem Datenänderungsprotokoll können Änderungen an Geschäftsobjekten, die sich intern als Aggregates ausprägen, protokolliert werden. Hierdurch wird eine zeit- und nutzerbezogene Nachvollziehbarkeit der Datensituation im System gewährleistet. Das Datenänderungsprotokoll beantwortet typsicherweise die Frage, wer wann welche Änderungen an einem Geschäftsobjekt vorgenommen hat. Dieses ist insbesondere für Wirtschaftsprüfer, die Recherchen über den Verlauf von Datenänderungen durchführen wollen, relevant und erforderlich! Das Datenänderungsprotokoll erfüllt somit eine Compliance-Anforderung. Da das Datenänderungsprotokoll nur die fachliche Applikationslogik und insbesondere deren Persistenz betrifft, ist dieses ausschließlich in Ländern angebunden.
Um das Datenänderungsprotokoll zu nutzen, muss das Repository des entsprechenden Aggregates konfiguriert werden. Es müssen für jedes zu protokollierende Aggregate ein ChangeLogEventBuilder erstellt werden, die die entsprechende Datenänderungseinträge in Form von ChangeLogAggregateEvent und ChangeLogEntityEvent erstellen.
Für alle Entitäten eines Aggregats, die protokolliert werden sollen, müssen also eine Reihe von ChangeLogEventBuildern erstellt werden. Diese sind derart zu implementieren, dass sie die korrekten Datenänderungsprokolleinträge zurückgeben. Da Änderungen pro Aggregat geschrieben werden und für dieses auch allgemeine Informationen zu schreiben sind, muss für jede Entität eine Navigation zur Elternteil-Entität bis zum Aggregate Root analog zum Coarse Grained Lock möglich sein: Jede Entität muss IAggregateChild<T>
und IEntity
implementieren (außer dem Aggregate Root).
public partial class Inhaltsangabe : IAggregateChild<Buch>, IEntity { private ISet<Seite> seiten; private BuchId parentId; public Inhaltsangabe(InhaltsangabeId id, BuchId buchId, IEnumerable<Seite> seiten) : base(id) { ... parentId = buchId ?? throw new ArgumentNullException(nameof(buchId)); } object IAggregateChild<Buch>.ParentId { get => parentId; set => parentId = (BuchId)value ?? throw new ArgumentNullException(nameof(value)); } ... }
Die Implementierung eines ChangelogEventBuilders zeigt folgender Code:
internal sealed class BuchChangeLogAggregateEventBuilder : ChangeLogAggregateEventBuilder<Buch> { ... protected override ChangeLogAggregateEvent WhenAggregateChanged(Buch rootEntity, OperationType rootEntityOperationType) { if (rootEntity == null) { throw new ArgumentNullException(nameof(rootEntity)); } ISessionTokenProvider sessionTokenProvider = IocContainer.Resolve<ISessionTokenProvider>(); string sessionToken = sessionTokenProvider.Token; return new ChangeLogAggregateEvent( new BusinessEntity("BuchId", rootEntity.Id.Value, ((IBuchData)rootEntity).Titel), "Änderung am Geschäftsobjekt BuchId.") { SessionToken = sessionToken }; } }
Alle ChangelogEventBuilder werden über einen Configurator des Repositorys angeschlossen:
public class BuchChangeLogConfigurator : ChangeLogNHibernateConfigurator, IBuchChangeLogConfigurator { ... protected override IEnumerable<Type> ChangeLogEventBuilderTypes { get { yield return typeof(BuchChangeLogEntityEventBuilder); yield return typeof(InhaltsangabeChangeLogEntityEventBuilder); yield return typeof(VerlagChangeLogEntityEventBuilder); yield return typeof(PreisChangeLogEntityEventBuilder); yield return typeof(SeiteChangeLogEntityEventBuilder); yield return typeof(BuchChangeLogAggregateEventBuilder); } } }
Um verteilte Transaktionen zu vermeiden, werden diese Daten zunächst im Land in einer bestehenden Transaktion persistiert und anschließend weiter verarbeitet. Hierzu muss eine entsprechende Tabelle im Datenbank-Schema des Landes angelegt werden.
Die Bibliothek kann über einen Castle-Installer wie folgt angeschlossen werden:
windsorContainer.Install(new ChangeLogClientInstaller(IsInsideWcfOperation)); windsorContainer.Install(new ChangeLogServiceInstaller(IsInsideWcfOperation));
Geschäftsprozessprotokollierung
Das Geschäftsprozessprotokoll enthält detaillierte Ablaufinformationen zu einzelnen Geschäftsprozessen. Damit kann der (tatsächliche) Ablauf von Geschäftsprozessen Schritt-für-Schritt und im Nachhinein nachvollzogen werden.
Um einen Eintrag in das Geschäftsprozessprotokoll zu schreiben, wird die Schnittstelle IBusinessProcessProtocolClient
verwendet. Das eigentliche Schreiben erfolgt dann wie in folgendem Codebeispiel:
businessProcessProtocolClient .NewEntry("Eintrag für das Protokoll") // Neuen Protokolleintrag erzeugen .AppendParameter("Parameter1", "Wert1") // Parameter hinzufügen .AppendParameter("Parameter2", 2) .Write(); // Eintrag in das Geschäftsprozessprotokoll schreiben
Tracing
Zur Implementierung des Tracings in Ländern muss in den nutzenden Bausteinen wie dem Usecase Controller ebenfalls die Schnittstelle Schleupen.CS.PI.SB.Diagnostics.Logging.ILogger
<> injiziert werden, über die dann unter der Haube mithilfe von NLog protokolliert wird.
Das folgende Code-Beispiel zeigt die Anbindung des Loggings:
// Direkte Injektion des ILogger public BuecherAusleihenController(..., ILogger<BuecherAusleihenController> logger) { ... }
Distributed-Tracing
Für die Nutzung des Distributed-Tracings ist keine explizite Implementierung notwendig. Lediglich die Anbindung an die Serviceinfrastruktur durch einen Castle-Installer ist notwendig.