circle-search-26-api / app /services /projects_service.py
ktsn-ud
余計なコードを削除
2e6521d
Raw
History Blame Contribute Delete
3.15 kB
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