academylessonsrepository pattern without overengineering

AI Academy

Active
AI Academy/Building Grand Service with AI/Контроль архитектуры и технического долга

Repository pattern без лишней архитектуры

Зачем AcademyProgressRepository нужен именно здесь, и когда такой pattern был бы лишним.

Not started25 min

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`.

academy-progress-repository.tsts
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 знает, как сохранить состояние. Такое разделение не делает систему тяжелой, если каждый слой маленький и нужен для реальной замены зависимости.

Без boundary
  • React-компонент знает про cookie или SQL.
  • Смена storage ломает UI.
  • Трудно проверить idempotency отдельно.
  • Production gap прячется в компонентах.
С boundary
  • UI вызывает action.
  • Service валидирует user и lesson.
  • Repository сохраняет progress.
  • Adapter можно заменить без редизайна.
Когда repository был бы лишним

Если feature читает только статический локальный config и никогда не будет менять storage, отдельный repository может быть избыточным. Pattern оправдан, когда есть реальная смена adapter или транзакционная логика.

  1. 1
    Сначала найди настоящую изменяемую зависимость

    В Academy это progress storage. Контент пока локальный typed source, поэтому для него отдельный repository не нужен.

  2. 2
    Определи минимальный interface

    Не добавляй CRUD на будущее. Нужны только markViewed, markCompleted и чтение progress.

  3. 3
    Спрячь adapter

    Cookie и будущий PostgreSQL implementation должны жить за одним контрактом.

Practice

A real WorkerHubs scenario for this lesson.

Выделить persistence interface

Представь feature, которая сначала хранит настройки в local storage, а позже должна перейти на database. Спроектируй interface без лишних методов.

Назови изменяемую зависимость.
Опиши 3–5 методов interface.
Определи demo adapter и production adapter.
Напиши, какие компоненты не должны знать о storage.

Resources