it-swarm.com.de

Die Verwendung eines "Pass-Through (God) Service" ist schlecht, oder?

Mein Team hat eine neue Service-Schicht in unserer Anwendung entwickelt. Sie haben eine Reihe von Diensten erstellt, die ihre Schnittstellen implementieren (z. B. ICustomerService, IUserService usw.). Das ist soweit ziemlich gut.

Hier wird es etwas seltsam: Wir haben eine Klasse namens "CoreService", die so aussieht:

// ICoreService interface implements the interfaces of 
// all services in the system 100+ 
public class CoreService : ICoreService 
{
    // I don't like these lazy instance variables. I think they are pointless
    private readonly Lazy<CustomerService> _customerService; 
    private readonly Lazy<UserService> _userService;

    public CoreService()
    {
        // These violate the Dependency inversion principle. 
        // It also news up its dependencies, which is bad.
        _customerService = new CustomerService();
        _userService = new UserService();

        // And so forth
    }

    #region ICustomerService
    public long? GetCustomerCount()
    {
        return _customerService.GetCustomerCount();
    }
    #endregion

    #region IUserService
    public User GetUser(int userId, int customerId)
    {
        return _userService.GetUser(userId, customerId);
    }
    #endregion

    // ...
    // And 100 other regions for all services
}

Die Argumentation des Teams ist, dass Controller in der konsumierenden Anwendung CoreService leicht instanziieren und ihre Dienste nutzen können, und es würde keine Leistungsprobleme verursachen, da alles "Lazy" ist.

Ich habe versucht zu erklären, dass dies ein schlechtes Design ist, weil:

  1. Wir verletzen das Prinzip der Abhängigkeitsinversion, indem wir jede einzelne Abhängigkeit und ihre Abhängigkeiten faul instanziieren.
  2. Als Ergebnis von # 1 eliminieren wir die Testbarkeit unserer Dienstleistungen. Wir können die Scheinabhängigkeit unserer Dienste nicht länger aufheben und sie für Unit-Tests einspeisen.
  3. CoreService scheint mir nur ein "God Object" -Antimuster zu sein.
  4. Wir sollten nicht einmal etwas in unseren Controllern instanziieren. Wir sollten nur die erforderlichen Abhängigkeiten der Controller einfügen. (Wenn zum Beispiel CustomerController fünf verschiedene Dienste benötigt, injizieren Sie diese einfach über den Konstruktor!)

Ist mein Argument gültig? Gibt es andere Verstöße gegen Best Practices, die mir hier fehlen? Jede Eingabe wäre hier sehr dankbar.

Bearbeiten: Ich habe den Titel aktualisiert, da diese Frage als Duplikat markiert wird. Dieser Dienst ist nicht unbedingt ein Gott-Objekt, sondern ein "Passthrough" - oder ein Fassadendienst. Ich entschuldige mich für den Fehler.

45
Vin Shahrdar

Dies ist kein Gott Objekt .

Es scheint so, als ob es so viel hier gibt, aber in gewisser Weise tut es überhaupt nichts. Hier gibt es keinen Verhaltenscode. Dies ist kein allmächtiger Gott, der alles tut. Es findet einfach alles. Es ist überhaupt weniger ein echtes Objekt als vielmehr eine Datenstruktur.

Dieses Muster hat einen genaueren Namen: Service Locator . Es steht in starkem Kontrast zu Dependency InjectionFowler Muster.

Ihre Beschwerde über die Testbarkeit ist gültig, aber das Problem ist größer. Das Hauptprinzip, gegen das hier verstoßen wird, ist das Interface Segregation Prinzip.

Hier ist der Grund: Wenn ich zu dieser Sache komme, um das Problem Dependency Inversion zu überarbeiten, würde ich einfach aufhören, CoreService hart zu codieren. Ich würde es durch einen Parameter ersetzen, der übergeben werden muss. Können Sie sehen, warum das nicht wirklich hilft?

Abgesehen davon, dass es ärgerlich ist, einen CoreService -Parameter zu sehen, der an alles übergeben wird, behebt dies das Abhängigkeitsproblem nicht wirklich, da das Sehen, dass ein Objekt CoreService benötigt, mir Null über seine tatsächlichen Bedürfnisse sagt, weil CoreService bietet Zugriff auf alles und jeden. Wir externalisieren Abhängigkeiten, um die Bedürfnisse klar zu machen.

Wenn wir dem Prinzip der Schnittstellentrennung folgen, sehen wir, dass ein Objekt, das gerade CoreService benötigt, tatsächlich 5 von 100 Dingen benötigt, die es bereitstellt. Was diese 5 Dinge brauchen, ist ein guter beschreibender Name.

Wenn ich diesen Namen sehe, weiß ich, was dieses Ding braucht. Ich kann Wege finden, um diese 5 Dinge bereitzustellen. Es gibt zwei oder 42 Möglichkeiten, diese fünf Dinge bereitzustellen. Eine dieser Möglichkeiten könnte ein Test sein, aber bei dieser Idee geht es um viel mehr als nur um das Testen. Das Testen macht dieses Problem nur schmerzlich offensichtlich.

Um diesen Namen anzugeben und einen Konstruktor mit 5 Parametern zu vermeiden, können Sie ein Parameterobjekt einführenrefactoring.com , c2.com vorausgesetzt, diese 5 Dinge repräsentieren eine zusammenhängende Idee mit bitte einem guten Namen. (Es können 2 Ideen sein, die 2 Namen benötigen, die sich in diesen 5 Dingen verstecken. Wenn dies in Ordnung ist, brechen Sie sie auf und benennen Sie sie).

Sie könnten versucht sein, dieses Parameterobjekt wiederzuverwenden, wenn Sie ein anderes Objekt finden, das es benötigt. Angenommen, das Objekt benötigt noch etwas anderes. Also fügen Sie dem Parameterobjekt etwas anderes hinzu. Auch wenn das erste Objekt dies nicht benötigt. Nun sind wir auf dem Weg, wieder einen Service Locator zu erstellen. Sie stoppen dies, indem Sie Dinge nicht akzeptieren, die Sie nicht brauchen. Darum geht es beim Prinzip der Schnittstellentrennung.

Ein anderes Muster, das sich hier versteckt, scheint Singleton zu seinc2.com. Die Abhängigkeitsinjektion bietet eine wunderbare Alternative dazu. Erstellen Sie einfach einmal das, was Sie in main benötigen, und übergeben Sie Verweise auf das, was Sie erstellt haben, an alles, was es benötigt. Sobald Sie Ihr Diagramm langlebiger Objekte erstellt haben, rufen Sie eine Methode auf, um das Ticken des Ganzen zu starten. Nur versuche nicht verrückt zu werden . Es ist in Ordnung, dies mit Fabriken und anderen Schöpfungsmustern aufzubrechen, solange Sie Schöpfungs- und Verhaltenscode getrennt halten.

Um andere zu überzeugen, müssen Sie zeigen, wie dieses Problem behoben werden kann. Machen Sie einige Dinge, die ihre Bedürfnisse ausdrücken, und schreiben Sie Code, der diese Bedürfnisse erfüllt. Eine einfache Fabrik mit einem guten Namen und einer eigenen Datei, in der man leben kann, erledigt dies oft. Wenn Sie nicht mehr weiterkommen müssen, lassen Sie die Fabrik dieses Problem lösen. Jetzt ist sich Ihr Objekt, einschließlich seiner Klassendatei, ihres Unsinns glücklicherweise nicht bewusst und es ist Ihnen egal, wann Sie den Rest reparieren.

Es informiert auch über seine Abhängigkeiten, was schlecht ist.

Das ist ein bisschen simpel. Es ist schlecht, Abhängigkeiten innerhalb des Client-Objekts neu zu erstellen, wenn Sie keine Möglichkeit bieten, sie zu überschreiben. Sie scheinen C # zu verwenden. C # hat Argumente benannt ! Dies bedeutet, dass Sie eine Abhängigkeit mit einem optionalen Argument erfüllen können. Stellen Sie nur sicher, dass Sie einen guten Standardwert dafür haben. Gute Standardwerte sollten mit Bedacht gewählt werden. Verwenden Sie keine, die Menschen überraschen.

Wie @JimmyJames hervorhebt, ist das hirnlos faule Laden alles andere als ideal. Es beschleunigt die Dinge nicht wirklich. Es ändert sich nur, wenn Sie dafür bezahlen. Es kann unvorhersehbar machen, wenn Sie dafür bezahlen, und es kann schwierig sein, Konfigurationsprobleme zu diagnostizieren. Es ist im Wesentlichen das Caching-Problem. Weithin als eines der schwierigsten Probleme in der Informatik anerkannt (nachdem man den Dingen gute Namen gegeben hat). Also bitte nicht gedankenlos benutzen.

49
candied_orange

Wir haben so etwas wie einen tatsächlichen Gottesdienst.

Der Zugriff auf das gesamte Backend erfolgt über das Frontend über eine einzige Schnittstelle mit etwa 15 Methoden, die alle Stream als Argument verwenden (da es sich tatsächlich um ein Remoting für einen anderen Prozess handelt). Jetzt ist es lächerlich, gegen die Stream-API zu programmieren, also haben wir das genau einmal gemacht. Das Serviceobjekt ist dieses einzelne Objekt mit denselben 15 Methoden und einigen zusätzlichen, die darauf hinauslaufen, Argumente zu transformieren und eines der anderen aufzurufen. Das Serviceobjekt wird nach Belieben aus der Backend-Schnittstelle und einer Schnittstelle für ein Objekt erstellt, das den angemeldeten Benutzer beschreibt.

Der Gottesdienst scheint alles zu tun. Tatsächlich werden Anforderungen für etwa 500 Zeilen serialisiert und Antworten deserialisiert. Die Entwurfsphilosophie der Methoden besteht darin, dass ein Methodenaufruf eine Transaktion ist und der Client eine beliebige große Speichertransaktion erstellen kann, indem er eine IEnumerable von zu speichernden Objekten übergibt.

Die API hat tatsächlich mehrere Implementierungen, einschließlich zwei drei verschiedene Mocks für Unit-Tests. Der Gottesdienst hat genau eine Implementierung und implementiert keine Schnittstellen.

Ich denke, die Idee, all diese separaten Dienste zu haben, ist einfach albern, und das Erstellen und Injizieren all dieser Dienste ist noch alberner. Zumindest macht dieser Gottesdienst sie alle faul, so dass es viel einfacher ist, damit umzugehen.

Ich habe noch nie ein gutes Design gesehen, bei dem es eine gute Idee war, die Dienste einzeln auf Konfigurationsebene auszutauschen. Sie wollten sie immer in großen Stücken (oder wahrscheinlicher auf einmal) gegen eine der wenigen gültigen Konfigurationen austauschen, daher hat dieses God Service-Design wenig Nachteile. Es wäre leicht an die Handhabung einer zu implementierenden Implementierung aller zu ladenden Komponentendienste basierend auf einem einzelnen Konfigurationseintrag anzupassen. Da unnatürliche Chimärenkonfigurationen nicht mehr existieren können, sind weniger Kopfschmerzen zu unterstützen.

2
Joshua

In einfachen Worten, wie ich Ihr Problem verstanden habe ... Sie haben viele interne Dienste und dann einen anderen externen Dienst, der zur Interaktion und zum Aufrufen all dieser internen Dienste verwendet wird.

In der Welt der Zwiebelarchitektur nennen wir interne Dienste Domänendienste und externe Dienste als Anwendungsdienste. Wie im Bild unten gezeigt,

(enter image description here

Es ist immer eine schlechte Praxis, alle internen Dienste von einem einzigen externen Dienst aus anzurufen.

Das Konzept ist, dass externe\Anwendungsdienste nicht schlecht sind, soweit sie logisch sinnvoll sind, und nicht nur eine Dump-Station für alles.

Falls Ihre Domänendienste voneinander abhängig sind, können Sie sie in einen dritten Dienst extrahieren, der sie ordnungsgemäß benennt, und gemeinsam genutzte Methoden in sie einfügen.

0
Mathematics