Ankündigung

Einklappen
Keine Ankündigung bisher.

Service Locator ein Anti Pattern

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Service Locator ein Anti Pattern

    Hallo zusammen


    Da ich mein Design mal wieder in Frage gestellt habe, habe ich mir wiedereinmal etliche Resourchen angeschaut. Doch das ganze scheint gar nicht so einfach zu sein in der Praxis wie eben immer theoretisch beschrieben.

    Ich habe z.b. für meine Models eine Action Klasse, welche ich den Container injectiere um an diverse Services zuzugreifen im Fall wenn man einen Service benötigt wie z.b einen Logger.

    Die services werden nur erstellt bei Verlangen, was ja auch gut ist.

    PHP-Code:
    class Article extends Action {

        public function 
    create(array $data) {

            
    // dependecies:
            // validator
            // languages

            // z.b. Logger
            // z.B. ImgCreator
        
    }
    }

    $action = new Article($storageInterface$container);
    $created $action->create(
        
    'author' => 'me',
        
    'title' => [
            
    => 'Title de',
            
    => 'Title en'
        
    ]
    );

    // oder
    $created $action->create($request->post()->getAll());

    if (!empty(
    $created)) {
        
    // success returns the created model array
    } else {
        
    // error



    Dann stiess ich auf folgendes bei PHP-Di.org http://php-di.org/doc/best-practices.html:

    Dass der Service Locator Pattern ein Anti-Pattern ist und es wird somit zu Dependency Injection geraten.


    Dies hat zwar seine Vorteile aber eben auch Nachteile wie, dass alle Dedencies Objekte erstellt werden obwohl man diese vielleicht in einer Methode gar nicht braucht.


    Symfony sowie Laravel stellen den Container ja auch in den Controllern zur Verfügung und wird ja auch benutzt.

    Mich würde gerne eure Meinung dazu interessieren. Besten Dank im voraus.


  • #2
    Ich habe mich da auch kürzlich durchgewühlt und bin ebenso über diesen Unterschied zwischen Laravel (und anscheined auch Symfony) und der Empfehlung von php-di gestolpert.

    Meine aktuelle Lösung ist:
    - Abhängigkeiten die in jeder Action gebraucht werden, injeziere ich im Konstruktor (bei mir ist das aktuell ein "Response" Objekt)
    - Alle anderen Abhängigkeiten werden erst in der Action injeziert. Meine Actions werden von meinem DI Container aufgerufen (php-di ) und damit steht mir auch an dieser Stelle Autowiring zur Verfügung. Gegebenenfalls vorhandene Routenparameter werden dann in entsprechende skalare Gegenstücke übergeben.

    Beispiel:

    PHP-Code:
    class Controller
    {
        protected 
    $response;

        public function 
    __construct(Container $response)
        {
            
    $this->response $response;
        }

    PHP-Code:
    class AccountController extends Controller
    {
        public function 
    testAction(string $test,
                                   
    AccountRepository $accountRepositorySession $session) {
            
    var_dump($accountRepository$session$test);
        }

    Kommentar


    • #3
      Hey danke dir ChromOxid für den Tip. Das wäre natürlich ein Lösung, jedoch eben auch nicht optimal.

      Weil 1.:
      Benötigt man viele "Services" was bei mir in der "ActionModel" der Fall hat man viel Aufwand wenn man eine Action ausführen will.

      2:
      In der Action benötige ich manchmal nur ein Service, wenn ein gewisser Parameter vorhanden ist. Dann wären wir wieder bei einem unötigen Objekt.

      PHP-Code:
      class Article extends Action {

          public function 
      create(array $data) {

              
      $dataBag = new DataBag($data);

              if (
      $dataBag->has('images')) {

                  
      $fileAction $this->getService('Media/FileAction');
              }
          }

      Deshalb das ganze ist eben in der Praxis nicht so leicht. Der Service Locator gefällt mir eigentlich ganz gut und löst auch Probleme.

      Kommentar


      • #4
        Ich Persönlich nutze den DI Ansatz, die Vorteile sehe ich da, dass ich von Außen bereits sehe welche Abhängigkeiten die Klasse hat.

        Manchmal erstelle ich mir extra php Dateien die einmal Importe oder so machen müssen oder irgendwelche Checks etc. Ohne Routing, mini Scripte die an der Hauptlogik Vorbei ausgeführt werden, da ist es Praktisch anhand der Parameter zu sehen was ich alles übergeben muss.

        Nachteil hat es für mich beim erstellen des Controllers. Dieser Muss auch als Service Registriert werden und dort müsste man ihm die Unterservices injezieren. Ich kann somit nicht "Lazy Routing" anwenden.

        Das mit dem "unnötigen Objekt" die Objekte sind in anonymen Funktionen gekapselt die werden nur dann verwendet, wenn diese auch gebraucht werden. Wenn du jetzt Media/FileAction übergeben würdest, würde da erstmal ncihts passieren.


        Es ist ein Spannendes Thema, ich selbst habe noch keine Lösung die mir gefällt, einfach nur einen Container an den Controller zu übergeben halte ich für falsch.
        apt-get install npm -> npm install -g bower -> bower install <package> YOLO https://www.paypal.me/BlackScorp

        Kommentar


        • #5
          Danke Dir BlackScorp für die Erläuterungen, es ist auf jedenfall ein spannedes Thema, welches mich aber fast zum verzweifeln bringt

          Das mit dem "unnötigen Objekt" die Objekte sind in anonymen Funktionen gekapselt die werden nur dann verwendet, wenn diese auch gebraucht werden. Wenn du jetzt Media/FileAction übergeben würdest, würde da erstmal ncihts passieren.
          Versteh ich nicht ganz, das Object muss doch erstellt werden, wenn ich dies in der Methode injektiere:

          PHP-Code:
          $product->create(FileAction $fileAction$request->post()->getAll()); 

          Irgendwie sehe ich ja die Vorteile von DI auch, welche eigentlich auch Sinn machen.. Das Problem ist eben der Controller. Ansonsten habe ich mit dem DI Pattern keine Probleme. Ja dann muss ich mein Controller Design wirklich überarbeiten, vielleicht finde ich ja eine Lösung mit der ich Leben kann

          Kommentar


          • #6
            was ich mir gut vorstellen kann, wäre das "autowiring" von php-ci damit könnte man "Lazy Routing" auch einbauen, somit wären dir alle deine getService Aufrufe ersparrt, ich glaube autowiring von php-ci lässt sich sogar cachen. Ich selbst habe aber es noch nie ausprobiert gehabt.
            apt-get install npm -> npm install -g bower -> bower install <package> YOLO https://www.paypal.me/BlackScorp

            Kommentar


            • #7
              Also in den Symfony Best Practices liest man:
              ..If you extend the base Controller class, you can access services directly from the container via $this->container->get() or $this->get(). But instead, you should use dependency injection to fetch services...
              https://symfony.com/doc/current/best...ching-services

              Kommentar


              • #8
                Ja jedoch das autowiring bringt mir in den meisten Fällen nichts da ich meistens Interfaces implemetiere.

                Ich habe meinen eigenen Container deshalb erstellt, welcher mir ein Definition File (Factory Klasse) gerade lädt - je nach Service - falls vorhanden natürlich.

                Zeichen32 Danke für den Link.

                Kommentar


                • #9
                  Zitat von strub Beitrag anzeigen
                  Ja jedoch das autowiring bringt mir in den meisten Fällen nichts da ich meistens Interfaces implemetiere.
                  Beim Autowire schaut er nach interfaces und guckt ob er eindeutig eine Implementierung findet, wenn ja, wird eine neue Instanz davon erstellt. Nur wenn du mehr als ein mal ein Interface implementierst, musst du es dann konfigurieren und wenn du Parameter übergeben musst die Strings sind und keine Interfaces

                  apt-get install npm -> npm install -g bower -> bower install <package> YOLO https://www.paypal.me/BlackScorp

                  Kommentar


                  • #10
                    Beim Autowire schaut er nach interfaces und guckt ob er eindeutig eine Implementierung findet
                    Bist Du dir da sicher. Ich habe dies gestern php-di getested und musste eine Definition erstellen. Woher soll der den Wissen welches interface er übergeben soll?

                    Und wie du schon sagtest bei Parametern oder eben auch bei Methoden. Denn die Methoden sind ja optional.
                    Da eben meine Services meisten mit Methoden konifgurierbar sind müsste ich sowieso, eine Definition setzen, was mir eben nicht gefällt. Denn wenn der Service gar nicht gebraucht wird, hat man trotzdem das ganze Setup im Container.

                    Deshalb wird bei meinem Container erst bei ->get('Mailer') die Konfiguration erstellt ohne vorher eine Defintion zu definieren, diese wird automatisch je nach Namespace path und container konigfuration gesucht


                    z.B
                    $container->get('Mailer/Mail')

                    Mailer/Mail
                    Mailer/Config/ServiceProvider

                    Kommentar


                    • #11
                      Zitat von strub Beitrag anzeigen

                      Bist Du dir da sicher. Ich habe dies gestern php-di getested und musste eine Definition erstellen. Woher soll der den Wissen welches interface er übergeben soll?
                      Nein, du kannst per Reflection Class dir die Interfaces einer Klasse holen und dann ein ArrayMap aufbauen

                      PHP-Code:
                      <?php

                      interface Foo{

                      }

                      class 
                      Bar implements Foo{}

                      $reflection = new ReflectionClass('Bar');


                      echo 
                      'Parents: '.var_dump($reflection->getInterfaces());
                      anschließend kannst du nachschauen was als Typehint steht und dann gucken, wenn es ein interface ist, dann gucke ob es exakt eine Implementierung hat, wenn es kein Interface ist, dann übergebe die Implementierung.

                      Zitat von strub Beitrag anzeigen
                      Und wie du schon sagtest bei Parametern oder eben auch bei Methoden. Denn die Methoden sind ja optional.
                      Da eben meine Services meisten mit Methoden konifgurierbar sind müsste ich sowieso, eine Definition setzen, was mir eben nicht gefällt. Denn wenn der Service gar nicht gebraucht wird, hat man trotzdem das ganze Setup im Container.

                      Deshalb wird bei meinem Container erst bei ->get('Mailer') die Konfiguration erstellt ohne vorher eine Defintion zu definieren, diese wird automatisch je nach Namespace path und container konigfuration gesucht


                      z.B
                      $container->get('Mailer/Mail')

                      Mailer/Mail
                      Mailer/Config/ServiceProvider
                      PHP-Code:
                      <?php

                      class Article extends Action {
                          public function 
                      __construct(SomeServiceInterface $fileAction)
                          {
                              
                      $this->fileAction $fileAction;
                          }

                          public function 
                      create(array $data) {

                              
                      $dataBag = new DataBag($data);

                              if (
                      $dataBag->has('images')) {

                                  
                      $fileAction $this->fileAction;
                                  
                      $fileAction->doSomething(); //hier wird erst php etwas tun, davor wird nur referenz auf die Funktion weitergereicht
                              
                      }
                          }
                      }
                      In meisten Beispielen wird statt get('Mailer/Mail') dann get(MailerInterface::class) eingesetzt, so dass du nicht mit Strings hantieren musst und deine IDE dir Code completion zeigt
                      apt-get install npm -> npm install -g bower -> bower install <package> YOLO https://www.paypal.me/BlackScorp

                      Kommentar


                      • #12
                        Zitat von strub Beitrag anzeigen
                        Benötigt man viele "Services" was bei mir in der "ActionModel" der Fall hat man viel Aufwand wenn man eine Action ausführen will.
                        Mit PHP-DI hast du damit eigentlich überhaupt keinen Aufwand:

                        PHP-Code:
                        $controller $container->get(MyController::class);
                        $container->call([$controller'myAction']); 

                        Zitat von strub Beitrag anzeigen
                        In der Action benötige ich manchmal nur ein Service, wenn ein gewisser Parameter vorhanden ist. Dann wären wir wieder bei einem unötigen Objekt.
                        Da stellt sich zunächst die Frage warum dich dieses Objekt überhaupt stört. Wenn die Erzeugung "optionaler Abhängigkeiten" kostspielig ist, dann nutze Lazy Injection. Wenn es zu viele "optionale Abhängigkeiten" werden, dann wird es vermutlich Zeit die Aufgaben auf mehrere Controller zu verteilen. Services bei Methoden-Aufrufen zu injizieren ist zwar hässlich, im Rahmen eines Controllers aber zu vernachlässigen. Das von dir geschilderte Problem könntest du umgehen indem du bspw. eine Factory verwendest, deine UseCases (mit Bilder, ohne Bilder) auf zwei Controller verteilst oder die Erzeugung des Controllers "interceptest". Das stinkt aber alles nach over-engineering.

                        Zitat von strub Beitrag anzeigen
                        Ja jedoch das autowiring bringt mir in den meisten Fällen nichts da ich meistens Interfaces implemetiere.
                        Gerade das ist doch *der* UseCase für einen DIC. Du legst im Voraus fest, welche Implementierungen du für welche Interfaces nutzen willst und entwickelst nur noch gegen Interfaces. Kleines Beispiel: du hast ein MailerInterface. Während der Entwicklung soll der DebugMailer verwendet werden, im Produktivbetrieb hingegen der SmtpMailer. Ebenso ist es denkbar dass du ein CacheInterface hast und ControllerA den FileSystemCache haben will, aber ControllerB den RedisCache möchte. Das lässt sich mit PHP-DI super easy konfigurieren. Welche Implementierung man haben will, muss man aber manuell festlegen, denn PHP-DI scannt natürlich nicht deinen ganzen Code nach passenden Implementierungen ab.

                        Zitat von strub Beitrag anzeigen
                        Da eben meine Services meisten mit Methoden konifgurierbar sind müsste ich sowieso, eine Definition setzen, was mir eben nicht gefällt. Denn wenn der Service gar nicht gebraucht wird, hat man trotzdem das ganze Setup im Container.
                        Du kannst die Erzeugung von Services auch an Factories delegieren - damit entschlackst du deine Konfiguration. Diese Factories werden auch nur dann instanziiert wenn der entsprechende Service angefordert wird. Ansonsten cached PHP-DI deine Konfiguration, sodass man in Sachen Performance keinen spürbaren Impact hat.

                        Zitat von strub Beitrag anzeigen
                        Deshalb wird bei meinem Container erst bei ->get('Mailer') die Konfiguration erstellt ohne vorher eine Defintion zu definieren, diese wird automatisch je nach Namespace path und container konigfuration gesucht
                        Was ist der Vorteil?

                        Zitat von BlackScorp
                        Nein, du kannst per Reflection Class dir die Interfaces einer Klasse holen und dann ein ArrayMap aufbauen
                        Ich glaube du verdrehst da was, denn das geht nur wenn du im Voraus die Klasse kennst die das angeforderte Interface implementiert. PHP-DI kennt deine Klassen aber nicht und wird auch nicht dein Dateisystem nach diesen Klassen durchsuchen. Du musst also für jedes Interface konkret festlegen welche Implementierung du verwenden willst. Wenn deine Abhängigkeit bereits eine Klasse (und kein Interface) ist brauchst du hingegen gar nichts zu tun, das regelt dann das Autoloading.

                        Kommentar


                        • #13
                          Zitat von lottikarotti Beitrag anzeigen

                          Ich glaube du verdrehst da was, denn das geht nur wenn du im Voraus die Klasse kennst die das angeforderte Interface implementiert. PHP-DI kennt deine Klassen aber nicht und wird auch nicht dein Dateisystem nach diesen Klassen durchsuchen. Du musst also für jedes Interface konkret festlegen welche Implementierung du verwenden willst. Wenn deine Abhängigkeit bereits eine Klasse (und kein Interface) ist brauchst du hingegen gar nichts zu tun, das regelt dann das Autoloading.
                          http://php-di.org/doc/autowiring.html#limitations ah ja sehe ich auch gerade, danke, da habe ich wohl PHP-DI zu viel Magic abverlangt..
                          apt-get install npm -> npm install -g bower -> bower install <package> YOLO https://www.paypal.me/BlackScorp

                          Kommentar


                          • #14
                            Zitat von BlackScorp Beitrag anzeigen
                            http://php-di.org/doc/autowiring.html#limitations ah ja sehe ich auch gerade, danke, da habe ich wohl PHP-DI zu viel Magic abverlangt..
                            Na ja, was soll PHP-DI denn auch machen? Die Konfiguration ist letztlich aber auch kein Problem, sie gefällt dem TE nur nicht

                            Kommentar


                            • #15
                              Zitat von lottikarotti Beitrag anzeigen
                              Na ja, was soll PHP-DI denn auch machen?
                              Alles, wenn ich meine Einstellungen sehe, habe ich in 95% der Fälle eine 1:1 beziehung, wirklich selten gibt es mal mehr als eine Implementierung, dafür muss ich aber die Controller auch in DI injezieren und die Objekte aufbereiten. Es ist am Anfang umständlich.
                              apt-get install npm -> npm install -g bower -> bower install <package> YOLO https://www.paypal.me/BlackScorp

                              Kommentar

                              Lädt...
                              X