Представьте: вы создаёте API, которое должно пережить тенденции фреймворков, выдержать миграции баз данных и пройти через неизбежные встречи под девизом «давайте перепишем всё на Rust». Добро пожаловать в мир чистой архитектуры в Go, где мы разделяем проблемы, словно дипломаты, распределяющие спорные территории. Сегодня мы создадим организованный API TODO, который будет более упорядоченным, чем полочка для специй у моей бабушки.
Закладываем фундамент
Начнём с создания основы нашего проекта:
go mod init github.com/yourname/todo-clean
Теперь давайте установим наш набор инструментов для выживания в цифровом мире:
go get github.com/gorilla/mux github.com/jmoiron/sqlx \
github.com/lib/pq github.com/kelseyhightower/envconfig
Наша структура каталогов будет выглядеть следующим образом:
todo-clean/
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── entity/
│ ├── usecase/
│ ├── delivery/
│ └── repository/
Эта структура делает наш код таким же организованным, как квартира чемпиона по тетрису.
Раскрываем чистые слои
1. Уровень сущностей — наша бизнес-ДНК
В entity/todo.go
:
package entity
type Todo struct {
ID string `json:"id" db:"id"`
Title string `json:"title" validate:"required"`
Description string `json:"description"`
Completed bool `json:"completed"`
}
type TodoRepository interface {
Store(todo *Todo) error
FindByID(id string) (*Todo, error)
Delete(id string) error
}
Эти сущности — конституция нашего приложения, они не заботятся о HTTP или базах данных, только о бизнес-правилах.
2. Уровень прецедентов использования — мозг
В usecase/todo.go
:
package usecase
type TodoInteractor struct {
repo entity.TodoRepository
}
func (ti *TodoInteractor) CreateTodo(t entity.Todo) error {
if t.Title == "" {
return errors.New("title cannot be empty")
}
return ti.repo.Store(&t)
}
// Добавьте другие методы CRUD
Здесь живёт бизнес-логика, полностью независимая от фреймворка. Это как учить свою собаку командам — собаке всё равно, говорите ли вы по-английски или на клингонском.
3. Слой доставки — посланник
В delivery/http/handler.go
:
package http
func NewTodoHandler(usecase usecase.TodoInteractor) *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/todos", createTodoHandler(usecase)).Methods("POST")
// Добавьте другие маршруты
return r
}
func createTodoHandler(uc usecase.TodoInteractor) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var todo entity.Todo
json.NewDecoder(r.Body).Decode(&todo)
if err := uc.CreateTodo(todo); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}
w.WriteHeader(http.StatusCreated)
}
}
Наши HTTP-обработчики — это просто переводчики, преобразующие HTTP-команды в команды бизнес-логики.
4. Слой репозиториев — оборотень
В repository/postgres/todo.go
:
package postgres
type TodoRepo struct {
db *sqlx.DB
}
func (tr *TodoRepo) Store(t *entity.Todo) error {
_, err := tr.db.NamedExec(`
INSERT INTO todos (id, title, description, completed)
VALUES (:id, :title, :description, :completed)`, t)
return err
}
// Реализуйте другие методы репозитория
Смена баз данных похожа на замену шин — автомобиль (бизнес-логика) продолжает движение.
Соединяем всё вместе
В cmd/api/main.go
:
func main() {
var cfg config.AppConfig
envconfig.MustProcess("TODO", &cfg)
db := sqlx.MustConnect("postgres", cfg.DatabaseURL)
defer db.Close()
todoRepo := repository.NewPostgresTodoRepo(db)
todoUseCase := usecase.NewTodoInteractor(todoRepo)
handler := delivery.NewTodoHandler(todoUseCase)
log.Printf("Запуск сервера на %s", cfg.ServerAddress)
log.Fatal(http.ListenAndServe(cfg.ServerAddress, handler))
}
Эта основная функция — мастер марионеток, координирующий все наши уровни.
Тестирование: наша страховочная сеть
func TestTodoCreation(t *testing.T) {
mockRepo := new(MockTodoRepo)
useCase := usecase.NewTodoInteractor(mockRepo)
t.Run("valid todo", func(t *testing.T) {
err := useCase.CreateTodo(entity.Todo{Title: "Test"})
assert.Nil(t, err)
})
t.Run("empty title", func(t *testing.T) {
err := useCase.CreateTodo(entity.Todo{Title: ""})
assert.ErrorContains(t, err, "не может быть пустым")
})
}
Наши тесты доказывают, что бизнес-правила сохраняются, даже если завтра мы перейдём на MongoDB.
Результат: гибкость на первом месте
Нужно добавить gRPC? Просто создайте новую реализацию доставки. Хотите кэшировать задачи? Создайте декоратор репозитория. Это как LEGO для backend-разработчиков. Вот и всё — чистая архитектура в Go, которая более удобна в обслуживании, чем Tesla. Помните, хорошая архитектура заключается не в следовании тенденциям, а в создании систем, которые переживут неверные решения вашего будущего «я». А теперь вперёд и организуйте эти кодовые базы, как будто вы упорядочиваете стартап в духе Мари Кондо!