niedziela, 19 kwietnia 2020

Komunikacja pomiędzy komponentami (Fasady i Adaptery)

 
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 │ ║
║      └───────┘                     ║     ║    └───────┘             └───────┘ ║
╚════════════════════════════════════╝     ╚════════════════════════════════════╝


    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 │ ║
║      └───────┘                     ║    └╥────┘      └───────┘      └───────┘ ║
╚════════════════════════════════════╝     ╚════════════════════════════════════╝


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 │ ║
║      └───────┘                     ║       └╥────┘      └───────┘      └───────┘ ║
╚════════════════════════════════════╝        ╚════════════════════════════════════╝

    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