2.1 Ogólna architektura
Na tym etapie zaprojektujemy architekturę aplikacji do zarządzania zadaniami. Określimy, jak będą współdziałać frontend, backend i baza danych oraz jakie komponenty będą zawarte w każdym z nich.
Aplikacja będzie składała się z trzech głównych komponentów:
- Frontend (ReactJS): część kliencka, zapewniająca interakcję użytkownika z systemem.
- Backend (Flask): część serwerowa, przetwarzająca żądania od frontendu i współdziałająca z bazą danych.
- Database (PostgreSQL): magazyn danych dla użytkowników i zadań.
Architektura będzie wyglądała następująco:
+-------------+ +-------------+ +--------------+
| | | | | |
| Frontend +------->+ Backend +------->+ Database |
| (ReactJS) | | (Flask) | | (PostgreSQL) |
| | | | | |
+-------------+ +-------------+ +--------------+
Współpraca między komponentami
- Frontend: wysyła żądania HTTP do backendu w celu wykonania operacji CRUD (tworzenie, odczyt, aktualizacja, usuwanie zadań).
- Backend: przetwarza żądania HTTP od frontendu, wykonuje logikę biznesową i współdziała z bazą danych.
- Database: przechowuje i dostarcza dane na żądanie od backendu.
2.2 Opis każdego komponentu
1. Frontend (ReactJS):
- Komponenty interfejsu: komponenty do rejestracji i logowania użytkowników, tworzenia i edytowania zadań, przeglądania listy zadań.
- Interakcja z API: użycie biblioteki Axios do wysyłania HTTP requestów do backendu.
2. Backend (Flask):
- REST API: implementacja endpointów do zarządzania użytkownikami i zadaniami.
- Modele danych: definiowanie modeli danych dla użytkowników i zadań z użyciem SQLAlchemy.
- Logika biznesowa: obsługa logiki aplikacji, w tym walidacja danych i zarządzanie sesjami użytkowników.
3. Database (PostgreSQL):
- Tabele: tabele do przechowywania informacji o użytkownikach i zadaniach.
- Relacje między tabelami: definiowanie relacji między tabelami użytkowników i zadań (np. jeden użytkownik może mieć wiele zadań).
4. Sieciowa interakcja
Cała komunikacja między komponentami będzie odbywać się przez protokół HTTP. Frontend będzie komunikować się z backendem przez REST API, a backend — z bazą danych przez zapytania SQL.
- Frontend: port 3000 do rozwoju i testowania.
- Backend: port 5000 do interakcji z frontendem.
- Database: port 5432 do interakcji z backendem.
2.3 Szczegółowy opis każdego komponentu
1. Podstawowa struktura danych
Do przechowywania danych o użytkownikach i zadaniach w bazie danych PostgreSQL stworzymy dwie tabele: users
i tasks
.
Tablica users
:
-
id (int, primary key)
: unikalny identyfikator użytkownika. username (varchar, unique)
: nazwa użytkownika.password (varchar)
: hash hasła użytkownika.
Tablica tasks
:
-
id (int, primary key)
: unikalny identyfikator zadania. title (varchar)
: tytuł zadania.description (text)
: opis zadania.-
owner_id (int, foreign key)
: identyfikator użytkownika, któremu zadanie jest przypisane. -
status (varchar)
: status zadania (na przykład, wykonane/niewykonane).
2. Projektowanie API
Backend będzie dostarczać RESTful API do interakcji z frontendem. Przykładowa lista endpointów:
- Użytkownicy:
- POST /users: tworzenie nowego użytkownika.
- GET /users: uzyskanie listy wszystkich użytkowników.
- GET /users/:id: uzyskanie informacji o konkretnym użytkowniku.
- PUT /users/:id: aktualizacja informacji o użytkowniku.
- DELETE /users/:id: usunięcie użytkownika.
- Zadania:
- POST /tasks: tworzenie nowego zadania.
- GET /tasks: uzyskanie listy wszystkich zadań.
- GET /tasks/:id: uzyskanie informacji o konkretnym zadaniu.
- PUT /tasks/:id: aktualizacja informacji o zadaniu.
- DELETE /tasks/:id: usunięcie zadania.
2.4 Modele danych
Tak będzie wyglądał kod w Python do pracy z tabelami bazy danych:
Model Użytkownika:
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
tasks = db.relationship('Task', backref='owner', lazy=True)
def to_dict(self):
return {
"id": self.id,
"username": self.username
}
Model Zadania:
from app import db
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
description = db.Column(db.Text, nullable=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
status = db.Column(db.String(20), nullable=False, default="niewykonana")
def to_dict(self):
return {
"id": self.id,
"title": self.title,
"description": self.description,
"owner_id": self.owner_id,
"status": self.status
}
2.5 Trasy i kontrolery
Przykład implementacji API po stronie serwera:
from app import app, db
from app.models import Task, User
from flask import request, jsonify
@app.route('/tasks', methods=['GET'])
def get_tasks():
tasks = Task.query.all()
return jsonify([task.to_dict() for task in tasks])
@app.route('/tasks', methods=['POST'])
def create_task():
data = request.get_json()
new_task = Task(
title=data['title'],
description=data.get('description'),
owner_id=data['owner_id'],
status=data.get('status', "niewykonane")
)
db.session.add(new_task)
db.session.commit()
return jsonify(new_task.to_dict()), 201
@app.route('/tasks/<int:id>', methods=['GET'])
def get_task(id):
task = Task.query.get_or_404(id)
return jsonify(task.to_dict())
@app.route('/tasks/<int:id>', methods=['PUT'])
def update_task(id):
data = request.get_json()
task = Task.query.get_or_404(id)
task.title = data['title']
task.description = data.get('description')
task.status = data.get('status', task.status)
task.owner_id = data['owner_id']
db.session.commit()
return jsonify(task.to_dict())
@app.route('/tasks/<int:id>', methods=['DELETE'])
def delete_task(id):
task = Task.query.get_or_404(id)
db.session.delete(task)
db.session.commit()
return '', 204
2.6 Przykład komunikacji z serwerem od strony frontendu
Przykład komponentu React do wyświetlania listy zadań:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const TaskList = () => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
axios.get('http://localhost:5000/tasks')
.then(response => {
setTasks(response.data);
})
.catch(error => {
console.error('Wystąpił błąd podczas pobierania zadań!', error);
});
}, []);
return (
<div>
<h1>Lista zadań</h1>
<ul>
{tasks.map(task => (
<li key={task.id}>{task.title} - {task.status}</li>
))}
</ul>
</div>
);
};
export default TaskList;
GO TO FULL VERSION