--------------- <> -----------------
--- KHOA HỌC - CÔNG NGHỆ - GIÁO DỤC - VIỆC LÀM ---
--- Học để đi cùng bà con trên thế giới ---

Tìm kiếm trong Blog

Ebook2LateX (10) - Lập trình Web services

Bài trước: Ebook2LateX (9) - Web services
-----

10. Lập trình Web services

Tới bài học này, bạn đã thực hiện được các việc sau:

- Đã tạo môi trường ảo venv, cài đặt các thư viện cần thiết như FastAPI, Sqlalchemy, Alembic, và Python-dotenv

- Đã thiết lập kết nối với PostgreSQL, định nghĩa các bảng dữ liệu (Users, Documents, FormulaEntries, Logs) trong models.py và thực hiện migration để tạo bảng tự động

- Đã tạo tập tin main.py với mã nguồn "Hello World" và khởi chạy thành công Web server bằng Uvicorn

- Đã viết script seed.py để tự động nạp dữ liệu vào database

- Đã thực hiện commit các thay đổi và đẩy dự án lên GitHub

Trong phần này, chúng ta sẽ tìm hiểu một số nội dung sau:

- Hệ thống lại hoạt động của ứng dụng web trên nền tảng Python

- Lập trình minh họa để hiểu sâu về hoạt động của hệ thống

- Làm quen với việc tạo ra các services với kỹ thuật RESTful API

10.1 Mô hình client-server

Các ứng dụng web hoạt động dựa trên mô hình client-server.

Ý tưởng của mô hình client-server, đơn giản chỉ là: máy khách (client) gửi một yêu cầu (request) đến máy chủ (server), máy chủ sẽ xử lý và trả kết quả về cho máy khách.

Xem hình minh họa về mô hình client-server:


Mô hình client-server gồm một số thành phần:

- Client: khởi phát yêu cầu, gửi yêu cầu tới server, nhận kết quả từ server trả về. Client có thể là trình duyệt, ứng dụng viết bằng python/javascript hoặc bất kỳ ứng dụng nào mà có phát sinh ra HTTP request

- Server: có vai trò cung cấp dịch vụ, xử lý và trả về kết quả cho máy client

- Môi trường truyền thông tin: hạ tầng mạng (LAN, Internet), bao gồm cả phần cứng và phần mềm

- Giao thức truyền thông tin: các chuẩn công nghệ giúp giao tiếp và truyền thông tin giữa server-client. Ví dụ HTTP, HTTPS

Trong mô hình client-server, khi môi trường truyền đã được kết nối và sẵn sàng, thì client luôn là nơi khởi phát của ứng dụng. Client sẽ gửi một HTTP request tới server. Khi server nhận được request, nó sẽ xử lý và trả kết quả về cho client bằng một HTTP response

Client là thành phần chủ động. Server là thành phần bị động.

Chúng ta sẽ thấy cách hoạt động của một ứng dụng web rất khác so với cách hoạt động của chương trình trên máy cục bộ (ví dụ Microsoft Word). Trong Microsoft Word, mọi thao tác đều được thực hiện ngay trên máy người dùng, từ việc ra lệnh, xử lý và trả về kết quả. Cũng là chương trình xử lý văn bản, nhưng Google Docs là một ứng dụng hoạt động theo mô hình client-server. Nếu không có kết nối mạng thì Google Docs sẽ không hoạt động được.

Để hiểu rõ hơn về mô hình client-server, chúng ta sẽ cùng quan sát các bước của quá trình mở một ứng dụng web:

Do ứng dụng web hoạt động theo mô hình client-server, nên để có trang web trên trình duyệt, cần trải qua các bước sau:

Bước 1: Người dùng nhập địa chỉ trang web (URL) vào thanh địa chỉ. Ví dụ: http://example.com/hello.php

Bước 2: Trình duyệt dựa vào URL trong thanh địa chỉ, kết nối tới máy web server, gửi yêu cầu tới web server (ví dụ yêu cầu: gửi cho nội dung trang web hello.php)

Bước 3: Web server xử lý yêu cầu, gửi trả kết quả về cho trình duyệt (ví dụ nội dung trang web dưới dạng mã nguồn HTML, CSS và JavaScript)

Bước 4: Trình duyệt thực thi mã HTML, CSS, JavaScript và hiển thị trang web ra màn hình

Xem hình minh họa:


Chúng ta cùng thực hành, quan sát trình duyệt mở một ứng dụng web trên máy tính:

Bước 1. Mở trình duyệt web. Ví dụ Chrome

Bước 2. Nhập vào đường dẫn của trình duyệt địa chỉ một trang web, bấm Enter để trình duyệt lấy trang web từ máy server, và hiển thị nội dung ra màn hình. Ví dụ thanhnien.vn

Bước 3. Mở Developer tools của trình duyệt bằng một số cách sau:

- Bấm tổ hợp ba phím Ctrl+Shift+I

- hoặc bấm phím F12

- hoặc vào menu của trình duyệt tìm tới mục Developer tools

- hoặc bấm chuột phải vào trang web, chọn Inspect

Bước 4. Trong cửa sổ của Developer tools, bấm chuột vào mục Network

Bước 5. Quan sát sẽ thấy các tập tin HTML (thanhnien.vn), CSS (các tập tin có phần mở rộng là css), JavaScript (các tập tin có phần mở rộng là js) được server gửi về cho client

Bước 6. Bấm chuột vào các tập tin do server gửi về và quan sát nội dung của nó ở cửa sổ bên phải. Nhớ chọn mục Response

10.2 Hoạt động của ứng dụng web trên Python

Để trải nghiệm với ứng dụng web trên Python, chúng ta cùng thực hành và quan sát cách hệ thống này hoạt động.

Hình minh họa các thành phần của hệ thống:


Mô tả hoạt động của hệ thống:

- [Bước 1] Khởi chạy phần mềm Uvicorn, đây chính là phần mềm web server

- [Bước 2] Mở trình duyệt, gõ vào thanh địa chỉ: http://localhost:8000/ để gửi request tới Uvicorn, gửi tới cổng 8000, chuyển tới hàm get() của FastAPI.

- [Bước 3] Hàm get() của FastAPI sẽ xử lý yêu cầu từ client

- [Bước 4] FastAPI trả kết quả về cho trình duyệt

10.3 Bài tập và câu hỏi

Bài tập

Bài tập 10a. Hãy viết một chương trình đơn giản, để thực hiện việc sau:

- Mở trình duyệt, gửi một số qua thanh địa chỉ

- FastAPI nhận số từ trình duyệt, nhân với 10 rồi gửi trả lại cho trình duyệt

Bài tập 10b. Hãy viết một chương trình đơn giản, để thực hiện việc sau:

- Mở trình duyệt, gửi nhãn hiệu (brand) và kích thước (size) đôi giày bạn muốn mua (qua thanh địa chỉ), ví dụ hiệu Nike, kích thước 42

- FastAPI nhận brand và size từ trình duyệt, xử lý và trả về chuỗi: Bạn muốn mua giày [brand] kích thước [size] đúng không?

Câu hỏi

Câu 10.1 Trong mô hình client-server, thành phần nào đóng vai trò là nơi khởi phát yêu cầu (request)? 

A. Server 

B. Client 

C. Môi trường truyền tin 

D. Cơ sở dữ liệu

Câu 10.2 Đâu là các ví dụ về giao thức truyền thông tin dùng để kết nối giữa máy khách và máy chủ trong ứng dụng web? 

A. CPU và RAM 

B. LAN và Internet 

C. HTTP và HTTPS 

D. HTML và CSS

Câu 10.3 Phần mềm nào được sử dụng làm Web server chuẩn ASGI để chạy các ứng dụng FastAPI trong dự án web Python?

A. PostgreSQL 

B. React 

C. Uvicorn 

D. SQLAlchemy

Câu 10.4 Thứ tự các bước cơ bản để một ứng dụng web Python xử lý yêu cầu là gì? 

A. FastAPI trả kết quả -> Uvicorn nhận request -> Hàm xử lý thực thi

B. Khởi chạy Uvicorn -> Nhận request từ trình duyệt -> FastAPI xử lý -> Trả kết quả về trình duyệt

C. Mở trình duyệt -> FastAPI xử lý -> Khởi chạy Uvicorn -> Nhận kết quả

D. FastAPI nhận request trực tiếp từ trình duyệt -> Trả kết quả cho Uvicorn

Câu 10.5 Tại sao FastAPI được coi là một framework phù hợp cho các dịch vụ web hiện đại đòi hỏi hiệu suất cao? 

A. Vì nó chỉ chạy được trên hệ điều hành Windows

B. Vì nó hỗ trợ lập trình bất đồng bộ (async) mạnh mẽ và dựa trên nền tảng Starlette, Pydantic

C. Vì nó không cần cài đặt Python

D. Vì nó tự động kết nối với tất cả các loại cơ sở dữ liệu mà không cần cấu hình

Câu 10.6 Trong mã nguồn FastAPI, ký hiệu @app.get("/") (Decorator) có ý nghĩa gì? 

A. Khai báo một biến số nguyên cho ứng dụng

B. Định nghĩa một ghi chú (comment) không có giá trị thực thi

C. Điều hướng yêu cầu (routing) từ người dùng truy cập vào trang chủ (web root) để gọi hàm xử lý tương ứng

D. Dùng để cài đặt thư viện FastAPI vào máy tính

Câu 10.7 Khi đang phát triển ứng dụng, lệnh nào sau đây giúp bạn khởi chạy server sao cho mọi thay đổi trong mã nguồn main.py sẽ được tự động cập nhật ngay lập tức mà không cần khởi động lại thủ công? 

A. python main.py 

B. uvicorn main:app 

C. uvicorn main:app --reload 

D. pip install fastapi

Câu 10.8 Giả sử bạn có tập tin mã nguồn tên là api_service.py và biến khởi tạo ứng dụng là my_web_app = FastAPI(). Lệnh đúng để khởi chạy server này bằng Uvicorn là: 

A. uvicorn api_service:my_web_app 

B. uvicorn my_web_app:api_service 

C. python api_service.py 

D. run uvicorn api_service

-----
Bài sau:

Ebook2LateX (9) - Web services

Bài trước: Ebook2LateX (8) - Nhập dữ liệu tự động
-----

9. Web services

Tới phần này, chúng ta đã thực hiện được các việc sau:

- Thiết lập môi trường quản lý mã nguồn với Git

- Thiết lập hệ thống cơ sở dữ liệu

Trước khi bắt tay vào làm phần backend, chúng ta sẽ tìm hiểu về Dịch vụ web (web services).

9.1 Web services là gì?

Dịch vụ web (tiếng Anh là web services hay web service), để tiện trình bày sẽ dùng luôn từ gốc là web services.

Web services:

- Là một dịch vụ, do thiết bị điện tử này cung cấp cho một thiết bị điện tử khác, quá trình trao đổi được thực hiện trên môi trường web (world wide web, WWW, Internet, HTTP)

- Một máy server trên mạng sẽ luôn lắng nghe các yêu cầu từ một cổng cụ thể, để cung cấp các tài nguyên web như HTML, JSON, XML, images và tạo ra các dịch vụ cho các ứng dụng có sử dụng web services

Xem hình minh họa về web services,


So sánh trang web và web services

Ở một góc nhìn khác, kết quả trả về của một web services khá giống một trang web, tuy nhiên có một số khác biệt sau:

 

Trang web

Kết quả trả về của web services

Đối tượng sử dụng

Con người

Chương trình

Kiểu dữ liệu

Thông tin, dữ liệu

Thông tin, dữ liệu

Kiểu hiển thị

Thông tin con người có thể đọc được

Dữ liệu dạng XML, JSON

Một số định nghĩa khác về web service

- Web services là một hệ thống phần mềm, được thiết kế để hỗ trợ khả năng tương tác giữa các ứng dụng trên các máy tính khác nhau, thông qua mạng Internet, giao diện chung và sự gắn kết của nó được mô tả bằng XML

- Là tài nguyên phần mềm có thể xác định bằng địa chỉ URL

- Thực hiện các chức năng và đưa ra các thông tin người dùng yêu cầu

- Ứng dụng cơ bản của web services là tích hợp các hệ thống

- Các ứng dụng được tích hợp với cơ sở dữ liệu và các ứng dụng khác, người sử dụng sẽ giao tiếp với cơ sở dữ liệu để tiến hành phân tích và lấy dữ liệu

Xem hình minh họa,


- Web services là một tập hợp các giao thức và tiêu chuẩn mở được sử dụng để trao đổi dữ liệu giữa các ứng dụng hoặc giữa các hệ thống

- Các ứng dụng phần mềm được viết bằng các ngôn ngữ lập trình khác nhau hoặc chạy trên các nền tảng khác nhau, chúng có thể sử dụng các web services để trao đổi dữ liệu qua lại theo cách tương tự như liên lạc giữa các quá trình trên một máy tính

Xem hình minh họa,


9.2 Triển khai Web services trong Ebook2LateX

Để hiểu rõ hơn về Web services, chúng ta sẽ triển khai trong dự án Ebook2LateX.

Ứng dụng Ebook2LateX gồm 2 thành phần: Frontend và Backend.

Trong đó, thành phần Backend sẽ cung cấp các dịch vụ dưới dạng các Web services cho phần Frontend.

Thành phần Frontend và Backend sẽ giao tiếp với nhau thông qua Web services.

- Frontend sẽ sử dụng framework React

- Backend sẽ sử dụng framework FastAPI

Như vậy, React sẽ kết nối với FastAPI để trao đổi dữ liệu. Nghĩa là React sẽ sử dụng các dịch vụ web mà FastAPI cung cấp.

Cài đặt FastAPI

Như bạn đã biết, để làm ứng dụng web phía backend, bạn có thể lập trình từ đầu, có thể dùng CMS, hoặc có thể dùng framework.

Trong phần này chúng ta sẽ sử dụng framework FastAPI.

FastAPI là một web framework dựa trên Python, hỗ trợ lập trình bất đồng bộ mạnh mẽ.

FastAPI là framework mã nguồn mở, được Sebastián Ramírez người Colombia phát hành lần đầu 2018.

Để cài đặt và chạy được FastAPI, bạn cần có Python (phiên bản 3.8 trở lên) đã được cài đặt trên máy tính. Bạn có thể kiểm tra bằng lệnh: 

python -V

Ví dụ:

C:\Users\VIET HOANG - VTS>python -V

Python 3.14.0

Chúng ta đã có thư mục dự án là Ebook2LateX, đã có thư mục “môi trường ảo” là venv. Xem hình minh họa:


Chúng ta sẽ cài đặt FastAPI vào thư mục “môi trường ảo” (venv). Việc sử dụng môi trường ảo giúp cô lập các thư viện của dự án này, tránh xung đột với các dự án khác trên máy tính.

- Tại cửa sổ dòng lệnh (CMD), di chuyển dấu nhắc lệnh vào thư mục Ebook2LateX, kích hoạt môi trường ảo:

D:\DuAn\Ebook2LateX>venv\scripts\activate

(venv) D:\DuAn\Ebook2LateX>

- Gõ lệnh sau để cài đặt FastAPI: pip install "fastapi[all]"

(venv) D:\DuAn\Ebook2LateX>pip install "fastapi[all]"

Tham số “all” sẽ cài đặt luôn các thư viện bổ trợ cần thiết như uvicorn, pydantic, và các công cụ hỗ trợ xử lý dữ liệu form.

Uvicorn là gì?

Uvicorn là phần mềm web server, dùng để chạy các ứng dụng web viết bằng Python, hỗ trợ xử lý bất đồng bộ (ASGI - Asynchronous Server Gateway Interface).

Pydantic là gì?

Pydantic là một thư viện Python dùng để kiểm tra dữ liệu (validation) và xác thực dữ liệu (parsing) dựa trên các gợi ý kiểu (type hints).

Sau khi cài đặt xong, bạn nên chạy lệnh pip freeze > requirements.txt ở thư mục gốc để ghi lại danh sách các thư viện, giúp việc triển khai lên Docker sau này được đồng bộ. Như vậy trong dự án sẽ có 2 tập tin requirements.txt, một tập tin trong thư mục backend và một trong thư mục gốc. Bạn nên đọc thêm về mục đích của 2 tập tin requirements.txt này.

Viết đoạn mã nguồn đầu tiên (Hello World)

Trong thư mục backend, tạo tập tin có tên main.py và nhập vào đoạn mã sau (bạn nên tự tay gõ lại để nhớ và hiểu mã nguồn):

[main.py]

# Goi thu vien FastAPI

from fastapi import FastAPI

# Tao doi tuong app tu class FastAPI

app = FastAPI()

# Tao decorator cho app.get(“/”) 

@app.get("/")

# khi nguoi dung truy cap vao web root, goi ham sau

def read_root():

    return {"message": "Chao mung ban den voi Ebook2LateX!"}

Khởi chạy ứng dụng

Trở lại chương trình dòng lệnh (CMD), chạy lệnh sau để khởi động phần mềm web server:

uvicorn main:app --reload

Trong đó,

- main: là tên tập tin (main.py)

- app: là biến được khởi tạo trong code (app = FastAPI())

- --reload: Chế độ tự động tải lại server khi bạn thay đổi mã nguồn (rất hữu ích khi đang phát triển)

Nếu cửa sổ dòng lệnh xuất các thông tin sau là web server đã khởi chạy thành công:

←[32mINFO←[0m:     Started server process [←[36m62384←[0m]

←[32mINFO←[0m:     Waiting for application startup.

←[32mINFO←[0m:     Application startup complete.

(Đừng tắt cửa sổ dòng lệnh này, nếu tắt là tắt web server).

Kiểm tra kết quả

Sau khi khởi chạy ứng dụng thành công, bạn có thể mở trình duyệt web, truy cập vào địa chỉ sau:

http://127.0.0.1:8000

Bạn sẽ thấy câu chào mừng trên trình duyệt:

["Chao mung ban den voi Ebook2LateX!"]

Thực hiện commit trạng thái của dự án vào Git

D:\DuAn\Ebook2LateX>git status

        backend/main.py

        requirements.txt

D:\DuAn\Ebook2LateX>git add .

D:\DuAn\Ebook2LateX>git commit -m "feat: cai dat FastAPI"

9.3 Bài tập và câu hỏi

Bài tập

Bài tập 9a. Cài đặt các nội dung trong phần lý thuyết.

Bài tập 9b. Sử dụng File Explorer, tìm trong thư mục dự án (Ebook2LateX) và cho biết: sau khi cài đặt, mã nguồn của framework FastAPI được lưu ở đâu?

Câu hỏi

Câu 9.1 Web services là gì?

A. Là một loại phần cứng dùng để lưu trữ dữ liệu trên Internet

B. Là dịch vụ do thiết bị điện tử này cung cấp cho thiết bị điện tử khác thông qua môi trường mạng (WWW, HTTP)

C. Là một ngôn ngữ lập trình mới dùng để xây dựng giao diện người dùng

D. Là một hệ điều hành dành riêng cho các máy chủ lưu trữ tập tin PDF

Câu 9.2 Tại sao trong một dự án web, Backend và Frontend lại cần trao đổi dữ liệu thông qua định dạng JSON hoặc XML?

A. Vì đây là các định dạng giúp tăng dung lượng lưu trữ của ổ cứng

B. Vì đây là các ngôn ngữ lập trình có thể thực hiện các phép toán phức tạp

C. Vì đây là các định dạng dữ liệu chuẩn giúp các hệ thống khác nhau (như Python và React) có thể hiểu và làm việc với nhau

D. Vì các định dạng này giúp mã hóa dữ liệu để không ai có thể đọc được

Câu 9.3 Ai là người đã phát hành phiên bản đầu tiên của FastAPI vào năm 2018?

A. Guido van Rossum

B. Sebastián Ramírez

C. Mark Zuckerberg

D. Brendan Eich

Câu 9.4 Đặc điểm nào sau đây giúp FastAPI đạt được hiệu suất cao tương đương với Go hoặc Node.js?

A. Sử dụng giao thức SOAP để truyền tin

B. Dựa trên nền tảng Starlette, Pydantic và hỗ trợ lập trình bất đồng bộ (async)

C. Chỉ chạy được trên các máy chủ có cấu hình phần cứng cực mạnh

D. Không cần sử dụng bất kỳ thư viện hỗ trợ nào từ bên thứ ba

Câu 9.5 ASGI (Asynchronous Server Gateway Interface) là gì?

A. Là một thư viện dùng để vẽ biểu đồ toán học trong Python

B. Là chuẩn giao diện hỗ trợ lập trình bất đồng bộ cho các web server và ứng dụng Python

C. Là một hệ quản trị cơ sở dữ liệu thay thế cho PostgreSQL

D. Là một công cụ dùng để đóng gói ứng dụng vào Docker

Câu 9.6 Uvicorn đóng vai trò gì trong dự án web viết bằng Python?

A. Là một framework để viết mã nguồn Frontend

B. Là một hệ quản trị cơ sở dữ liệu quan hệ

C. Là một máy chủ Web (Web Server) chuẩn ASGI dùng để chạy ứng dụng FastAPI

D. Là trình soạn thảo mã nguồn chuyên dụng cho Python

Câu 9.7 Khi bạn thực hiện lệnh uvicorn main:app --reload trong terminal, tham số --reload có tác dụng gì đối với quá trình phát triển dự án?

A. Tự động cài đặt lại toàn bộ thư viện trong requirements.txt

B. Tự động sao lưu dữ liệu từ PostgreSQL sang một tập tin khác

C. Tự động phát hiện thay đổi trong mã nguồn và khởi động lại server để áp dụng thay đổi ngay lập tức

D. Xóa bỏ các tập tin rác trong thư mục venv/

Câu 9.8 Trong Python, "Decorator" thường được nhận diện bằng ký hiệu nào đặt ngay phía trên định nghĩa hàm?

A. Dấu #

B. Dấu $

C. Dấu &

D. Dấu @

-----
Bài sau: Ebook2LateX (10) - Lập trình Web services

Ebook2LateX (8) - Nhập dữ liệu tự động

Bài trước: Ebook2LateX (7) - Tạo các bảng
-----

8. Nhập dữ liệu tự động

8.1 Nhập dữ liệu tự động

Sau các bài học trước, chúng ta đã thực hiện:

- Tạo cơ dữ liệu cho dự án

- Tạo các bảng dữ liệu

- Thiết lập quan hệ giữa các bảng

- Lưu lại trạng thái dự án vào hệ thống Git

- Đẩy dự án lên Github

- Biết sử dụng ORM (SQLAlchemy)

- Biết sử dụng data migration (Alembic)

Tuy nhiên, trong các bảng chưa có dữ liệu. Việc nhập dữ liệu thủ công vào các bảng sẽ mất nhiều thời gian.

Trong phần này chúng ta sẽ viết script để tự động nhập dữ liệu mẫu vào các bảng. 

Quá trình tự động nhập dữ liệu vào các bảng được gọi là seeding. Seeding có nghĩa thông thường là “gieo hạt”. Trong cơ sở dữ liệu nó là quá trình “gieo dữ liệu” vào các bảng.

Tại sao cần nhập dữ liệu tự động?

- Để tối ưu hóa thời gian và nguồn lực: Thay vì tiêu tốn hàng giờ để nhập liệu thủ công từng bản ghi qua pgAdmin, một script tự động có thể tạo ra hàng nghìn dữ liệu chuẩn xác chỉ trong vài giây, giúp lập trình viên tập trung vào việc viết mã logic

- Kiểm thử giao diện và trải nghiệm người dùng (UI/UX): Dữ liệu mẫu giúp mô phỏng "hình hài" thực tế của ứng dụng. Ví dụ, cho phép kiểm tra xem các công thức toán học dài có làm vỡ khung giao diện không, hay các tính năng phân trang, tìm kiếm và cuộn trang có hoạt động mượt mà hay không

- Đảm bảo tính nhất quán trong làm việc nhóm: Cung cấp một bộ dữ liệu chuẩn duy nhất cho tất cả thành viên. Điều này giúp loại bỏ tình trạng sai lệch dữ liệu giữa các máy tính cá nhân, giúp việc tái hiện lỗi (debug) và phối hợp phát triển trở nên đồng bộ

- Xác thực cấu trúc và các ràng buộc hệ thống: Quá trình đổ dữ liệu tự động là cách nhanh nhất để kiểm tra tính đúng đắn của thiết kế cơ sở dữ liệu, đảm bảo các mối quan hệ khóa ngoại và các ràng buộc dữ liệu hoạt động đúng như mong đợi

Các bước để tạo dữ liệu

Để thực hiện tạo dữ liệu, chúng ta sẽ xây dựng các script Python sử dụng chính các Model (Users, Documents, FormulaEntries, Logs) đã định nghĩa bằng SQLAlchemy.

- Bước 1: Khởi tạo kết nối Database, chúng ta sử dụng đối tượng Session từ SQLAlchemy để giao tiếp với PostgreSQL thông qua chuỗi kết nối đã cấu hình trong tập tin .env.

Bạn cần đảm bảo: trong tập tin /Ebook2LateX/backend/.evn, đã có chuỗi kết nối, ví dụ: DATABASE_URL=postgresql://postgres:p%40ssword1@localhost:5432/ebook2latex_db

(Lưu ý: Khác với khi làm việc với Alembic, Python sẽ báo lỗi nếu chúng ta mã hóa “@” thành “%%40”, nên chúng ta sẽ mã hóa “@” thành “%40”).

Bạn cũng cần đảm bảo đã cài đặt thư viện python-dotenv để đọc thông tin từ .evn. (Xem lại phần 7.2 Cài đặt). Nếu chưa cài đặt, bạn chạy lệnh sau:

pip install python-dotenv

Viết mã khởi tạo trong database.py

Trong thư mục backend, tạo thư mục app. Trong app tạo tập tin database.py với nội dung sau:

[database.py]

import os

from dotenv import load_dotenv

from sqlalchemy import create_engine

from sqlalchemy.orm import sessionmaker, declarative_base


# Tải các biến môi trường từ tập tin .env

load_dotenv()


# 1. Lấy chuỗi kết nối từ biến môi trường

SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL")


# 2. Tạo Engine: Đây là "nguồn" kết nối chính tới Database

engine = create_engine(SQLALCHEMY_DATABASE_URL)


# 3. Tạo SessionLocal: Mỗi thực thể của lớp này sẽ là một phiên làm việc database

# autocommit=False: Đảm bảo dữ liệu chỉ được lưu khi ta ra lệnh commit()

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


# 4. Tạo Base class: Các models (User, Document...) sẽ kế thừa từ đây

Base = declarative_base()

Giải thích thêm về đoạn mã trên

- load_dotenv(): Hàm này đọc tập tin .env và đưa các cấu hình vào hệ thống, để Python có thể đọc được qua os.getenv

- create_engine: Đóng vai trò là trung tâm điều khiển. Nó giữ các kết nối thực tế tới PostgreSQL. Bạn chỉ nên tạo một Engine duy nhất cho toàn bộ ứng dụng

- sessionmaker: Đây là thành phần tạo ra các đối tượng Session. Mỗi khi Backend nhận được một yêu cầu (request) từ người dùng, chúng ta sẽ mở một Session từ thành phần này để truy vấn dữ liệu

- bind=engine: Kết nối thành phần tạo session này với Engine đã tạo ở trên

Liên kết Models với cấu trúc thư mục mới

Vì bạn vừa đưa tập tin database.py vào thư mục app, bạn cần đảm bảo tập tin models.py (nơi định nghĩa các bảng Users, Documents...) cũng nằm trong thư mục app này để đồng bộ.

Di chuyển tập tin models.py vào thư mục /backend/app/.

Mở tập tin models.py, tìm dòng from database import Base và sửa thành (nếu chưa có thì thêm):

from .database import Base  # Dấu chấm đại diện cho việc import cùng thư mục

[models.py]

import uuid

from sqlalchemy import Column, String, Integer, ForeignKey, DateTime, Boolean, Text, Numeric

from sqlalchemy.dialects.postgresql import UUID, JSONB

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import relationship

from sqlalchemy.sql import func

from .database import Base


# Khởi tạo lớp Base để các Model kế thừa

Base = declarative_base()

...

Viết Script để tạo dữ liệu (seed.py)

Bây giờ chúng ta sẽ viết một tập tin Python để tự động tạo dữ liệu. Hãy tạo tập tin seed.py nằm trong thư mục /backend/ (cùng cấp với thư mục app).

Nội dung cho tập tin seed.py:

[seed.py]

import uuid

from app.database import SessionLocal

from app.models import User, Document, FormulaEntry


def seed_data():

    # 1. Khởi tạo phiên làm việc (Session)

    db = SessionLocal()

    

    try:

        print("Đang tạo dữ liệu...")


        # 2. Tạo dữ liệu cho bảng User

        # Lưu ý: Trong thực tế mật khẩu cần được băm (hash), ở đây ta nhập mật khẩu tượng trưng

        test_user = User(

            user_id=uuid.uuid4(),

            username_email="teo@dalat.edu.vn",

            password_hash="hashed_password_here",

            full_name="Lê Văn Tèo",

            role="Admin"

        )

        db.add(test_user)

        db.flush() # Đẩy dữ liệu tạm thời để lấy user_id cho bảng sau


        # 3. Tạo dữ liệu mẫu cho bảng Documents

        test_doc = Document(

            id=uuid.uuid4(),

            user_id=test_user.user_id,

            file_name="Giao_trinh_Toan_12.pdf",

            file_path_url="/uploads/toan12.pdf",

            status="Completed"

        )

        db.add(test_doc)

        db.flush()


        # 4. Tạo dữ liệu mẫu cho bảng FormulaEntries (Công thức LaTeX)

        formula = FormulaEntry(

            id=uuid.uuid4(),

            document_id=test_doc.id,

            latex_content=r"\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}",

            order_index=1

        )

        db.add(formula)


        # 5. Xác nhận lưu toàn bộ thay đổi vào Database

        db.commit()

        print("Tạo dữ liệu thành công! Kiểm tra pgAdmin4 để xem kết quả.")


    except Exception as e:

        print(f"Có lỗi xảy ra: {e}")

        db.rollback() # Hoàn tác nếu có lỗi để tránh rác dữ liệu

    finally:

        db.close() # Luôn đóng kết nối sau khi xong


if __name__ == "__main__":

    seed_data()

Chạy lệnh Seeding

Sau khi viết xong tập tin seed.py, bạn mở Terminal tại thư mục /backend/ và chạy lệnh sau:

python seed.py

Sau bước này, bạn có thể mở pgAdmin 4, chuột phải vào các bảng và chọn View Data để xem kết quả là những dòng dữ liệu mẫu đầu tiên.

Cập nhật Git

Thực hiện commit vào Git để lưu lại trạng thái.

- Trong cửa sổ dòng lệnh (CMD), dấu nhắc lệnh đang ở thư mục gốc dự án (Ebook2LateX), nhập các lệnh sau:

(venv) D:\DuAn\Ebook2LateX>git status

HEAD detached at 4766c6a

Changes not staged for commit:

  (use "git add/rm <file>..." to update what will be committed)

  (use "git restore <file>..." to discard changes in working directory)

        deleted:    backend/models.py


Untracked files:

  (use "git add <file>..." to include in what will be committed)

        backend/app/

        backend/seed.py


no changes added to commit (use "git add" and/or "git commit -a")

(venv) D:\DuAn\Ebook2LateX>git add .

(venv) D:\DuAn\Ebook2LateX>git commit -m "feat: tao du lieu tu dong"

Cập nhật dự án lên Github

Kiểm tra kết nối giữa máy local với kho chứa ở xa?

Mở chương trình dòng lệnh, dấu nhắc tại thư mục dự án, gõ lệnh sau:

(venv) D:\DuAn\Ebook2LateX>git remote -v

origin  https://github.com/legiacong/Ebook2LateX.git (fetch)

origin  https://github.com/legiacong/Ebook2LateX.git (push)

Vậy là yên tâm để đẩy dự án lên Github:

(venv) D:\DuAn\Ebook2LateX>git push origin main

Ý nghĩa lệnh trên: Đẩy dự án lên repo có tên gọi tắt là origin, đẩy vào nhánh main.

8.2 Bài tập và câu hỏi

Bài tập

Bài tập 8a. Lập trình và cài đặt các nội dung trong phần lý thuyết.

Khi làm các bài tập, các bạn đừng sợ làm hỏng Database. Nếu làm sai, bạn có thể xóa database, tạo lại và chạy lệnh upgrade head cùng seed.py. Đó chính là sức mạnh của các công cụ bạn vừa học.

Bài tập 8b. Mở rộng cấu trúc (Schema Expansion)

Mục tiêu: Giúp người học hiểu cách thay đổi cấu trúc Database mà không làm mất dữ liệu hiện có.

Yêu cầu 1: Thêm tính năng "Yêu thích" (Likes/Stars):

- Yêu cầu: Thêm một bảng mới tên là UserFavorites để lưu việc người dùng đánh dấu các công thức (FormulaEntry) họ quan tâm

- Thao tác: Định nghĩa Model mới > Chạy alembic revision --autogenerate > alembic upgrade head

Yêu cầu 2: Quản lý phiên bản tài liệu:

- Yêu cầu: Thêm một cột version (kiểu Integer, mặc định là 1) vào bảng Documents

- Thao tác: Cập nhật models.py > Thực hiện migration để cập nhật database thực tế

Bài 8c: Truy vấn Nâng cao

Mục tiêu: Sử dụng Session và Relationship để lấy dữ liệu phức tạp.

Yêu cầu 1: Thống kê báo cáo (Reporting):

- Yêu cầu: Viết một script Python sử dụng SessionLocal để in ra danh sách tất cả các User, kèm theo số lượng tài liệu mà mỗi người đã tải lên

- Gợi ý: Sử dụng hàm func.count() trong SQLAlchemy

Yêu cầu 2: Tìm kiếm công thức:

- Yêu cầu: Viết một hàm nhận vào một từ khóa (ví dụ: "sqrt") và trả về tất cả các FormulaEntry có chứa từ khóa đó trong cột latex_content

Bài tập 8d. Seeding thực tế 

Mục tiêu: Làm quen với việc tạo dữ liệu mẫu số lượng lớn và ngẫu nhiên.

Yêu cầu 1: "Gieo dữ liệu" số lượng lớn với thư viện Faker:

- Thay vì nhập tay từng dòng như seed.py, hãy cài đặt thư viện Faker (pip install faker)

- Viết script tự động tạo ra 50 người dùng với tên và email ngẫu nhiên, mỗi người dùng có từ 2-5 tài liệu mẫu

Yêu cầu 2: Seeding từ tệp JSON:

- Tạo một tệp data.json chứa danh sách 10 công thức toán học

- Viết script seed_from_json.py đọc dữ liệu từ tập tin này và nạp vào bảng FormulaEntries

Câu hỏi

Câu 8.1 Trong dự án Python, thư viện python-dotenv được cài đặt và sử dụng nhằm mục đích chính là gì? 

A. Để tự động vẽ sơ đồ cơ sở dữ liệu cho dự án

B. Để nạp các biến môi trường (như chuỗi kết nối Database) từ tập tin .env vào hệ thống

C. Để mã hóa các tập tin hình ảnh thành định dạng LaTeX

D. Để gửi email thông báo cho người dùng khi có dữ liệu mới

Câu 8.2 Trong tập tin database.py, đối tượng engine được tạo ra bằng hàm create_engine đóng vai trò gì trong hệ thống?

A. Là nơi chứa các câu lệnh SQL dùng để tạo bảng

B. Là giao diện người dùng để nhập liệu thủ công vào PostgreSQL

C. Là "trung tâm điều khiển" giữ kết nối thực tế và quản lý việc giao tiếp giữa ứng dụng Python với PostgreSQL

D. Là một thư mục dùng để lưu trữ các tập tin PDF được tải lên

Câu 8.3 Tại sao chúng ta nên sử dụng SessionLocal (phiên làm việc) thay vì kết nối trực tiếp vào Database mỗi khi cần thao tác dữ liệu?

A. Để tăng tốc độ mạng Internet của máy tính

B. Để SQLAlchemy có thể quản lý các thay đổi một cách có hệ thống, cho phép xác nhận lưu (commit) hoặc hoàn tác (rollback) khi có lỗi xảy ra

C. Vì PostgreSQL không cho phép kết nối trực tiếp từ ngôn ngữ Python

D. Để tự động sao lưu dữ liệu lên hệ thống đám mây Github

Câu 8.4 Trong tập tin seed.py, nếu bạn muốn tạo một bản ghi cho bảng Document mà bảng này có khóa ngoại liên kết với bảng User, bạn cần phải thực hiện bước nào dưới đây để đảm bảo tính toàn vẹn dữ liệu? 

A. Phải tạo và lưu thông tin User trước, sau đó lấy user_id của người dùng đó để gán vào thuộc tính user_id của đối tượng Document

B. Chỉ cần nhập một tên người dùng bất kỳ vào bảng Document mà không cần quan tâm bảng User

C. Xóa bỏ ràng buộc khóa ngoại trong models.py trước khi chạy lệnh seeding

D. Phải chạy lệnh alembic upgrade head ngay bên trong tập tin seed.py

-----
Bài sau: Ebook2LateX (9) - Web services