Fluent Interfaces zum Erstellen von APIs

In meinem Betrieb setzen wir SharePoint als Intranetplattform und GDI als Buchhaltungssystem ein. Kürzlich hatte ich die Anforderung, dass man Urlaubsdaten, welche die Mitarbeiter über SharePoint pflegen, ins GDI Lohnsystem zu überführen. Als ich bei GDI anfragte, gab man mir eine Beschreibung einer Standard CSV-Import-Schnittstelle oder einfach ausgedrückt: Eine Liste wie ich ich eine CSV Datei mit Datensätzen aufbauen muss.

Um das Programm, welches die Daten aus SharePoint extrahiert und GDI als CSV zur Verfügung stellt, für etwaige Dritte, die das Programm später pflegen müssen, einfach zu halten, habe ich mit sogenannten Fluent Interfaces eine rudimentäre API implementiert. Das hat den Vorteil, dass die Logik, welche zum Erstellen einer gültigen Export-Datei benötigt wird, sauber abstrahiert ist. Keiner, der z.B. den Zugriff auf SharePoint ändern will, muss sich noch mit dem Datenformat auseinandersetzen. Wie bin ich vorgegangen?

Zunächst habe ich mir für jedes Feld, welches bei Urlaubseinträgen gefüllt sein muss, ein Interface geschrieben. Der Einfachheit halber habe ich alles in die Datei IGdiApi.cs gepackt. Die Datei sieht so aus:

 

   1: public interface IGdiContracts_Personalnr

   2: {

   3:     IGdiContracts_Fehlzeitennr SetPersonalnr(Int32 personnelNo);

   4: }

   5:

   6: public interface IGdiContracts_Fehlzeitennr

   7: {

   8:     IGdiContracts_Beginn SetFehlzeitennr(Int32 timesAbsentNo);

   9: }

  10:

  11: public interface IGdiContracts_Beginn

  12: {

  13:     IGdiContracts_Ende SetBeginn(DateTime begin);

  14: }

  15:

  16: public interface IGdiContracts_Ende

  17: {

  18:     IGdiContracts_GanzeTage SetEnde(DateTime end);

  19: }

  20:

  21: public interface IGdiContracts_GanzeTage

  22: {

  23:     IGdiContracts_GdiEntry SetGanzeTage(bool allDayEvent);

  24: }

  25:

  26: public interface IGdiContracts_GdiEntry

  27: {

  28:     string GetCsvLine();

  29: }

 

Im Anschluss habe ich die Datei GdiApi.cs erstellt. Die Datei enthält zwei Klassen (wie clean das ist, sei mal dahingestellt). Die erste Klasse bildet den späteren Einstiegspunkt für die API:

   1: public class GdiApi : IGdiContracts_Personalnr

   2: {

   3:     public IGdiContracts_Fehlzeitennr SetPersonalnr(int personnelNo)

   4:     {

   5:         return new GdiEntry(personnelNo);

   6:     }

   7: }

 

Es folgte die zweite Klasse, die die restlichen Interfaces implementiert:

   1: class GdiEntry : IGdiContracts_Fehlzeitennr, IGdiContracts_Beginn, IGdiContracts_Ende, 

                       IGdiContracts_GanzeTage, IGdiContracts_GdiEntry

   2: {

   3:     Int32 _personnelNummer;

   4:     Int32 _timesAbsentNo;

   5:     DateTime _begin;

   6:     DateTime _end;

   7:     char _beginIsAHalfDay;

   8:     char _endIsAHalfDay;

   9:

  10:     public GdiEntry(Int32 personalNummer)

  11:     {

  12:         _personnelNummer = personalNummer;

  13:     }

  14:

  15:     public IGdiContracts_Beginn SetFehlzeitennr(Int32 timesAbsentNo)

  16:     {

  17:         _timesAbsentNo = (Int32) timesAbsentNo;

  18:         return this;

  19:     }

  20:

  21:     public IGdiContracts_Ende SetBeginn(DateTime begin)

  22:     {

  23:         _begin = begin;

  24:         return this;

  25:     }

  26:

  27:     public IGdiContracts_GanzeTage SetEnde(DateTime end)

  28:     {

  29:         _end = end;

  30:         return this;

  31:     }

  32:

  33:     public IGdiContracts_GdiEntry SetGanzeTage(bool allDayEvent)

  34:     {

  35:        //Logik zur Berechnung von _beginIsAHalfDay und _endIsAHalfDay

  36:         return this;

  37:     }

  38:

  39:     public string GetCsvLine()

  40:     {

  41:         System.Text.StringBuilder result = new StringBuilder();

  42:

  43:         result.Append(DateTime.Now.ToString("yyyy") + ";"); //aktuelles Jahr

  44:         result.Append(DateTime.Now.ToString("MM") + ";"); //aktueller Monat

  45:         result.Append(_personnelNummer.ToString() + ";"); //Personalnummer

  46:         //es folgen viele weitere leere Felder

  47:         result.Append(_timesAbsentNo.ToString() + ";"); //Fehlzeitennummer

  48:         result.Append(";"); //Bemerkung zur Fehlzeit

  49:         result.Append(_begin.ToShortDateString() + ";"); //Fehlzeiten Beginn

  50:         result.Append(_end.ToShortDateString() + ";"); //Fehlzeiten Ende

  51:         result.Append(_beginIsAHalfDay + ";"); //Halber Tag Beginn

  52:         result.Append(_endIsAHalfDay + ";"); //Halber Tag Ende

  53:         result.Append("N;"); //Keine Führung auf der Lohnsteuerkarte

  54:         result.Append("N;"); //Arbeitsunfähigkeit durch Unfall verursacht

  55:         result.Append("N;"); //Arbeitunfähigkeitsbescheinigung liegt vor

  56:         result.Append("N;"); //Fehlzeiten fürs Baugewerbe

  57:

  58:         return result.ToString();

  59:     }

  60: }

Die API wird nun wie folgt genutzt:
   1: new GdiApi().

   2:                     SetPersonalnr(iPersonalNr).

   3:                     SetFehlzeitennr(iFehlzeit).

   4:                     SetBeginn(dtStart).

   5:                     SetEnde(dtEnd).

   6:                     SetGanzeTage(bAllDayEvent).

   7:                     GetCsvLine();

Der Vorteil ist, dass der Aufrufer der API genau in dieser Reihenfolge vorgehen muss, um an den benötigten Datensatz zu kommen. Ihm wird genau gesagt wie er Vorgehen muss. Die Logik, wie sich der Datensatz zusammenbaut, ist dem Aufrufer unbekannt. Er weiß beispielsweise nichts von der Berechnung der halben Tage oder den unzähligen zusätzlichen, leeren Feldern.

Warum habe ich nicht einfach alle Interfaces in der ersten Klasse implementiert? Das hätte den Nachteil, dass der Aufrufer zu diesem Zeitpunkt noch alle Methoden gesehen und nicht gewusst hätte, mit welcher er anfangen soll.

Warum habe ich nicht für jedes Interface eine separate Klasse verwendet? Das hätte ich auch machen können, allerdings hätte ich dann die bereits gesetzten Informationen in irgendeiner Form weiter übergeben müssen. Eine Möglichkeit wäre gewesen, ein DTO zu definieren und dieses immer im Konstruktor in die nächste Klasse weiter zu reichen.

Warum habe ich es so gemacht? Weil die Methoden größtenteils nur 2 Zeilen beinhalten und weil eine starke Kohäsion der Daten bzw. Methoden besteht. Vergleicht bitte dazu das Prinzip tell don’t ask.

Zu guter Letzt will ich euch noch auf den Artikel Generische Fluent API mit flexiblen Modulen, den ich soeben noch gefunden habe, aufmerksam machen. Zugegebenermaßen ist er eher advanced und sehr lang, sodass auch ich ihn nicht vollständig durchgelesen habe, aber es ist ein sehr gutes Beispiel.

One thought on “Fluent Interfaces zum Erstellen von APIs

  1. […] Die Spaltennamen sind selbsterklärend. Nun habe ich mit freundlicher Unterstützung von Olaf Didszun eine Kommandozeilenanwendung geschrieben, die anhand der gesetzten Eigenschaften in der Mitarbeiterliste die Daten aus dem Kalender ausliest und als CSV Datei exportiert (vgl. meinen Artikel dazu hier). […]

    Gefällt mir

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: