![]() |
| 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
}

Brak komentarzy:
Prześlij komentarz