Jan 23

Architektura multi-tenant na przykładzie produktów SoftwarePlant

Do you prefer to read this in English? Click here

Aby dobrze zrozumieć mechanizm działania architektury wielo-tenantowej, należy zacząć od zrozumienia różnic między poszczególnymi modelami dystrybucji oprogramowania: single-tenant i multi-tenant. W obydwu przypadkach odbiorcą usługi jest tzw. ‘tenant’ lub inaczej ‘dzierżawca’. W praktyce, słowem tym najczęściej określa się wszystkich pracowników tej samej firmy, która wykupiła dostęp do aplikacji.

Model single-tenant można podzielić na dwa zasadnicze sposoby dystrybucji:

  • On Premises, gdzie dla każdego klienta tworzona jest dedykowana instancja z osobną bazą danych i osobnym systemem. Oprogramowanie instalowane jest na infrastrukturze klienta, która pozostaje jego własnością. Na kliencie spoczywa odpowiedzialność za utrzymanie niezbędnego sprzętu, w tym koszty związane z jego rozbudową. 
  • SaaS, czyli model w którym oprogramowanie jest instalowane na infrastrukturze producenta, która jest przez niego utrzymywana i pozostaje jego własnością. Tenanci mają dostęp do wykupionej aplikacji poprzez systemy chmurowe. Z tego powodu jest to rozwiązanie często mylone z dystrybucją multi-tenant. Należy jednak pamiętać, że w modelu SaaS dla każdego tenanta wciąż tworzona jest osobna instancja na dedykowanym serwerze.

Single-tenant application model

Zaletą modelu single-tenant jest bezpieczeństwo danych. Ponieważ dla każdego tenanta tworzona jest dedykowana instancja, nie ma możliwości aby doszło do przecieku danych między różnymi klientami. Przy projektowaniu aplikacji, co najwyżej należy wziąć pod uwagę, że tenant może potrzebować aby poszczególni użytkownicy w ramach jego organizacji mieli różne uprawnienia, oraz że jedna grupa użytkowników nie powinna widzieć danych innych użytkowników. Ponieważ są to ograniczenia w ramach jednej firmy, nie stanowi to większego wyzwania w procesie projektowania, rozwoju, czy instalacji aplikacji.

Pomimo tych korzyści, rozwiązanie single-tenant ma swoje zasadnicze ograniczenia, które w dużej mierze sprowadzają się do wysokich kosztów i problemów ze skalowalnością systemu. Kosztowne jest samo uruchomienie aplikacji która, w zależności od swojej złożoności i liczby użytkowników, będzie zajmowała określoną przestrzeń na serwerze. Ponadto, w modelu single-tenant wydajność bazowa serwera musi być w stanie utrzymać nie tylko sam system, ale także maksymalne obciążenie (tzw. load) wynikające z użytkowania. Innymi słowy: każda maszyna musi być tak dobra, jak maksymalny load, który jest w stanie wygenerować dany tenant na swojej aplikacji. Należy przy tym pamiętać, że od momentu wdrożenia, system będzie wymagał odpowiedniego utrzymania bez względu na to czy aplikacja jest w danym momencie aktywnie wykorzystywana przez tenanta, czy też nie.
Ponadto, w modelu single-tenant skalowalność systemu jest zawsze horyzontalna. W praktyce oznacza to, że dodanie kolejnego tenanta wymusza zakup kolejnej maszyny, która będzie w stanie go obsłużyć. Pochłania to ogromne ilości zasobów i generuje wysokie koszty. Jest to w szczególności nieopłacalne w przypadku rozwiązań dedykowanych do szerokiej grupy klientów, które powinny ich kosztować nie więcej niż kilka dolarów za użytkownika miesięcznie.

Alternatywą jest zaprojektowanie i wdrożenie aplikacji w oparciu o model multi-tenant. W tym modelu jedna instancja aplikacji jest w stanie obsługiwać wielu tenantów. Jest to możliwe, gdyż aplikacja ma świadomość wielo-tenantowości, czyli istnienia różnych odbiorców. Jest to rozwiązanie, które wymaga znacznie bardziej wyrafinowanej architektury niż w modelu single-tenant. Ponieważ tenanci obsługiwani są przez ten sam serwer i korzystają z tej samej bazy danych, należy zagwarantować, że dane pomiędzy organizacjami nie będą widoczne. Co więcej, system powinien mieć świadomość wielo-tenantowości jedynie na etapie identyfikacji tenanta. Na etapie obsługi żądania, aplikacja powinna już działać tak jak w modelu single-tenant. Wynika to z zasad dobrego projektowania kodu, gdzie tzw. cross-cutting concerns (jak security, transakcje, logowanie, audyt i własnie multi-tenancy) nie powinny ingerować w logikę biznesową, a wręcz przeciwnie – powinny być dla niej możliwie niewidoczne. Takie podejście zapewnia dużo większą czytelność kodu, co przekłada się na tańszy rozwój oraz utrzymanie.
System w modelu multi-tenant daje większą swobodę w obszarze skalowalności, dzięki możliwość zastosowania zarówno skalowania horyzontalnego, jak i wertykalnego w zależności od potrzeb. Oznacza to, że przy zwiększającej się ilości tenantów, producent może, ale nie musi, nabywać nowe maszyny. Zamiast tego, może rozbudowywać już te posiadane poprzez zwiększenie ilość RAM, CPU, lub wymianę procesorów na lepsze.
System w modelu multi-tenant daje większą swobodę w obszarze skalowalności, dzięki możliwość zastosowania zarówno skalowania horyzontalnego, jak i wertykalnego w zależności od potrzeb. Oznacza to, że przy zwiększającej się ilości tenantów, producent może, ale nie musi, nabywać nowe maszyny. Zamiast tego, może rozbudowywać już te posiadane poprzez zwiększenie ilość RAM, CPU, lub wymianę procesorów na lepsze.

Multi-tenant application distribution model

Jak tworzyć rozwiązania w modelu multi-tenant

Sztuka tworzenia rozwiązań w modelu multi-tenant sprowadza się w dużej mierze – zgodnie z zasadami pisania dobrego kodu – do tworzenia tych rozwiązań w sposób ortogonalny do funkcjonalności w systemie. Oznacza to, że aspekt niefunkcjonalny, który ma wpływ na utrzymanie systemu, koszty, przyszły rozwój skalowalność systemu, nie powinien krzyżować się lub w jakikolwiek sposób kolidować z funkcjonalnościami w systemie. Ponadto, rozwiązania te powinny być implementowane w sposób deklaratywny, a to wymaga zaprojektowania ich jako aspekt systemu a nie jego wrodzoną część.
Jeżeli zadba się o te elementy od samego początku, to logika związana z wielo-tenantowością aplikacji nie będzie przenikała do logiki biznesowej i tym samym nie będzie przenikała do samego kodu. Wówczas, zrozumienie systemu i jego rozwój będzie łatwiejsze dla dewelopera.

Dbałość o to aby wielo-tenantowość była oddzielona od logiki biznesowej nie wynika jednak wyłączenie z dbałości o jakość kodu. Jest to także niezwykle istotny aspekt bezpieczeństwa. Nieumiejętna implementacja tego rozwiązania może spowodować, że dane zaczną przeciekać i w efekcie tenanci będą mieli nawzajem dostęp do swoich danych.

Bezpieczeństwo w rozwiązaniach multi-tenant

W rozwiązaniu multi-tenant dane są pogrupowane tak, aby łatwo można było zidentyfikować do którego tenanta należą. Dane każdego tenanta można podzielić na trzy zasadnicze mechanizmy:

  1. bazy danych;
  2. pamięć podręczna (ang. cache), czyli mechanizm umożliwiający szybki dostępu do danych z bazy danych lub z innych zasobów. Warto tutaj wspomnieć, że żaden z mechanizmów cachowania bibliotek w Javie, nie wspiera natywnie architektur multi-tenant;
  3. Asynchroniczność danych, która powinna nieść ze sobą kontekst tenanta. Praca zlecona asynchronicznie to tzw. JOB.

Aby nasze rozwiązanie wspierało wielo-tenantowośc, to powyższe mechanizmy muszą być świadome istnienia różnych odbiorców. Ważne jest natomiast aby API, które te mechanizmy udostępniają dla logiki biznesowej, nie miało już aspektów wielo-tenantowości.

Kolejnym ważnym elementem, znacznie podnoszącym bezpieczeństwo aplikacji w modelu multi-tenant, jest szczelne zabezpieczenie całego systemu funkcjonalnością wspierającą prawidłowy przepływ żądań od użytkowników. Przy każdym żądaniu użytkownika, na czas trwania tego żądania, system powinien przestawić się na środowisko danego tenanta. Co to oznacza? Aplikacja powinna widzieć dane tenanta wysyłającego żądanie i nie widzieć danych innych dzierżawców. Jednocześnie, zanim takie żądanie wejdzie do logiki biznesowej (czyli zanim zostanie obsłużone), należy je przepuścić przez mechanizm, który ustawi odpowiedni kontekst dzierżawcy dla cache, połączenia do bazy danych oraz asynchroniczności.

W tym celu należy zaprojektować ścieżkę działań aplikacji według pięciu kroków:

  1. Wysłanie żądania przez użytkownika
  2. System endpoint
  3. Wykonanie logiki biznesowej
  4. Wyczyszczenie kontekstu tenanta
  5. Zwrócenie odpowiedzi do klienta

Z perspektywy wielo-tenantowości najbardziej istotne oraz złożone są punkty 2 i 4, czyli system endpoint oraz wyczyszczenie kontekstu tenanta.

Multi-tenant application model at SoftwarePlant

T1, T2, T3, T4 – pogrupowane dane dla każdego tenanta

System endpoint w architekturze multi-tenant

System endpoint to warstwa na którą składają się wszystkie punkty wejściowe do systemu. Jest niezwykle ważne, żeby ta warstwa była na tyle szczelnie zaprojektowana, aby wszystkie przychodzące z zewnątrz systemu żądania musiały przez nią przejść. Rolą system endpoint jest wcześniej wspomniana konfiguracja środowiska tenanta, jeszcze zanim żądanie zostanie skierowane głębiej. Konfiguracja przebiega w trzech etapach:

1 Identyfikacja tenanta na podstawie żądania przychodzącego. W tym kroku wyróżnia się 2 przewodnie metody:

  • Basic Authentication
  • Bearer Authentication

Najpopularniejszymi metodami są te z rodziny Bearer Authentication, w których tenant jest identyfikowany za pomocą tokenu. Token może dostarczać dane bezpośrednio np. w technologii JWT, lub pośrednio w rozwiązaniach opartych np. o OAuth 2.0.

2 Dokonanie autentykacji i autoryzacji użytkownika. Warto pamiętać, że autentykacja to weryfikacja tożsamości użytkownika, a autoryzacja to weryfikacja uprawnień użytkownika o podanej tożsamości. 

3 Konfiguracja kontekstu aplikacji na podstawie tenant ID, czyli właściwe ustawienie środowiska tenanta:

  • DB.setTenant(TenantID)
  • CACHE.setTenant(TenantID)
  • JOB.setTenant(TenantID)

Po wykonaniu wszystkich trzech etapów, środowisko jest ustawione pod konkretnego tenanta i użytkownik może przejść do wykonywania logiki biznesowej.

Dla bezpieczeństwa danych klienta, przy wychodzeniu żądania z systemu należy wykonać czwarty krok ze ścieżki działań aplikacji, czyli wyczyścić kontekst tenanta:

  • DB.clearTenantContext(TenantID)
  • CACHE.clearTenantContext(TenantID)
  • JOB.clearTenantContext(TenantID)

Prawidłowe funkcjonowanie warstwy system endpoint jest krytyczne dla działania aplikacji w modelu multi-tenant. Warstwę należy zaaplikować:

  • na wszelkie Java API wyeksponowane na zewnątrz systemu, oraz
  • na poziomie wszystkich usług REST. Pomocny jest silnik JAX-RS lub podobny z mechanizmem aspektowego wpinania kodu.

Autorzy:

Tom Kucharski, CEO SoftwarePlant
Bartłomiej Jańczak, Software Engineer at SoftwarePlant
Michał Mrozowski, Java Developer at SoftwarePlant

About The Author

Tom Kucharski - Software Engineer and CEO of SoftwarePlant