Введение
В данной статье будет рассмотрена практическая интеграция MongoDB с веб-приложением на Go, построенным на базе маршрутизатора Gorilla Mux. Цель — получить минимальный, но функциональный REST API с поддержкой CRUD-операций над сущностью Book, при этом соблюдая лучшие практики структурирования кода.
Материал рассчитан на разработчиков, знакомых с Go, HTTP API и основами работы с базами данных.
Выбор стека
Go — компилируемый язык с лаконичным синтаксисом, встроенной поддержкой параллелизма и богатой стандартной библиотекой для работы с сетью. Эти качества делают его удобным выбором для разработки API-сервисов.
Почему Gorilla Mux
Gorilla Mux — зрелый, широко используемый HTTP-роутер для Go, который поддерживает:
-
маршрутизацию с шаблонами (
/books/{id}); -
фильтрацию по HTTP-методу;
-
middleware.
Почему MongoDB
MongoDB — документно-ориентированная NoSQL-БД, оптимизированная для хранения неструктурированных или слабо структурированных данных, что делает её подходящей для быстрого прототипирования и гибких схем.
Архитектура проекта
Структура каталогов для примера:
/cmd /api main.go /internal /books handler.go model.go repository.go /pkg /db mongo.go go.mod
Такое разделение позволяет изолировать доменную логику (internal/books) от инфраструктурных компонентов (pkg/db).
Подготовка окружения
Установка MongoDB (Linux, apt)
sudo apt update sudo apt install -y mongodb
После установки убедитесь, что сервис запущен:
systemctl status mongodb
Подключение к shell:
mongo
Cоздадим базу данных booksdb и коллекцию books:
use booksdb db.books.insertOne({ title: "A Song of Ice and Fire", authors: ["George R.R. Martin", "Phyllis Eisenstein"], publish_date: ISODate("1996-08-01"), publisher: { name: "Bantam Books", country: "US" } })
Подключение зависимостей
go mod init github.com/username/mongo-gorilla-api go get github.com/gorilla/mux go get go.mongodb.org/mongo-driver/mongo go get go.mongodb.org/mongo-driver/mongo/options
Примечание: В отличие от устаревшего mgo.v2, рекомендуется использовать официальный mongo-driver.
Инициализация подключения к MongoDB
/pkg/db/mongo.go:
package db import ( "context" "log" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func Connect(uri string) *mongo.Database { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri)) if err != nil { log.Fatalf("Ошибка подключения к MongoDB: %v", err) } if err := client.Ping(ctx, nil); err != nil { log.Fatalf("MongoDB не отвечает: %v", err) } log.Println("Соединение с MongoDB установлено") return client.Database("booksdb") }
Модель данных
/internal/books/model.go:
package books import "go.mongodb.org/mongo-driver/bson/primitive" type Book struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` Title string `bson:"title" json:"title"` Authors []string `bson:"authors" json:"authors"` PublishDate string `bson:"publish_date" json:"publish_date"` Publisher Publisher `bson:"publisher" json:"publisher"` } type Publisher struct { Name string `bson:"name" json:"name"` Country string `bson:"country" json:"country"` }
Репозиторий
/internal/books/repository.go:
package books import ( "context" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) type Repository struct { col *mongo.Collection } func NewRepository(db *mongo.Database) *Repository { return &Repository{col: db.Collection("books")} } func (r *Repository) FindAll(ctx context.Context) ([]Book, error) { var books []Book cur, err := r.col.Find(ctx, bson.D{}) if err != nil { return nil, err } defer cur.Close(ctx) for cur.Next(ctx) { var book Book if err := cur.Decode(&book); err != nil { return nil, err } books = append(books, book) } return books, nil } func (r *Repository) Insert(ctx context.Context, book Book) (*mongo.InsertOneResult, error) { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return r.col.InsertOne(ctx, book) }
HTTP-обработчики
/internal/books/handler.go:
package books import ( "context" "encoding/json" "net/http" "time" ) type Handler struct { repo *Repository } func NewHandler(repo *Repository) *Handler { return &Handler{repo: repo} } func (h *Handler) GetBooks(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() books, err := h.repo.FindAll(ctx) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(books) } func (h *Handler) CreateBook(w http.ResponseWriter, r *http.Request) { var book Book if err := json.NewDecoder(r.Body).Decode(&book); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := h.repo.Insert(ctx, book) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }
Точка входа
/cmd/api/main.go:
package main import ( "log" "net/http" "github.com/gorilla/mux" "github.com/username/mongo-gorilla-api/internal/books" "github.com/username/mongo-gorilla-api/pkg/db" ) func main() { database := db.Connect("mongodb://localhost:27017") repo := books.NewRepository(database) handler := books.NewHandler(repo) r := mux.NewRouter() r.HandleFunc("/books", handler.GetBooks).Methods("GET") r.HandleFunc("/books", handler.CreateBook).Methods("POST") log.Println("Сервер запущен на :8080") log.Fatal(http.ListenAndServe(":8080", r)) }
Проверка API
Создание книги:
curl -X POST http://localhost:8080/books \ -H "Content-Type: application/json" \ -d '{"title":"Go in Action","authors":["William Kennedy"],"publish_date":"2015-11-26","publisher":{"name":"Manning","country":"US"}}'
Получение списка:
curl http://localhost:8080/books
Заключение
В результате мы получили минималистичный, но масштабируемый REST API на Go, интегрированный с MongoDB. Использование официального драйвера и структурированного подхода к архитектуре позволяет легко расширять сервис, добавляя валидацию, аутентификацию и более сложные бизнес-правила.
В дальнейшем можно:
-
Вынести конфигурацию в
.envи использоватьviperилиenvconfig; -
Добавить логирование (
zerolog,zap); -
Реализовать graceful shutdown;
-
Подключить Swagger/OpenAPI для документации.
ссылка на оригинал статьи https://habr.com/ru/articles/935102/
Добавить комментарий