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.
Książkę Design Patterns: Elements of Reusable Object-Oriented Software możesz kupić klikając w TEN link. Kupując z tego linku wspierasz rozwój bloga.
Źródła i materiały dodatkowe
- Design Patterns: Elements of Reusable Object-Oriented Software – Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994)
- Serwer pośredniczący
- Squid (oprogramowanie)
- What is a Reverse Proxy vs. Load Balancer?
- Serwery proxy aplikacji
- MobX – Configuration
- MDN – Proxy
- JavaScript Proxy
- A practical guide to Javascript Proxy
- Proxy Design Pattern
- Proxy – wzorce projektowe w C++
- Proxy Design Pattern
- Design Patterns – Proxy Pattern
- Proxy – Wzorzec Projektowy Pełnomocnik
- Proxy Design Pattern Tutorial
Zapisz się na mailing i odbierz e-booka
Odbierz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer i realnie zwiększ swoje szanse na rozmowie rekrutacyjnej! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.