Nhớ hồi đó một mình mình làm dự án document-extraction, ăn ngủ với nó 2 tuần liền cuối cùng cũng làm xong được 1 bản MVP cho sếp.
Dự án này mình thử code theo hướng OOP cho Python vì trước giờ mình luôn giữ mãi 1 tình yêu cho Java OOP .
Đầu tiên cơ bản nhất, mình thử triển khai singleton service cho Python (framework mình dùng là FastAPI). Ở FastAPI có vẻ không hỗ trợ sẵn singleton pattern như Spring của Java, nên mình phải tự code, bình thường thì mình sẽ triển khai như sau:
class LinkExtractionSevice:
pass
_extraction_service = Optional[LinkExtractionSevice] = None
def get_link_extraction_service() -> LinkExtractionSevice:
global _extraction_service
if _extraction_service is None:
_extraction_service = LinkExtractionSevice()
return _extraction_service
Cách này giúp tạo ra một instance duy nhất của ExtractionService trong suốt vòng đời của ứng dụng. Khi cần thì chỉ việc gọi Depends() với get_link_extraction_service mỗi khi cần sử dụng service này.
Ví dụ:
@app.post("/extract")
async def extract(
request: Request,
extraction_service: LinkExtractionService = Depends(get_link_extraction_service)
):
pass
Tầng Service ở backend bình thường sẽ gọi đến tầng Repository để thao tác với database. Hàm get_link_extraction_service khi đó sẽ khởi tạo 1 class repository như sau,
def get_link_extraction_service(db: Session = Depends(get_mysql_session)) -> LinkExtractionService:
# Service được khởi tạo với một DB session
link_extraction_repository = LinkExtractionRepository(db)
return LinkExtractionService(link_extraction_repository)
Service này sẽ nhận 1 loạt link, kiểm tra xem link nào chưa được xử lý ở dưới DB thì gọi lên rồi xử lý ngầm bằng BackgroundTasks
của FastAPI để có thể trả về ngay cho client mà không cần chờ xử lý xong.
@router.post("/api/v1/extraction/link-extraction")
async def extract_from_links_batch_background_endpoint(
batch_request: LinkExtractionBatchRequest,
background_tasks: BackgroundTasks,
link_service: LinkExtractionService = Depends(get_link_extraction_service),
db: Session = Depends(get_mysql_session)
):
link_content_repo = get_link_content_repostitory(db)
# ...
for link_req_item_dto in batch_request.links:
# Kiểm tra link đã tồn tại chưa
existing_content = link_content_repo.find_by_link_id(...)
if existing_content:
# Bỏ qua nếu đã có
continue
# Nếu chưa có, thêm vào background task
background_tasks.add_task(
link_service.process_single_link_and_set_final_status,
link_req_item_dto
)
# ...
return {"message": "Các link đang được xử lý!"}
Khi bắt đầu kiểm thử, mình phát hiện là task trích xuất thông tin từ link hoạt động tốt nhưng mà không hiểu sao không thêm dữ liệu
vừa trích xuất vào database được. Mình debug thì thấy hàm process_single_link_and_set_final_status chạy đúng, lấy được dữ liệu rồi
nhưng mà khi gọi hàm save của repository thì không thấy dữ liệu được thêm vào database.
Thế là sau 7749 lần kiểm tra rồi debug với gemini thì có 1 vấn đề vô cùng cơ bản:
Không lưu được dữ liệu về database là do… không có instance DB nào được khởi tạo cả.
Nguyên nhân là khi request /api/v1/extraction/link-extraction