Wszystkie artykuły

Architektura OpenArca — Stack i Decyzje Projektowe

P
Piotr Tomczak · Visio Lab / OpenArca
| | 13 min czytania

OpenArca został zaprojektowany w oparciu o jedną zasadę: zachować architekturę wystarczająco prostą dla prawdziwego self-hostingu. Każdy wybór technologiczny, każda decyzja strukturalna i każde celowe pominięcie wynika z tej idei.


Dlaczego architektura ma znaczenie w open-source narzędziach workflow

Większość projektów open source nie upada z powodu braku funkcji. Upadają, bo architektura zbyt utrudnia ludziom wdrożenie, zrozumienie i wnoszenie wkładu do kodu.

Narzędzie self-hosted wymagające klastra Kubernetes, trzech baz danych i kolejki komunikatów nie jest naprawdę self-hosted w żadnym praktycznym sensie. Technicznie można je samodzielnie hostować — jeśli dysponuje się zespołem infrastrukturalnym do jego utrzymania. Dla małych zespołów deweloperskich, dla których OpenArca jest zbudowany, to rozróżnienie ma ogromne znaczenie.

To samo dotyczy doświadczenia kontrybutorów. Projekt open source z czystą, przewidywalną architekturą przyciąga kontrybucje. Projekt z warstwami abstrakcji, niestandardowymi frameworkami i nieudokumentowanymi wewnętrznymi konwencjami tworzy mur, przez który wspięto się tylko najbardziej zdeterminowani deweloperzy.

Architektura OpenArca jest kształtowana przez dwa ograniczenia: musi być trywialnie wdrażalna i natychmiast zrozumiała. Wszystko inne wynika z tego.


Podstawowy stack technologiczny

Frontend: React 18, Vite, React Router, dnd-kit

Frontend to single-page application oparta na React 18, zbudowana z Vite jako narzędziem budowania i serwerem deweloperskim. Routing jest obsługiwany przez React Router. Interakcje drag-and-drop — używane do manipulacji tablicą Kanban i zmiany kolejności TODO — są zasilane przez dnd-kit.

Te wybory są celowo mainstreamowe. React to najbardziej powszechnie rozumiany framework frontendowy w ekosystemie JavaScript. Vite stał się standardowym narzędziem budowania dla nowych projektów React, oferując szybkie pętle informacji zwrotnej podczas developmentu i prostą konfigurację. React Router to domyślne rozwiązanie routingu, które większość deweloperów React już zna.

dnd-kit zasługuje na osobną wzmiankę, bo drag-and-drop jest centralny dla modelu interakcji OpenArca. Deweloperzy zmieniają kolejność swoich list TODO przeciągając zadania. Zgłoszenia przesuwają się między kolumnami Kanban przez przeciąganie kart. dnd-kit został wybrany ze względu na wsparcie dostępności, kompozycyjne API i charakterystyki wydajnościowe — radzi sobie z drag-and-drop opartym na listach, którego wymagają narzędzia workflow, bez narzutu bardziej ogólnych rozwiązań.

Nie ma niestandardowego frameworka komponentów, własnościowego design systemu ani niekonwencjonalnego wzorca zarządzania stanem. Deweloper React klonujący repozytorium po raz pierwszy powinien być w stanie nawigować po codebase w ciągu minut, a nie godzin.

Backend: Node.js 20, Express, SQLite, Zod, JWT

Backend to aplikacja Node.js 20 działająca na Express. Dane są przechowywane w SQLite przez better-sqlite3. Walidacja żądań używa schematów Zod. Uwierzytelnianie jest obsługiwane przez tokeny JWT w połączeniu z logowaniem passwordless opartym na OTP.

Express został wybrany ponad nowszymi alternatywami jak Fastify czy Hono z prostego powodu: to framework HTTP, który najszersza liczba deweloperów Node.js już rozumie. Gdy kontrybutor otwiera pliki tras, widzi znane wzorce — łańcuchy middleware, handlery żądań, obiekty odpowiedzi. Nie ma krzywej uczenia się poza własną logiką biznesową aplikacji.

Zod obsługuje walidację na granicy API. Każde przychodzące żądanie jest walidowane względem typowanego schematu, zanim dotrze do logiki biznesowej. Zapewnia to bezpieczeństwo typów w czasie wykonania, jasne komunikaty błędów dla konsumentów API i dokumentację jako kod dla formatu żądania. Zapobiega też całej klasie błędów, gdzie nieprawidłowe dane prześlizgują się do bazy danych i powodują problemy dalej.

Tokeny JWT zarządzają stanem sesji. W połączeniu z przepływem logowania OTP tworzy to system uwierzytelniania bezstanowy po stronie serwera — nie ma magazynu sesji do zarządzania, nie ma zależności od Redis do przechowywania tokenów i nie ma złożoności wokół synchronizacji sesji we wdrożeniach wieloinstancyjnych.

Usługi pomocnicze: Docker Compose, Mailpit, Multer

Cel wdrożenia to Docker Compose. Cały stack — frontend, backend i wszelkie usługi pomocnicze — działa z jednego polecenia docker-compose up. Nie ma warstwy orkiestracji, nie ma service mesh, nie ma zewnętrznych zależności, które trzeba osobno provisionować.

Mailpit zapewnia lokalne testowanie e-mail podczas developmentu. Gdy przepływ logowania OTP wysyła kod uwierzytelniający, Mailpit przechwytuje go lokalnie, dzięki czemu deweloperzy mogą testować pełne doświadczenie logowania bez konfigurowania zewnętrznej usługi e-mail. W produkcji zastępuje to dowolny dostawca SMTP, którego używa zespół.

Multer obsługuje przesyłanie plików — załączniki do zgłoszeń, obrazy, dokumenty. Jest skonfigurowany z rozsądnymi limitami rozmiaru i ograniczeniami typów plików jako część podejścia security-by-default.


Dlaczego SQLite?

To decyzja, która najbardziej zaskakuje ludzi, więc zasługuje na pełne wyjaśnienie.

Większość narzędzi workflow — nawet tych skierowanych do małych zespołów — domyślnie używa PostgreSQL lub MySQL. Uzasadnienie to zazwyczaj połączenie “lepiej skaluje”, “jest bardziej solidna” i “to, czego używają poważne aplikacje”. OpenArca celowo idzie pod prąd tej konwencji, używając SQLite jako swojej podstawowej bazy danych.

Uzasadnienie jest praktyczne, nie ideologiczne.

Zero narzutu operacyjnego. SQLite to baza danych wbudowana. Działa jako część procesu aplikacji, czytając i pisząc do pojedynczego pliku na dysku. Nie ma serwera bazy danych do zainstalowania, konfigurowania, monitorowania ani aktualizowania. Nie ma pul połączeń do dostrajania, nie ma uwierzytelniania do konfigurowania, nie ma konfiguracji sieciowej między aplikacją a bazą danych. Dla narzędzia self-hosted eliminuje to całą kategorię złożoności wdrożeniowej.

Trywialne kopie zapasowe. Tworzenie kopii zapasowej bazy danych SQLite oznacza skopiowanie pliku. Żadnego pg_dump, żadnego zarządzania binarnym logiem, żadnej konfiguracji point-in-time recovery. Dla pięcioosobowego zespołu deweloperów samodzielnie hostującego swoje narzędzie workflow, “skopiuj plik do swojej lokalizacji backupu” to właściwy poziom złożoności.

Idealne dopasowanie do modelu współbieżności. SQLite bez problemów obsługuje wzorce odczytu/zapisu narzędzia workflow dla małego i średniego zespołu. Piętnastoosobowy zespół nie generuje tysięcy równoczesnych zapisów na sekundę. Tworzą zgłoszenia, aktualizują statusy, zmieniają kolejność zadań — operacje, z którymi SQLite radzi sobie komfortowo z włączonym trybem WAL.

Parytet lokalnego developmentu. Gdy kontrybutor klonuje repo i uruchamia serwer deweloperski, dostaje dokładnie ten sam silnik bazy danych, który działa w produkcji. Nie ma luki “działa na moim komputerze” spowodowanej różnicami wersji bazy danych, dostępnością rozszerzeń czy niezgodnościami konfiguracji.

Kompromis jest jasny: SQLite nie będzie skalować do tysięcy równoczesnych użytkowników ani ogromnych zbiorów danych. To jest w porządku. OpenArca jest zaprojektowany dla zespołów liczących 2–15 deweloperów. W tej skali SQLite nie jest kompromisem — to optymalny wybór. Jeśli zespół wyrośnie z SQLite, prawdopodobnie wyrósł też z wzorców workflow, dla których OpenArca jest zaprojektowany, a ścieżka migracji do PostgreSQL jest dobrze udokumentowana i prosta.


Dlaczego Node + React?

Wybór technologiczny dla podstawowego stacku nie dotyczył tego, który framework jest technicznie “najlepszy” — chodziło o optymalizację dla jak największej puli kontrybutorów i jak najbardziej przewidywalnego doświadczenia deweloperskiego.

Wdrożenie kontrybutorów. JavaScript i TypeScript to najszerzej używane języki w świecie tworzenia aplikacji webowych. Full-stackową aplikację Node.js + React może zrozumieć i do niej wnosić wkład jak największa możliwa pula deweloperów open source. Wybór mniej popularnego stacku — Elixir, Rust, Go — mógłby oferować zalety techniczne, ale drastycznie zmniejszyłby pulę osób, które mogą wnosić znaczący wkład bez uczenia się nowego języka.

Szybka iteracja. Node.js i React mają dojrzałe ekosystemy z dobrze przetestowanymi bibliotekami dla praktycznie każdej powszechnej potrzeby. Przesyłanie plików, walidacja formularzy, drag-and-drop, obsługa dat, generowanie PDF — dla każdego z nich istnieje utrzymywana, udokumentowana biblioteka. Oznacza to, że czas deweloperski idzie na budowanie funkcji workflow, a nie reimplementację infrastruktury.

Przewidywalne doświadczenie deweloperskie. Gdy deweloper otwiera PR w OpenArca, wie, czego się spodziewać. Handlery tras Express. Komponenty React z hooks. Schematy Zod do walidacji. CSS modules lub Tailwind do stylowania. Nie ma niestandardowych makr, kroków generowania kodu ani niekonwencjonalnych procesów budowania, które wymagają wyjaśnienia. Kod jest tym, czym jest.

Ta przewidywalność to strategiczny wybór. Każdy niekonwencjonalny wzorzec w codebase to bariera dla kontrybucji. OpenArca minimalizuje te bariery, używając najbardziej powszechnie rozumianych narzędzi w najbardziej konwencjonalny sposób.


Backend projektowany od strony workflow

Wiele aplikacji zaczyna od generycznych encji CRUD — użytkownicy, elementy, komentarze — i nakłada logikę biznesową przez kontrolery i serwisy. OpenArca odwraca ten wzorzec. Backend jest projektowany najpierw wokół konceptów workflow, a przechowywanie danych to szczegół implementacyjny.

Podstawowe modele domenowe to nie abstrakcyjne “zasoby”. To konkretne obiekty workflow: stany cyklu życia zgłoszenia, zadania realizacyjne dewelopera i logika synchronizacji, która je ze sobą wyrównuje.

Zgłoszenie w OpenArca to nie tylko wiersz w bazie danych z polem statusu. To encja z zdefiniowanym cyklem życia — zestawem prawidłowych przejść stanów, zasadami własności i efektami ubocznymi, które wyzwalają się, gdy przechodzi z jednego stanu do drugiego. Gdy zgłoszenie przesuwa się z “Gotowe” do “W toku”, system nie aktualizuje po prostu kolumny. Ocenia, co jeszcze musi się stać: czy TODO dewelopera musi się zaktualizować? Czy widok Kanban musi odzwierciedlić nowe aktywne zadanie? Czy są powiadomienia do wysłania?

Zadania realizacyjne dewelopera są modelowane oddzielnie od zgłoszeń, bo reprezentują inną kwestię. Zgłoszenie to jednostka pracy z perspektywy zespołu. Zadanie to jednostka realizacji z perspektywy dewelopera. Logika synchronizacji między nimi — która jest jedną z definiujących cech OpenArca — jest explicite modelowana w backendzie, a nie pozostawiana jako niejawne zachowanie wynikające z interakcji UI.

To podejście oznacza, że logika biznesowa jest odkrywalna przez czytanie kodu. Kontrybutor patrzący na maszynę stanów zgłoszenia może zrozumieć dokładnie, co się dzieje, gdy praca przesuwa się przez system. Nie ma potrzeby śledzenia przez warstwy generycznego middleware, by zrozumieć workflow.


Decyzje bezpieczeństwa

Bezpieczeństwo w OpenArca nie jest modułem przykręconym z boku. Jest wbudowane w podstawowe przepływy aplikacji od podstaw.

Kontrola dostępu oparta na rolach (RBAC) zarządza tym, co każdy użytkownik może widzieć i robić. Uprawnienia są sprawdzane na warstwie API, nie tylko w UI. Oznacza to, że nawet jeśli ktoś skonstruuje bezpośrednie żądanie API, zasady autoryzacji nadal obowiązują. Role są celowo proste — nie ma rozbudowanej hierarchii poziomów uprawnień, która wymaga dokumentacji do zrozumienia.

Walidacja własności zapewnia, że użytkownicy mogą modyfikować tylko zasoby, do których mają uzasadniony dostęp. Każdy endpoint mutacji weryfikuje, że żądający użytkownik ma właściwą relację do zmienianego zasobu. Zapobiega to klasie podatności, gdzie prawidłowy uwierzytelniony użytkownik może uzyskać dostęp lub zmodyfikować dane innego użytkownika przez manipulację parametrami żądania.

Limity przesyłania są konfigurowane na poziomie infrastruktury. Ograniczenia rozmiaru pliku, dozwolone typy plików i limity miejsca są egzekwowane przez Multer, zanim przesłana zawartość dotrze do logiki aplikacji. To podejście defense-in-depth — nawet jeśli logika aplikacji miałaby błąd, middleware przesyłania nadal egzekwowałby ograniczenia.

Logowanie OTP bez hasła całkowicie usuwa hasła z systemu. Brak konfiguracji hashowania haseł, którą można źle skonfigurować. Brak przepływów resetowania hasła do zabezpieczenia. Brak bazy danych poświadczeń do ochrony przed naruszeniami. Użytkownicy uwierzytelniają się, otrzymując jednorazowy kod przez e-mail, który generuje JWT do zarządzania sesją. To drastycznie upraszcza powierzchnię bezpieczeństwa, jednocześnie zapewniając doświadczenie logowania, które jest faktycznie wygodniejsze niż tradycyjne hasła.

Filozofia jest taka, że domyślne ustawienia bezpieczeństwa powinny być prawidłowe od razu. Zespół wdrażający OpenArca nie powinien potrzebować czytania przewodnika hartowania bezpieczeństwa, by mieć w miarę bezpieczną instalację. Domyślne ustawienia powinny być bezpieczne, a zespół powinien podejmować decyzje tylko w kwestiach, które są naprawdę specyficzne dla zespołu — jak który dostawca e-mail używać do dostarczania OTP.


Filozofia projektowania: czego OpenArca celowo unika

Zrozumienie tego, czego nie ma w architekturze, jest równie ważne jak zrozumienie tego, co jest.

Brak mikroserwisów. Cała aplikacja działa jako jeden proces. Dla skali, do której OpenArca jest skierowany, mikroserwisy dodałyby złożoność wdrożeniową, narzut operacyjny i trudność debugowania bez zapewnienia znaczących korzyści. Architektura monolityczna z czystymi wewnętrznymi granicami jest prostsza do wdrożenia, prostsza do debugowania i prostsza w rozumowaniu.

Brak ciężkiej orkiestracji. Nie ma Kubernetes, nie ma service mesh, nie ma orkiestracji kontenerów poza Docker Compose. Zespół powinien być w stanie uruchomić OpenArca na pojedynczym VPS, NAS lub zapasowej maszynie pod czyimś biurkiem. Wymagania infrastrukturalne są celowo minimalne.

Brak ORM. Zapytania do bazy danych używają bezpośrednio better-sqlite3 z parametryzowanym SQL. Dzięki temu warstwa danych jest przezroczysta — możesz przeczytać zapytanie i zrozumieć dokładnie, co robi, bez śledzenia przez warstwy abstrakcji ORM. Dla codebase, który priorytetyzuje czytelność dla kontrybutorów, ta bezpośredniość ma znaczenie.

Brak przedwczesnej abstrakcji. Codebase opiera się pokusie tworzenia generycznych frameworków dla rzeczy, które są aktualnie używane tylko w jednym miejscu. Jeśli wzorzec pojawia się raz, jest implementowany bezpośrednio. Jeśli pojawia się trzy razy, jest wyodrębniany. To sprawia, że kod jest konkretny i zakorzeniony w rzeczywistych przypadkach użycia, a nie hipotetycznych przyszłych wymaganiach.

Podstawowa zasada jest taka, że złożoność powinna być zarabiana, nie zakładana. Każda warstwa abstrakcji, każda dodatkowa usługa, każda opcja konfiguracyjna to koszt. OpenArca płaci ten koszt tylko wtedy, gdy korzyść jest jasna i natychmiastowa.


Podsumowanie

Architektura OpenArca to zestaw celowych kompromisów zoptymalizowanych pod kątem dwóch rzeczy: bezproblemowego self-hostingu i dostępności dla kontrybutorów. SQLite zamiast PostgreSQL, bo prostota operacyjna jest ważniejsza niż teoretyczna skala. Node + React, bo zasięg wśród kontrybutorów jest ważniejszy niż nowość techniczna. Projekt monolityczny, bo prostota wdrożenia jest ważniejsza niż modna architektura.

Każdy wybór służy temu samemu celowi: narzędzie workflow, które mały zespół może wdrożyć w ciągu minut, nowy kontrybutor może zrozumieć w ciągu popołudnia, a projekt może utrzymywać bez gromadzenia przypadkowej złożoności.

Architektura nie jest sprytna. Jest celowo zwyczajna — i to właśnie sprawia, że działa.


Złożoność powinna być zarabiana, nie zakładana. OpenArca zarabia swoją prostotę, wybierając właściwe narzędzie do rzeczywistego problemu, a nie teoretycznego.

Wypróbuj OpenArca — darmowy i self-hosted

Open source na licencji AGPL-3.0. Wdrożenie z Docker w minuty.

Zobacz na GitHub