Przejdź do treści

DevStyle - Strona Główna
Dlaczego prosty model User to za mało? Archetyp Party w praktyce (cz. 1)

Dlaczego prosty model User to za mało? Archetyp Party w praktyce (cz. 1)

Bartłomiej Słota

4 grudnia 2025

Backend

To część 1 z serii o Archetypie Party. W tej serii krok po kroku pokazuję, dlaczego proste modele użytkowników zawodzą i jak Archetyp Party rozwiązuje te problemy: od koncepcji, przez implementację, aż po zaawansowane zastosowania.

Opis problemu

Poziom 1

Znaczna część oprogramowania tworzona na całym świecie jest przeznaczona dla różnych typów użytkowników końcowych: osób indywidualnych, pracowników, innych aplikacji i systemów. Dlatego istnieje duża szansa, że w pewnym momencie staniesz przed wyzwaniem stworzenia systemu zarządzania użytkownikami.

Być może już implementowałeś moduł umożliwiający:

  • rejestrację użytkowników,
  • aktualizację ich danych (imię, nazwisko, adres),
  • uwierzytelnianie (w tym ustawianie haseł lub jednorazowych kodów dostępu),
  • autoryzację (w tym definiowanie ról).

 Poziom 2 

Jeśli oprogramowanie służy do automatyzacji procesów generujących przychody, użytkownik często reprezentuje naszego klienta. Klientami mogą być zarówno osoby fizyczne, jak i firmy. Czasami klient jest jednoosobową działalnością gospodarczą, łączącą cechy zarówno osoby fizycznej, jak i firmy. W niektórych usługach rejestracja użytkownika jest równoznaczna z rejestracją klienta, choć nie musi to być regułą. Możliwe są między innymi następujące scenariusze: 

  • Użytkownik rejestruje się w systemie, ale ponieważ nie wykupił abonamentu (i technicznie nie jest klientem), ma ograniczony dostęp do usług — w tej sytuacji podmiot użytkownika jest tworzony przed utworzeniem powiązanego podmiotu klienta (jeśli w ogóle do tego dojdzie). 
  • W systemie pojawia się klient, z którym podpisujemy umowę o świadczenie usług. Dopiero po podpisaniu umowy może zaistnieć konieczność utworzenia jednego lub kilku kont użytkowników — w tej sytuacji podmiot użytkownika jest tworzony później. 

Poziom 3 

Problem może stać się jeszcze bardziej złożony, gdy: 

  • osoba lub firma nie jest ani użytkownikiem, ani klientem, ale na przykład potencjalnym klientem, z którym prowadzimy negocjacje 
  • firma składa się z wielu działów i chcemy umożliwić różnym działom wykonywanie różnych operacji w systemie (na przykład możemy mieć hierarchię firmy, w której każda spółka zależna może dokonywać zakupów, ale rozliczenia są obsługiwane wyłącznie przez spółkę macierzystą jako płatnika) 
  •  firma, która jest naszym klientem, może być jednocześnie naszym dostawcą/partnerem (np. firma produkująca światłowody zamawia umowy abonamentowe na usługi internetowe i telefoniczne dla swoich pracowników, jednocześnie świadcząc usługi jako dostawca kabli) 
  • firma, z którą podpisaliśmy umowę, zintegruje się z naszym systemem za pośrednictwem API (tj. użytkownicy nie będą logować się do naszego systemu za pośrednictwem interfejsu użytkownika) 

Co dalej?

Powyższe przykłady pokazują, że zarządzanie użytkownikami, klientami, partnerami i różnymi typami podmiotów może być bardzo złożonym zagadnieniem, w którym podmioty mogą pełnić wiele ról i pozostawać w różnych relacjach między sobą. 

Możliwe rozwiązania 

Zacznijmy od User

Ewolucyjne pojawianie się nowych przypadków biznesowych często skutkuje ewolucyjnym projektowaniem rozwiązań. Często spotykamy się z rozwiązaniem, które zaczyna się od klasy User, która zarządza podstawowymi danymi użytkownika jako osoby, a także uwierzytelnianiem i zarządzaniem rolami: 

Na początku wygląda to dobrze, ale gdy użytkownik zaczyna być jednocześnie klientem, pojawiają się komplikacje.

Kiedy User może być Customer

Gdy użytkownik może być klientem, powszechnie stosowanym rozwiązaniem jest dodanie flagi (boolean flag)

 

W przypadkach, gdy klientami są osoby fizyczne (klienci indywidualni), może to być wystarczające. Jednakże, jeśli klientami są firmy, pojawiają się problemy, takie jak fakt, że firma nie ma imienia i nazwiska, ale nazwę handlową i numer identyfikacji podatkowej, a często ma różne rodzaje adresów (np. kontaktowy, rozliczeniowy). Próba spełnienia tych wymagań w ramach klasy User spowodowałaby dodanie kolejnego zestawu pól danych i zasad dotyczących adresów, które byłyby używane tylko w niektórych przypadkach: 

Oczywiste jest, że model ten odbiega od rzeczywistości, w której tworzenie użytkownika i klienta często następuje w różnym czasie. Takie rozwiązanie zazwyczaj ma negatywny wpływ na łańcuch produkcyjny, ponieważ niezależne strumienie zmian zbiegają się w jednej klasie, zwiększając jej złożoność i zmniejszając możliwość ponownego wykorzystania oraz testowalność. W kodzie po stronie klienta model ten wymaga ciągłych kontroli w celu ustalenia, czy obiekt reprezentuje użytkownika, klienta, czy też ewentualnie obie te osoby. 

Próba obsługi dodatkowych przypadków (takich jak wprowadzenie pojęcia potencjalnego klienta- lead) w ramach tego modelu tylko pogorszy sytuację. Po raz kolejny modele użytkownika(user) i klienta(client) często ulegają rozbudowie ze względu na konieczność dodania różnych flag i pól znaczników, takich jak Instant agreementSignatureDate, Instant validTo, boolean active, itp. Krytycznym wymogiem, który stanowi wyzwanie dla tego modelu, jest potrzeba ustanowienia relacji jeden do wielu między klientem a użytkownikiem. 

A co z oddzielnymi modelami? 

Rozwiązaniem, które rozwiązuje wspomniane problemy, często obserwowane w takich sytuacjach, jest utworzenie klas specyficznych dla danego przypadku użycia, a mianowicie User, Customer i Lead. Dochodzimy teraz do punktu, w którym każdy konkretny przypadek wymaga utworzenia nowego, wyspecjalizowanego modelu. Z jednej strony jest to korzystne, ponieważ model lepiej odzwierciedla siły działające w problemie biznesowym. Jednak gdy firma lub osoba fizyczna pojawia się w wielu formach, rozwiązanie ponownie staje się skomplikowane. 

Na przykład jednoosobowa działalność gospodarcza może rozpocząć działalność jako potencjalny klient, następnie stać się klientem, dla którego zakładamy konto użytkownika, a następnie ta sama jednostka gospodarcza staje się naszym partnerem. W rezultacie musielibyśmy powielać te same dane w klasach User, Customer i Lead i Partner lub tworzyć klasy jako iloczyny kartezjańskie funkcji i struktur danych każdego typu jednostki, takie jak CustomerPartner. W tym przypadku rozwiązanie jednego problemu generuje kolejny. 

W rezultacie takie rozwiązania są semantycznie nieprawidłowe, co oznacza, że model nie odzwierciedla rzeczywistej istoty problemu biznesowego, komplikując tym samym programowanie zarówno po stronie modelu, jak i kodu klienta. Model charakteryzuje się wysoką złożonością i słabą skalowalnością funkcjonalną. Zazwyczaj w takich sytuacjach obserwujemy stosowanie oddzielnych modeli przez różne zespoły, co skutecznie utrudnia spójność danych, a bardzo podobne prace muszą być wykonywane w wielu miejscach przez wiele osób. 

Architektura rozproszona 

Często spotykamy się z sytuacjami, w których modele takie jak User, Customer, Partner i inne są wdrażane w oddzielnych aplikacjach. Takie podejście zwiększa złożoność, zmuszając aplikacje klienckie do integracji z wieloma źródłami danych. Utrzymanie spójności między tymi aplikacjami jest zarówno trudne, jak i kosztowne, co zazwyczaj ma negatywny wpływ na wydajność dostaw i operacyjną. Wprowadzenie nowych funkcji często wymaga zmian w wielu aplikacjach zarządzanych przez różne zespoły. Ponadto problemy z spójnością danych mogą skutkować zgłaszanymi przez klientów błędami, które muszą być analizowane i rozwiązywane jednocześnie przez kilka zespołów programistów. 

Podsumowanie

Widzimy więc, że typowe podejścia, od klasy User, przez flagi i oddzielne modele, aż po architekturę rozproszoną, szybko się komplikują i nie oddają rzeczywistości biznesowej.  To moment, w którym warto zadać pytanie: czy istnieje prostszy i bardziej skalowalny sposób?

W części 2 przejdziemy do rozwiązania: Archetypu Party, który odwraca perspektywę: w centrum modelu stawia podmiot (osobę lub organizację), a role stają się jego dynamicznymi atrybutami.

Zobacz również