Kontekst
Klient prowadzi 10-letni biznes z trzema punktami detalicznymi w galeriach handlowych. Osiem lat temu zamówił u zewnętrznej agencji autorski system do zarządzania zamówieniami — zastąpił arkusze kalkulacyjne i papierowe segregatory, stając się mission-critical dla codziennej pracy we wszystkich trzech lokalizacjach.
Pierwotny zespół developerski zniknął kilka lat po wdrożeniu. Klient próbował zatrudniać kolejnych programistów do kontynuacji prac — każdy rezygnował po kilku dniach od zajrzenia w kod. Kiedy zgłosił się do mnie, system wciąż działał, ale z narastającymi problemami:
- brak backupów
- brak dokumentacji
- zahardkodowana konfiguracja logowania wskazująca na zewnętrznego dostawcę SaaS (trial niemal na pewno wygasł — autoryzacja była po cichu omijana przez
DEV_MODE=true) - kilkanaście zgłoszonych bugów, których nie dało się naprawić bez ingerencji w kod
- koszty hostingu (duży dedykowany VPS) wyraźnie nieproporcjonalne do skali biznesu
Klient przyszedł z dokumentem „plan zmian" zawierającym 21 wymagań funkcjonalnych, z jednym pytaniem: czy ten system warto rozwijać, czy przepisujemy od zera?
Podejście
Zaproponowałem dwuetapowy model współpracy, zamiast wchodzenia w prace na ślepo.
Etap 1 — Płatny audyt techniczny (4 dni)
Zanim cokolwiek tknąłem, przeanalizowałem kod i bazę danych, a następnie dostarczyłem pisemny raport:
- ocena jakości kodu i bazy danych
- każde z 21 wymagań zmapowane na konkretne zmiany (które pliki, które tabele, estymacja wysiłku)
- rekomendacja: rozwijać vs. przepisać z uzasadnieniem
- wycena pozycyjna etapu wdrożeniowego
Rekomendacja: rozwijać istniejący system. Mimo wieku kod był na tyle czytelny, a dane historyczne na tyle wartościowe, by zachować je bez migracji.
Etap 2 — Umowa z ceną stałą i harmonogramem
Na podstawie audytu zaproponowałem umowę z ceną stałą i pełnym przeniesieniem praw autorskich. Zakres podzielony na 3 grupy:
- A — Usunięcia (4 punkty): nieużywane moduły i pola usunięte w celu uproszczenia UX
- B — Naprawy bugów (5 punktów): każde zgłoszone zgłoszenie
- C — Nowe funkcje (12 punktów): faktyczna wartość biznesowa
Płatność z góry, 5 dni na uwagi klienta po dostarczeniu, kolejne 5 dni na poprawki w ramach pierwotnej ceny.
Wybrane decyzje techniczne
1. Wymiana backendu, zachowanie frontendu
Oryginalny backend (Java Spring Boot + Hibernate ORM) był porzucony. Zamiast go reanimować, przepisałem go na Node.js / Express, zachowując 100% kontraktu API: te same routy, ten sam kształt JSON-a, to samo zachowanie snake_case/camelCase, te same konwersje dat, ta sama semantyka pustych pól. Frontend React 15 nie wymagał zmiany ani jednej linii.
Efekt: zero ryzyka regresji UI, cała złożoność skupiona w jednym miejscu.
2. Generyczny CRUD świadomy schematu
Zamiast pisać po pięć handlerów na każdą tabelę, zaimplementowałem helper
crud(router, path, table, options), który:
- ładuje schemat tabeli przez
DESCRIBEprzy starcie - automatycznie konwertuje typy (
tinyint(1)↔ boolean,DECIMAL↔ number, DATE ze strefą lokalną) - obsługuje mapowanie snake_case ↔ camelCase
- udostępnia hooki
beforeSavedla logiki biznesowej
Dodanie kolejnej tabeli z pełnym CRUD to teraz jedna linia.
3. Migracja bazy danych bez przestoju
Nie zatrzymałem starej aplikacji — postawiłem nowy backend obok niej, wskazałem mu tę samą instancję MySQL i przełączyłem DNS / nginx dopiero po weryfikacji. Stara wersja stała jako siatka bezpieczeństwa rollback przez 72h.
4. Autoryzacja od zera zamiast reanimacji Auth0
Trial płatnego dostawcy dawno wygasł. Zamiast go odnawiać, napisałem własny moduł
(bcrypt + JWT + middleware requireRole) na prostej tabeli users.
Korzyści dla klienta:
- zero zewnętrznych zależności
- zero opłat subskrypcyjnych
- pełna kontrola nad listą kont
5. HTTPS przez reverse proxy
Caddy 2 + automatyczny Let’s Encrypt + przekierowanie http→https — klient dostaje kłódkę w przeglądarce, nie myśląc nigdy o odnawianiu certyfikatu.
Stack (docelowy)
- Backend: Node.js 16 + Express + mysql2
- Autoryzacja: bcrypt + jsonwebtoken + express-rate-limit
- Frontend: React 15 (react-scripts 0.8) — nieprzepisany
- Baza danych: MySQL 5.7
- Process manager: PM2 (autostart systemd po reboocie)
- Reverse proxy / TLS: Caddy 2 + Let’s Encrypt
- Hosting: Linux VPS
Wybrane dostarczone funkcje
- autouzupełnianie klienta po fragmencie telefonu/nazwiska (agregowane z zamówień historycznych)
- masowe aktualizacje cen z filtrem po serii/numerze, tryby procentowy i kwotowy, podgląd przed zatwierdzeniem
- masowy wydruk zamówień w jednym wywołaniu (page-break per zamówienie przez CSS print)
- ilość stanu rozbita z pojedynczej liczby na listę pozycji (np. „3m × 3 + 1m") z automatycznym sumowaniem
- filtr zamówień zakończonych z masowym oznaczeniem jako zrealizowane i undo
- kolorystyczne oznaczenie lokalizacji w tabeli
- role użytkowników (admin / sprzedaż / magazyn) z uprawnieniami
writeRolesper tabela - 2 szablony wydruków dopasowane do workflow klienta (iterowane na podstawie zdjęć z telefonu wysyłanych SMS-em)
Rezultaty
- 21 punktów kontraktowych — 100% dostarczone.
- Zero regresji frontendu po wymianie silnika backendu.
- Migracja bazy danych bez przestoju (wieczorne okno serwisowe, ~15 minut).
- HTTPS na własnej subdomenie klienta zamiast adresu IP.
- Hasła użytkowników zhashowane (bcrypt cost 10), JWT z 30-dniowym TTL.
- Baza danych backupowana retroaktywnie przed każdym deployem.
- Tego samego dnia akceptacja klienta, zgoda na kontynuację współpracy.
Proces i miękkie aspekty
Część, którą większość freelancerów pomija:
- Dostępność po wdrożeniu. Każdy SMS dostawał odpowiedź w godzinę. Każda uwaga — fix w ciągu dnia.
- Iteracyjne dopracowywanie wydruków na podstawie zdjęć z telefonu od właściciela. Dwie rundy iteracji na szablon wydruku.
- Decyzje pytane, nie narzucane. Przy każdej niejednoznaczności (struktura ról, format domeny) pytałem i dokumentowałem odpowiedź na piśmie.
- Transparentna komunikacja przed wdrożeniem. Przed większymi zmianami (autoryzacja, masowe aktualizacje zamówień zakończonych) wysyłałem mailowe uprzedzenie z instrukcją dla personelu na następny poranek.
Wyzwania i lekcje
1. React 15 i React.createClass
Komponenty zbudowane na createClass — bez hooków, bez klas ES6. Ręczne
this.bind, mixins, mutacja stanu przez this.state.x = y
(antywzorzec, ale spójny z otaczającym kodem). Pokusa modernizacji była silna, ale klient
nie zobaczyłby wartości.
2. Kodowanie i MySQL 8 vs 5.7
Lokalny XAMPP (MySQL 5.7) i Docker (MySQL 8) różnie obsługują caching_sha2_password.
Fix: skrypty migracyjne uruchamiane przez Node (nie CLI mysql), wszystko trzymane na
tym samym kliencie (mysql2).
3. CRLF w plikach .sql
Migracje edytowane na Windowsie miały zakończenia linii CRLF, co okazjonalnie ucinało
komendy w MySQL CLI. Fix: uruchamiałem każdą migrację przez Node (split po
;, filtrowanie komentarzy, zapytanie po zapytaniu).
4. bcrypt 6 wymaga Node ≥18, prod ma Node 16
Ostrzeżenie o niewspieranej wersji, ale funkcjonalnie wszystko OK. Zostawione z notatką na kolejną iterację (aktualizacja OS + Node).
5. Propagacja DNS i niejasne nameservery
Domena zarejestrowana u jednego dostawcy, DNS obsługiwane przez innego. Cache publicznych
resolverów vs. authoritative DNS — początkowo błędnie zdiagnozowałem problem jako
opóźnienie propagacji. Lekcja: zawsze pytaj authoritative nameserver, nie tylko
8.8.8.8.
Dalsze możliwości
Z projektu wyrósł kolejny roadmap: automatyczne powiadomienia SMS do klientów, raporty sprzedażowe, migracja na tańszy hosting (potencjalna oszczędność ~2000 PLN/rok) oraz modernizacja OS + Node. Te elementy są poza zakresem umowy bazowej, dostępne jako osobne etapy.
Co to case study mówi o tym, jak pracuję
- Nie wchodzę w legacy w ciemno. Płatny audyt jako osobny etap chroni obie strony przed nierealistycznymi oczekiwaniami.
- Rozliczam się za rezultaty, z jasnym zakresem i formalną akceptacją. Klient wie dokładnie, co dostaje, ja wiem dokładnie, co mam dostarczyć.
- Myślę o kliencie po wdrożeniu. Zhashowane hasła, backupy przed migracją, dokumentacja stanu produkcji, instrukcje operacyjne — rzeczy, o których nikt nie myśli, dopóki coś się nie zepsuje.
- Nie wciskam przepisania „bo nowe jest lepsze". React 15 działa, MySQL 5.7 działa, Node 16 działa. Modernizacja stacku to osobna decyzja biznesowa, nie mój techniczny fetysz.
- Transparentna komunikacja, w prostym języku. Klienci nietechniczni dostają maile, które mogą czytać bez mentalnego tłumaczenia z angielskiego.
Zgodnie z § 6 NDA obejmującym kod źródłowy, bazę danych i dane klientów, niniejsze case study pomija: nazwę firmy, dokładną lokalizację, dane osobowe pracowników i klientów, konkretne parametry infrastruktury oraz treść raportu z audytu. Skupia się wyłącznie na procesie, decyzjach technicznych i jakościowych rezultatach.