Repository pattern без лишней архитектуры
Зачем AcademyProgressRepository нужен именно здесь, и когда такой pattern был бы лишним.
Repository pattern легко превратить в модное слово. Но в AI Academy он появился не ради архитектурной красоты. Он решал конкретную проблему: UI уже работает, а storage должен позже переехать с demo cookie fallback на PostgreSQL. Если компоненты напрямую знают о cookie или SQL, замена storage станет переписыванием feature. Если они зависят от interface, меняется adapter, а пользовательский путь остается прежним.
В Grand Service это особенно важно, потому что root persistence layer еще не готов. Было бы неправильно писать SQL прямо в React-компоненте или делать вид, что PostgreSQL уже подключен. Вместо этого сервисный слой вызывает `AcademyProgressRepository`, а конкретная реализация выбирается отдельно: сейчас demo cookie adapter, позже `PostgresAcademyProgressRepository`.
export interface AcademyProgressRepository {
markViewed(input: {
userId: string;
courseSlug: string;
lessonSlug: string;
viewedAt: Date;
}): Promise<AcademyLessonProgress>;
markCompleted(input: {
userId: string;
courseSlug: string;
lessonSlug: string;
completedAt: Date;
}): Promise<AcademyLessonProgress>;
listCourseProgress(input: {
userId: string;
courseSlug: string;
}): Promise<AcademyLessonProgress[]>;
}Dependency boundary
Boundary означает: UI не импортирует storage. Lesson page знает, что можно нажать Complete. Progress service знает, что нужно проверить lesson slug и получить current user. Repository знает, как сохранить состояние. Такое разделение не делает систему тяжелой, если каждый слой маленький и нужен для реальной замены зависимости.
- React-компонент знает про cookie или SQL.
- Смена storage ломает UI.
- Трудно проверить idempotency отдельно.
- Production gap прячется в компонентах.
- UI вызывает action.
- Service валидирует user и lesson.
- Repository сохраняет progress.
- Adapter можно заменить без редизайна.
Если feature читает только статический локальный config и никогда не будет менять storage, отдельный repository может быть избыточным. Pattern оправдан, когда есть реальная смена adapter или транзакционная логика.
- 1Сначала найди настоящую изменяемую зависимость
В Academy это progress storage. Контент пока локальный typed source, поэтому для него отдельный repository не нужен.
- 2Определи минимальный interface
Не добавляй CRUD на будущее. Нужны только markViewed, markCompleted и чтение progress.
- 3Спрячь adapter
Cookie и будущий PostgreSQL implementation должны жить за одним контрактом.
Practice
A real WorkerHubs scenario for this lesson.
Представь feature, которая сначала хранит настройки в local storage, а позже должна перейти на database. Спроектируй interface без лишних методов.