Żądanie handlowe staje się niebezpieczne, gdy przestaje być jawną decyzją produktową i zaczyna materializować się jako if ($tenantId === ...). Początkowo rozwiązuje pilną potrzebę. Z czasem ten warunek pojawia się w kontrolerach, szablonach, procesach kolejkowych, eksportach i API. Rezultatem nie jest konfiguracja, lecz ukryte warianty produktu, trudne do przetestowania, wyjaśnienia i wycofania.
Konfiguracja dla klienta w SaaS PHP powinna umożliwiać celowe i zarządzane różnice, a nie utrwalać każdy historyczny wyjątek. Przydatne pytanie nie brzmi: „czy możemy zrobić to dla tego klienta?”, lecz: „czy ta zmiana stanowi stabilny wymiar produktu, którego mogą potrzebować inni klienci, z trwałymi zasadami i możliwością zapewnienia wsparcia?”.
Sygnał ostrzegawczy: trwały wyjątek w kodzie

Istnieje różnica między dostosowaniem doświadczenia a utrzymywaniem ukrytej gałęzi produktu. Warto zareagować, zanim konkretne żądanie utworzy którykolwiek z tych sygnałów:
- Identyfikator tenanta, domena lub klient pojawia się w logice biznesowej.
- Ta sama reguła jest powielana w interfejsie, API i workerze asynchronicznym.
- Zespół nie potrafi odpowiedzieć, którzy klienci mają wyjątek ani kto go zatwierdził.
- Zmiana planu zmienia zachowania funkcjonalne bez centralnej definicji.
- Usunięcie dostosowania wymaga wyszukania warunków w kilku repozytoriach lub usługach.
Wyjątek może być uzasadniony podczas rozpoznania potrzeb lub migracji, lecz musi mieć właściciela, datę przeglądu i drogę wyjścia: przekształcenie go w możliwość produktową, odizolowanie jako dedykowanej integracji albo odrzucenie. Pozostawienie go bez klasyfikacji zmienia dług techniczny w nieudokumentowaną obietnicę handlową.
Nie myl konfiguracji, uprawnień, możliwości produktowych i rozwoju na zamówienie
Mechanizmy te odpowiadają na różne pytania. Ich mieszanie prowadzi do nieprzejrzystych projektów i sprzecznych reguł.
- Konfiguracja: określa, jak zachowuje się istniejąca już funkcja dla tenanta. Na przykład format numeracji, domyślny język lub to, czy przepływ wymaga dodatkowego zatwierdzenia.
- Uprawnienia: określają, co tożsamość może zrobić w ramach tenanta. Użytkownik może mieć uprawnienie do zatwierdzania płatności, nawet jeśli zatwierdzenie jest skonfigurowane jako obowiązkowe.
- Możliwości produktowe: wskazują, czy tenant ma dostęp do funkcji lub limitu operacyjnego. Mogą zależeć od umowy, planu lub kontrolowanej aktywacji, ale nie powinny zawierać całej logiki domenowej.
- Rozwój na zamówienie: obejmuje zachowanie, które nie wpisuje się w wymiar produktu nadający się do ponownego wykorzystania, takie jak integracja z własnym systemem klienta lub indywidualne dostosowanie warunków umowy.
Praktyczna reguła pomaga podjąć decyzję: jeśli zmienia się kto wykonuje działanie, użyj uprawnień; jeśli zmienia się czy funkcja istnieje lub jest dostępna, użyj możliwości produktowych; jeśli zmienia się jak działa dostępna funkcja, użyj konfiguracji. Jeśli model biznesowy zmienia się wyłącznie dla jednego przypadku, nie ukrywaj tego jako flagi.
Co powinno być konfigurowalne, a co pozostać w rdzeniu
Opcja zasługuje na wejście do katalogu konfiguracji, gdy ma jasną semantykę, skończony zbiór wartości, znane walidacje i rozsądne oczekiwanie ponownego użycia. Musi też zapewniać zrozumiałe wsparcie: ktoś powinien móc wyjaśnić efekt jej zmiany bez analizowania kodu.
Dobrymi kandydatami są zwykle parametry prezentacji, polityki powiadomień, progi, sekwencje zatwierdzeń, preferencje regionalne i wybory między już obsługiwanymi przepływami. Natomiast w rdzeniu powinny pozostać niezmienniki bezpieczeństwa, integralność danych, podstawowe obliczenia finansowe oraz reguły, których zmiana wymagałaby ponownej interpretacji istniejących encji lub umów.
Nie zamieniaj dowolnych danych w konfigurację wyłącznie dla elastyczności. Pole JSON bez schematu może ukrywać zależności niemożliwe do odkrycia. Gdy opcja modyfikuje krytyczną regułę, zdefiniuj typy, dozwolone wartości, warunki użycia i konsekwencje dla wcześniejszych danych.
Zbuduj zarządzany model konfiguracji
Pojedynczy klucz nie wystarcza. Każda definicja w katalogu powinna obejmować metadane umożliwiające bezpieczne zarządzanie produktem:
- Klucz i opis funkcjonalny: stabilne nazwy, ukierunkowane na domenę, a nie na szczegóły implementacji.
- Właściciel: zespół lub osoba odpowiedzialna za decyzje o jej rozwoju i wycofaniu.
- Zakres: globalny, tenant, jednostka organizacyjna, projekt lub użytkownik. Unikaj domyślnego zezwalania na wszystkie zakresy.
- Wartość domyślna: jawne zachowanie, gdy nie istnieje nadpisanie.
- Typ i walidacja: wartość logiczna, enumeracja, liczba z zakresem lub struktura walidowana schematem.
- Zależności: wymagania względem innych opcji, możliwości produktowych lub stanu migracji.
- Wrażliwość: klasyfikacja danych i zasady dostępu do odczytu oraz modyfikacji.
- Cykl życia: data wprowadzenia, przeglądu, deprecjacji i planowanego wycofania, gdy ma zastosowanie.
W PHP scentralizuj ustalanie konfiguracji w usłudze domenowej, na przykład TenantSettings, i przekazuj typowane obiekty zamiast tablic bez kontraktu. Aplikacja może łączyć wartość globalną, wartość tenanta i wartość bardziej szczegółową za pomocą udokumentowanego pierwszeństwa. Brak wartości musi zawsze oznaczać wartość domyślną, a nie interpretację różniącą się u każdego konsumenta.
$policy = $tenantSettings->approvalPolicy($tenantId);
if ($policy->requiresSecondApproval()) {
$workflow->requestSecondApproval($order);
}
Magazyn danych może być relacyjny lub dokumentowy, lecz katalog i walidacja nie powinny zależeć od formy trwałego przechowywania. Prowadź również niezmienną historię zmian: poprzednią i nową wartość, aktora, moment, powód oraz kanał modyfikacji. Historia nie zastępuje rejestru audytowego działań biznesowych, ale pozwala odtworzyć, jaka konfiguracja obowiązywała.
Oceniaj decyzję na właściwej granicy
Problem rozproszonych warunków nie rozwiązuje się przez przeniesienie ich wszystkich do kontrolera. Konfiguracja, która wpływa na regułę biznesową, musi być oceniana w usłudze lub polityce domenowej stosującej tę regułę. Kontroler tłumaczy żądanie; szablon prezentuje wynik; żaden z nich nie powinien samodzielnie decydować o polityce tenanta.
W przypadku złożonych zachowań używaj zarejestrowanych strategii lub polityk zamiast łańcuchów wartości logicznych. Polityka fakturowania może wybrać implementację spośród obsługiwanych trybów po zweryfikowaniu, że tenant ma wymaganą możliwość produktową. Dzięki temu interfejs, API i kolejka wywołują tę samą decyzję.
Szablony mogą otrzymywać już przygotowany widok, w tym wskaźniki możliwości produktowych służące do pokazywania lub ukrywania działań. Ukrycie przycisku nie jest autoryzacją. API musi stosować uprawnienia, możliwości produktowe i konfigurację po stronie serwera, nawet jeśli interfejs nie udostępnia danej operacji.
Możliwości produktowe i limity bez usztywniania planów
Plan handlowy może przyznawać możliwości produktowe, lecz nie powinien przekształcać się w zbiór if ($plan === '...'). Modeluj stabilną możliwość produktową, taką jak advanced_approvals lub api_access, i ustalaj, które tenanty ją mają, na podstawie źródła danych umownych lub administracyjnych. Następnie logika funkcjonalna sprawdza tę możliwość, a nie nazwę planu.
Limity wymagają jeszcze bardziej precyzyjnej definicji: co jest liczone, w jakim oknie czasowym, kiedy blokada jest stosowana oraz jak zachowują się ponowienia i procesy kolejkowe. Limit musi być obserwowalny i spójny we wszystkich punktach wejścia. Jeśli integracja tworzy zasoby poza głównym interfejsem, nie może omijać tej samej kontroli.
Zmieniaj ustawienia bezpiecznie i odwracalnie
Modyfikacja opcji może mieć natychmiastowy wpływ na trwające zadania, istniejące rekordy lub integracje. Przed zapisaniem zweryfikuj typ, uprawnienia administracyjne, zależności i zgodność z bieżącym stanem. Gdy wpływ jest istotny, zapewnij podgląd zmiany: który przepływ zostanie aktywowany, które ograniczenia narusza i na które przyszłe operacje wpłynie.
Stopniowa aktywacja różni się od udostępnienia opcji w całym interfejsie. Możesz włączyć możliwość produktową dla kontrolowanej grupy tenantów i obserwować jej zachowanie, zanim udostępnisz ją ogólnie. Zdefiniuj również wycofanie: jaka wartość przywraca poprzedni stan, czy są powiązane migracje danych oraz co dzieje się z operacjami rozpoczętymi przy nowej konfiguracji.
Zmiana odwracalna w interfejsie może nie być odwracalna w danych. Przed aktywowaniem nowej polityki traktuj oba wymiary osobno.
Utrzymuj spójność w kolejkach, API i integracjach
Procesy asynchroniczne wprowadzają dodatkową decyzję: ustalić konfigurację podczas wykonywania zadania czy zachować migawkę podczas jego tworzenia. W przypadku działań, które muszą respektować obowiązującą politykę, ustalaj ją podczas wykonywania i uwzględniaj tenant w kontekście zadania. W przypadku dokumentów, obliczeń lub komunikacji, które muszą odtworzyć pierwotną decyzję, przechowuj jawną wersję lub migawkę wraz z poleceniem.
Nie mieszaj obu opcji bez wyraźnego zadeklarowania tego. Ponowienie może zmienić wynik, jeśli odczytuje zaktualizowaną konfigurację. Zdefiniuj idempotencję, wersję konfiguracji i oczekiwane zachowanie przy ponowieniach. Integracje zewnętrzne potrzebują równoważnych kontraktów: wstępnej walidacji, obsługi błędów, limitów i śledzalności dla każdego tenanta, bez wysyłania sekretów lub danych osobowych do dziennika diagnostycznego.
Audyt i wsparcie: wyjaśniaj zaobserwowane zachowanie

Wsparcie musi umieć odpowiedzieć, dlaczego klient widzi dany przepływ, a nie tylko jaką wartość ma klucz. Rejestruj ślad decyzji zawierający identyfikator tenanta, wersję definicji, źródło wartości efektywnej — domyślną lub nadpisanie —, istotne możliwości produktowe i wynik oceny. Ogranicz dostęp do tych informacji oraz maskuj wrażliwe wartości.
Uzupełnij tę śledzalność metrykami użycia dla każdej opcji, błędami walidacji, nieudanymi zmianami i opcjami bez użycia. Konfiguracja bez użycia może być przestarzała; konfiguracja używana przez długi czas tylko przez jednego tenanta zasługuje na przegląd produktowy. Celem nie jest eliminowanie wszystkich różnic, lecz sprawienie, aby każda różnica była jawna, sprawdzalna, obserwowalna i usuwana, gdy przestaje wnosić wartość.



