Spaces:
Sleeping
Sleeping
| import os | |
| import time | |
| from threading import RLock | |
| from typing import List, Optional | |
| from app.repositories.projects_repository import ProjectsRepository | |
| from schemas.projects import Project | |
| class ProjectsService: | |
| """企画データ取得のサービス層。短時間キャッシュを挟んで DB 負荷を抑える。""" | |
| def __init__( | |
| self, | |
| repository: ProjectsRepository, | |
| cache_ttl_seconds: Optional[int] = None, | |
| ) -> None: | |
| self._repository = repository | |
| if cache_ttl_seconds is None: | |
| cache_ttl_seconds = int(os.getenv("PROJECTS_CACHE_TTL", "300")) | |
| self._cache_ttl = cache_ttl_seconds | |
| self._cache: Optional[List[Project]] = None | |
| self._cache_at: Optional[float] = None | |
| self._lock = RLock() | |
| def list_projects(self) -> List[Project]: | |
| """企画一覧を取得する。 | |
| 企画データは全てのカラムを含む。 | |
| キャッシュが有効ならそれを返し、無効ならデータベースから取得してキャッシュを更新する。 | |
| """ | |
| now = time.monotonic() | |
| with self._lock: | |
| # キャッシュが有効ならそれを返す | |
| if ( | |
| self._cache is not None | |
| and self._cache_at is not None | |
| and now - self._cache_at < self._cache_ttl | |
| ): | |
| return [item.model_copy() for item in self._cache] | |
| # キャッシュが無効ならデータベースから取得してキャッシュを更新する | |
| projects = [item.model_copy() for item in self._repository.list_projects()] | |
| self._cache = list(projects) | |
| self._cache_at = now | |
| return [item.model_copy() for item in projects] | |
| def get_project(self, project_id: str) -> Optional[Project]: | |
| """企画IDから企画データを取得する | |
| 企画データは全てのカラムを含む。 | |
| キャッシュが有効ならそれを返し、無効ならデータベースから取得する。 | |
| """ | |
| if not project_id: | |
| return None | |
| now = time.monotonic() | |
| with self._lock: | |
| # キャッシュが有効ならそれを返す | |
| if ( | |
| self._cache is not None | |
| and self._cache_at is not None | |
| and now - self._cache_at < self._cache_ttl | |
| ): | |
| for item in self._cache: | |
| if item.projectId == project_id: | |
| return item.model_copy() | |
| return None | |
| # キャッシュが無効ならlist_projects経由でデータベースから取得 | |
| projects = self.list_projects() | |
| for item in projects: | |
| if item.projectId == project_id: | |
| return item.model_copy() | |
| return None | |
| def invalidate_cache(self) -> None: | |
| """キャッシュを無効化する。次回の取得時に必ずデータベースから再取得される。""" | |
| with self._lock: | |
| self._cache = None | |
| self._cache_at = None | |