PostgreSQL i aplikacje: łączenie bazy danych z prawdziwymi projektami

Witaj z powrotem.
W poprzedniej lekcji zbudowałeś małą bazę danych dla sklepu.
Utworzyłeś:
customers
categories
products
orders
order_items
Użyłeś:
- kluczy głównych;
- kluczy obcych;
JOIN;- funkcji agregujących;
- indeksów;
- myślenia jak w prawdziwym projekcie.
Bardzo dobrze.
Teraz PostgreSQL nie jest już tylko tajemniczym stworzeniem z terminala.
Zaczyna być prawdziwym narzędziem.
Ale zostaje jedno bardzo ważne pytanie:
Jak aplikacja naprawdę łączy się z PostgreSQL?
Bo w prawdziwych projektach użytkownicy nie otwierają psql i nie wpisują SQL ręcznie.
Zazwyczaj.
Aplikacja backendowa łączy się z bazą danych.
Potem aplikacja czyta dane.
Zapisuje dane.
Aktualizuje dane.
Usuwa dane.
Miejmy nadzieję, że nie wszystkie dane.
To byłby bardzo dramatyczny wtorek.
Dzisiaj nauczymy się, jak aplikacje łączą się z PostgreSQL.
Ta lekcja nie dotyczy jednego konkretnego frameworka.
Te zasady pasują do:
Django
Spring Boot
Node.js
Express
NestJS
Laravel
Rails
aplikacji Go
każdego backendu, który używa PostgreSQL
Frameworki się zmieniają.
Zasady połączenia zostają.
Bardzo przydatne.
Bardzo bazodanowe.
Bardzo “proszę, nie wkładaj hasła bezpośrednio do kodu”.
Czego się nauczysz
W tej lekcji nauczysz się:
- jak aplikacje łączą się z PostgreSQL;
- czym jest połączenie z bazą danych;
- co oznacza host, port, nazwa bazy danych, username i password;
- czym jest connection string;
- jak utworzyć użytkownika bazy danych dla aplikacji;
- dlaczego nie powinieneś używać superusera
postgresw aplikacji; - jak używać zmiennych środowiskowych;
- jak działa plik
.env; - typowych przykładów połączenia;
- podstawowych zasad bezpieczeństwa;
- typowych błędów połączenia;
- różnic między lokalnym developmentem a produkcją.
Pod koniec tej lekcji zrozumiesz most między PostgreSQL a aplikacjami backendowymi.
Bo baza danych bez aplikacji jest użyteczna.
Ale baza danych połączona z aplikacją staje się potężna.
Jak magazyn połączony ze sklepem.
Albo jak ekspres do kawy połączony ze zmęczonym programistą.
Ważna infrastruktura.
Podstawowa idea
Aplikacja potrzebuje informacji połączeniowych, żeby rozmawiać z PostgreSQL.
Zwykle potrzebuje:
host
port
nazwa bazy danych
username
password
Przykład:
host: localhost
port: 5432
database: shop_db
username: shop_user
password: strong_password
Aplikacja używa tych wartości, żeby otworzyć połączenie.
Potem może wysyłać zapytania SQL.
Na przykład:
SELECT * FROM products;
Aplikacja wysyła zapytanie.
PostgreSQL je wykonuje.
PostgreSQL zwraca wynik.
Aplikacja używa wyniku.
Może pokazuje produkty na stronie.
Może tworzy odpowiedź API.
Może generuje raport.
Może się psuje, bo ktoś zapomniał średnika.
Klasyka.
Host PostgreSQL
Host mówi aplikacji, gdzie działa PostgreSQL.
W lokalnym developmencie hostem często jest:
localhost
albo:
127.0.0.1
To oznacza:
PostgreSQL działa na tej samej maszynie co aplikacja.
Przykład:
host=localhost
Na produkcji PostgreSQL może działać na innym serwerze.
Przykład:
host=db.example.com
albo:
host=10.0.0.5
albo wewnątrz Dockera:
host=postgres
Host zależy od tego, gdzie mieszka baza danych.
Aplikacja potrzebuje poprawnego adresu.
Inaczej będzie pukać do złych drzwi.
PostgreSQL nie odpowie z domu, w którym nie mieszka.
Bardzo rozsądne.
Port PostgreSQL
PostgreSQL zwykle nasłuchuje na porcie:
5432
Dlatego typowe połączenie używa:
port=5432
Port jest jak numer drzwi na serwerze.
Serwer może uruchamiać wiele usług.
PostgreSQL domyślnie używa portu 5432.
Jeśli zmieniłeś port, musisz użyć nowego.
Ale dla początkujących najczęściej odpowiedź brzmi:
5432
Rzadki moment prostoty.
Ciesz się nim.
Nazwa bazy danych
Jeden serwer PostgreSQL może zawierać wiele baz danych.
Na przykład:
learning_postgresql
shop_db
portfolio_db
blog_db
Twoja aplikacja musi wiedzieć, której bazy używać.
Przykład:
database=shop_db
Jeśli aplikacja połączy się z niewłaściwą bazą, dzieją się dziwne rzeczy.
Tabele mogą nie istnieć.
Dane mogą wyglądać na stare.
Twój mózg może zacząć debugować zły wszechświat.
Zawsze sprawdzaj nazwę bazy danych.
Mała literówka.
Duży ból głowy.
Username i password
PostgreSQL używa użytkowników i haseł do kontroli dostępu.
Przykład:
username=shop_user
password=strong_password
Użytkownik określa, co aplikacja może robić.
Użytkownik może mieć uprawnienia takie jak:
łączenie się z bazą danych
czytanie tabel
wstawianie wierszy
aktualizowanie wierszy
usuwanie wierszy
tworzenie tabel
Nie dawaj każdej aplikacji pełnych mocy superusera.
To jak dać dziecku piłę łańcuchową, bo chciało przeciąć kartkę papieru.
Technicznie skuteczne.
Głęboko nierozsądne.
Nie używaj użytkownika postgres w aplikacjach
PostgreSQL często ma domyślnego użytkownika administracyjnego:
postgres
Ten użytkownik jest potężny.
Bardzo potężny.
Zbyt potężny dla zwykłych aplikacji.
Nie używaj postgres jako użytkownika aplikacji.
Zamiast tego utwórz osobnego użytkownika dla aplikacji.
Przykład:
shop_user
blog_user
app_user
portfolio_user
Dlaczego?
Bo jeśli aplikacja ma błąd albo zostanie zaatakowana, chcesz ograniczyć szkody.
Użytkownik aplikacji powinien mieć tylko te uprawnienia, których potrzebuje.
Nie klucze do całego królestwa.
Królestwo bazy danych ma smoki.
I faktury.
Tworzenie bazy danych dla aplikacji
Otwórz PostgreSQL jako użytkownik postgres:
sudo -iu postgres psql
Utwórz bazę danych:
CREATE DATABASE shop_db;
Utwórz użytkownika:
CREATE USER shop_user WITH PASSWORD 'change_this_password';
Daj użytkownikowi dostęp do bazy:
GRANT CONNECT ON DATABASE shop_db TO shop_user;
Teraz połącz się z bazą:
\c shop_db
Daj uprawnienia do schematu public:
GRANT USAGE ON SCHEMA public TO shop_user;
Pozwól użytkownikowi pracować z tabelami:
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO shop_user;
Pozwól użytkownikowi używać sekwencji.
To ważne dla ID typu SERIAL.
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO shop_user;
Dla przyszłych tabel możesz ustawić domyślne uprawnienia:
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO shop_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO shop_user;
To już dużo lepsze niż używanie superusera postgres.
Aplikacja dostaje dostęp.
Ale nie nieograniczoną władzę.
Bardzo cywilizowane.
Testowanie nowego użytkownika
Wyjdź z PostgreSQL:
\q
Teraz spróbuj połączyć się jako nowy użytkownik:
psql -h localhost -U shop_user -d shop_db
PostgreSQL zapyta o hasło.
Wpisz hasło, które utworzyłeś.
Jeśli połączenie działa, wejdziesz do psql.
Potem przetestuj:
SELECT current_user;
Powinieneś zobaczyć:
shop_user
Dobrze.
Teraz twoja aplikacja też może używać tego użytkownika.
Drzwi bazy danych się otwierają.
Ale tylko z właściwym kluczem.
Bardzo bezpiecznie.
Bardzo dorośle.
Czym jest connection string?
Connection string umieszcza wszystkie informacje połączeniowe w jednej linii.
Typowy format PostgreSQL:
postgresql://username:password@host:port/database
Przykład:
postgresql://shop_user:change_this_password@localhost:5432/shop_db
Zawiera:
username: shop_user
password: change_this_password
host: localhost
port: 5432
database: shop_db
Wiele narzędzi i frameworków używa connection stringów.
Są kompaktowe.
Przydatne.
I niebezpieczne, jeśli wklejasz je wszędzie.
Bo zawierają hasła.
Traktuj connection stringi jak klucze.
Nie publikuj ich.
Nie commituj ich na GitHuba.
Nie wysyłaj ich przypadkowym ludziom.
Nie wkładaj ich do screenshotów.
Przyszły ty ci podziękuje.
Ludzie od bezpieczeństwa też przestaną krzyczeć.
Zmienne środowiskowe
Aplikacje zwykle czytają ustawienia bazy danych ze zmiennych środowiskowych.
Zmienne środowiskowe to wartości przechowywane poza kodem.
Przykład:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password
Albo:
DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db
Dlaczego używać zmiennych środowiskowych?
Bo kod nie powinien zawierać sekretów.
Źle:
hasło bezpośrednio w kodzie źródłowym
Dobrze:
hasło w zmiennej środowiskowej
To pozwala używać różnych ustawień dla:
lokalnego developmentu
testów
produkcji
Ten sam kod.
Inna konfiguracja.
Bardzo przydatne.
Bardzo profesjonalne.
Bardzo “nauczyłem się po bolesnych błędach”.
Plik .env
Podczas lokalnego developmentu wiele projektów używa pliku .env.
Przykład:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password
Albo:
DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db
Aplikacja czyta ten plik przy starcie.
Ważne:
Nie commituj plików .env z prawdziwymi hasłami.
Dodaj .env do .gitignore.
Przykład .gitignore:
.env
.env.local
.env.production
Możesz stworzyć bezpieczny plik przykładowy:
.env.example
Przykład:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=your_password_here
To pokazuje innym programistom, jakie zmienne są potrzebne.
Ale nie ujawnia prawdziwych sekretów.
Dobra praktyka.
Bardzo dobra.
Złota gwiazdka.
Bazodanowa złota gwiazdka.
Przykład: ogólne ustawienia aplikacji
Wiele aplikacji potrzebuje ustawień takich jak:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password
Potem aplikacja tworzy połączenie.
Koncepcyjnie:
Połącz się z DB_HOST na porcie DB_PORT.
Użyj DB_NAME.
Zaloguj się przez DB_USER i DB_PASSWORD.
Niektóre frameworki wolą jeden URL:
DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db
Oba style są popularne.
Którego użyjesz, zależy od frameworka.
Ale idea jest ta sama.
Twoja aplikacja potrzebuje wskazówek, gdzie jest baza danych.
Bez wskazówek błąka się jak turysta z zepsutym GPS-em.
Przykład: ustawienia Django
W Django ustawienia bazy danych często wyglądają tak:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "shop_db",
"USER": "shop_user",
"PASSWORD": "change_this_password",
"HOST": "localhost",
"PORT": "5432",
}
}
Ale wpisywanie hasła bezpośrednio tutaj nie jest dobrym pomysłem.
Lepiej tak:
import os
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("DB_NAME"),
"USER": os.environ.get("DB_USER"),
"PASSWORD": os.environ.get("DB_PASSWORD"),
"HOST": os.environ.get("DB_HOST", "localhost"),
"PORT": os.environ.get("DB_PORT", "5432"),
}
}
Teraz hasło pochodzi ze zmiennych środowiskowych.
Dużo lepiej.
Django dostaje dane połączeniowe.
Twój kod zostaje bezpieczniejszy.
Wszyscy są mniej nerwowi.
Może oprócz developera CSS.
Ale to inny kurs.
Przykład: ustawienia Spring Boot
W Spring Boot application.properties może wyglądać tak:
spring.datasource.url=jdbc:postgresql://localhost:5432/shop_db
spring.datasource.username=shop_user
spring.datasource.password=change_this_password
Lepiej ze zmiennymi środowiskowymi:
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
Wtedy środowisko może zawierać:
DB_URL=jdbc:postgresql://localhost:5432/shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password
To dużo bezpieczniejsze niż hardcodowanie sekretów.
W prawdziwych projektach hasło bazy danych nie powinno mieszkać w repozytorium Git.
Git pamięta wszystko.
Jak słoń.
Ale z historią commitów.
Przykład: połączenie Node.js
Aplikacja Node.js może używać connection stringa:
DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db
Potem aplikacja go czyta.
Koncepcyjnie:
const connectionString = process.env.DATABASE_URL;
Wiele bibliotek Node.js wspiera taki styl.
Ważna idea nie polega na konkretnej bibliotece.
Ważna idea brzmi:
Czytaj sekrety ze zmiennych środowiskowych.
Nie wpisuj ich bezpośrednio do kodu.
Ta zasada idzie za tobą przez wszystkie języki.
Python.
Java.
JavaScript.
Go.
PHP.
Wszędzie.
Sekrety w kodzie są jak otwarte okna zimą.
Zły pomysł.
Później kosztowny.
Lokalny development vs produkcja
Lokalny development zwykle oznacza:
aplikacja i baza danych działają na twoim komputerze
host = localhost
port = 5432
Produkcja zwykle oznacza:
aplikacja działa na serwerze
baza danych może działać na innym serwerze
host nie jest localhost
hasło jest silniejsze
bezpieczeństwo ma większe znaczenie
backupy mają większe znaczenie
monitoring ma większe znaczenie
W lokalnym developmencie błędy są irytujące.
Na produkcji błędy stają się fakturami.
Bardzo motywujące.
Dlatego konfiguracja powinna być elastyczna.
Możesz mieć:
lokalną bazę danych
bazę testową
bazę produkcyjną
Każde środowisko ma inne wartości.
Ten sam kod aplikacji.
Różne zmienne środowiskowe.
To normalne.
To profesjonalne.
To pozwala nie zmieniać kodu tylko po to, żeby połączyć się gdzie indziej.
Docker i nazwy hostów PostgreSQL
Jeśli aplikacja i PostgreSQL działają w Docker Compose, hostem często jest nazwa serwisu.
Przykład:
services:
app:
environment:
DB_HOST: postgres
DB_PORT: 5432
postgres:
image: postgres
Wewnątrz Dockera aplikacja może łączyć się z:
postgres
a nie z:
localhost
Dlaczego?
Bo wewnątrz kontenera aplikacji localhost oznacza sam kontener aplikacji.
Nie kontener PostgreSQL.
To bardzo częsty błąd.
Bardzo częsty.
Bardzo bolesny.
Bardzo Docker.
Prosta zasada:
Z twojego komputera: localhost może działać.
Z innego kontenera: użyj nazwy serwisu.
Więc w Docker Compose host bazy danych może być:
postgres
albo:
db
zależnie od nazwy serwisu.
Networking Dockera jest potężny.
I czasem pikantny.
Typowe błędy połączenia
Connection refused
Przykładowy błąd:
connection refused
Zwykle oznacza:
PostgreSQL nie działa.
Zły host.
Zły port.
PostgreSQL tam nie nasłuchuje.
Firewall blokuje połączenie.
Sprawdź status PostgreSQL:
systemctl status postgresql
Na niektórych systemach nazwa usługi może być inna.
Możesz też spróbować:
psql -h localhost -U shop_user -d shop_db
Jeśli psql nie może się połączyć, aplikacja prawdopodobnie też nie może.
Nie obwiniaj frameworka od razu.
Najpierw sprawdź bazę danych.
Frameworki często są winne.
Ale nie zawsze.
Password authentication failed
Przykładowy błąd:
password authentication failed for user
Zwykle oznacza:
zły username
złe hasło
użytkownik nie istnieje
zmienna środowiskowa ma złą wartość
Sprawdź username:
SELECT current_user;
Sprawdź, czy użytkownik istnieje:
\du
Jeśli trzeba, zresetuj hasło:
ALTER USER shop_user WITH PASSWORD 'new_password_here';
Potem zaktualizuj zmienną środowiskową.
Nie aktualizuj tylko swojej pamięci.
Aplikacje nie potrafią czytać twojej pamięci.
Na szczęście.
Database does not exist
Przykładowy błąd:
database "shop_db" does not exist
To zwykle oznacza dokładnie to, co mówi.
Sprawdź bazy danych:
\l
Utwórz bazę, jeśli trzeba:
CREATE DATABASE shop_db;
Albo popraw nazwę bazy w konfiguracji.
Ten błąd często jest tylko literówką.
Mała literówka z dużą osobowością.
Permission denied
Przykładowy błąd:
permission denied for table products
To oznacza, że użytkownik połączył się poprawnie.
Ale nie ma uprawnień do wykonania czegoś.
Daj uprawnienia:
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO shop_user;
Dla sekwencji:
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO shop_user;
Jeśli aplikacja może czytać, ale nie może wstawiać wierszy z ID SERIAL, problemem mogą być uprawnienia do sekwencji.
Uprawnienia PostgreSQL są potężne.
I trochę irytujące.
Jak poważny ochroniarz z papierologią.
Table does not exist
Przykładowy błąd:
relation "products" does not exist
Możliwe powody:
Połączyłeś się z niewłaściwą bazą danych.
Tabela nie została utworzona.
Tabela jest w innym schemacie.
Migracja nie została uruchomiona.
Nazwa tabeli jest inna.
Sprawdź tabele:
\dt
Sprawdź aktualną bazę danych:
SELECT current_database();
Ten błąd często zdarza się, kiedy aplikacja łączy się z jedną bazą, a ty stworzyłeś tabele w innej.
Klasyk.
Bolesny.
Bardzo edukacyjny.
Podstawowe zasady bezpieczeństwa
Oto proste zasady.
Nie zaawansowane bezpieczeństwo.
Podstawowe przetrwanie.
Nie wpisuj haseł do kodu
Źle:
hasło w kodzie źródłowym
Dobrze:
hasło w zmiennej środowiskowej
Kod można udostępniać.
Sekretów nie.
Bardzo proste.
Bardzo ignorowane.
Bardzo niebezpieczne.
Nie commituj .env
Dodaj .env do .gitignore.
.env
.env.local
.env.production
Jeśli przypadkiem commitujesz prawdziwe hasło, zmień je.
Nie wystarczy usunąć plik z ostatniego commita i udawać, że nic się nie stało.
Historia Git pamięta.
Git nie jest twoim przyjacielem w tej sytuacji.
Git jest świadkiem.
Używaj osobnego użytkownika bazy danych
Nie używaj superusera postgres w aplikacji.
Utwórz użytkownika aplikacji:
shop_user
Daj mu tylko potrzebne uprawnienia.
To ogranicza szkody, jeśli coś pójdzie źle.
A coś kiedyś pójdzie źle.
To nie pesymizm.
To rozwój oprogramowania.
Używaj mocnych haseł
Nie używaj:
password
123456
postgres
admin
qwerty
To nie są hasła.
To zaproszenia.
Używaj mocnych haseł.
Zwłaszcza na produkcji.
Twoja baza danych zasługuje na więcej.
Trzymaj produkcyjną bazę prywatnie
Produkcyjna baza PostgreSQL zwykle nie powinna być otwarta na cały internet.
Lepiej:
sieć prywatna
reguły firewall
ograniczony dostęp po IP
bezpieczna konfiguracja hostingu
Jeśli baza danych jest publicznie dostępna, bezpieczeństwo robi się dużo poważniejsze.
Bazy danych lubią prywatność.
Użytkownicy też.
Prawnicy też.
Migracje aplikacji
Wiele frameworków używa migracji.
Migracja to kontrolowana zmiana w bazie danych.
Przykłady:
utworzenie tabeli
dodanie kolumny
zmiana typu kolumny
utworzenie indeksu
usunięcie tabeli
Zamiast ręcznie zmieniać bazę za każdym razem, framework śledzi zmiany.
Django ma migracje.
Spring Boot często używa narzędzi takich jak Flyway albo Liquibase.
ORM-y Node.js też mają narzędzia migracji.
Idea brzmi:
Struktura bazy danych powinna być wersjonowana.
To ważne, bo aplikacja i baza danych muszą do siebie pasować.
Jeśli kod oczekuje kolumny email, a baza jej nie ma, aplikacja się psuje.
Bardzo pewnie siebie.
Migracje pomagają kontrolować strukturę.
Nie są magią.
Ale są lepsze niż losowe ręczne zmiany o północy.
ORM vs czysty SQL
Wiele aplikacji używa ORM.
ORM oznacza Object-Relational Mapping.
Przykłady:
Django ORM
Hibernate / JPA
Prisma
TypeORM
Sequelize
SQLAlchemy
ORM pozwala pracować z wierszami bazy danych jak z obiektami.
Przykładowa idea:
obiekt Product
obiekt Customer
obiekt Order
ORM generuje SQL.
To wygodne.
Ale nadal musisz rozumieć SQL.
Dlaczego?
Bo kiedy coś robi się wolne albo się psuje, ORM nie uratuje twojej duszy.
Wygeneruje SQL.
PostgreSQL wykona SQL.
Problemy wydajności nadal są problemami SQL.
Więc nauka SQL nie jest stratą czasu.
Nawet jeśli używasz ORM.
Właściwie szczególnie wtedy, gdy używasz ORM.
Bo wiesz, co dzieje się pod kocem.
A czasem pod kocem jest potworne zapytanie.
Typowy przepływ aplikacji
Typowy przepływ backendu wygląda tak:
Użytkownik otwiera stronę.
Frontend wysyła request do backendu.
Backend łączy się z PostgreSQL.
Backend wykonuje zapytanie.
PostgreSQL zwraca dane.
Backend zwraca JSON albo HTML.
Frontend pokazuje wynik.
Przykład:
GET /products
Zapytanie backendu:
SELECT
p.id,
p.name,
p.price,
c.name AS category_name
FROM products AS p
JOIN categories AS c
ON p.category_id = c.id
ORDER BY p.name;
Backend zwraca JSON:
[
{
"id": 1,
"name": "Laptop",
"price": 900.00,
"category": "Electronics"
}
]
Tak PostgreSQL staje się częścią aplikacji.
Użytkownik nigdy nie widzi SQL.
Ale SQL wykonuje pracę.
Jak pracownik za kulisami.
Cichy.
Ważny.
Trochę niedoceniany.
Connection pooling
Otwarcie połączenia z bazą danych zajmuje czas.
Jeśli każdy request otwiera nowe połączenie i od razu je zamyka, wydajność może ucierpieć.
Wiele aplikacji używa connection pool.
Connection pool utrzymuje kilka otwartych połączeń z bazą i używa ich ponownie.
Prosta idea:
Nie twórz nowego połączenia za każdym razem.
Używaj ponownie istniejących połączeń.
Większość frameworków obsługuje to za ciebie.
Ale warto znać tę ideę.
Bo systemy produkcyjne bardzo przejmują się połączeniami.
Zbyt wiele połączeń może przeciążyć PostgreSQL.
Zbyt mało połączeń może spowolnić aplikację.
Równowaga ma znaczenie.
Jak kawa.
Za mało źle.
Za dużo i słyszysz kolory.
Praktyka
Utwórz nową bazę danych:
CREATE DATABASE app_demo_db;
Utwórz nowego użytkownika:
CREATE USER app_demo_user WITH PASSWORD 'change_this_password';
Daj prawo połączenia:
GRANT CONNECT ON DATABASE app_demo_db TO app_demo_user;
Połącz się z bazą:
\c app_demo_db
Utwórz prostą tabelę:
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
title VARCHAR(150) NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Daj uprawnienia do schematu:
GRANT USAGE ON SCHEMA public TO app_demo_user;
Daj uprawnienia do tabel:
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_demo_user;
Daj uprawnienia do sekwencji:
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_demo_user;
Wstaw testowy wiersz:
INSERT INTO messages (title, content)
VALUES ('Hello App', 'This row could be read by a backend application.');
Przetestuj połączenie:
psql -h localhost -U app_demo_user -d app_demo_db
Potem uruchom:
SELECT * FROM messages;
Jeśli to działa, masz bazę danych, użytkownika, uprawnienia i działające połączenie.
To fundament integracji z aplikacjami.
Nie efektowny.
Bardzo ważny.
Jak dobra instalacja elektryczna.
Nikt jej nie widzi.
Wszyscy narzekają, kiedy nie działa.
Mini wyzwanie
Utwórz bazę danych dla aplikacji blogowej.
Wymagania:
nazwa bazy danych: blog_app_db
nazwa użytkownika: blog_app_user
tabele: authors, posts
Zasady:
- każdy autor ma unikalny email;
- każdy post należy do autora;
- tytuł posta jest wymagany;
- treść posta może być długim tekstem;
- posty mają datę utworzenia.
Utwórz bazę danych:
CREATE DATABASE blog_app_db;
Utwórz użytkownika:
CREATE USER blog_app_user WITH PASSWORD 'change_this_password';
Daj prawo połączenia:
GRANT CONNECT ON DATABASE blog_app_db TO blog_app_user;
Połącz się:
\c blog_app_db
Utwórz tabele:
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
author_id INTEGER NOT NULL REFERENCES authors(id),
title VARCHAR(150) NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Daj uprawnienia:
GRANT USAGE ON SCHEMA public TO blog_app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO blog_app_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO blog_app_user;
Utwórz przydatne indeksy:
CREATE INDEX idx_posts_author_id
ON posts(author_id);
CREATE INDEX idx_posts_created_at
ON posts(created_at);
Teraz utwórz plik .env.example dla aplikacji:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=blog_app_db
DB_USER=blog_app_user
DB_PASSWORD=your_password_here
Albo:
DATABASE_URL=postgresql://blog_app_user:your_password_here@localhost:5432/blog_app_db
Potem odpowiedz:
Które wartości można bezpiecznie commitować?
Które wartości powinny zostać sekretami?
Dlaczego .env powinien być ignorowany przez Git?
Dlaczego aplikacja nie powinna używać użytkownika postgres?
To praktyczne.
To realne.
To nudny fundament, który zapobiega ekscytującym katastrofom.
A w bazach danych nudne często jest dobre.
Bardzo dobre.
Typowe błędy
Używanie postgres wszędzie
Nie rób tego.
Użytkownik postgres służy do administracji.
Twoja aplikacja powinna mieć własnego użytkownika.
Daj mu tylko potrzebne uprawnienia.
To podstawowe bezpieczeństwo.
Podstawowe bezpieczeństwo nie jest opcjonalne.
To jak noszenie butów w warsztacie.
Wpisywanie sekretów do kodu
Źle:
spring.datasource.password=my_real_password
Źle:
"PASSWORD": "my_real_password"
Źle:
const password = "my_real_password";
Używaj zmiennych środowiskowych.
Twój kod nie jest sejfem.
Zapominanie o uprawnieniach do sekwencji
Jeśli aplikacja może wstawiać wiersze, ale dostaje błędy uprawnień do sekwencji, pamiętaj:
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;
SERIAL używa sekwencji.
Sekwencje potrzebują uprawnień.
PostgreSQL jest precyzyjny.
Czasem boleśnie precyzyjny.
Łączenie się z niewłaściwą bazą danych
Zawsze sprawdzaj:
SELECT current_database();
Wiele błędów to po prostu:
Utworzyłem tabelę w jednej bazie.
Moja aplikacja łączy się z inną bazą.
To boli.
Ale jest bardzo częste.
Złe używanie localhost w Dockerze
W Docker Compose localhost z kontenera aplikacji zwykle oznacza kontener aplikacji.
Nie kontener bazy danych.
Użyj nazwy serwisu bazy danych.
Przykład:
postgres
albo:
db
Docker się nie myli.
Jest tylko bardzo dosłowny.
Jak PostgreSQL.
Pewnie dobrze się dogadują.
Podsumowanie
Dzisiaj nauczyłeś się:
- aplikacje łączą się z PostgreSQL za pomocą hosta, portu, nazwy bazy, username i password;
- PostgreSQL zwykle używa portu
5432; - connection string łączy dane połączeniowe w jednym URL;
- sekrety aplikacji powinny pochodzić ze zmiennych środowiskowych;
- pliki
.envsą przydatne lokalnie, ale nie powinny być commitowane z prawdziwymi sekretami; - aplikacje nie powinny używać superusera
postgres; - osobni użytkownicy bazy danych dla aplikacji są bezpieczniejsi;
- użytkownicy potrzebują uprawnień do tabel i sekwencji;
psqljest przydatny do testowania połączeń;- Docker zmienia sposób działania nazw hostów;
- typowe błędy to złe hasło, zła baza danych, brak uprawnień i connection refused;
- migracje pomagają zarządzać strukturą bazy danych;
- ORM-y są przydatne, ale znajomość SQL nadal jest ważna;
- connection pooling pomaga aplikacjom ponownie używać połączeń z bazą danych.
To ważna lekcja.
Teraz rozumiesz, jak PostgreSQL łączy się z prawdziwymi aplikacjami.
Tutaj wiedza o bazie danych staje się backendową mocą.
Baza danych przechowuje prawdę.
Aplikacja używa prawdy.
Użytkownik widzi wynik.
A gdzieś pośrodku programista ma nadzieję, że zmienne środowiskowe są poprawne.
Bardzo realne.
Bardzo profesjonalne.
Bardzo poniedziałkowe.
Następna lekcja
W następnej lekcji porozmawiamy o backupach, restore i podstawowej konserwacji bazy danych.
Bo stworzenie bazy danych jest dobre.
Używanie bazy danych jest lepsze.
Ale możliwość odzyskania danych po problemie jest bezcenna.
Backupy są nudne.
Aż do dnia, kiedy ratują ci życie.
Wtedy stają się piękne.