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. 

Der Anschluss der Diagnoseprotokollierung erfolgt mithilfe des Schleupen.CS-Frameworks, das ein Castle-Installer zum Anschluss in den Bausteinen bereitstellt. Die Nutzung zeigt der folgende Code.

windsorContainer.Install(new ServiceBusClientInstaller(IsInsideWcfOperation) { DatabaseSchema = DatabaseInfo.DatabaseSchema });

Zur Nutzung wird die Schnittstelle IDiagnosticsWriter 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 IDiagnosticsWriter diagnosticsWriter;
  public BuecherAusleihenController(
    ...
    IDiagnosticsWriter diagnosticsWriter)
  {
    ...
    this.diagnosticsWriter = diagnosticsWriter ?? throw new ArgumentNullException(nameof(diagnosticsWriter));
  }

  public async Task VerlaengereAusleihfristAsync(Sid benutzerSid, IEnumerable<AusleiheId> ausleihenIds, TimeSpan fristverlaengerung)
  {
      ..
    try
    {
      ...
      ausleihen.VerlaengereUm(fristverlaengerung);
      ...
    }
    catch (Exception exception)
    {
      diagnosticsWriter.WriteTrace(new TraceEvent(exception));
    }
  }
  ...
}

Über diesen Weg können Meldung der Levels FehlerWarnungInfo manuell eingestellt werden. 

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 die Schnittstelle ILogger injiziert werden, über die dann mithilfe von NLog protokolliert wird.

Das folgende Code-Beispiel zeigt die Anbindung des Loggings:

// Direkte Injektion des ILogger
public BuecherAusleihenController(..., ILogger<TestClass> 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.

Cookie Consent mit Real Cookie Banner