In einem Gespräch mit unseren Webentwicklern habe ich den Kollegen einen Vorschlag für die Implementierung des Zugangs zu unserem Kundenportals unterbreitet: Unsere PHP Entwickler sind inzwischen ebenfalls auf den Ansatz des Testens umgestiegen, sodass sie ihre PHP und JavaScript Architektur anpassen müssen. Leider fehlt es an Frameworks, die einem ein Teil der Arbeit abnehmen könnten, wie z.B. Mocking Frameworks a la FakeItEasy. Falls jemand Frameworks kennt, dann melde er sich bitte bei mir.
Anforderungen (vereinfacht): An unserem Kundenportal ‘MyHeco’ soll man sich einloggen, ausloggen und das Password zurücksetzen können. Die Webanwendung konsumiert dabei .NET basierende Webservices!
Dies soll nun möglichst clean unter Einhaltung gängiger Prinzipien (wie z.B. dem Single Responsibility Principle) umgesetzt werden. Wie bereits erwähnt, muss die Architektur so gewählt werden, dass der Code einfach zu testen ist. Das ist v.a. auch deswegen nicht ganz trivial, weil Aufrufe nach ‘Außen’ zu Webservices gehen.
Hier also mein Vorschlag (natürlich in C# formuliert):
1: interface IAuthenticateMyHecoUser
2: {
3: MyHecoUser Login(string email, string password);
4: }
5:
6: interface ILogoutMyHecoUser
7: {
8: void Logout(string session);
9: }
10:
11: interface IResetMyHecoPassword
12: {
13: void ResetPassword(string email);
14: }
Schreibe 3 Interfaces für die eigentlichen Aktionen, z.B. Login. Natürlich könnte dafür auch nur ein Interface geschrieben werden werden, allerdings empfinde ich es so als sauberer und die Interfaces lassen sich sprechend benennen (ohne Weasel Words, also Füllwörter; tollen Artikel dazu gibt es von Johannes).
Danach benötigen wir ein Interface für die Webservice-Kommunikation, welches ich an dieser Stelle leer lasse:
1: public interface IERPCommunication
2: {
3:
4: }
Was ich ebenfalls zu den Kontrakten zähle (vergleicht dazu diesen Artikel), ist das DTO MyHecoUser, welches später die gefüllten Daten zum Austausch innerhalb von PHP enthalten soll.
1: public class MyHecoUser
2: {
3: public readonly int Kundennummer;
4: public readonly string Session;
5: //und diverse weitere
6: }
Zu guter Letzt benötigen wir noch die Implementierung, die so aussehen könnte. Je nach Umfang und Größe des Codes, wäre es auch durchaus denkbar, die Implementierung auf verschiedene Klassen aufzusplitten. Aber in diesem Fall implementiert eine Klasse die 3 Interfaces aus dem Code Snippet ganz oben.
1: public class MyHeco : IAuthenticateMyHecoUser, ILogoutMyHecoUser, IResetMyHecoPassword
2: {
3: readonly IERPCommunication _webservice;
4:
5: public MyHeco(IERPCommunication webservice)
6: {
7: _webservice = webservice;
8: }
9:
10: public MyHecoUser Login(string email, string password)
11: {
12: throw new System.NotImplementedException();
13: }
14:
15: public void Logout(string session)
16: {
17: throw new System.NotImplementedException();
18: }
19:
20: public void ResetPassword(string email)
21: {
22: throw new System.NotImplementedException();
23: }
24: }
Durch die Abstraktion der Webservice-Kommunikation hinter einem Interface und dem Injizieren im Konstruktor, können die Kollegen nun für den Test einen selbstgeschriebenen Fake hineinreichen. Ich validiere hier zugegebenermaßen den Parameter nicht, da ich in unserer Architektur auf IoC mit Castle.Windsor setze. Die Benennung spricht mich persönlich auch an, da sie sehr aussagekräftig ist:
MyHeco.Login oder Myheco.Logout sind eindeutig. Die Kohäsion scheint mir ebenfalls recht hoch, da die 3 möglichen von der Fachabteilung definierten MyHeco Aktionen in einer Klasse gekapselt sind. Trotzdem wäre eine spätere Auftrennung sehr einfach, da in der konsumierenden Klasse nur das konkrete Interface reingegeben wird. Beispiel: In einer Bestellung muss sich der Kunde erst einloggen. Also bekommt die Logik für eine Bestellung im Konstruktor ein IAuthenticateMyHecoUser reingereicht. Ist in Zukunft die Implementierung nicht mehr in der Klasse MyHeco, ändert sich die Logik der Bestellung in keiner Weise.