429 words
2 minutes
async-problem-python
2025-06-22
No Tags

ref: https://aistudio.google.com/app/prompts?state=%7B%22ids%22:%5B%221oA291R7K0imB4ToT0Ovbn1E8A5AF8fYr%22%5D,%22action%22:%22open%22,%22userId%22:%22110707663579420557117%22,%22resourceKeys%22:%7B%7D%7D&usp=sharing

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

async-problem-python
https://this-is-toilacube.web.app/posts/async-problem-python/
Author
toilacube
Published at
2025-06-22
License
CC BY-NC-SA 4.0