Od prostego skryptu do rosnącego projektu – punkt startu
Jak wygląda typowy pierwszy skrypt w Pythonie
Początek zazwyczaj jest bardzo prosty: jeden plik .py, kilka importów, odrobina logiki i wywołanie wszystkiego na samym dole. Kod uruchamia się „od góry do dołu”, bez większego planu architektonicznego. Często taki skrypt powstaje jako szybka automatyzacja jakiegoś żmudnego zadania: pobierz dane z API, coś przelicz, wygeneruj raport, wyślij maila.
Struktura jest intuicyjna, ale zupełnie nieprzygotowana na wzrost. Funkcje są pisane ad hoc, nazwy zmiennych wynikają z chwili, a konfiguracja (np. klucze API) ląduje twardo w kodzie. Nie ma wydzielonej warstwy dostępu do danych, nie ma abstrakcji, nie ma testów. Jest za to jedna, kluczowa cecha: skrypt działa. I na początku to zupełnie wystarcza.
Problem w tym, że taki skrypt łatwo zmienia się w rozrastający się „potwór”. Z każdą kolejną poprawką dokładamy nową funkcję, nowego if-a, nową gałąź logiki. Przez chwilę to jeszcze trzyma się w ryzach, ale już po kilku tygodniach trudno jest odpowiedzieć na proste pytanie: „co dokładnie robi ten plik i od czego zależy?”.
Objawy, że skrypt przestaje wystarczać
Moment przejścia z „małego skryptu” do „rosnącego projektu” zwykle nie jest zaplanowany. Najpierw pojawiają się objawy, że coś zaczyna boleć, np.:
- Duplikacja kodu – kopiujesz te same 20 linii w trzech miejscach, bo „na szybko” trzeba było coś dodać.
- Trudne debugowanie – jedno uruchomienie skryptu trwa kilka minut, a błąd pojawia się po 80% przebiegu.
- Brak testów – każdą zmianę sprawdzasz ręcznie, bo nie ma sensownego sposobu na automatyczne testy.
- Globalny stan – zmienne globalne, pojedyncze instancje klientów, które są modyfikowane z różnych miejsc.
- Brak separacji odpowiedzialności – to samo miejsce w kodzie odpowiada za pobieranie danych, ich przetwarzanie i prezentację.
Gdy takie symptomy zaczynają się nawarstwiać, rośnie też koszt każdej zmiany. Dodanie nowej funkcji, która wcześniej zajmowała godzinę, zajmuje nagle pół dnia, bo ciągle coś się „wysypuje” w innym miejscu.
Różnica mentalna: skrypt vs aplikacja
Przeskok, który trzeba wykonać, jest przede wszystkim mentalny. Skrypt ma po prostu zadziałać. Jest tworzony, żeby szybko rozwiązać jeden problem. Aplikacja ma dać się utrzymać i rozwijać przez dłuższy czas – zwykle przez więcej niż jedną osobę.
Ten drugi tryb wymusza zupełnie inne decyzje:
- Zaczynasz świadomie myśleć o strukturze katalogów.
- Myślisz o tym, jak będą wyglądały testy i pipeline CI.
- Definiujesz granice odpowiedzialności – gdzie kończy się logika domenowa, a zaczyna obsługa API, bazy czy plików.
- Przestajesz dorzucać logikę „gdzie popadnie”, a zaczynasz ją lokalizować w odpowiednich modułach.
Różnica nie wynika z wielkości kodu, ale z intencji. Nawet stosunkowo mała ilość kodu, pisana z myślą o utrzymaniu, będzie zorganizowana inaczej niż szybki skrypt.
Dwa wektory wzrostu: złożoność domeny i rosnący ruch
Projekty Pythonowe rosną zwykle w dwóch głównych wymiarach:
- Złożoność domeny – coraz więcej przypadków użycia, reguł biznesowych, integracji z innymi systemami.
- Ruch/obciążenie – rosnąca liczba użytkowników, danych, żądań na sekundę, zadań w kolejce.
Złożoność domeny wymusza modularność, dobrą architekturę i czytelny podział odpowiedzialności. Wzrost ruchu z kolei pcha projekt w stronę skalowania, cache’owania, kolejek i w końcu – mikroserwisów. Czasem obydwa wektory rosną jednocześnie i wtedy bałagan w kodzie bardzo szybko odbija się na wydajności i stabilności systemu.
Krótki przykład: od skryptu do systemu analitycznego
Klasyczny scenariusz: ktoś pisze w Pythonie skrypt do generowania cotygodniowego raportu sprzedaży. Na początku:
- Skrypt łączy się z bazą, robi SELECT, tworzy plik CSV i wysyła go mailem.
- Kod jest w jednym pliku, uruchamiany z crona.
Po kilku miesiącach biznes prosi o dodatkowe raporty, filtrowanie po regionach, generowanie wykresów PDF i topkę klientów. Skrypt zaczyna obsługiwać 10+ różnych raportów. Pojawia się pragnienie interfejsu webowego, żeby każdy menedżer mógł sam wygenerować raport. W efekcie twórca skryptu ma już nie „skrypt”, ale zalążek systemu analitycznego. Jeśli w tym momencie nadal pracuje w jednym pliku, ewolucja dalszej architektury będzie bardzo bolesna.
Pierwszy krok wzrostu: porządkowanie jednego repozytorium i struktury projektu
Konwersja skryptu do prostego pakietu
Pierwszy sensowny krok w ewolucji kodu w Pythonie to przekształcenie luźnego skryptu w pakiet. Oznacza to:
- utworzenie katalogu na kod źródłowy, np.
src/, - wydzielenie modułów tematycznych,
- dodanie pliku
__init__.py, aby katalog stał się pakietem.
Przykładowa minimalna struktura rosnącego projektu może wyglądać tak:
project-root/
src/
myapp/
__init__.py
main.py
services.py
repository.py
config.py
tests/
test_basic.py
pyproject.toml / setup.cfg
README.md
Sam fakt wydzielenia modułów wymusza myślenie w kategoriach odpowiedzialności. Funkcje związane z dostępem do bazy trafiają do repository.py, logika biznesowa do services.py, a punkt wejścia do main.py. Skrypt przestaje być jedną sekwencją instrukcji, a staje się zbiorem współpracujących modułów.
Minimalna struktura katalogów dla rosnącej aplikacji
Przed pojawieniem się drugiego dewelopera dobrze jest mieć choćby prosty, ale przemyślany układ katalogów. Często sprawdza się wariant:
project-root/
src/
myapp/
__init__.py
api/
core/
infrastructure/
config/
tests/
unit/
integration/
scripts/
configs/
Prosta interpretacja:
- api/ – warstwa wejściowa: HTTP (FastAPI, Django), CLI (typer, click), gRPC itp.
- core/ – logika domenowa, modele, reguły biznesowe.
- infrastructure/ – adaptery do baz danych, kolejek, systemu plików, usług zewnętrznych.
- config/ – ładowanie i walidacja konfiguracji, profile środowiskowe.
- tests/ – wyraźnie wydzielone testy jednostkowe i integracyjne.
- scripts/ – narzędzia developerskie, migracje, joby maintenance’owe.
Taki podział pozwala nowej osobie w zespole szybko zrozumieć, gdzie szukać konkretnego typu funkcjonalności. Jednocześnie nie jest przesadnie skomplikowany – nadaje się nawet dla kilku tysięcy linii kodu.
Rozdzielenie warstw: biznes, dane, prezentacja
Przy rosnącym projekcie w Pythonie warto zastosować prostą zasadę: każda warstwa ma swoją odpowiedzialność. Przykładowy trójpodział:
- Logika biznesowa (core) – reguły domenowe, operacje na modelach, scenariusze użycia.
- Dostęp do danych (infrastructure) – ORM, zapytania SQL, klienci API.
- Prezentacja/Interfejs (api) – HTTP endpointy, CLI, serializacja/parsowanie danych z zewnątrz.
W praktyce oznacza to, że:
- funkcje z warstwy API nie powinny wykonywać SQL-a bezpośrednio,
- logika biznesowa powinna działać na abstrakcjach (interfejsach repozytoriów), nie na konkretnych klientach bazy,
- kod związany z HTTP (request, response, status code) powinien być oddzielony od reguł domenowych.
Odseparowanie warstw umożliwia późniejsze testowanie logiki domenowej bez odpalania całej aplikacji, łatwiejszą wymianę bazy danych czy kolejek oraz prostsze przejście na mikroserwisy – bo granice warstw staną się naturalnymi punktami cięcia.
Zasady nazewnictwa ułatwiające rozwój
Przy jednym deweloperze można żyć z nazwami typu do_stuff(), ale w większym zespole to droga do frustracji. Kilka prostych zasad bardzo poprawia czytelność:
- Funkcje nazywaj od akcji w domenie, np.
generate_monthly_report(),approve_order(). - Moduły grupuj tematycznie:
users_service.py,orders_repository.py, zamiasthelpers.py,misc.py. - Unikaj nazw „technicznych” dla kodu domenowego: lepiej
billing.pyniżpayment_utils.py. - Stałe konfiguracyjne trzymaj w jednym miejscu (moduł
settingslubconfig), nie rozrzucaj po plikach.
Przejrzyste nazewnictwo to rodzaj dokumentacji, która utrzymuje się sama. Przy przejściu w stronę mikroserwisów pozwala szybko namierzyć kod odpowiedzialny za konkretny kawałek domeny.
Mała checklista przed zaproszeniem drugiego dewelopera
Zanim do projektu wejdzie kolejna osoba, warto wykonać kilka podstawowych porządków:
- Utworzyć spójną strukturę katalogów (np.
src/myapp/core, api, infrastructure). - Wydzielić pliki konfiguracyjne i przestać trzymać sekrety w kodzie.
- Dołożyć pierwsze testy jednostkowe kluczowych funkcji biznesowych.
- Skonfigurować narzędzia formatowania (black, isort) i lintery (flake8, ruff).
- Napisać zwięzły README z instrukcją uruchomienia projektu.
Te kilka punktów potrafi oszczędzić wiele godzin tłumaczenia i zmniejsza ryzyko, że nowa osoba zacznie dorzucać logikę „gdziekolwiek się kompiluje”.

Od „big ball of mud” do monolitu z prawdziwego zdarzenia
Czym jest „big ball of mud” w Pythonie
Big ball of mud to nieformalny termin opisujący system, który działa, ale nie ma żadnej sensownej struktury. W Pythonie typowe symptomy to:
- Ogromne pliki z setkami lub tysiącami linii (np. gigantyczny
views.pyczytasks.py). - Cykliczne importy między modułami – bez
from x import ynic nie da się uruchomić. - Globalny stan (poziom modułu) używany w różnych kontekstach, co prowadzi do trudnych do wykrycia błędów.
- Brak jasnego podziału na moduły domenowe – wszystko miesza się ze wszystkim.
Co ważne, big ball of mud może dotyczyć także systemu webowego opartego na frameworku (Django, Flask, FastAPI). Sam fakt używania popularnego frameworka nie gwarantuje dobrej architektury – można mieć monstrualny, jednoplikowy widok z całością logiki biznesowej, który praktycznie uniemożliwia refaktoryzację.
Wprowadzanie granic – pierwszy krok do sensownego monolitu
Odchudzenie big ball of mud do rozsądnego monolitu zaczyna się od wprowadzenia granic. W praktyce oznacza to:
- zidentyfikowanie obszarów domeny (np. użytkownicy, płatności, raporty, produkty),
- przypisanie istniejącego kodu do tych obszarów, nawet jeśli na początku jest to umowne,
- stopniowe przenoszenie funkcji i klas do nowych modułów, pozostawiając po sobie cienką warstwę wywołań w starym miejscu.
Dobrą techniką jest wprowadzenie lekkich bounded contexts – czyli logicznych granic, w których używa się spójnego języka i modeli. Np. moduł billing nie powinien wiedzieć nic o wewnętrznej reprezentacji użytkownika w module users, poza tym, co udostępniają wyraźnie zdefiniowane interfejsy.
Refaktoryzację warto prowadzić małymi krokami, zaczynając od najbardziej dotkliwych miejsc, np. gigantycznych plików z widokami czy zadaniami w kolejce. Każde wyciągnięcie fragmentu logiki do osobnej funkcji lub modułu z jasnym interfejsem zmniejsza stopień „błotnistości” systemu.
Pakiety domenowe vs pakiety techniczne
Pakiety domenowe zamiast technicznych „szuflad”
Przy przechodzeniu z big ball of mud do sensownego monolitu kluczowe jest odejście od pakietów zorganizowanych wyłącznie technicznie (models/, views/, serializers/) na rzecz pakietów domenowych. Chodzi o to, żeby kod grupować według pojęć biznesowych, a nie według typów plików.
Przykład niekorzystnego układu:
myapp/
models/
user.py
order.py
views/
user_views.py
order_views.py
services/
user_service.py
order_service.py
Tu wszystko jest porozrzucane: żeby zrozumieć, jak działa obsługa użytkownika, trzeba skakać między kilkoma katalogami. Dużo lepiej sprawdza się podejście „feature-first”:
myapp/
users/
__init__.py
models.py
api.py
service.py
repository.py
orders/
__init__.py
models.py
api.py
service.py
repository.py
shared/
db.py
email.py
auth.py
Cały kod dotyczący konkretnego obszaru domeny (np. użytkownicy) trafia do jednego pakietu. Zespół wie, że sprawy użytkowników załatwia się w users/. Podczas późniejszego cięcia na mikroserwisy taki pakiet domenowy bardzo często staje się bazą nowego serwisu.
Redukcja zależności między pakietami
Sam podział na pakiety domenowe nie wystarczy, jeśli dalej każdy moduł importuje wszystko od każdego. Żeby monolit pozostał zrozumiały, potrzebne są jasne kierunki zależności. Pomaga kilka prostych zasad:
- pakiety domenowe mogą korzystać z
shared/, ale nie odwrotnie, - komunikacja między pakietami domenowymi odbywa się przez jawne API (np. funkcje/fasady w
service.py), a nie przez bezpośredni dostęp do modeli wewnętrznych, - API jednego pakietu minimalizuje ujawnianie szczegółów implementacyjnych; zwraca proste typy lub dedykowane DTO, zamiast wewnętrznych obiektów ORM.
Przykład dobrej zależności między pakietami:
# orders/service.py
from myapp.users.service import get_user_for_billing
def create_order(user_id: int, items: list[dict]) -> int:
user = get_user_for_billing(user_id)
# ... reszta logiki zamówienia
# users/service.py
from .repository import get_user
def get_user_for_billing(user_id: int) -> dict:
user = get_user(user_id)
return {
"id": user.id,
"billing_address": user.billing_address,
}
Pakiet orders nie wie nic o wewnętrznym modelu użytkownika. Widzi tylko to, co użytkownicy wystawiają jako kontrakt. Takie ograniczenie wiedzy to inwestycja w przyszłe wydzielenie usług.
Od monolitu „frameworkowego” do monolitu domenowego
Typowy etap pośredni: monolit jest poprawnie podzielony z punktu widzenia frameworka (np. Django: apps.py, models.py, views.py), ale granice nie pokrywają się z domeną biznesową. Wtedy framework staje się „kostiumem”, pod który trzeba wszyć porządniejszy szkielet domenowy.
Dobrze działa podejście dwuwarstwowe:
- warstwa frameworkowa – pliki, których framework wymaga w konkretnej strukturze (np. w Django:
views.py,admin.py,urls.py), - warstwa domenowa – własne pakiety i moduły, niezależne od frameworka, z logiką biznesową i interfejsami.
Widoki, routery, komendy CLI stają się cienkimi adapterami wywołującymi domain.services.*. Dzięki temu:
- łatwiej testować logikę niezależnie od HTTP czy CLI,
- przejście na inny framework (lub dodanie drugiego interfejsu, np. CLI obok HTTP) wymaga mniejszego wysiłku,
- monolit zaczyna przypominać zestaw usług domenowych zamiast stosu „widoki->ORM->SQL”.
Moduł „shared” – użyteczny, ale niech nie zjada projektu
W większych monolitach zawsze pojawia się pokusa tworzenia gigantycznego modułu shared/common/utils. To naturalne miejsce na wspólny kod, ale szybko zamienia się w czarną dziurę, która wciąga wszystko.
Żeby uniknąć kolejnej big ball of mud w jednym katalogu, przydają się proste zasady:
- do
shared/trafia tylko kod bez zależności domenowych (np. logger, helper do paginacji, klient Redis, wrapper do wysyłki maili), - jeśli moduł w
shared/zaczyna „znać” szczegóły jakiegoś bounded contextu, przenieś go z powrotem do odpowiedniego pakietu domenowego, - zamiast pliku
utils.py, twórz wyspecjalizowane moduły, np.datetime_tools.py,string_formatting.py.
Krótko mówiąc, shared służy infrastrukturze i narzędziom, nie logice biznesowej.
Kluczowe decyzje przed wejściem w świat mikroserwisów
Czy monolit faktycznie „nie daje rady”?
Najpierw wypada uczciwie odpowiedzieć na jedno pytanie: czy monolit jest realnym ograniczeniem, czy tylko przeszkadza „ideologicznie”. Przed ruszeniem w stronę mikroserwisów sprawdź kilka rzeczy:
- Czy pojawiają się problemy wydajnościowe, których nie da się rozwiązać skalowaniem monolitu „w poziomie” (więcej instancji) lub optymalizacją baz danych?
- Czy zespoły blokują się wzajemnie, bo pracują stale w tych samych modułach, a każdy release jest bolesnym merge hell?
- Czy istnieją wyraźne, relatywnie niezależne obszary domenowe, które można byłoby dostarczać i rozwijać oddzielnie?
Jeżeli główny problem to chaos w kodzie, brak testów i przypadkowa architektura, przejście na mikroserwisy prawie na pewno tylko go rozszerzy na sieć mikroserwisów. Najpierw trzeba doprowadzić monolit do „zdrowego” stanu: czyste granice, testowalna logika, podstawowe obserwowalności (logi, metryki, trace’y).
Stabilność domeny vs eksperymenty
Mikroserwisy lubią domeny relatywnie stabilne, z w miarę trwałymi granicami. Jeżeli produkt jest w fazie intensywnych pivotów i tygodniowych zmian założeń, lepiej zostać w dobrze ułożonym monolicie. Więcej zyskasz na szybkości refaktoryzacji niż na „czystej” separacji usług.
Dobre sygnały, że domena fragmentu systemu jest dojrzała:
- API zewnętrzne (np. kontrakty z klientami B2B) prawie się nie zmienia,
- reguły biznesowe są dobrze udokumentowane, a zmiany dotyczą raczej parametrów niż całej logiki,
- zespół ma wspólny język pojęć, nie trwa już ciągłe przepychanie się o definicje.
Taki obszar łatwiej przenieść do mikroserwisu, bo nie trzeba co sprint zmieniać granic. Serwis żyje we własnym tempie, a jego API może stać się stabilnym kontraktem dla reszty organizacji.
Organizacja zespołów i ownership
Mikroserwisy bez odpowiedniej struktury zespołowej prowadzą prosto do bałaganu. Monolit „zniesie” brak wyraźnej odpowiedzialności, bo wszystko jest w jednym miejscu. W świecie wielu usług trzeba jasno określić, kto jest właścicielem którego kawałka systemu.
Przed migracją odpowiedz na kilka pytań:
- Czy istnieją zespoły aligned z obszarami domenowymi (np. „Zespół Płatności”, „Zespół Zamówień”), a nie tylko technicznymi (np. „Frontend”, „Backend”, „DevOps”)?
- Czy każdy zespół ma kompetencje do obsłużenia pełnego cyklu życia swojej usługi (kod, testy, deployment, monitoring)?
- Czy procesy release’owe pozwalają niezależnie wydawać i rollbackować poszczególne serwisy?
Bez tego mikroserwisy stają się tylko techniczną ciekawostką. Zespoły dalej blokują się wzajemnie, ale teraz dodatkowo walczą z kontraktami między usługami.
Dane: jedna baza czy wiele?
Monolit najczęściej używa jednej bazy danych (lub klastra) z pojedynczym schematem. Mikroserwisy preferują podejście „database per service”, czyli każdy serwis zarządza własnymi danymi. Przejście między tymi modelami to jedno z najtrudniejszych miejsc migracji.
Przed podjęciem decyzji przyda się krótka analiza:
- Jak bardzo dane różnych obszarów domeny są ze sobą sprzężone (np. twarde joiny między tabelami
usersiorders)? - Czy są procesy, które wymagają atomowych transakcji obejmujących wiele obszarów (np. utworzenie zamówienia + rezerwacja środków + wysłanie faktury)?
- Czy zespół zna wzorce kompensacji i eventual consistency (sagi, outbox pattern, idempotentne operacje)?
Jeżeli logika systemu jest pełna rozlanych joinów, czasem sensowniejszy jest etap pośredni: jeden schemat logiczny na bounded context w tej samej instancji bazy. Dopiero gdy granice są sprawdzone, można iść w pełne rozdzielenie fizyczne.

Jak kroić monolit – zasady wyznaczania granic serwisów
Granice według domeny, nie według warstw
Najczęstszy antywzorzec: serwis „od API”, serwis „od bazy danych”, serwis „od autoryzacji”. To dalej techniczne cięcie, które zwiększa ilość ruchu sieciowego, ale nie rozwiązuje problemów złożoności domenowej.
Lepsze podejście to cięcie według bounded contextów, które już i tak zostały wprowadzone w monolicie. Przykładowy podział:
- Users – rejestracja, profil, logowanie, zarządzanie kontem,
- Orders – składanie i obsługa zamówień, statusy, historia,
- Billing – płatności, faktury, rozliczenia,
- Catalog – produkty, dostępność, ceny katalogowe.
Każdy z tych kontekstów ma pełny „stos” w środku (API, logika, dane). Warstwy techniczne (HTTP, baza) są powielone w każdym serwisie, ale logika staje się niezależna. Monolit, który wcześniej miał takie pakiety, wręcz podpowiada naturalny podział.
Analiza zależności w monolicie
Zanim cokolwiek potniesz, potrzebujesz rzetelnego obrazu, jak moduły ze sobą rozmawiają. Można to zrobić ręcznie, ale lepiej wesprzeć się narzędziami:
- proste skrypty korzystające z
astw Pythonie do zebrania grafu importów, - narzędzia typu
snakefood,pydepsczydeptrydo wizualizacji zależności, - analiza logów/metryk (które moduły są najczęściej wywoływane razem, które endpointy generują największy ruch).
Po wygenerowaniu grafu importów szukaj klastrów – grup modułów, które intensywnie rozmawiają ze sobą, a słabo z resztą. To często naturalne kandydaty na osobny serwis.
Identyfikacja „hotspotów” biznesowych
Nie każdy bounded context musi od razu stać się osobnym serwisem. Zazwyczaj lepiej zacząć od obszarów, które:
- często się zmieniają,
- są krytyczne wydajnościowo,
- lub są wąskim gardłem organizacyjnym (wiele zespołów modyfikuje ten sam kod).
Przykład z praktyki: sklep internetowy, w którym monolit obsługuje wszystko. Zespół zauważa, że każdy release wymaga dużej ostrożności, bo zmiany w promocjach łatwo psują koszyk. Naturalnym kandydatem na pierwszy serwis staje się Pricing/Promotions, bo:
- często dostaje nowe funkcje,
- ma sporo specyficznej logiki,
- reszta systemu potrzebuje od niego głównie „wyniku obliczeń”, nie surowych danych.
Testy jako zabezpieczenie podczas cięcia
Refaktoryzacja monolitu do mikroserwisów bez sensownego pokrycia testami jest ryzykiem, które łatwo zamienić w długi przestój produkcji. Minimalny zestaw zabezpieczeń:
- testy jednostkowe kluczowej logiki domenowej w wybranych bounded contextach,
- testy integracyjne obecnych API monolitu (np. kontraktowe dla HTTP/gRPC),
- jeśli to możliwe – snapshoty odpowiedzi dla najważniejszych scenariuszy, które posłużą jako benchmark po wydzieleniu serwisu.
Przy pierwszym mikroserwisie dobrym wzorcem jest strangler fig pattern: nowy serwis przejmuje ruch stopniowo, a monolit pełni rolę proxy. Testy kontraktowe i porównanie odpowiedzi pomagają wychwycić różnice w zachowaniu zanim trafią do wszystkich użytkowników.
Wspólny kod, zależności i kontrakty między serwisami
Minimalizacja współdzielonych bibliotek
Najczęściej zadawane pytania (FAQ)
Kiedy prosty skrypt w Pythonie powinien stać się „prawdziwą” aplikacją?
Najprostszy sygnał: każda zmiana zaczyna boleć. Jeśli dodanie małej funkcji zajmuje pół dnia, bo coś ciągle psuje się w innym miejscu, skrypt mentalnie stał się aplikacją, tylko struktura jeszcze za tym nie nadąża.
Inne typowe objawy to: duplikacja tych samych fragmentów kodu, trudne debugowanie długich przebiegów, ręczne „klikanie” wszystkich scenariuszy po każdej zmianie, rosnąca liczba if-ów w jednym pliku oraz globalne zmienne używane w wielu miejscach. Jeśli widzisz kombinację tych sygnałów – czas wydzielić moduły, testy i prostą architekturę.
Jak krok po kroku przekształcić pojedynczy skrypt w pakiet Pythona?
Najprostsza ścieżka to kilka konkretnych kroków:
- utwórz katalog
src/i w nim podkatalog z nazwą aplikacji, np.src/myapp/, - przenieś główny skrypt do
main.pyw tym katalogu, - dodaj pusty plik
__init__.py, żeby Python traktował katalog jako pakiet, - podziel kod na pliki tematyczne, np.
services.py(logika),repository.py(dostęp do danych),config.py(konfiguracja).
Na końcu dodaj folder tests/ z choćby jednym prostym testem oraz plik pyproject.toml lub setup.cfg, jeśli planujesz instalację pakietu. Samo przejście z jednego pliku do pakietu wymusza klarowniejszy podział odpowiedzialności.
Jaka struktura katalogów jest dobra dla rosnącej aplikacji w Pythonie?
Dla większości projektów dobrze sprawdza się prosty, ale konsekwentny układ:
src/myapp/api/– HTTP endpointy, CLI, wszystko co „wystawia” aplikację na zewnątrz,src/myapp/core/– logika domenowa, przypadki użycia, modele,src/myapp/infrastructure/– baza danych, kolejki, system plików, zewnętrzne API,src/myapp/config/– ładowanie i walidacja konfiguracji, profile środowisk,tests/unit/itests/integration/– osobno testy jednostkowe i integracyjne,scripts/– jednorazowe joby, migracje, narzędzia developerskie.
Taki podział pozwala nowej osobie szybko zorientować się, gdzie trafić z konkretną zmianą: nowy endpoint trafia do api/, nowa reguła biznesowa do core/, a nowy klient do bazy lub API – do infrastructure/.
Jak oddzielić logikę biznesową od warstwy dostępu do danych i prezentacji?
Praktyczny sposób to trójwarstwowy podział:
- core – „czysty” kod domenowy (funkcje typu
generate_monthly_report()), bez importów HTTP czy ORM, - infrastructure – konkretne implementacje repozytoriów i klientów (SQLAlchemy, klienci REST),
- api – framework (FastAPI, Django, typer), mapowanie request/response na wywołania funkcji z core.
W praktyce endpoint HTTP wywołuje przypadek użycia z core/, przekazując mu „abstrakcyjne” repozytorium (np. interfejs). Sama implementacja repozytorium w infrastructure/ wie, jak pogadać z bazą czy zewnętrznym API. Dzięki temu logikę domenową testujesz bez serwera HTTP i bez prawdziwej bazy.
Po czym poznać, że projekt zbliża się do momentu podziału na mikroserwisy?
Najpierw rośnie złożoność domeny i ruch, a monolit zaczyna mieć problemy wydajnościowe lub organizacyjne. Typowe sygnały:
- osobne zespoły dotykają ciągle tych samych fragmentów monolitu i blokują się nawzajem,
- zmiana małej funkcji (np. raportowania) wymaga wdrożenia całej aplikacji,
- niektóre moduły mają inne wymagania niefunkcjonalne (np. raporty ciężkie obliczeniowo vs. proste API do CRUD),
- skalujesz całość, choć tylko część systemu faktycznie generuje obciążenie.
Jeśli granice między modułami w monolicie są już jasne (np. core podzielone na poddomeny), łatwiej później wyciąć poszczególne fragmenty jako osobne serwisy. Bez tego przejście do mikroserwisów zwykle kończy się chaosem sieciowym zamiast porządku.
Jakie nazwy funkcji i modułów pomagają w utrzymaniu większego projektu w Pythonie?
Przy większym zespole nazwy muszą od razu mówić, co kod robi i w jakiej domenie działa. Zamiast ogólnych do_stuff() lepiej stosować nazwy akcji biznesowych: generate_monthly_report(), approve_order(), calculate_customer_lifetime_value().
Podobnie z modułami – nazwy typu services.py i repository.py są akceptowalne na start, ale szybko możesz je rozbić według domeny: reports_service.py, orders_repository.py, billing_service.py. Kto nowy wejdzie w projekt, od razu zobaczy nie tylko techniczną warstwę, ale też mapę pojęć biznesowych.
Jak zacząć dodawać testy do projektu, który wyrósł z jednego skryptu?
Najprościej: zacznij od testów logiki domenowej, bez dotykania bazy czy HTTP. Najpierw wydziel funkcje „czystej” logiki do modułów w core/, a potem dopisz do nich testy jednostkowe w tests/unit/.
Dobry minimalny plan to: skonfigurować pytest, dodać kilka testów dla najważniejszych przypadków użycia (np. generowanie kluczowego raportu), a dopiero później obejmować testami integracyjnymi bazę, kolejki czy endpointy HTTP. Taka kolejność szybko daje zwrot – przy każdej zmianie możesz odpalić szybkie testy i złapać regresje, zanim puścisz długi, „produkcyjny” przebieg skryptu.






