Skip to content

Repository files navigation

Postservice

GraphQL-сервис на Go для постов и иерархических комментариев.

Возможности

  • создание и чтение постов;
  • включение и выключение комментариев только автором поста;
  • создание комментариев первого уровня и ответов на комментарии;
  • неограниченная вложенность комментариев на уровне модели данных;
  • постраничное чтение комментариев по одному уровню дерева;
  • GraphQL subscriptions для новых комментариев под конкретным постом;
  • два режима хранения: memory и postgres;
  • Dockerfile и docker-compose для запуска сервиса;

Запуск

Локально с in-memory storage:

STORAGE_TYPE=memory go run ./cmd/server

Через Docker Compose с PostgreSQL:

docker compose up --build

GraphQL playground будет доступен на:

http://localhost:8080/

GraphQL endpoint:

http://localhost:8080/query

Healthcheck:

http://localhost:8080/health

Идентификация пользователя

Все мутации требуют HTTP-заголовок X-User-ID.

В GraphQL Playground заголовок задается в панели Headers:

{
  "X-User-ID": "user-1"
}

При создании поста X-User-ID сохраняется как authorID. Изменить commentsEnabled может только пользователь с тем же ID. Имя автора остается отображаемым полем и не используется для проверки прав.

Конфигурация

Переменные окружения:

HTTP_ADDR=:8080
STORAGE_TYPE=memory
POSTGRES_DSN=postgres://postservice:postservice@localhost:5432/postservice?sslmode=disable

STORAGE_TYPE может быть:

  • memory;
  • postgres.

Если выбран postgres, переменная POSTGRES_DSN обязательна.

Хранение комментариев

Комментарии хранятся через модель parent_id.

Комментарий первого уровня имеет parent_id = NULL. Ответ на комментарий содержит parent_id родительского комментария.

В PostgreSQL основная таблица комментариев содержит:

  • id;
  • post_id;
  • parent_id;
  • author_id;
  • author_name;
  • text;
  • created_at.

Для быстрого чтения веток создан индекс:

(post_id, parent_id, created_at, id)

In-memory storage устроен похожим образом: комментарии лежат в map по id, а отдельный индекс группирует id комментариев по паре postID + parentID. Поэтому при чтении ответов не нужно сканировать все комментарии.

Создание комментария атомарно проверяет существование поста, флаг comments_enabled, существование родителя и принадлежность родителя тому же посту. В PostgreSQL проверка и вставка выполняются в одной транзакции с блокировкой строки поста. В memory-режиме используется общий RWMutex постов и блокировка индекса комментариев.

На уровне PostgreSQL принадлежность родителя тому же посту дополнительно защищена составным внешним ключом (parent_id, post_id).

Пагинация

Комментарии не возвращаются одним бесконечным деревом. Клиент запрашивает конкретный уровень:

  • комментарии первого уровня под постом;
  • ответы на конкретный комментарий;
  • следующую страницу той же ветки.

Это защищает сервис от тяжелых запросов при большой вложенности и большом количестве комментариев.

Текущая пагинация использует limit и offset.

Ограничения:

  • значение по умолчанию: 20;
  • максимум: 100;
  • отрицательные значения запрещены.

GraphQL операции

Основные queries:

query {
  posts(limit: 20, offset: 0) {
    id
    authorID
    title
    authorName
    commentsEnabled
    createdAt
  }
}
query {
  comments(postID: "post-id", parentID: null, limit: 20, offset: 0) {
    id
    parentID
    authorName
    text
    createdAt
  }
}

Основные mutations:

mutation {
  createPost(input: {authorName: "Alice", title: "Hello", text: "First post"}) {
    id
    title
  }
}
mutation {
  createComment(input: {postID: "post-id", authorName: "Bob", text: "Nice post"}) {
    id
    text
  }
}

Subscription:

subscription {
  commentAdded(postID: "post-id") {
    id
    text
    authorName
  }
}

Архитектура

Основные пакеты:

  • cmd/server - точка входа;
  • internal/config - конфигурация через env;
  • internal/auth - идентичность пользователя в context;
  • internal/domain - сущности и доменные ошибки;
  • internal/service - бизнес-логика;
  • internal/storage - интерфейсы хранилища;
  • internal/storage/memory - in-memory реализация;
  • internal/storage/postgres - PostgreSQL реализация и миграции;
  • internal/graph - GraphQL-схема и резолверы;
  • internal/pubsub - subscriptions для новых комментариев.

Subscriptions

WebSocket subscription commentAdded(postID) получает новые комментарии только выбранного поста. У каждого клиента есть ограниченный буфер. Если клиент перестает читать и переполняет его, subscription закрывается явно: клиент должен переподключиться и перечитать комментарии через query. Благодаря этому медленный клиент не блокирует создание комментариев и не вызывает неограниченный рост памяти.

Тесты

Unit-тесты, race detector и статический анализ:

go test ./...
go test -race ./...
go vet ./...

PostgreSQL integration-тест запускается при наличии отдельной тестовой базы:

TEST_POSTGRES_DSN='postgres://postservice:postservice@localhost:5432/postservice?sslmode=disable' \
  go test ./internal/storage/postgres

Тесты покрывают бизнес-правила, Unicode-ограничение комментариев, владельца поста, пагинацию, конкурентный memory storage, GraphQL-контракт, коды ошибок, конфигурацию и subscriptions.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages