Platon's Cave 藕r贸d艂o |
Tworz膮c aplikacj臋 opart膮 na komponentach - pomimo ich niezale偶no艣ci - czasami przychodzi potrzeba komunikacji mi臋dzy nimi. Zale偶no艣ci powinny by膰 ograniczone do niezb臋dnego minimum, a liczba udost臋pnionych klas jak najmniejsza. Nale偶y te偶 dba膰 o to by komunikacja mi臋dzy komponentami by艂a jednostronna. Przypadek w kt贸rym wymiana danych nast臋puje dwustronnie, 艣wiadczy o niew艂a艣ciwym rozdzieleniu odpowiedzialno艣ci w modu艂ach.
Bezpo艣rednie odwo艂ania do zewn臋trznego komponentu
╔════════════════════════════════════╗ ╔════════════════════════════════════╗
║ Component Order ║ ║ Component Product ║
╠════════════════════════════════════╣ ╠════════════════════════════════════╣
║ ┌───────┐ ║ ║ ┌───────┐ ║
║ │ Class │ ┌───────┐ ║ ║ │ Class │ ┌───────┐ ║
║ └───────┘┌───────┐ │ Class │ ║ ║ └───────┘ ┌───────┐ │ Class │ ║
║ │ Class │ └───────┘ ║ ║ ┌─────>│ Class │ └───────┘ ║
║ └───────┘ ║ ║ │ └───────┘ ║
║ ┌───────┐ ║ ║ │ ┌───────┐ ┌───────┐ ║
║ │ Class ├───────────────────────╫─────╫──────┘ │ Class │ ┌─>│ Class │ ║
║ └───────┘ ║ ║ └───────┘ │ └───────┘ ║
║ ┌───────┐ ║ ║ ┌────────────────────┤ ║
║ ┌───────┐ │ Class ├─────────╫─────╫─┤ ┌───────┐ │ ┌───────┐ ║
║ │ Class │ └───────┘ ║ ║ └─>│ Class │ └──>│ Class │ ║
║ └───────┘ ║ ║ └───────┘ └───────┘ ║
╚════════════════════════════════════╝ ╚════════════════════════════════════╝
║ Component Order ║ ║ Component Product ║
╠════════════════════════════════════╣ ╠════════════════════════════════════╣
║ ┌───────┐ ║ ║ ┌───────┐ ║
║ │ Class │ ┌───────┐ ║ ║ │ Class │ ┌───────┐ ║
║ └───────┘┌───────┐ │ Class │ ║ ║ └───────┘ ┌───────┐ │ Class │ ║
║ │ Class │ └───────┘ ║ ║ ┌─────>│ Class │ └───────┘ ║
║ └───────┘ ║ ║ │ └───────┘ ║
║ ┌───────┐ ║ ║ │ ┌───────┐ ┌───────┐ ║
║ │ Class ├───────────────────────╫─────╫──────┘ │ Class │ ┌─>│ Class │ ║
║ └───────┘ ║ ║ └───────┘ │ └───────┘ ║
║ ┌───────┐ ║ ║ ┌────────────────────┤ ║
║ ┌───────┐ │ Class ├─────────╫─────╫─┤ ┌───────┐ │ ┌───────┐ ║
║ │ Class │ └───────┘ ║ ║ └─>│ Class │ └──>│ Class │ ║
║ └───────┘ ║ ║ └───────┘ └───────┘ ║
╚════════════════════════════════════╝ ╚════════════════════════════════════╝
W tym przypadku Order Component musi mie膰 pe艂n膮 wiedz臋 na temat us艂ug jakie oferuje Product Component.
namespace CompanyName\OrderComponent\Service; use CompanyName\ProductComponent\Model\ProductInterface; use CompanyName\ProductComponent\Model\ProductFactory; final class CreateOrder { private ProductInterface $product; public function __constructor(ProductFactory $productFactory) { $this->product = $productFactory->getInstance(); } // Interfejs publiczny }
⚠️ Komponent Order musi posiada膰 wiedz臋 na temat wsp贸艂dzia艂ania ze sob膮 klas z innego komponentu, ich umiejscowienia i przeznaczenia,
⚠️ ma dost臋p do klas reprezentuj膮cych szerokie spektrum zachowa艅, gdy tak naprawd臋 potrzebuje tylko cz臋艣ci z nich,
⚠️ jest ca艂kowicie uzale偶niony od interfejsu publicznego klas zewn臋trznego modu艂u, w przypadku jego zmiany istnia艂aby mo偶liwo艣膰 modyfikacji kodu kt贸ry z nich korzysta.
Komponent Produktu kt贸ry pomimo, 偶e jest niezale偶nym bytem udost臋pnia swoje us艂ugi innym komponentom, a w zwi膮zku z tym powinien w jaki艣 spos贸b demonstrowa膰 ten zamiar. Nawi膮zuj膮c komunikacje z zewn臋trznym komponentem, nie wiemy jakie zachowania mog膮 wykracza膰 poza jego granic臋, a kt贸ry absolutnie nie powinny.
Ujawnianie zewn臋trznych us艂ug mo偶na zrealizowa膰 tworz膮c specjaln膮 klas臋 Fasady przeznaczonej tylko w tym celu. Le偶y ona na granicy komponentu i zbiera wszystkie zachowania, kt贸re mog膮 by膰 udost臋pnione klientom. Prezentuj膮c us艂ugi w ten spos贸b tworzymy niejako komunikacyjny interfejs publiczny dla innych cz臋艣ci systemu, ograniczaj膮c wiedz臋 na temat jego wewn臋trznego dzia艂ania.
Jest to oczywi艣cie wszystko w codziennych decyzjach (dobrej woli) zespo艂u deweloperskiego by przestrzega膰 z dyscyplin膮 tych zasad, poniewa偶 nie ma fizyczny barier by omin膮膰 Fasad臋.
Fasada po stronie us艂ugodawcy
╔════════════════════════════════════╗ ╔════════════════════════════════════╗
║ Component Order ║ ║ Component Product ║
╠════════════════════════════════════╣ ╠════════════════════════════════════╣
║ ┌───────┐ ║ ┌╨────┐ ┌───────┐ ║
║ │ Class │ ┌───────┐ ║ │ F │ │ Class │ ║
║ └───────┘┌───────┐ │ Class │ ║ │ │ └───────┘ ┌───────┐ ║
║ │ Class │ └───────┘ ║ │ A │ ┌───────┐ │ Class │ ║
║ └───────┘ ║ │ ├────────>│ Class │ └───────┘ ║
║ ┌───────┐ ║ │ S │ └───────┘ ║
║ │ Class ├───────────────────────╫───>│ │ ┌───────┐ ┌───────┐ ║
║ └───────┘ ║ │ A │ │ Class │ ┌─>│ Class │ ║
║ ┌───────┐ ║ │ │ └───────┘ │ └───────┘ ║
║ │ Class ├─────────╫───>│ D │ ┌──────────────┤ ║
║ ┌───────┐ └───────┘ ║ │ ├──┤ ┌───────┐ │ ┌───────┐ ║
║ │ Class │ ║ │ A │ └──>│ Class │ └──>│ Class │ ║
║ └───────┘ ║ └╥────┘ └───────┘ └───────┘ ║
╚════════════════════════════════════╝ ╚════════════════════════════════════╝
║ Component Order ║ ║ Component Product ║
╠════════════════════════════════════╣ ╠════════════════════════════════════╣
║ ┌───────┐ ║ ┌╨────┐ ┌───────┐ ║
║ │ Class │ ┌───────┐ ║ │ F │ │ Class │ ║
║ └───────┘┌───────┐ │ Class │ ║ │ │ └───────┘ ┌───────┐ ║
║ │ Class │ └───────┘ ║ │ A │ ┌───────┐ │ Class │ ║
║ └───────┘ ║ │ ├────────>│ Class │ └───────┘ ║
║ ┌───────┐ ║ │ S │ └───────┘ ║
║ │ Class ├───────────────────────╫───>│ │ ┌───────┐ ┌───────┐ ║
║ └───────┘ ║ │ A │ │ Class │ ┌─>│ Class │ ║
║ ┌───────┐ ║ │ │ └───────┘ │ └───────┘ ║
║ │ Class ├─────────╫───>│ D │ ┌──────────────┤ ║
║ ┌───────┐ └───────┘ ║ │ ├──┤ ┌───────┐ │ ┌───────┐ ║
║ │ Class │ ║ │ A │ └──>│ Class │ └──>│ Class │ ║
║ └───────┘ ║ └╥────┘ └───────┘ └───────┘ ║
╚════════════════════════════════════╝ ╚════════════════════════════════════╝
Na fasad臋 InternalCommunication zosta艂 na艂o偶ony interfejs InternalCommunicationInterface by u艂atiw膰 testowanie.
namespace CompanyName\ProductComponent\Facade; use CompanyName\ProductComponent\Model\ProductInterface; use CompanyName\ProductComponent\Model\ProductFactory; final class InternalCommunication implements InternalCommunicationInterface { private ProductFactory $productFactory; public function __constructor(ProductFactory $productFactory) { $this->productFactory = $productFactory; } /** * @inheritDoc */ public function getProduct(): ProductInterface { return $this->productFactory->getInstance(); } }
Klient Fasady nie ma wiedzy na temat tworzenia klasy ProductInterface:
namespace CompanyName\OrderComponent\Service; use CompanyName\ProductComponent\Model\ProductInterface; use CompanyName\ProductComponent\Facade\InternalCommunicationInterface; final class CreateOrder { private ProductInterface $product; public function __constructor(InternalCommunicationInterface $productFacade) { $this->product = $productFacade->getProduct(); } // Interfejs publiczny }
✔️ Komponent Product udost臋pnia interfejs publiczny innym cz臋艣ci膮 systemu, hermetyzuj膮c wiedz臋 o jego wewn臋trznym dzia艂aniu,
⚠️ komponenty konsumenckie w dalszym ci膮gu, bezpo艣rednio operuj膮 na obiektach z zewn臋trznego komponentu, oraz wiedz膮 o ich klasach.
Adaptery po stronie us艂ugobiorcy
Aby jak najbardziej uniezale偶ni膰 klasy korzystaj膮ce z us艂ug zewn臋trznego komponentu, najlepiej by艂oby operowa膰 w nich tylko na ich lokalnych odpowiednikach. Obiekt Product w r贸偶nych cz臋艣ciach systemu mo偶e charakteryzowa膰 si臋 innymi zachowaniami determinowanymi przez kontekst komponentu, dlatego najlepiej jakby ka偶dy z nich posiada艂 sw贸j interfejs dla klas reprezentuj膮cych produkty. W tym celu, z innym komponentem komunikowa膰 si臋 b臋d膮 jedynie klasy b臋d膮ce Adapterami realnych obiekt贸w zwracanych przez Fasad臋 zewn臋trznego komponentu.
╔════════════════════════════════════╗ ╔════════════════════════════════════╗
║ Component Order ║ ║ Component Product ║
╠════════════════════════════════════╣ ╠════════════════════════════════════╣
║ ┌───────┐ ║ ┌╨────┐ ┌───────┐ ║
║ │ Class │ ┌───────┐ ║ │ F │ │ Class │ ║
║ └───────┘┌───────┐ │ Class │ ║ │ │ └───────┘ ┌───────┐ ║
║ │ Class │ └───────┘ ║ │ A │ ┌───────┐ │ Class │ ║
║ └───────┘ ║ │ ├────────>│ Class │ └───────┘ ║
║ ┌───────┐ ┌───╨───┐ │ S │ └───────┘ ║
║ │ Class ├──────────────────>│Adapter│──>│ │ ┌───────┐ ┌───────┐ ║
║ └───────┘ └───╥───┘ │ A │ │ Class │ ┌─>│ Class │ ║
║ ┌───────┐ ┌───╨───┐ │ │ └───────┘ │ └───────┘ ║
║ │ Class ├──────>│Adapter│──>│ D │ ┌──────────────┤ ║
║ ┌───────┐ └───────┘ └───╥───┘ │ ├──┤ ┌───────┐ │ ┌───────┐ ║
║ │ Class │ ║ │ A │ └──>│ Class │ └──>│ Class │ ║
║ └───────┘ ║ └╥────┘ └───────┘ └───────┘ ║
╚════════════════════════════════════╝ ╚════════════════════════════════════╝
║ Component Order ║ ║ Component Product ║
╠════════════════════════════════════╣ ╠════════════════════════════════════╣
║ ┌───────┐ ║ ┌╨────┐ ┌───────┐ ║
║ │ Class │ ┌───────┐ ║ │ F │ │ Class │ ║
║ └───────┘┌───────┐ │ Class │ ║ │ │ └───────┘ ┌───────┐ ║
║ │ Class │ └───────┘ ║ │ A │ ┌───────┐ │ Class │ ║
║ └───────┘ ║ │ ├────────>│ Class │ └───────┘ ║
║ ┌───────┐ ┌───╨───┐ │ S │ └───────┘ ║
║ │ Class ├──────────────────>│Adapter│──>│ │ ┌───────┐ ┌───────┐ ║
║ └───────┘ └───╥───┘ │ A │ │ Class │ ┌─>│ Class │ ║
║ ┌───────┐ ┌───╨───┐ │ │ └───────┘ │ └───────┘ ║
║ │ Class ├──────>│Adapter│──>│ D │ ┌──────────────┤ ║
║ ┌───────┐ └───────┘ └───╥───┘ │ ├──┤ ┌───────┐ │ ┌───────┐ ║
║ │ Class │ ║ │ A │ └──>│ Class │ └──>│ Class │ ║
║ └───────┘ ║ └╥────┘ └───────┘ └───────┘ ║
╚════════════════════════════════════╝ ╚════════════════════════════════════╝
Adapter produktu z innego komponentu implementuje interfejs OrderedProductInterface, kt贸ry znajduje si臋 w katalogu Model wraz z innymi klasami tego typu w komponencie OrderComponent. Dzi臋ki takiemu zabiegowi, klasy logiki biznesowej znajduj膮ce si臋 w przestrzeni nazw \CompanyName\OrderComponent\* (np. klasy serwisowe) nie zdaj膮 sobie sprawy, 偶e operuj膮 na owini臋tym obiekcie z innego komponentu. Aby u艂atwi膰 korzystanie z tego adaptera przez inne klasy, nale偶y zarejestrowa膰 go w Kontenerze Zale偶no艣ci i ukry膰 go pod interfejsem kt贸ry implementuje.
W celu zachowania przejrzysto艣ci projektu, nale偶y umieszcza膰 wszystkie klasy komunikuj膮ce si臋 z innymi komponentami w jednym katalogu. Spogl膮daj膮c na ich zadeklarowane importy innych klas, 艂atwo b臋dzie mo偶na dostrzec zale偶no艣ci do klasy z poza komponentu macierzystego.
namespace CompanyName\OrderComponent\Adapter; use CompanyName\ProductComponent\{ Model\ProductInterface, Facade\InternalCommunicationInterface }; use CompanyName\OrderComponent\Model\OrderedProductInterface; final class ProductAdapter implements OrderedProductInterface { private ProductInterface $product; public function __constructor(InternalCommunicationInterface $productFacade) { $this->product = $productFacade->getProduct(); } /** * @inheritDoc */ public function someBehavior(): bool { return $this->product->someBehavior(); } }
Ostateczna forma serwisu domenowego z komponentu OrderComponent nie posiada 偶adnej wiedzy na temat innych komponent贸w.
namespace CompanyName\OrderComponent\Service; use CompanyName\OrderComponent\Model\OrderedProductInterface; final class CreateOrder { private OrderedProductInterface $product; public function __constructor(OrderedProductInterface $product) { $this->product = $product; } // Interfejs publiczny }