Wzorzec projektowy Proxy - okładka

Wzorzec projektowy Proxy

Opublikowano Kategorie Czysty kodCzas czytania 5min

Wzorzec projektowy Proxy (Pełnomocnik) to jeden ze wzorców strukturalnych opisanych przez bandę czworga w Design Patterns — Elements of Reusable Object-Oriented Software. Jeśli chcesz poznać inne wzorce projektowe lub dowiedzieć się czym są wzorce, to koniecznie sprawdź mój wpis o wzorcach projektowych.

Proxy jako serwer pośredniczący

Często pierwszym skojarzeniem ze słowem proxy jest serwer proxy. Jest to skojarzenie jak najbardziej słuszne.  Ideą proxy jest pośredniczenie między miejscem wywołania akcji, np. zapytania a miejscem docelowym, w którym dana akcja ma coś wykonać. Przykładowymi zastosowaniami proxy są:

  • odciążenie serwera docelowego poprzez buforowanie lub cache-owanie żądań;
  • odizolowanie serwera docelowego — może zaistnieć taki przypadek, gdy ruch do serwera docelowego jest kierowany całkowicie poprzez serwer proxy;
  • filtrowanie czy modyfikacja danych wejściowych.

Mechanizm Proxy w JavaScript

Z nadejściem standardu ES6, do języka JavaScript został dodany mechanizm proxy w postaci klasy Proxy. Idea użycia tej klasy jest taka sama jak w postaci serwera pośredniczącego. Klasa Proxy pozwala na ustanowienie pełnomocnika między programistą a obiektem docelowym. Przy tworzeniu nowej instancji klasy Proxy należy przekazać obiekt docelowy oraz handler, który odpowiada za obsługę logiki w samym Proxy. Przykład kodu wykorzystującego JS’owe proxy przedstawiam na poniższym fragmencie kodu.


const user = {
    firstName: 'Foo',
    lastName: 'Bar',
    email: '[email protected]',
    password: 'superSecretPassword'
}

const handler = {
    secretProperties: [ 'password' ],
  
    get( target, property ) {
        return this.secretProperties.includes( property ) ? undefined : target[ property ];
    }
}

const proxyUser = new Proxy( user, handler );

console.log( proxyUser.password ); // undefined
console.log( proxyUser.email ); // [email protected]

Przedstawiony fragment kodu przy próbie odczytu ukrywa wartości zdefiniowane w tablicy secretProperties. Dzięki temu, w przypadku przedstawionego kodu próba odczytu właściwości password zakończy się niepowodzeniem. Zamiast hasła zostanie zwrócona wartość undefined. Oczywiście, przedstawiony przykład jest czysto edukacyjny i nie ma on zastosowania produkcyjnego. Ma on za zadanie jedynie przedstawić sposób działania Proxy w JS. Analogicznie do przedstawionego przykładu można by wykorzystać metodę set, na przykład do walidacji adresu e-mail czy siły hasła podczas zmiany wartości. Jednym z popularnych rozwiązań wykorzystujących mechanizm JS’owych Proxy jest MobX. Informację tę można znaleźć w oficjalnej dokumentacji projektu.

Wzorzec projektowy Proxy

Mając już podstawowe pojęcie, na czym polega idea proxy, można przejść do zapoznania się ze wzorcem projektowym Proxy. Analogicznie do zastosowań serwera pośredniczącego, tak i wzorzec projektowy Proxy pozwala na rozwiązanie kilku problemów:

  • w przypadku tworzenia kosztownych obiektów pozwala na ograniczenie tego zachowania lub wdrożenie mechanizmu buforowania albo cache;
  • kontrola dostępu do miejsca docelowego;
  • ukrycie dodatkowych szczegółów implementacyjnych związanych z dostępem do zasobu.

Do przedstawienia przykładowego wzorca projektowego Proxy posłuży mi poniższy fragment kodu.


interface IDataRepository {
    get(): ISecretData;
}

interface ISecretData {
    secretValue: string;
}

interface IAuthenticationServer {
    authenticate( request: Record<string,unknown> ): Promise<boolean>;
}

class SomeService {
    public constructor(
        private readonly _dataRepository: IDataRepository
    ) {}
    public async getData(
        requester: Record<string,unknown>,
        authenticationProxy: IAuthenticationServer
    ): Promise<ISecretData> {
        const isAuthenticated: boolean = await authenticationProxy.authenticate( requester );

        if ( !isAuthenticated ) {
            throw new Error( 'Access Denied. Requester is not authenticated!' );
        }

        return this._dataRepository.get();
    }
}

class AuthenticationProxy implements IAuthenticationServer {
    public constructor(
        private readonly _authenticationServer: IAuthenticationServer
    ) {}

    public async authenticate( requestData: Record<string,unknown> ): Promise<boolean> {
        const request: Record<string,unknown> = {
            destination: createDestination( 'some-destination' ),
            body: prepareRequestBody( requestData.accessToken ),
            headers: prepareRequestHeaders()
        }

        return this._authenticationServer.authenticate( request );
    }
}

Pierwszym elementem, na który warto zwrócić uwagę, jest identyczny interfejs w przypadku AuthenticationProxy, jak i AuthenticationServer. Obydwie klasy spełniają wspólny interfejs IAuthenticationServer.

W przedstawionym przypadku proxy jest odpowiedzialne za poprawne przygotowanie zapytania do serwera uwierzytelniającego. Klasa AuthenticationProxy przygotowuje adres docelowy, dodaje wymagane nagłówki i przygotowuje ciało zapytania, w rezultacie zwraca informację prawda/fałsz czy token przekazany przez requestera jest poprawny. Dzięki wykorzystaniu proxy programista odpowiedzialny za kod klasy SomeService nie jest zmuszony znać szczegółów implementacyjnych związanych z komunikacją z serwerem uwierzytelniającego. Ponadto, sam kod logiki biznesowej nie zawiera żadnego kodu niepowiązanego z tą logiką. Dzięki temu kod staje się znacznie prostszy, bardziej czytelny, prostszy w rozbudowie i utrzymaniu.

W dalszej rozbudowie przedstawionego kodu nic nie stoi na przeszkodzie, aby w klasie AuthenticationProxy została dodana logika związana z cache-owaniem rezultatów żądań. Przykładowo, jeśli dwa żądania z takim samym tokenem zostały wysłane w bardzo krótkim odstępie czasu, to istnieje znikome prawdopodobieństwo, że token stracił swoją ważność w tym czasie. Wykorzystując proxy, można ograniczyć ilość zapytań do serwera uwierzytelniającego i zwrócić odpowiedź przechowywaną w cache. Oczywiście, wszystko zależy od specyfiki aplikacji. Z pewnością znajdą się aplikacje, gdzie zastosowanie takiego uproszczenia byłoby krytycznym błędem bezpieczeństwa.

Podsumowanie

Mam nadzieję, że po lekturze tego artykułu idea proxy oraz wzorzec projektowy Proxy nie będą Ci obce! Zachęcam do zostawienia komentarza oraz zapoznania się ze źródłami i materiałami dodatkowymi.

Źródła i materiały dodatkowe

Dominik Szczepaniak

Zawodowo Senior Software Engineer w CKSource. Prywatnie bloger, fan włoskiej kuchni, miłośnik jazdy na rowerze i treningu siłowego.

Inne wpisy, które mogą Cię zainteresować

Zapisz się na mailing i odbierz e-booka

Zapisując się na mój mailing, otrzymasz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.

Subscribe
Powiadom o
guest

0 komentarzy
Inline Feedbacks
View all comments