PHP Suche Pattern für API Differenzierung

[ChAoZ]

Rear Admiral
Registriert
Jan. 2010
Beiträge
5.239
Hallo zusammen,

ich bin grad dran an einem "Mock-Service" welcher unser Backend (Oracle) mockt.
Das Problem ist, es gibt nur einen Endpoint, an welchen viele Daten geschickt werden, u.a. der Webservice welcher aufgerufen werden soll. In dem Payload ist also die "Funktion" enthalten welche auf der Oracle aufgerufen werden.

Nun muss ich im Mock-Service diese Parameter auswerten und den richtigen Webservice "mocken" welcher dort benötigt wird.
Hoffe soweit ist alles verständlich.

Aktuell mache ich das mit einer Factory.
PHP:
class WebserviceFactory
{
    public static function factory(Request $request): Webservice
    {
        $webserviceName = static::getWebserviceNameByFunctionName($request);
        switch ($webserviceName) {
            case 'FETCH_EXAMPLE_DATA__BY_ID':
                $webservice = new FetchExampleDataByIdWebservice();
                break;
            case 'FETCH_EXAMPLE_DATA__BY_NAME':
                $webservice = new FetchExampleDataByNameWebservice();
                break;
            default:
                throw new InvalidArgumentException("Webservice $webserviceName is undefined", ResultCode::GENERAL_ERROR);
        }

        return $webservice;
    }

    private static function getWebserviceNameByFunctionAndShaping(Request $request): string
    {
        return sprintf('%s__%s', $request->function, $request->variation);
    }
}

Es läuft. Es ist erweiterbar, es ist lesbar aber es gefällt mir dennoch nicht.
Später wird das Switch-Case sicherlich um die 30 Cases haben, das wird dann unübersichtlich und spätestens dann muss die Stelle nochmal angefasst werden.

Welche Pattern eignen sich dafür?

Sowas wie das, gefällt mir aber auch nicht so recht...
PHP:
$webservice = $request->function . '__' . $request->variation;
if (class_exists($webservice)) {
    return new $webservice();
}

Danke fürs Feedback.
 
Mir fällt spontan nichts ein, was das deutlich verschönern könnte.
Wie du sagst, es ist erweiterbar, es ist lesbar, du hälst die Switch-Cases schön kurz.

Das Problem ist halt die schlechte API. Ein Endpunkt für alles und dann magisch über die Payload unterschiedliche Services ansteuern ist halt mieses Design, und das führt dann dazu, dass man z.B. wie hier in Tests nicht ordentlich getrennt die Mocks aufbauen kann.

Kürzer gestalten könnte man es, wenn man da mit Reflection draufgeht und dadurch kein Switch mehr braucht, sondern die Services direkt anhand des strings vom Service-Namen instantiiert. Aber das würde ich hier eher als Code Smell sehen, führt nur zu mehr Magie im Code und ist schlechter zu debuggen.
 
  • Gefällt mir
Reaktionen: guzzisti
Das "Problem" wirst du immer in irgendeiner Form haben. Du willst anhand eines Inputs entscheiden welcher der 30 Services angesprochen werden soll. Ob man die Unterscheidung anhand der URL selbst oder anhand des Payloads macht, spielt keine Rolle.

Der Unterschied ist also eigentlich nur: Übernimmt das Framework das switch-case anhand der URL, oder machst du das switch-case von Hand anhand des Payloads.

Ich würde mir da auch keine großen Gedanken machen. Weniger Code kann es nicht werden, weil diese Zuordnung immer irgendwo stattfinden muss. Man kann also nur erreichen, dass es "anders" aussieht bzw. anders verteilt wird.
 
Match fällt mir da ein, finde ich übersichtlicher als das Switch
PHP:
$webservice = match ($webserviceName)
{
    'FETCH_EXAMPLE_DATA__BY_ID' => new FetchExampleDataByIdWebservice(),
    'FETCH_EXAMPLE_DATA__BY_NAME' => new FetchExampleDataByNameWebservice()
};

return $webservice;
 
  • Gefällt mir
Reaktionen: Physikbuddha, guzzisti, Kalsarikännit und eine weitere Person
Okay danke euch. Ich werde noch etwas weiter überlegen aber ich sehe da auch keine andere Möglichkeit.
Ob SwichCase oder Match ist eigentlich das gleiche was den Lesefluss angeht, nimmt sich nicht viel aber ja... aber bitte mit einem default Wert ;)
 
Technisch würd mir da eine Map einfallen, um das Switch bzw. das match zu vermeiden. Praktisch weiß ich aber nicht, was du vorhast. Ein Mock verbirgt normalerweise ungewollte Funktionen, um die eigentliche Funktionalität testen zu können.

Schau dir auch gerne mal:
https://refactoring.guru/design-patterns/strategy
https://www.tutorialspoint.com/design_pattern/strategy_pattern.htm
https://www.freecodecamp.org/news/a-beginners-guide-to-the-strategy-design-pattern/
https://en.wikipedia.org/wiki/Strategy_pattern
https://www.linkedin.com/advice/0/how-do-you-use-strategy-pattern-php-switch-between
sowie
https://stackoverflow.com/questions/3834091/strategy-pattern-with-no-switch-statements
an.
 
  • Gefällt mir
Reaktionen: [ChAoZ]
Spricht etwas dagegen auf ein conditional zu verzichten und stattdessen gleich einen validen Klassennamen aus den Parametern function und variation zu bilden, den du dann einfach instanziierst?
So wie es aussieht, lassen sich die Funktionsnamen mit ihren Varianten ja zumindest in deinem Beispiel direkt mappen.

PHP:
public static function factory(Request $request): Webservice
{
    $className = self::toClassName($request->function, $request->variation);

    if (!class_exists($className)) {
        throw new InvalidArgumentException("Webservice $className is undefined", ResultCode::GENERAL_ERROR);
    }
 
    return new $className();
}

private static function toClassName(string ...$strings): string
{
    $result = '';

    foreach ($strings as $str) {
        $str = str_replace('_', ' ', strtolower($str));
        $str = str_replace(' ', '', ucwords($str));
        $result .= $str;
        
       return $result;
    }
}

Wenn jeder WebService im gleichen Format daherkommt (es existiert immer eine Variation) dann reicht auch ein Oneliner, inline oder als Funktion
PHP:
private static function toClassName(string $str): string
{
    return str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($str))));
}

Damit das funktioniert, muss nur die Namensgebung konsistent sein/bleiben. Im Controller/Request Handler muss dann nie wieder etwas editiert werden, die Mapping Regel ist ja auch einfach zu verstehen und umzusetzen.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: [ChAoZ] und CyborgBeta
Autokiller677 schrieb:
Das Problem ist halt die schlechte API.
... wenn die Java-Leute von Oracle mit ihrem SOAP-Gehirn versuchen REST-APIs zu designen ...
 
  • Gefällt mir
Reaktionen: [ChAoZ] und lokked
Zurück
Oben