![]() |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 } |
Brak komentarzy:
Prześlij komentarz