Wieviel Typsicherheit?
Eine Facade ist an sich eine prozedurale Schnittstelle. Es gibt hier durchaus Parallelen zum Transaction Script-Entwurfsmuster sowie zu den MVC-Controllern, wie wir sie dank der gängigen Frameworks in den späten Nullerjahren geschrieben haben.
Sehen wir uns gemeinsam eine konkrete Fassade an. Wir definieren sie erst einmal als Interface, schließlich wollen wir ja die Schnittstelle getrennt von der Implementierung betrachten:
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\facade\howMuchTypeSafety;
interface TheFacade
{
public function someCommand(string $parameter): void;
public function someQuery(int $firstParameter, float $secondParameter): array;
}
Im Grunde haben wir in so einer Schnittstelle immer zwei Arten von Methoden, manche Menschen bezeichnen diese als Getter und Setter. Ich mag diese Bezeichnungen nicht wirklich, weil sie für mich einen zu starken Fokus auf Manipulation von Daten beziehungsweise den Zustand eines Objektes haben. Anstelle Setter könnten wir auch Mutator sagen, das betont, dass wir eine Zustandsänderung in einem Objekt intendieren. Das klingt für mich schon mal besser als "Daten verändern". Es geht aber noch besser.
Sagen wir doch statt Getter einfach Query. Damit meinen wir eine Frage, die wir stellen und auf die wir eine Antwort in Form von einem konkreten Rückgabewert erwarten. Falls Du jetzt das array
als Rückgabewert nicht gut findest, dann hast Du schon viel gelernt, möglicherweise sogar von mir.
Ich mag Array als Rückgabewert auch nicht besonders, weil ein Array in PHP kann sehr vieles sein (eine Liste, eine Map, eine Collection von Objekten usw.). Auf der anderen Seite ist es in Webanwendungen durchaus üblich, das Array beispielsweise nach JSON zu konvertieren und durch eine HTTP-Response zum Aufrufer zurückzuschicken. Ich sehe es allerdings nicht als Verantwortung einer fachlichen Fassade, JSON zurückzugeben beziehungsweise zu erzeugen. Das ist eher ein Aspekt, der für das HTTP-Framework relevant ist. Daher geben wir in der fachlichen Fassade einfach ein Array zurück, auch wenn es keine generelle Best Practice werden soll, dies zu tun.
Wichtig ist dabei:
Methoden, die Zustand verändern, bezeichne ich gerne als Command. Damit ist nicht das Command-Pattern aus dem Gang of Four-Buch gemeint, sondern ein Kommando im Sinne von CQRS oder auch CQS. Ich gebe meiner Anwendung also ein Kommando wie "verkaufe mir einen blauen ElePHPanten" "deaktiviere Benutzerkonto" oder "schalte die Photovoltaikanlage ab". Ob die Anwendung dieses Kommando dann auch ausführt, ist eine andere Frage und hängt vom allgemeinen Zustand der Welt beziehungsweise der Anwendung ab.
Wenn wir eine Fassade definieren, dann markiert diese eine wichtige Boundary (Grenze) in unserer Architektur. "Hinter" der Fassade befindet sich das Subsystem, das wir verbergen und auf das wir den Zugriff vereinfachen wollen. "Vor" der Fassade ist der Code, der unser Subsystem benutzen möchte, wir könnten auch Client-Code dazu sagen. Wenn wir jetzt Wertobjekte, die wir innerhalb unseres Subsystems definiert haben, als Parameter für die Fassade fordern, dann zwingen wir den Client-Code, mit genau diesen Wertobjekten zu arbeiten und mit eventuellen Exceptions, die deren Konstruktoren werfen, umzugehen. Das verletzt das Kapselungsprinzip unseres Subsystems. Daher finde ich es durchaus okay, in einer fachlichen Fassade rein skalare Werte als Parameter zu deklarieren. Auch das soll allerdings keine generelle Best Practice sein.
Nun wollen wir ja eigentlich möglichst viel Typsicherheit haben. Daher erstellen wir noch eine Fassade mit domänenspezifische Typen:
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\facade\howMuchTypeSafety;
interface TypeSafeFacade
{
public function someCommand(Parameter $parameter): void;
public function someQuery(
FirstParameter $firstParameter,
SecondParameter $secondParameter
): SomeQueryResult;
}
Jetzt können wir diese beiden Fassaden ineinander schachteln, um nach außen eine Schnittstelle mit skalaren Datentypen anzubieten, aber intern trotzdem Typsicherheit zu haben:
<?php declare(strict_types=1);
namespace spriebsch\tfd\designPatterns\facade\howMuchTypeSafety;
class NestedFacade implements TheFacade
{
private readonly TypeSafeFacade $facade;
public function __construct(TypeSafeFacade $facade)
{
$this->facade = $facade;
}
public function someCommand(string $parameter): void
{
try {
$this->facade->someCommand(new Parameter($parameter));
}
catch (Exception $exception) {
throw new SubsystemException($exception->getMessage(), $exception->getCode(), $exception);
}
}
public function someQuery(int $firstParameter, float $secondParameter): array
{
try {
return $this->facade->someQuery(
new FirstParameter($firstParameter),
new SecondParameter($secondParameter)
)->asArray();
}
catch (Exception $exception) {
throw new SubsystemException($exception->getMessage(), $exception->getCode(), $exception);
}
}
}
Jetzt geschieht das Wertobjekt-Mapping als Teil unseres Subsystems und ist keine Verantwortung des Client-Codes mehr.
Wir müssen natürlich, die ganzen Fehler, die auftreten können, innerhalb unserer NestedFacade
sinnvoll behandeln und können nicht einfach jegliche Exceptions aus unserer Fassade "entkommen" lassen.